add sms authorization
This commit is contained in:
parent
bebabce346
commit
acd0dd5b9c
@ -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 (
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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"),
|
||||
)
|
||||
|
@ -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
|
||||
|
147
app/user/cmd/api/internal/logic/auth/smsauthorizationlogic.go
Normal file
147
app/user/cmd/api/internal/logic/auth/smsauthorizationlogic.go
Normal file
@ -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
|
||||
}
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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"`
|
||||
}
|
||||
|
@ -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"` // 版本号
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -48,7 +48,8 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
GrantTypeFace string = "face"
|
||||
AuthorizationGrantTypeFace = "face"
|
||||
AuthorizationGrantTypeSms = "sms"
|
||||
)
|
||||
const (
|
||||
AuthorizationStatusPending = "pending"
|
||||
|
@ -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'
|
||||
]
|
||||
};
|
||||
|
@ -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"
|
||||
)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user