diff --git a/app/user/cmd/api/desc/user.api b/app/user/cmd/api/desc/user.api index 9730149..bdc4e04 100644 --- a/app/user/cmd/api/desc/user.api +++ b/app/user/cmd/api/desc/user.api @@ -125,12 +125,23 @@ service main { @handler cancelOut post /user/cancelOut + + @doc "绑定手机号" + @handler bindMobile + post /user/bindMobile (BindMobileReq) returns (BindMobileResp) } type ( UserInfoResp { UserInfo User `json:"userInfo"` } + + BindMobileReq { + Mobile string `json:"mobile" validate:"required,mobile"` + Code string `json:"code" validate:"required"` + } + BindMobileResp { + } ) //============================> auth v1 <============================ @@ -147,7 +158,7 @@ service main { type ( sendSmsReq { Mobile string `json:"mobile" validate:"required,mobile"` - ActionType string `json:"actionType" validate:"required,oneof=login register query agentApply"` + ActionType string `json:"actionType" validate:"required,oneof=login register query agentApply bindMobile"` } ) diff --git a/app/user/cmd/api/internal/handler/routes.go b/app/user/cmd/api/internal/handler/routes.go index 28af3ff..8f7d3a2 100644 --- a/app/user/cmd/api/internal/handler/routes.go +++ b/app/user/cmd/api/internal/handler/routes.go @@ -370,6 +370,12 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { server.AddRoutes( []rest.Route{ + { + // 绑定手机号 + Method: http.MethodPost, + Path: "/user/bindMobile", + Handler: user.BindMobileHandler(serverCtx), + }, { Method: http.MethodPost, Path: "/user/cancelOut", diff --git a/app/user/cmd/api/internal/handler/user/bindmobilehandler.go b/app/user/cmd/api/internal/handler/user/bindmobilehandler.go new file mode 100644 index 0000000..80c3628 --- /dev/null +++ b/app/user/cmd/api/internal/handler/user/bindmobilehandler.go @@ -0,0 +1,29 @@ +package user + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "qnc-server/app/user/cmd/api/internal/logic/user" + "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 BindMobileHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.BindMobileReq + 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 := user.NewBindMobileLogic(r.Context(), svcCtx) + resp, err := l.BindMobile(&req) + result.HttpResult(r, w, resp, err) + } +} diff --git a/app/user/cmd/api/internal/logic/user/bindmobilelogic.go b/app/user/cmd/api/internal/logic/user/bindmobilelogic.go new file mode 100644 index 0000000..657d393 --- /dev/null +++ b/app/user/cmd/api/internal/logic/user/bindmobilelogic.go @@ -0,0 +1,97 @@ +package user + +import ( + "context" + "database/sql" + "fmt" + + "qnc-server/app/user/cmd/api/internal/svc" + "qnc-server/app/user/cmd/api/internal/types" + "qnc-server/app/user/model" + "qnc-server/common/ctxdata" + "qnc-server/common/xerr" + "qnc-server/pkg/lzkit/crypto" + + "github.com/pkg/errors" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/redis" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +type BindMobileLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewBindMobileLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BindMobileLogic { + return &BindMobileLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *BindMobileLogic) BindMobile(req *types.BindMobileReq) (resp *types.BindMobileResp, err error) { + userID, getUserIdErr := ctxdata.GetUidFromCtx(l.ctx) + if getUserIdErr != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "绑定手机号, %v", getUserIdErr) + } + 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", "bindMobile", 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) + } + + userModel, err := l.svcCtx.UserModel.FindOne(l.ctx, userID) + if err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "绑定手机号, %v", err) + } + if userModel.Mobile.Valid && userModel.Mobile.String != "" { + return nil, errors.Wrapf(xerr.NewErrMsg("账号已绑定手机号,无法再次绑定"), "绑定手机号, %v", err) + } + userAuthModel, err := l.svcCtx.UserAuthModel.FindOneByUserIdAuthType(l.ctx, userID, model.UserAuthTypeH5Mobile) + if err != nil && !errors.Is(err, model.ErrNotFound) { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "绑定手机号, %v", err) + } + if userAuthModel != nil { + return nil, errors.Wrapf(xerr.NewErrMsg("账号已绑定手机号,无法再次绑定"), "绑定手机号, %v", err) + } + + var userAuth model.UserAuth + userAuth.UserId = userID + userAuth.AuthType = model.UserAuthTypeH5Mobile + userAuth.AuthKey = encryptedMobile + transErr := l.svcCtx.UserAuthModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { + _, err = l.svcCtx.UserAuthModel.Insert(ctx, session, &userAuth) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "绑定手机号, %v", err) + } + userModel.Mobile = sql.NullString{ + String: encryptedMobile, + Valid: true, + } + _, err = l.svcCtx.UserModel.Update(l.ctx, session, userModel) + if err != nil { + return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "绑定手机号, %v", err) + } + return nil + }) + if transErr != nil { + return nil, transErr + } + + return &types.BindMobileResp{}, nil +} diff --git a/app/user/cmd/api/internal/types/types.go b/app/user/cmd/api/internal/types/types.go index 65fd892..f59c4c0 100644 --- a/app/user/cmd/api/internal/types/types.go +++ b/app/user/cmd/api/internal/types/types.go @@ -98,6 +98,14 @@ type AgentProductConfigResp struct { AgentProductConfig []AgentProductConfig } +type BindMobileReq struct { + Mobile string `json:"mobile" validate:"required,mobile"` + Code string `json:"code" validate:"required"` +} + +type BindMobileResp struct { +} + type Commission struct { ProductName string `json:"product_name"` Amount float64 `json:"amount"` @@ -473,5 +481,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"` + ActionType string `json:"actionType" validate:"required,oneof=login register query agentApply bindMobile"` }