From acd0dd5b9c14da478a645ab03ef4673728372e6d Mon Sep 17 00:00:00 2001 From: liangzai <2440983361@qq.com> Date: Tue, 3 Jun 2025 12:24:15 +0800 Subject: [PATCH] add sms authorization --- app/user/cmd/api/desc/user.api | 16 +- .../handler/auth/smsauthorizationhandler.go | 29 ++++ app/user/cmd/api/internal/handler/routes.go | 6 + .../logic/auth/initfaceverifylogic.go | 38 ++--- .../logic/auth/smsauthorizationlogic.go | 147 ++++++++++++++++++ .../internal/logic/pay/alipaycallbacklogic.go | 1 - .../api/internal/logic/pay/paymentlogic.go | 1 - .../logic/pay/wechatpaycallbacklogic.go | 46 ------ app/user/cmd/api/internal/types/types.go | 13 +- app/user/model/authorizationModel_gen.go | 26 ++-- app/user/model/vars.go | 3 +- deploy/script/gen_models.js | 8 +- deploy/script/gen_models.ps1 | 8 +- 13 files changed, 252 insertions(+), 90 deletions(-) create mode 100644 app/user/cmd/api/internal/handler/auth/smsauthorizationhandler.go create mode 100644 app/user/cmd/api/internal/logic/auth/smsauthorizationlogic.go diff --git a/app/user/cmd/api/desc/user.api b/app/user/cmd/api/desc/user.api index 2831ade..14f080e 100644 --- a/app/user/cmd/api/desc/user.api +++ b/app/user/cmd/api/desc/user.api @@ -165,12 +165,16 @@ service main { @doc "第三方拒绝授权" @handler rejectAuthorization post /rejectAuthorization (RejectAuthorizationReq) returns (RejectAuthorizationResp) + + @doc "短信授权" + @handler smsAuthorization + post /smsAuthorization (SmsAuthorizationReq) returns (SmsAuthorizationResp) } type ( sendSmsReq { Mobile string `json:"mobile" validate:"required,mobile"` - ActionType string `json:"actionType" validate:"required,oneof=login register query agentApply bindMobile realName"` + ActionType string `json:"actionType" validate:"required,oneof=login register query agentApply bindMobile realName authorization"` } // 发起人脸认证请求 @@ -202,6 +206,16 @@ type ( } RejectAuthorizationResp { } + + SmsAuthorizationReq { + OrderNo string `json:"order_no" validate:"required"` // 订单号 + Mobile string `json:"mobile" validate:"required,mobile"` + Code string `json:"code" validate:"required"` + } + SmsAuthorizationResp { + Passed bool `json:"passed"` // 是否通过 + OrderID int64 `json:"order_id"` + } ) //============================> notification v1 <============================ @server ( diff --git a/app/user/cmd/api/internal/handler/auth/smsauthorizationhandler.go b/app/user/cmd/api/internal/handler/auth/smsauthorizationhandler.go new file mode 100644 index 0000000..cbba05c --- /dev/null +++ b/app/user/cmd/api/internal/handler/auth/smsauthorizationhandler.go @@ -0,0 +1,29 @@ +package auth + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "qnc-server/app/user/cmd/api/internal/logic/auth" + "qnc-server/app/user/cmd/api/internal/svc" + "qnc-server/app/user/cmd/api/internal/types" + "qnc-server/common/result" + "qnc-server/pkg/lzkit/validator" +) + +func SmsAuthorizationHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.SmsAuthorizationReq + if err := httpx.Parse(r, &req); err != nil { + result.ParamErrorResult(r, w, err) + return + } + if err := validator.Validate(req); err != nil { + result.ParamValidateErrorResult(r, w, err) + return + } + l := auth.NewSmsAuthorizationLogic(r.Context(), svcCtx) + resp, err := l.SmsAuthorization(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/user/cmd/api/internal/handler/routes.go b/app/user/cmd/api/internal/handler/routes.go index ba5da01..82fb746 100644 --- a/app/user/cmd/api/internal/handler/routes.go +++ b/app/user/cmd/api/internal/handler/routes.go @@ -173,6 +173,12 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { Path: "/sendSms", Handler: auth.SendSmsHandler(serverCtx), }, + { + // 短信授权 + Method: http.MethodPost, + Path: "/smsAuthorization", + Handler: auth.SmsAuthorizationHandler(serverCtx), + }, }, rest.WithPrefix("/api/v1/auth"), ) diff --git a/app/user/cmd/api/internal/logic/auth/initfaceverifylogic.go b/app/user/cmd/api/internal/logic/auth/initfaceverifylogic.go index 06e4809..e3e6f00 100644 --- a/app/user/cmd/api/internal/logic/auth/initfaceverifylogic.go +++ b/app/user/cmd/api/internal/logic/auth/initfaceverifylogic.go @@ -64,25 +64,23 @@ func (l *InitFaceVerifyLogic) InitFaceVerify(req *types.InitFaceVerifyReq) (resp if decodeErr != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询授权信息失败: %v", decodeErr) } - if authorization.GrantType == "face" { - authorizationFaceBuilder := l.svcCtx.AuthorizationFaceModel.SelectBuilder(). - Where("authorization_id = ? AND status = ? AND certify_id != '' AND certify_url != ''", - authorization.Id, model.AuthorizationStatusPending). - OrderBy("create_time DESC"). - Limit(1) - existingFaces, err := l.svcCtx.AuthorizationFaceModel.FindAll(l.ctx, authorizationFaceBuilder, "") - if err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询人脸认证记录失败: %v", err) - } - if len(existingFaces) > 0 { - // 如果存在记录,直接返回最新的认证信息 - return &types.InitFaceVerifyResp{ - CertifyId: existingFaces[0].CertifyId, - CertifyUrl: existingFaces[0].CertifyUrl, - }, nil - } - + authorizationFaceBuilder := l.svcCtx.AuthorizationFaceModel.SelectBuilder(). + Where("authorization_id = ? AND status = ? AND certify_id != '' AND certify_url != ''", + authorization.Id, model.AuthorizationStatusPending). + OrderBy("create_time DESC"). + Limit(1) + existingFaces, err := l.svcCtx.AuthorizationFaceModel.FindAll(l.ctx, authorizationFaceBuilder, "") + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询人脸认证记录失败: %v", err) } + if len(existingFaces) > 0 { + // 如果存在记录,直接返回最新的认证信息 + return &types.InitFaceVerifyResp{ + CertifyId: existingFaces[0].CertifyId, + CertifyUrl: existingFaces[0].CertifyUrl, + }, nil + } + outerOrderNo := genOuterOrderNo() name, err := crypto.AesDecrypt(authorization.TargetName, key) if err != nil { @@ -106,6 +104,10 @@ func (l *InitFaceVerifyLogic) InitFaceVerify(req *types.InitFaceVerifyReq) (resp Int64: req.AuthType, Valid: true, } + authorization.GrantType = sql.NullString{ + String: model.AuthorizationGrantTypeFace, + Valid: true, + } _, err = l.svcCtx.AuthorizationModel.Update(l.ctx, session, authorization) if err != nil { return err diff --git a/app/user/cmd/api/internal/logic/auth/smsauthorizationlogic.go b/app/user/cmd/api/internal/logic/auth/smsauthorizationlogic.go new file mode 100644 index 0000000..ef04846 --- /dev/null +++ b/app/user/cmd/api/internal/logic/auth/smsauthorizationlogic.go @@ -0,0 +1,147 @@ +package auth + +import ( + "context" + "database/sql" + "encoding/hex" + "fmt" + + "qnc-server/app/user/cmd/api/internal/service" + "qnc-server/app/user/cmd/api/internal/svc" + "qnc-server/app/user/cmd/api/internal/types" + "qnc-server/app/user/model" + "qnc-server/common/xerr" + "qnc-server/pkg/lzkit/crypto" + + "github.com/bytedance/sonic" + "github.com/pkg/errors" + "github.com/redis/go-redis/v9" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +type SmsAuthorizationLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewSmsAuthorizationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SmsAuthorizationLogic { + return &SmsAuthorizationLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *SmsAuthorizationLogic) SmsAuthorization(req *types.SmsAuthorizationReq) (resp *types.SmsAuthorizationResp, err error) { + order, err := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, req.OrderNo) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询订单失败: %v", err) + } + + if order.Status != "paid" { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "订单未支付") + } + + authorization, err := l.svcCtx.AuthorizationModel.FindOneByOrderId(l.ctx, order.Id) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询授权信息失败: %v", err) + } + + if authorization.Status != "pending" { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "授权信息状态不正确") + } + key, decodeErr := hex.DecodeString(l.svcCtx.Config.Encrypt.SecretKey) + if decodeErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询授权信息失败: %v", decodeErr) + } + name, err := crypto.AesDecrypt(authorization.TargetName, key) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解密姓名失败: %v", err) + } + idCard, err := crypto.AesDecrypt(authorization.TargetIdcard, key) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解密身份证号失败: %v", err) + } + threeFactorVerificationResp, err := l.svcCtx.VerificationService.ThreeFactorVerification(service.ThreeFactorVerificationRequest{ + Mobile: req.Mobile, + Name: string(name), + IDCard: string(idCard), + }) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "三要素验证失败: %v", err) + } + if !threeFactorVerificationResp.Passed { + return nil, errors.Wrapf(xerr.NewErrMsg("该手机号码不是被查询人的手机号"), "三要素验证失败") + } + secretKey := l.svcCtx.Config.Encrypt.SecretKey + encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密手机号失败: %v", err) + } + // 检查手机号是否在一分钟内已发送过验证码 + redisKey := fmt.Sprintf("%s:%s", "authorization", encryptedMobile) + cacheCode, err := l.svcCtx.Redis.Get(redisKey) + if err != nil { + if errors.Is(err, redis.Nil) { + return nil, errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "验证码过期: %s", encryptedMobile) + } + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "读取验证码redis缓存失败, mobile: %s, err: %+v", encryptedMobile, err) + } + if cacheCode != req.Code { + return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "验证码不正确: %s", encryptedMobile) + } + err = l.svcCtx.AuthorizationModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { + + order, err := l.svcCtx.OrderModel.FindOne(ctx, authorization.OrderId) + if err != nil { + return errors.Wrapf(err, "查询订单失败") + } + redisKey := fmt.Sprintf(types.QueryCacheKey, order.UserId, order.OrderNo) + cache, cacheErr := l.svcCtx.Redis.GetCtx(ctx, redisKey) + if cacheErr != nil { + return fmt.Errorf("获取缓存内容失败: %+v", cacheErr) + } + var data types.QueryCacheLoad + err = sonic.Unmarshal([]byte(cache), &data) + if err != nil { + return fmt.Errorf("解析缓存内容失败: %+v", err) + } + // 插入新queryModel + query := &model.Query{ + OrderId: authorization.OrderId, + UserId: authorization.UserId, + ProductId: order.ProductId, + QueryParams: data.Params, + QueryState: "pending", + } + _, insertQueryErr := l.svcCtx.QueryModel.Insert(ctx, session, query) + if insertQueryErr != nil { + return errors.Wrapf(insertQueryErr, "保存查询失败") + } + + authorization.GrantType = sql.NullString{ + String: model.AuthorizationGrantTypeSms, + Valid: true, + } + // 更新主授权状态 + authorization.Status = model.AuthorizationStatusSuccess + _, err = l.svcCtx.AuthorizationModel.Update(ctx, session, authorization) + if err != nil { + return errors.Wrapf(err, "更新授权状态失败") + } + if asyncErr := l.svcCtx.AsynqService.SendQueryTask(authorization.OrderId); asyncErr != nil { + logx.Errorf("异步任务调度失败: %v", asyncErr) + return asyncErr + } + return nil + }) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "更新授权信息失败: %v", err) + } + return &types.SmsAuthorizationResp{ + OrderID: authorization.OrderId, + Passed: true, + }, nil +} diff --git a/app/user/cmd/api/internal/logic/pay/alipaycallbacklogic.go b/app/user/cmd/api/internal/logic/pay/alipaycallbacklogic.go index 3a36a5e..a116383 100644 --- a/app/user/cmd/api/internal/logic/pay/alipaycallbacklogic.go +++ b/app/user/cmd/api/internal/logic/pay/alipaycallbacklogic.go @@ -135,7 +135,6 @@ func (l *AlipayCallbackLogic) handleQueryOrderPayment(w http.ResponseWriter, not UserId: order.UserId, TargetName: encryptName, TargetIdcard: encryptIdcard, - GrantType: model.GrantTypeFace, Status: model.AuthorizationStatusPending, }) if err != nil { diff --git a/app/user/cmd/api/internal/logic/pay/paymentlogic.go b/app/user/cmd/api/internal/logic/pay/paymentlogic.go index df9da44..47dec0f 100644 --- a/app/user/cmd/api/internal/logic/pay/paymentlogic.go +++ b/app/user/cmd/api/internal/logic/pay/paymentlogic.go @@ -205,7 +205,6 @@ func (l *PaymentLogic) QueryOrderPayment(req *types.PaymentReq, session sqlx.Ses UserId: order.UserId, TargetName: encryptName, TargetIdcard: encryptIdcard, - GrantType: model.GrantTypeFace, Status: model.AuthorizationStatusPending, }) if err != nil { diff --git a/app/user/cmd/api/internal/logic/pay/wechatpaycallbacklogic.go b/app/user/cmd/api/internal/logic/pay/wechatpaycallbacklogic.go index 97d87ae..3b90a92 100644 --- a/app/user/cmd/api/internal/logic/pay/wechatpaycallbacklogic.go +++ b/app/user/cmd/api/internal/logic/pay/wechatpaycallbacklogic.go @@ -132,7 +132,6 @@ func (l *WechatPayCallbackLogic) handleQueryOrderPayment(w http.ResponseWriter, UserId: order.UserId, TargetName: encryptName, TargetIdcard: encryptIdcard, - GrantType: model.GrantTypeFace, Status: model.AuthorizationStatusPending, }) if err != nil { @@ -233,51 +232,6 @@ func (l *WechatPayCallbackLogic) handleRefund(order *model.AgentMembershipRechar if refundErr != nil { return refundErr } - } else { - refund, refundErr := l.svcCtx.AlipayService.AliRefund(ctx, order.OrderNo, order.Amount) - if refundErr != nil { - return refundErr - } - if refund.IsSuccess() { - redisKey := fmt.Sprintf(types.QueryCacheKey, order.UserId, order.OrderNo) - cache, cacheErr := l.svcCtx.Redis.Get(redisKey) - if cacheErr != nil { - return fmt.Errorf("获取缓存内容失败: %+v", cacheErr) - } - var data types.QueryCacheLoad - err := json.Unmarshal([]byte(cache), &data) - if err != nil { - return fmt.Errorf("解析缓存内容失败: %+v", err) - } - secretKey := l.svcCtx.Config.Encrypt.SecretKey - key, decodeErr := hex.DecodeString(secretKey) - if decodeErr != nil { - return fmt.Errorf("获取AES密钥失败: %+v", decodeErr) - } - decryptData, aesdecryptErr := crypto.AesDecrypt(data.Params, key) - if aesdecryptErr != nil { - return fmt.Errorf("解密参数失败: %+v", aesdecryptErr) - } - var paramsMap map[string]string - if err := json.Unmarshal([]byte(decryptData), ¶msMap); err != nil { - return fmt.Errorf("解析参数失败: %+v", err) - } - _, err = l.svcCtx.AuthorizationModel.Insert(l.ctx, nil, &model.Authorization{ - OrderId: order.Id, - UserId: order.UserId, - TargetName: paramsMap["name"], - TargetIdcard: paramsMap["id_card"], - GrantType: model.GrantTypeFace, - Status: model.AuthorizationStatusPending, - }) - if err != nil { - logx.Errorf("支付宝支付回调,插入授权信息失败: %+v", err) - return fmt.Errorf("插入授权信息失败: %+v", err) - } - } else { - logx.Errorf("支付宝退款失败:%v", refundErr) - return refundErr - } } return nil } diff --git a/app/user/cmd/api/internal/types/types.go b/app/user/cmd/api/internal/types/types.go index 438bcbe..d9ca433 100644 --- a/app/user/cmd/api/internal/types/types.go +++ b/app/user/cmd/api/internal/types/types.go @@ -553,6 +553,17 @@ type SaveAgentMembershipUserConfigReq struct { PriceRatio float64 `json:"price_ratio"` } +type SmsAuthorizationReq struct { + OrderNo string `json:"order_no" validate:"required"` // 订单号 + Mobile string `json:"mobile" validate:"required,mobile"` + Code string `json:"code" validate:"required"` +} + +type SmsAuthorizationResp struct { + Passed bool `json:"passed"` // 是否通过 + OrderID int64 `json:"order_id"` +} + type TimeRangeReport struct { Commission float64 `json:"commission"` // 佣金 Report int `json:"report"` // 报告量 @@ -627,5 +638,5 @@ type GetAppVersionResp struct { type SendSmsReq struct { Mobile string `json:"mobile" validate:"required,mobile"` - ActionType string `json:"actionType" validate:"required,oneof=login register query agentApply bindMobile realName"` + ActionType string `json:"actionType" validate:"required,oneof=login register query agentApply bindMobile realName authorization"` } diff --git a/app/user/model/authorizationModel_gen.go b/app/user/model/authorizationModel_gen.go index da099d9..8297267 100644 --- a/app/user/model/authorizationModel_gen.go +++ b/app/user/model/authorizationModel_gen.go @@ -56,19 +56,19 @@ type ( } Authorization struct { - Id int64 `db:"id"` - OrderId int64 `db:"order_id"` - GrantType string `db:"grant_type"` // 授权类型:face人脸,后续可扩展 - AuthType sql.NullInt64 `db:"auth_type"` // 1本人,2他人 - UserId int64 `db:"user_id"` - TargetName string `db:"target_name"` - TargetIdcard string `db:"target_idcard"` - Status string `db:"status"` // 授权状态:pending待授权、success已授权、expired已失效、revoked已撤销 - CreateTime time.Time `db:"create_time"` - UpdateTime time.Time `db:"update_time"` - DeleteTime sql.NullTime `db:"delete_time"` // 删除时间 - DelState int64 `db:"del_state"` - Version int64 `db:"version"` // 版本号 + Id int64 `db:"id"` + OrderId int64 `db:"order_id"` + GrantType sql.NullString `db:"grant_type"` // 授权类型:face人脸,sms短信 + AuthType sql.NullInt64 `db:"auth_type"` // 1本人,2他人 + UserId int64 `db:"user_id"` + TargetName string `db:"target_name"` + TargetIdcard string `db:"target_idcard"` + Status string `db:"status"` // 授权状态:pending待授权、success已授权、expired已失效、revoked已撤销、rejected被拒绝 + CreateTime time.Time `db:"create_time"` + UpdateTime time.Time `db:"update_time"` + DeleteTime sql.NullTime `db:"delete_time"` // 删除时间 + DelState int64 `db:"del_state"` + Version int64 `db:"version"` // 版本号 } ) diff --git a/app/user/model/vars.go b/app/user/model/vars.go index cb455aa..5ea6b65 100644 --- a/app/user/model/vars.go +++ b/app/user/model/vars.go @@ -48,7 +48,8 @@ const ( ) const ( - GrantTypeFace string = "face" + AuthorizationGrantTypeFace = "face" + AuthorizationGrantTypeSms = "sms" ) const ( AuthorizationStatusPending = "pending" diff --git a/deploy/script/gen_models.js b/deploy/script/gen_models.js index 4e7884f..c0caab1 100644 --- a/deploy/script/gen_models.js +++ b/deploy/script/gen_models.js @@ -21,10 +21,10 @@ const config = { targetDir: '../../app/user/model', // 表名列表 tables: [ - 'agent', + // 'agent', // 'agent_active_stat', - 'agent_audit', - 'agent_real_name' + // 'agent_audit', + // 'agent_real_name' // 'agent_closure', // 'agent_commission', // 'agent_commission_deduction', @@ -47,7 +47,7 @@ const config = { // 'user', // 'user_auth', // 'example', - // 'authorization', + 'authorization', // 'authorization_face' ] }; diff --git a/deploy/script/gen_models.ps1 b/deploy/script/gen_models.ps1 index 705ab7f..e8a9ae2 100644 --- a/deploy/script/gen_models.ps1 +++ b/deploy/script/gen_models.ps1 @@ -49,10 +49,10 @@ $TARGET_DIR = "../../app/user/model" # 表名列表 - 每个元素后必须有逗号分隔 $tables = @( - "agent", + # "agent", # "agent_active_stat", - "agent_audit", - "agent_real_name" + # "agent_audit", + # "agent_real_name" # "agent_closure", # "agent_commission", # "agent_commission_deduction", @@ -75,7 +75,7 @@ $tables = @( # "user", # "user_auth", # "example", - # "authorization", + "authorization" # "authorization_face" )