复制项目
This commit is contained in:
159
pkg/sms/bao.go
Normal file
159
pkg/sms/bao.go
Normal file
@@ -0,0 +1,159 @@
|
||||
// Copyright © 2023 OpenIM open source community. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sms
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/openimsdk/tools/log"
|
||||
)
|
||||
|
||||
func NewBao(endpoint, accessKeyId, accessKeySecret, signName, verificationCodeTemplateCode string) (SMS, error) {
|
||||
return &bao{
|
||||
endpoint: endpoint,
|
||||
accessKeyId: accessKeyId,
|
||||
accessKeySecret: accessKeySecret,
|
||||
signName: signName,
|
||||
verificationCodeTemplateCode: verificationCodeTemplateCode,
|
||||
statusMap: map[string]string{
|
||||
"0": "短信发送成功",
|
||||
"-1": "参数不全",
|
||||
"-2": "服务器空间不支持,请确认支持curl或者fsocket",
|
||||
"30": "密码错误",
|
||||
"40": "账号不存在",
|
||||
"41": "余额不足",
|
||||
"42": "帐户已过期",
|
||||
"43": "IP地址限制",
|
||||
"50": "内容含有敏感词",
|
||||
"51": "手机号码格式错误",
|
||||
"52": "短信内容为空",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
type bao struct {
|
||||
endpoint string
|
||||
accessKeyId string
|
||||
accessKeySecret string
|
||||
signName string
|
||||
verificationCodeTemplateCode string
|
||||
statusMap map[string]string
|
||||
}
|
||||
|
||||
func (b *bao) Name() string {
|
||||
return "bao-sms"
|
||||
}
|
||||
|
||||
func (b *bao) SendCode(ctx context.Context, areaCode string, phoneNumber string, verifyCode string) error {
|
||||
// 去除国家码中的+号
|
||||
areaCode = strings.TrimPrefix(areaCode, "+")
|
||||
|
||||
// 构建完整手机号
|
||||
var fullPhoneNumber string
|
||||
// 对于中国手机号,短信宝只需要11位数字
|
||||
if areaCode == "86" || areaCode == "086" {
|
||||
fullPhoneNumber = phoneNumber // 只发送手机号,不加国家码
|
||||
} else {
|
||||
fullPhoneNumber = areaCode + phoneNumber
|
||||
}
|
||||
|
||||
log.ZInfo(ctx, "SMSBao Build Phone", "areaCode", areaCode, "phoneNumber", phoneNumber, "fullPhoneNumber", fullPhoneNumber)
|
||||
|
||||
// 密码处理:如果已经是MD5格式(32位十六进制),直接使用;否则进行MD5加密
|
||||
password := b.accessKeySecret
|
||||
// 检查是否是MD5格式(32位十六进制小写字符串)
|
||||
if len(b.accessKeySecret) != 32 || !isHexString(b.accessKeySecret) {
|
||||
password = fmt.Sprintf("%x", md5.Sum([]byte(b.accessKeySecret)))
|
||||
}
|
||||
|
||||
// 构建短信内容
|
||||
content := fmt.Sprintf("您的验证码是%s。如非本人操作,请忽略本短信", verifyCode)
|
||||
if b.signName != "" {
|
||||
content = fmt.Sprintf("【%s】%s", b.signName, content)
|
||||
}
|
||||
|
||||
log.ZInfo(ctx, "SMSBao Content", "content", content)
|
||||
|
||||
// 构建请求URL - 短信宝API: https://api.smsbao.com/sms?u=用户名&p=MD5密码&m=手机号&c=内容
|
||||
// 如果endpoint已包含完整路径,直接使用;否则追加/sms
|
||||
smsURL := b.endpoint
|
||||
if !strings.Contains(smsURL, "/sms") {
|
||||
if !strings.HasSuffix(smsURL, "/") {
|
||||
smsURL += "/"
|
||||
}
|
||||
smsURL += "sms"
|
||||
}
|
||||
|
||||
apiURL := fmt.Sprintf("%s?u=%s&p=%s&m=%s&c=%s",
|
||||
smsURL,
|
||||
url.QueryEscape(b.accessKeyId),
|
||||
url.QueryEscape(password),
|
||||
url.QueryEscape(fullPhoneNumber),
|
||||
url.QueryEscape(content),
|
||||
)
|
||||
|
||||
log.ZInfo(ctx, "SMSBao Request", "url", apiURL, "phone", fullPhoneNumber, "areaCode", areaCode, "phoneNumber", phoneNumber)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "SMSBao Request Failed", err, "phone", fullPhoneNumber)
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "SMSBao Read Response Failed", err, "phone", fullPhoneNumber)
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
|
||||
result := strings.TrimSpace(string(body))
|
||||
log.ZInfo(ctx, "SMSBao Response", "phone", fullPhoneNumber, "status", resp.StatusCode, "result", result, "rawBody", string(body))
|
||||
|
||||
// 返回0表示成功
|
||||
if result != "0" {
|
||||
errMsg := b.statusMap[result]
|
||||
if errMsg == "" {
|
||||
errMsg = fmt.Sprintf("未知错误代码: %s", result)
|
||||
}
|
||||
log.ZError(ctx, "SMSBao Failed", fmt.Errorf("%s", errMsg), "code", result, "phone", fullPhoneNumber)
|
||||
return errs.New(errMsg)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isHexString 检查字符串是否是纯十六进制字符
|
||||
func isHexString(s string) bool {
|
||||
for _, c := range s {
|
||||
if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user