diff --git a/config.yaml.sample b/config.yaml.sample index a97b0710..92c7046f 100644 --- a/config.yaml.sample +++ b/config.yaml.sample @@ -65,6 +65,11 @@ SmsJuhe: Key: TplID: TplVal: "#code#=%s&#m#=%d" +SmsBao: + Gateway: https://www.smsbao.com/ + Username: xxx + Password: xxx + Sign: "【xxx】" Alipay: AppID: InProduction: True diff --git a/internal/conf/conf.go b/internal/conf/conf.go index c21f51e8..8295b229 100644 --- a/internal/conf/conf.go +++ b/internal/conf/conf.go @@ -46,6 +46,7 @@ var ( BigCacheIndexSetting *bigCacheIndexConf RedisCacheIndexSetting *redisCacheIndexConf SmsJuheSetting *smsJuheConf + SmsBaoSetting *smsBaoConf AlipaySetting *alipayConf TweetSearchSetting *tweetSearchConf ZincSetting *zincConf @@ -59,6 +60,7 @@ var ( LocalOSSSetting *localossConf JWTSetting *jwtConf WebProfileSetting *WebProfileConf + SmsType string ) func setupSetting(suite []string, noDefault bool) error { @@ -76,6 +78,7 @@ func setupSetting(suite []string, noDefault bool) error { objects := map[string]any{ "App": &AppSetting, + "SmsType": &SmsType, "Cache": &CacheSetting, "EventManager": &EventManagerSetting, "MetricManager": &MetricManagerSetting, @@ -96,6 +99,7 @@ func setupSetting(suite []string, noDefault bool) error { "RedisCacheIndex": &RedisCacheIndexSetting, "Alipay": &AlipaySetting, "SmsJuhe": &SmsJuheSetting, + "SmsBao": &SmsBaoSetting, "Pyroscope": &PyroscopeSetting, "Sentry": &sentrySetting, "Logger": &loggerSetting, diff --git a/internal/conf/setting.go b/internal/conf/setting.go index fdcb3379..913e6a8d 100644 --- a/internal/conf/setting.go +++ b/internal/conf/setting.go @@ -183,6 +183,13 @@ type smsJuheConf struct { TplVal string } +type smsBaoConf struct { + Gateway string + Username string + Password string + Sign string +} + type tweetSearchConf struct { MaxUpdateQPS int MinWorker int diff --git a/internal/dao/security/phone_verify_juhe.go b/internal/dao/security/phone_verify_juhe.go index c213d380..fb42e12a 100644 --- a/internal/dao/security/phone_verify_juhe.go +++ b/internal/dao/security/phone_verify_juhe.go @@ -2,19 +2,17 @@ package security import ( "fmt" + "github.com/rocboss/paopao-ce/internal/core" "net/http" "time" "github.com/cockroachdb/errors" "github.com/rocboss/paopao-ce/internal/conf" - "github.com/rocboss/paopao-ce/internal/core" "github.com/rocboss/paopao-ce/pkg/json" "gopkg.in/resty.v1" ) -var ( - _ core.PhoneVerifyService = (*juheSmsServant)(nil) -) +var _ core.PhoneVerifyService = (*juheSmsServant)(nil) type juhePhoneCaptchaRsp struct { ErrorCode int `json:"error_code"` diff --git a/internal/dao/security/phone_verify_smsbao.go b/internal/dao/security/phone_verify_smsbao.go new file mode 100644 index 00000000..1c22a661 --- /dev/null +++ b/internal/dao/security/phone_verify_smsbao.go @@ -0,0 +1,116 @@ +package security + +import ( + "crypto/md5" + "encoding/hex" + "fmt" + "github.com/rocboss/paopao-ce/internal/conf" + "github.com/rocboss/paopao-ce/internal/core" + "io" + "net/http" + "net/url" + "strings" + "time" + + "github.com/cockroachdb/errors" +) + +var _ core.PhoneVerifyService = (*smsBaoServant)(nil) + +// smsBaoPhoneCaptchaRsp 短信宝响应结构体 +type smsBaoPhoneCaptchaRsp struct { + Status string `json:"status"` // 响应状态,如 success 或 error + Code string `json:"code"` // 状态码 + Msg string `json:"msg"` // 状态消息 +} + +// smsBaoServant 短信宝服务结构体 +type smsBaoServant struct { + gateway string + username string + password string + sign string +} + +// SendPhoneCaptcha 发送短信验证码 +func (s *smsBaoServant) SendPhoneCaptcha(phone string, captcha string, expire time.Duration) error { + // 对密码进行 MD5 加密 + hasher := md5.New() + _, writeString := io.WriteString(hasher, s.password) + if writeString != nil { + return writeString + } + encryptedPassword := hex.EncodeToString(hasher.Sum(nil)) + + // 构建短信内容 + content := fmt.Sprintf("%s您的验证码是:%s,有效期 %d 分钟。就算猫娘来你家也不要告诉她。", s.sign, captcha, int(expire.Minutes())) + + // 对内容进行 URL 编码 + encodedContent := url.QueryEscape(content) + + // 构建请求 URL + requestURL := fmt.Sprintf("%s?u=%s&p=%s&m=%s&c=%s", s.gateway, s.username, encryptedPassword, phone, encodedContent) + + // 发送 HTTP 请求 + resp, err := http.Get(requestURL) + if err != nil { + return err + } + defer resp.Body.Close() + + // 检查响应状态码 + if resp.StatusCode != http.StatusOK { + return errors.New(resp.Status) + } + + // 读取响应内容 + body, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + + // 解析响应 + result := &smsBaoPhoneCaptchaRsp{} + // 假设响应是简单的 text/plain 格式,按行分割 + lines := strings.Split(string(body), "\n") + if len(lines) >= 1 { + result.Code = lines[0] + if len(lines) >= 2 { + result.Msg = strings.Join(lines[1:], "\n") + } + } + + // 处理响应结果 + switch result.Code { + case "0": + return nil + case "30": + return errors.New("密码错误!") + case "40": + return errors.New("账号不存在!") + case "41": + return errors.New("余额不足!") + case "42": + return errors.New("帐号过期!") + case "43": + return errors.New("IP地址限制!") + case "50": + return errors.New("内容含有敏感词!") + case "51": + return errors.New("手机号码不正确!") + case "-1": + return errors.New("手机号码不正确或缺少参数!") + default: + return errors.New(fmt.Sprintf("未知错误:%s - %s", result.Code, result.Msg)) + } +} + +// newSmsBaoServant 创建短信宝服务实例 +func newSmsBaoServant() *smsBaoServant { + return &smsBaoServant{ + gateway: conf.SmsBaoSetting.Gateway, + username: conf.SmsBaoSetting.Username, + password: conf.SmsBaoSetting.Password, + sign: conf.SmsBaoSetting.Sign, + } +} diff --git a/internal/dao/security/security.go b/internal/dao/security/security.go index 1556ad73..e6ded0fa 100644 --- a/internal/dao/security/security.go +++ b/internal/dao/security/security.go @@ -12,6 +12,8 @@ func NewPhoneVerifyService() core.PhoneVerifyService { switch strings.ToLower(smsVendor) { case "smsjuhe": return newJuheSmsServant() + case "smsbao": + return newSmsBaoServant() default: return newJuheSmsServant() }