f
This commit is contained in:
@@ -36,24 +36,26 @@ func NewBindMobileLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BindMo
|
||||
}
|
||||
|
||||
func (l *BindMobileLogic) BindMobile(req *types.BindMobileReq) (resp *types.BindMobileResp, err error) {
|
||||
// 从上下文中获取当前登录态的用户声明(可能是临时用户或正式用户),包含UserId/AuthType/AuthKey
|
||||
l.Infof("[BindMobile] 开始 | mobile: %s", req.Mobile)
|
||||
|
||||
claims, err := ctxdata.GetClaimsFromCtx(l.ctx)
|
||||
if err != nil && !errors.Is(err, ctxdata.ErrNoInCtx) {
|
||||
l.Errorf("[BindMobile] 获取登录态失败: %v", err)
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败: %v", err)
|
||||
}
|
||||
|
||||
// 当前登录用户信息(用于后续合并/绑定)
|
||||
currentUserID := claims.UserId
|
||||
currentAuthType := claims.AuthType
|
||||
currentAuthKey := claims.AuthKey
|
||||
l.Infof("[BindMobile] 当前登录态 | userId: %s, authType: %s", currentUserID, currentAuthType)
|
||||
|
||||
// 加密手机号(所有手机号以密文存储)
|
||||
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)
|
||||
}
|
||||
// 非开发环境下校验短信验证码(从Redis读取并比对)
|
||||
l.Infof("[BindMobile] 手机号加密完成 | mobile: %s, encryptedMobile: %s", req.Mobile, encryptedMobile)
|
||||
|
||||
if os.Getenv("ENV") != "development" {
|
||||
redisKey := fmt.Sprintf("%s:%s", "bindMobile", encryptedMobile)
|
||||
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
|
||||
@@ -68,59 +70,62 @@ func (l *BindMobileLogic) BindMobile(req *types.BindMobileReq) (resp *types.Bind
|
||||
}
|
||||
}
|
||||
|
||||
// 通过加密后的手机号查找目标用户(手机号用户视为正式用户)
|
||||
// 通过加密后的手机号查找目标用户(可能命中缓存)
|
||||
targetUser, err := l.svcCtx.UserModel.FindOneByMobile(l.ctx, sql.NullString{String: encryptedMobile, Valid: true})
|
||||
if err != nil && !errors.Is(err, model.ErrNotFound) {
|
||||
l.Errorf("[BindMobile] FindOneByMobile 查询失败: %v", err)
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查找用户失败: %v", err)
|
||||
}
|
||||
|
||||
var finalUserID string
|
||||
if targetUser == nil {
|
||||
// 手机号不存在:直接将当前用户升级为正式用户(写入mobile与mobile认证)
|
||||
finalUserID = currentUserID
|
||||
l.Infof("[BindMobile] FindOneByMobile 未命中 | targetUser=nil, encryptedMobile: %s | 分支: 手机号不存在,当前用户升级为正式用户", encryptedMobile)
|
||||
finalUserID := currentUserID
|
||||
currentUser, err := l.svcCtx.UserModel.FindOne(l.ctx, currentUserID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查找当前用户失败: %v", err)
|
||||
}
|
||||
currentUser.Mobile = sql.NullString{String: encryptedMobile, Valid: true}
|
||||
if _, err := l.svcCtx.UserModel.Update(l.ctx, nil, currentUser); err != nil {
|
||||
l.Errorf("[BindMobile] 更新 user.mobile 失败 | userId: %s, err: %v", currentUserID, err)
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新手机号失败: %v", err)
|
||||
}
|
||||
// 记录mobile认证(确保后续可通过手机号登录)
|
||||
l.Infof("[BindMobile] user.mobile 已更新 | userId: %s", currentUserID)
|
||||
if _, err := l.svcCtx.UserAuthModel.Insert(l.ctx, nil, &model.UserAuth{Id: uuid.NewString(), UserId: finalUserID, AuthType: model.UserAuthTypeMobile, AuthKey: encryptedMobile}); err != nil {
|
||||
l.Errorf("[BindMobile] 插入 user_auth(mobile) 失败 | userId: %s, err: %v", finalUserID, err)
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建手机号认证失败: %v", err)
|
||||
}
|
||||
// 使 UserModel.FindOneByMobile 的缓存失效,避免后续流程(如代理注册)仍命中“手机号不存在”的旧缓存
|
||||
l.Infof("[BindMobile] user_auth(mobile) 已插入 | userId: %s", finalUserID)
|
||||
userMobileCacheKey := fmt.Sprintf("cache:bdqr:user:mobile:%v", sql.NullString{String: encryptedMobile, Valid: true})
|
||||
_, _ = l.svcCtx.Redis.DelCtx(l.ctx, userMobileCacheKey)
|
||||
// 发放token(userType会根据mobile字段动态计算)
|
||||
l.Infof("[BindMobile] 已失效 FindOneByMobile 缓存 | key: %s | 后续代理注册将能正确按手机号查到本用户", userMobileCacheKey)
|
||||
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, finalUserID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成Token失败: %v", err)
|
||||
}
|
||||
now := time.Now().Unix()
|
||||
l.Infof("[BindMobile] 完成-升级正式用户 | userId: %s, mobile: %s", finalUserID, req.Mobile)
|
||||
return &types.BindMobileResp{AccessToken: token, AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire, RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter}, nil
|
||||
}
|
||||
|
||||
// 手机号已存在:进入账号合并或快捷登录流程
|
||||
finalUserID = targetUser.Id
|
||||
// 保护校验:若将不同用户进行合并,确保源用户不存在代理记录(临时用户不应为代理)
|
||||
l.Infof("[BindMobile] FindOneByMobile 命中 | targetUserId: %s, encryptedMobile: %s | 分支: 手机号已存在,进入合并或快捷登录", targetUser.Id, encryptedMobile)
|
||||
finalUserID := targetUser.Id
|
||||
if currentUserID != finalUserID {
|
||||
l.Infof("[BindMobile] 当前用户与手机号用户不同 | currentUserID: %s, finalUserID: %s, 需合并或绑定", currentUserID, finalUserID)
|
||||
agent, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, currentUserID)
|
||||
if err != nil && !errors.Is(err, model.ErrNotFound) {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败: %v", err)
|
||||
}
|
||||
if agent != nil {
|
||||
l.Infof("[BindMobile] 拒绝: 源用户已是代理 | currentUserID: %s, agentId: %s", currentUserID, agent.Id)
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("账号数据异常:源用户存在代理记录,请联系技术支持"), "")
|
||||
}
|
||||
}
|
||||
// 查找当前登录态使用的认证(例如uuid或微信openid)是否已存在
|
||||
existingAuth, err := l.svcCtx.UserAuthModel.FindOneByAuthTypeAuthKey(l.ctx, currentAuthType, currentAuthKey)
|
||||
if err != nil && !errors.Is(err, model.ErrNotFound) {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查找认证信息失败: %v", err)
|
||||
}
|
||||
// 如果当前认证已属于目标手机号用户,直接发放token(无需合并)
|
||||
if existingAuth != nil && existingAuth.UserId == finalUserID {
|
||||
l.Infof("[BindMobile] 当前认证已属于目标用户,直接发 token | finalUserID: %s", finalUserID)
|
||||
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, finalUserID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成Token失败: %v", err)
|
||||
@@ -128,6 +133,7 @@ func (l *BindMobileLogic) BindMobile(req *types.BindMobileReq) (resp *types.Bind
|
||||
now := time.Now().Unix()
|
||||
return &types.BindMobileResp{AccessToken: token, AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire, RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter}, nil
|
||||
}
|
||||
l.Infof("[BindMobile] 需合并/绑定认证 | existingAuth: %v, finalUserID: %s, authType: %s", existingAuth != nil, finalUserID, currentAuthType)
|
||||
|
||||
// 微信唯一性约束(按类型):
|
||||
// - H5 与 小程序各自只能绑定一个 openid(互不影响)
|
||||
@@ -150,11 +156,7 @@ func (l *BindMobileLogic) BindMobile(req *types.BindMobileReq) (resp *types.Bind
|
||||
}
|
||||
}
|
||||
|
||||
// 事务处理:
|
||||
// - 将当前登录态的认证(uuid / 微信openid 等)绑定到目标手机号用户(finalUserID)
|
||||
// - 将源用户(currentUserID)的业务数据(订单、报告)迁移到目标用户,避免数据分裂
|
||||
// - 对源用户执行软删除,清理无主临时账号,保持数据一致性
|
||||
// 注意:所有步骤必须在同一个事务中执行,任何一步失败均会回滚,确保原子性
|
||||
l.Infof("[BindMobile] 开始事务: 合并认证与数据 | sourceUserId: %s, targetUserId: %s", currentUserID, finalUserID)
|
||||
err = l.svcCtx.UserModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
|
||||
// 1) 认证绑定处理(UUID替换策略)
|
||||
if currentAuthType == model.UserAuthTypeUUID {
|
||||
@@ -232,14 +234,16 @@ func (l *BindMobileLogic) BindMobile(req *types.BindMobileReq) (resp *types.Bind
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
l.Errorf("[BindMobile] 事务失败 | sourceUserId: %s, targetUserId: %s, err: %v", currentUserID, finalUserID, err)
|
||||
return nil, err
|
||||
}
|
||||
l.Infof("[BindMobile] 事务完成-合并成功 | finalUserID: %s", finalUserID)
|
||||
|
||||
// 合并完成后生成token(userType会根据mobile字段动态计算)
|
||||
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, finalUserID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成Token失败: %v", err)
|
||||
}
|
||||
now := time.Now().Unix()
|
||||
l.Infof("[BindMobile] 完成-合并后发 token | finalUserID: %s, mobile: %s", finalUserID, req.Mobile)
|
||||
return &types.BindMobileResp{AccessToken: token, AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire, RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user