This commit is contained in:
Mrx
2026-02-28 14:15:07 +08:00
parent 1ea5ff1d88
commit a083fdca46
3 changed files with 91 additions and 18 deletions

View File

@@ -25,8 +25,10 @@ func SendSmsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
} }
// 获取客户端真实 IP // 获取客户端真实 IP
clientIP := getClientIP(r) clientIP := getClientIP(r)
// 获取 User-Agent 用于判断微信环境
userAgent := r.Header.Get("User-Agent")
l := auth.NewSendSmsLogic(r.Context(), svcCtx) l := auth.NewSendSmsLogic(r.Context(), svcCtx)
err := l.SendSms(&req, clientIP) err := l.SendSms(&req, clientIP, userAgent)
result.HttpResult(r, w, nil, err) result.HttpResult(r, w, nil, err)
} }
} }

View File

@@ -35,25 +35,25 @@ func NewSendSmsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SendSmsLo
} }
} }
func (l *SendSmsLogic) SendSms(req *types.SendSmsReq, clientIP string) error { func (l *SendSmsLogic) SendSms(req *types.SendSmsReq, clientIP string, userAgent string) error {
secretKey := l.svcCtx.Config.Encrypt.SecretKey secretKey := l.svcCtx.Config.Encrypt.SecretKey
encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey) encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey)
if err != nil { if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 加密手机号失败: %v", err) return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 加密手机号失败: %v", err)
} }
// 1. 滑块验证码校验(可选) // 1. 滑块验证码校验(可选,支持微信环境跳过验证
cfg := l.svcCtx.Config.Captcha cfg := l.svcCtx.Config.Captcha
captchaResult := captcha.VerifyOptional(captcha.Config{ captchaResult := captcha.VerifyOptionalWithUserAgent(captcha.Config{
AccessKeyID: cfg.AccessKeyID, AccessKeyID: cfg.AccessKeyID,
AccessKeySecret: cfg.AccessKeySecret, AccessKeySecret: cfg.AccessKeySecret,
EndpointURL: cfg.EndpointURL, EndpointURL: cfg.EndpointURL,
SceneID: cfg.SceneID, SceneID: cfg.SceneID,
}, req.CaptchaVerifyParam) }, req.CaptchaVerifyParam, userAgent)
if captchaResult.VerifyErr != nil { if captchaResult.VerifyErr != nil {
return captchaResult.VerifyErr return captchaResult.VerifyErr
} }
// 2. 防刷策略 // 2. 防刷策略
if captchaResult.Skipped { if captchaResult.Skipped {

View File

@@ -2,6 +2,7 @@ package captcha
import ( import (
"os" "os"
"strings"
"github.com/pkg/errors" "github.com/pkg/errors"
@@ -26,7 +27,77 @@ type VerifyResult struct {
VerifyErr error // 验证错误(如果有) VerifyErr error // 验证错误(如果有)
} }
// VerifyOptional 可选验证阿里云验证码 // isWeChatUserAgent 检测是否为微信浏览器的 User-Agent
func isWeChatUserAgent(userAgent string) bool {
if userAgent == "" {
return false
}
ua := strings.ToLower(userAgent)
return strings.Contains(ua, "micromessenger") || strings.Contains(ua, "wechat")
}
// VerifyOptionalWithUserAgent 可选验证阿里云验证码(支持微信环境跳过验证)
// 当 captchaVerifyParam 为空时返回 Skipped=true由调用方决定后续处理
// 当 userAgent 为微信浏览器时,跳过验证返回 Verified=true
func VerifyOptionalWithUserAgent(cfg Config, captchaVerifyParam string, userAgent string) VerifyResult {
// 开发环境可跳过验证
if os.Getenv("ENV") == "development" {
return VerifyResult{Verified: true, Skipped: false}
}
// 微信环境下跳过图形验证码校验
if isWeChatUserAgent(userAgent) {
return VerifyResult{Verified: true, Skipped: false}
}
// 没有滑块验证码参数,报错提示
if captchaVerifyParam == "" {
return VerifyResult{VerifyErr: errors.Wrapf(xerr.NewErrMsg("图形验证码校验失败"), "empty captchaVerifyParam")}
}
clientCfg := &openapi.Config{
AccessKeyId: tea.String(cfg.AccessKeyID),
AccessKeySecret: tea.String(cfg.AccessKeySecret),
}
clientCfg.Endpoint = tea.String(cfg.EndpointURL)
client, err := captcha20230305.NewClient(clientCfg)
if err != nil {
return VerifyResult{VerifyErr: errors.Wrapf(err, "create aliyun captcha client error")}
}
req := &captcha20230305.VerifyIntelligentCaptchaRequest{
SceneId: tea.String(cfg.SceneID),
CaptchaVerifyParam: tea.String(captchaVerifyParam),
}
resp, err := client.VerifyIntelligentCaptcha(req)
if err != nil {
return VerifyResult{VerifyErr: errors.Wrapf(err, "verify aliyun captcha error")}
}
if tea.BoolValue(resp.Body.Result.VerifyResult) {
return VerifyResult{Verified: true}
}
return VerifyResult{
VerifyErr: errors.Wrapf(xerr.NewErrMsg("图形验证码校验失败"), "aliyun captcha verify failed: code=%s, msg=%s",
tea.StringValue(resp.Body.Code), tea.StringValue(resp.Body.Message)),
}
}
// VerifyWithUserAgent 验证阿里云验证码(必须提供验证码参数,支持微信环境跳过验证)
func VerifyWithUserAgent(cfg Config, captchaVerifyParam string, userAgent string) error {
result := VerifyOptionalWithUserAgent(cfg, captchaVerifyParam, userAgent)
if result.VerifyErr != nil {
return result.VerifyErr
}
if result.Skipped {
return errors.Wrapf(xerr.NewErrMsg("图形验证码校验失败"), "empty captchaVerifyParam")
}
return nil
}
// VerifyOptional 可选验证阿里云验证码(兼容旧接口,不判断 User-Agent
// 当 captchaVerifyParam 为空时返回 Skipped=true由调用方决定后续处理 // 当 captchaVerifyParam 为空时返回 Skipped=true由调用方决定后续处理
func VerifyOptional(cfg Config, captchaVerifyParam string) VerifyResult { func VerifyOptional(cfg Config, captchaVerifyParam string) VerifyResult {
// 开发环境可跳过验证 // 开发环境可跳过验证
@@ -69,7 +140,7 @@ func VerifyOptional(cfg Config, captchaVerifyParam string) VerifyResult {
} }
} }
// Verify 验证阿里云验证码(必须提供验证码参数) // Verify 验证阿里云验证码(必须提供验证码参数,兼容旧接口
func Verify(cfg Config, captchaVerifyParam string) error { func Verify(cfg Config, captchaVerifyParam string) error {
result := VerifyOptional(cfg, captchaVerifyParam) result := VerifyOptional(cfg, captchaVerifyParam)
if result.VerifyErr != nil { if result.VerifyErr != nil {