From a083fdca4670272d8182412b20c6ec695b57e63e Mon Sep 17 00:00:00 2001 From: Mrx <18278715334@163.com> Date: Sat, 28 Feb 2026 14:15:07 +0800 Subject: [PATCH] f --- .../internal/handler/auth/sendsmshandler.go | 4 +- .../api/internal/logic/auth/sendsmslogic.go | 30 ++++---- pkg/captcha/aliyun.go | 75 ++++++++++++++++++- 3 files changed, 91 insertions(+), 18 deletions(-) diff --git a/app/main/api/internal/handler/auth/sendsmshandler.go b/app/main/api/internal/handler/auth/sendsmshandler.go index 827674b..af643c5 100644 --- a/app/main/api/internal/handler/auth/sendsmshandler.go +++ b/app/main/api/internal/handler/auth/sendsmshandler.go @@ -25,8 +25,10 @@ func SendSmsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { } // 获取客户端真实 IP clientIP := getClientIP(r) + // 获取 User-Agent 用于判断微信环境 + userAgent := r.Header.Get("User-Agent") l := auth.NewSendSmsLogic(r.Context(), svcCtx) - err := l.SendSms(&req, clientIP) + err := l.SendSms(&req, clientIP, userAgent) result.HttpResult(r, w, nil, err) } } diff --git a/app/main/api/internal/logic/auth/sendsmslogic.go b/app/main/api/internal/logic/auth/sendsmslogic.go index 3941b0f..cc98ba0 100644 --- a/app/main/api/internal/logic/auth/sendsmslogic.go +++ b/app/main/api/internal/logic/auth/sendsmslogic.go @@ -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 encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey) - if err != nil { - return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 加密手机号失败: %v", err) - } + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 加密手机号失败: %v", err) + } - // 1. 滑块验证码校验(可选) - cfg := l.svcCtx.Config.Captcha - captchaResult := captcha.VerifyOptional(captcha.Config{ - AccessKeyID: cfg.AccessKeyID, - AccessKeySecret: cfg.AccessKeySecret, - EndpointURL: cfg.EndpointURL, - SceneID: cfg.SceneID, - }, req.CaptchaVerifyParam) + // 1. 滑块验证码校验(可选,支持微信环境跳过验证) + cfg := l.svcCtx.Config.Captcha + captchaResult := captcha.VerifyOptionalWithUserAgent(captcha.Config{ + AccessKeyID: cfg.AccessKeyID, + AccessKeySecret: cfg.AccessKeySecret, + EndpointURL: cfg.EndpointURL, + SceneID: cfg.SceneID, + }, req.CaptchaVerifyParam, userAgent) - if captchaResult.VerifyErr != nil { - return captchaResult.VerifyErr - } + if captchaResult.VerifyErr != nil { + return captchaResult.VerifyErr + } // 2. 防刷策略 if captchaResult.Skipped { diff --git a/pkg/captcha/aliyun.go b/pkg/captcha/aliyun.go index 92d8e5e..88d79c1 100644 --- a/pkg/captcha/aliyun.go +++ b/pkg/captcha/aliyun.go @@ -2,6 +2,7 @@ package captcha import ( "os" + "strings" "github.com/pkg/errors" @@ -26,7 +27,77 @@ type VerifyResult struct { 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,由调用方决定后续处理 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 { result := VerifyOptional(cfg, captchaVerifyParam) if result.VerifyErr != nil {