This commit is contained in:
2026-02-28 19:29:08 +08:00
parent a88154e8e2
commit 7e2dda986f
2 changed files with 91 additions and 86 deletions

View File

@@ -38,15 +38,23 @@ func NewRegisterByInviteCodeLogic(ctx context.Context, svcCtx *svc.ServiceContex
}
}
// truncateAuthKey 截断 authKey 用于日志,避免敏感信息过长
func truncateAuthKey(s string) string {
if len(s) <= 12 {
return s
}
return s[:6] + "..." + s[len(s)-4:]
}
func (l *RegisterByInviteCodeLogic) RegisterByInviteCode(req *types.RegisterByInviteCodeReq) (resp *types.RegisterByInviteCodeResp, err error) {
l.Infof("[RegisterByInviteCode] 开始处理代理注册请求, mobile: %s, referrer: %s", req.Mobile, req.Referrer)
l.Infof("[RegisterByInviteCode] 开始处理代理注册请求 | mobile: %s, referrer: %s, code: %s", req.Mobile, req.Referrer, req.Code)
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)
}
l.Infof("[RegisterByInviteCode] 手机号加密完成, encryptedMobile: %s", encryptedMobile)
l.Infof("[RegisterByInviteCode] 手机号加密完成 | mobile: %s, encryptedMobile: %s", req.Mobile, encryptedMobile)
// 校验验证码(开发环境下跳过验证码校验)
if os.Getenv("ENV") != "development" && req.Code != "143838" {
@@ -76,9 +84,9 @@ func (l *RegisterByInviteCodeLogic) RegisterByInviteCode(req *types.RegisterByIn
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败: %v", err)
}
if claims != nil {
l.Infof("[RegisterByInviteCode] 当前登录用户, userId: %s, authType: %s, userType: %d, authKey: %s", claims.UserId, claims.AuthType, claims.UserType, claims.AuthKey)
l.Infof("[RegisterByInviteCode] 当前登录态 | userId: %s, authType: %s, userType: %d, authKeyPrefix: %s", claims.UserId, claims.AuthType, claims.UserType, truncateAuthKey(claims.AuthKey))
} else {
l.Infof("[RegisterByInviteCode] 未登录状态claimsnil将按未登录流程处理")
l.Infof("[RegisterByInviteCode] 未登录状态claimsnil将按未登录流程处理")
}
// 前置检查:如果当前用户是正式用户(有手机号),进行拦截检查
@@ -88,24 +96,25 @@ func (l *RegisterByInviteCodeLogic) RegisterByInviteCode(req *types.RegisterByIn
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户失败: %v", err)
}
if currentUser != nil && currentUser.Mobile.Valid && currentUser.Mobile.String != "" {
l.Infof("[RegisterByInviteCode] 当前用户是正式用户, userId: %s, mobile: %s", claims.UserId, currentUser.Mobile.String)
l.Infof("[RegisterByInviteCode] 前置检查-正式用户 | userId: %s, user.mobile: %s, 请求加密手机号: %s, 匹配: %v", claims.UserId, currentUser.Mobile.String, encryptedMobile, currentUser.Mobile.String == encryptedMobile)
// 当前用户是正式用户,检查是否已是代理
agent, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, claims.UserId)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理失败: %v", err)
}
if agent != nil {
l.Infof("[RegisterByInviteCode] 用户已是代理, userId: %s, agentId: %s", claims.UserId, agent.Id)
l.Infof("[RegisterByInviteCode] 前置检查-已是代理拒绝 | userId: %s, agentId: %s", claims.UserId, agent.Id)
return nil, errors.Wrapf(xerr.NewErrMsg("您已经是代理,不能重复注册"), "")
}
// 正式用户手机号必须匹配
if currentUser.Mobile.String != encryptedMobile {
l.Infof("[RegisterByInviteCode] 手机号不匹配, userId: %s, currentMobile: %s, requestMobile: %s", claims.UserId, currentUser.Mobile.String, encryptedMobile)
l.Infof("[RegisterByInviteCode] 前置检查-手机号不匹配拒绝 | userId: %s, currentMobile: %s, requestEncrypted: %s", claims.UserId, currentUser.Mobile.String, encryptedMobile)
return nil, errors.Wrapf(xerr.NewErrMsg("请输入当前账号的手机号码"), "")
}
l.Infof("[RegisterByInviteCode] 前置检查通过, 正式用户手机号匹配")
l.Infof("[RegisterByInviteCode] 前置检查通过 | 正式用户手机号匹配,非代理")
} else {
l.Infof("[RegisterByInviteCode] 当前用户是临时用户或无手机号, userId: %s", claims.UserId)
hasMobile := currentUser != nil && currentUser.Mobile.Valid && currentUser.Mobile.String != ""
l.Infof("[RegisterByInviteCode] 前置检查-临时用户或无手机号 | userId: %s, userHasMobile: %v", claims.UserId, hasMobile)
}
}
@@ -141,16 +150,16 @@ func (l *RegisterByInviteCodeLogic) RegisterByInviteCode(req *types.RegisterByIn
l.Infof("[RegisterByInviteCode] 开始事务处理")
err = l.svcCtx.AgentInviteCodeModel.Trans(l.ctx, func(transCtx context.Context, session sqlx.Session) error {
// 1. 查找目标用户(通过手机号)
// 1. 查找目标用户(通过手机号,可能命中缓存
targetUser, findUserErr := l.svcCtx.UserModel.FindOneByMobile(transCtx, sql.NullString{String: encryptedMobile, Valid: true})
if findUserErr != nil && !errors.Is(findUserErr, model.ErrNotFound) {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户失败, %v", findUserErr)
}
if targetUser != nil {
l.Infof("[RegisterByInviteCode] 找到目标用户, userId: %s", targetUser.Id)
l.Infof("[RegisterByInviteCode] FindOneByMobile 命中 | targetUserId: %s, encryptedMobile: %s | 走场景2(手机号已存在)", targetUser.Id, encryptedMobile)
} else {
l.Infof("[RegisterByInviteCode] 目标用户不存在, 将创建新用户")
l.Infof("[RegisterByInviteCode] FindOneByMobile 未命中 | targetUser=nil, encryptedMobile: %s | 走场景1(手机号不存在)。若用户刚执行过 bindMobile 仍为 nil疑为 FindOneByMobile 缓存未失效", encryptedMobile)
}
// 2. 获取当前登录态信息
@@ -168,21 +177,21 @@ func (l *RegisterByInviteCodeLogic) RegisterByInviteCode(req *types.RegisterByIn
// 3. 根据目标用户是否存在,处理用户和认证
if targetUser == nil {
// 场景1: 手机号不存在
l.Infof("[RegisterByInviteCode] 场景1: 手机号不存在, currentUserID: %s", currentUserID)
l.Infof("[RegisterByInviteCode] 分支: 场景1-手机号不存在 | currentUserID: %s, encryptedMobile: %s", currentUserID, encryptedMobile)
userID, err = l.handleMobileNotExists(transCtx, session, encryptedMobile, currentUserID)
if err != nil {
l.Errorf("[RegisterByInviteCode] 场景1失败 | currentUserID: %s, encryptedMobile: %s, err: %v", currentUserID, encryptedMobile, err)
return err
}
l.Infof("[RegisterByInviteCode] 场景1处理完成, userID: %s", userID)
l.Infof("[RegisterByInviteCode] 场景1完成 | userID: %s", userID)
} else {
// 场景2: 手机号已存在
l.Infof("[RegisterByInviteCode] 场景2: 手机号已存在, targetUserId: %s, currentUserID: %s", targetUser.Id, currentUserID)
l.Infof("[RegisterByInviteCode] 分支: 场景2-手机号已存在 | targetUserId: %s, currentUserID: %s, sameUser: %v", targetUser.Id, currentUserID, targetUser.Id == currentUserID)
userID, err = l.handleMobileExists(transCtx, session, targetUser, currentUserID, currentAuthType, currentAuthKey)
if err != nil {
l.Errorf("[RegisterByInviteCode] 场景2失败 | targetUserId: %s, currentUserID: %s, err: %v", targetUser.Id, currentUserID, err)
return err
}
l.Infof("[RegisterByInviteCode] 场景2处理完成, userID: %s", userID)
l.Infof("[RegisterByInviteCode] 场景2完成 | userID: %s", userID)
}
// 4. 处理邀请码和上级关系
var targetLevel int64
@@ -342,7 +351,7 @@ func (l *RegisterByInviteCodeLogic) RegisterByInviteCode(req *types.RegisterByIn
})
if err != nil {
l.Errorf("[RegisterByInviteCode] 事务处理失败: %v", err)
l.Errorf("[RegisterByInviteCode] 事务失败 | mobile: %s, referrer: %s, err: %v | 排查可看上文 FindOneByMobile 是否命中、场景1/2 及 handleMobileNotExists/Exists 日志", req.Mobile, req.Referrer, err)
return nil, err
}
@@ -464,82 +473,74 @@ func (l *RegisterByInviteCodeLogic) allocateAgentCode(ctx context.Context, sessi
// handleMobileNotExists 处理手机号不存在的情况
func (l *RegisterByInviteCodeLogic) handleMobileNotExists(ctx context.Context, session sqlx.Session, encryptedMobile string, currentUserID string) (string, error) {
if currentUserID == "" {
// 场景1.1: 未登录 + 手机号不存在 -> 创建新用户
l.Infof("[handleMobileNotExists] 场景1.1: 未登录+手机号不存在, 创建新用户")
l.Infof("[handleMobileNotExists] 子分支 1.1: 未登录+手机号不存在 | 创建新用户, encryptedMobile: %s", encryptedMobile)
newUser := &model.User{Id: uuid.NewString(), Mobile: sql.NullString{String: encryptedMobile, Valid: true}}
if _, err := l.svcCtx.UserModel.Insert(ctx, session, newUser); err != nil {
return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建用户失败: %v", err)
}
l.Infof("[handleMobileNotExists] 用户创建成功, userId: %s", newUser.Id)
// 创建 mobile 认证
l.Infof("[handleMobileNotExists] 1.1 用户创建 | userId: %s", newUser.Id)
l.Infof("[handleMobileNotExists] 1.1 即将插入 user_auth(mobile) | userId: %s, authKey: %s", newUser.Id, encryptedMobile)
if _, err := l.svcCtx.UserAuthModel.Insert(ctx, session, &model.UserAuth{
Id: uuid.NewString(),
UserId: newUser.Id,
AuthType: model.UserAuthTypeMobile,
AuthKey: encryptedMobile,
}); err != nil {
l.Errorf("[handleMobileNotExists] 1.1 插入 user_auth 失败(可能 Duplicate) | userId: %s, err: %v", newUser.Id, err)
return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建手机号认证失败: %v", err)
}
l.Infof("[handleMobileNotExists] 手机号认证创建成功, userId: %s", newUser.Id)
l.Infof("[handleMobileNotExists] 1.1 完成 | userId: %s", newUser.Id)
return newUser.Id, nil
} else {
// 场景1.2: 已登录临时用户 + 手机号不存在 -> 升级为正式用户
// 前置检查已保证不是正式用户,所以这里一定是临时用户
l.Infof("[handleMobileNotExists] 场景1.2: 已登录临时用户+手机号不存在, currentUserID: %s, 升级为正式用户", currentUserID)
currentUser, err := l.svcCtx.UserModel.FindOne(ctx, currentUserID)
if err != nil {
return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询当前用户失败: %v", err)
}
// 升级为正式用户
currentUser.Mobile = sql.NullString{String: encryptedMobile, Valid: true}
if _, err := l.svcCtx.UserModel.Update(ctx, session, currentUser); err != nil {
return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新手机号失败: %v", err)
}
l.Infof("[handleMobileNotExists] 用户升级为正式用户成功, userId: %s", currentUserID)
// 创建 mobile 认证
if _, err := l.svcCtx.UserAuthModel.Insert(ctx, session, &model.UserAuth{
Id: uuid.NewString(),
UserId: currentUserID,
AuthType: model.UserAuthTypeMobile,
AuthKey: encryptedMobile,
}); err != nil {
return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建手机号认证失败: %v", err)
}
l.Infof("[handleMobileNotExists] 手机号认证创建成功, userId: %s", currentUserID)
return currentUserID, nil
}
// 场景1.2: 已登录临时用户 + 手机号不存在 -> 升级为正式用户
l.Infof("[handleMobileNotExists] 子分支 1.2: 已登录+手机号不存在 | currentUserID: %s, encryptedMobile: %s, 将更新 user.mobile 并插入 user_auth", currentUserID, encryptedMobile)
currentUser, err := l.svcCtx.UserModel.FindOne(ctx, currentUserID)
if err != nil {
return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询当前用户失败: %v", err)
}
currentUser.Mobile = sql.NullString{String: encryptedMobile, Valid: true}
if _, err := l.svcCtx.UserModel.Update(ctx, session, currentUser); err != nil {
return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新手机号失败: %v", err)
}
l.Infof("[handleMobileNotExists] 1.2 user.mobile 已更新 | userId: %s", currentUserID)
l.Infof("[handleMobileNotExists] 1.2 即将插入 user_auth(mobile) | userId: %s, authKey: %s | 若报 Duplicate 说明该手机号已绑定疑缓存导致误走场景1", currentUserID, encryptedMobile)
if _, err := l.svcCtx.UserAuthModel.Insert(ctx, session, &model.UserAuth{
Id: uuid.NewString(),
UserId: currentUserID,
AuthType: model.UserAuthTypeMobile,
AuthKey: encryptedMobile,
}); err != nil {
l.Errorf("[handleMobileNotExists] 1.2 插入 user_auth 失败 | userId: %s, err: %v | 常见为 Duplicate entry 'mobile-xxx',多为 FindOneByMobile 缓存未失效", currentUserID, err)
return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建手机号认证失败: %v", err)
}
l.Infof("[handleMobileNotExists] 1.2 完成 | userId: %s", currentUserID)
return currentUserID, nil
}
// handleMobileExists 处理手机号已存在的情况
func (l *RegisterByInviteCodeLogic) handleMobileExists(ctx context.Context, session sqlx.Session, targetUser *model.User, currentUserID string, currentAuthType string, currentAuthKey string) (string, error) {
userID := targetUser.Id
l.Infof("[handleMobileExists] 开始处理, targetUserId: %s, currentUserID: %s", userID, currentUserID)
l.Infof("[handleMobileExists] 入口 | targetUserId: %s, currentUserID: %s, authType: %s", userID, currentUserID, currentAuthType)
// 检查目标用户是否已是代理
existingAgent, err := l.svcCtx.AgentModel.FindOneByUserId(ctx, userID)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败: %v", err)
}
if existingAgent != nil {
l.Infof("[handleMobileExists] 目标用户已是代理, userId: %s, agentId: %s", userID, existingAgent.Id)
l.Infof("[handleMobileExists] 目标已是代理拒绝 | userId: %s, agentId: %s", userID, existingAgent.Id)
return "", errors.Wrapf(xerr.NewErrMsg("该手机号已经是代理,不能重复注册"), "")
}
if currentUserID == "" {
// 场景2.1: 未登录 + 手机号存在 -> 直接使用目标用户(验证码已确认身份)
l.Infof("[handleMobileExists] 场景2.1: 未登录+手机号存在, 直接使用目标用户, userId: %s", userID)
l.Infof("[handleMobileExists] 子分支 2.1: 未登录+手机号存在 | 直接使用 targetUserId: %s", userID)
return userID, nil
} else if currentUserID == userID {
// 场景2.2: 已登录正式用户 + 手机号匹配 -> 直接使用
// 前置检查已保证手机号匹配且不是代理
l.Infof("[handleMobileExists] 场景2.2: 已登录正式用户+手机号匹配, userId: %s", userID)
return userID, nil
} else {
// 场景2.3: 已登录临时用户 + 手机号存在 -> 需要合并账号
// 前置检查已保证是临时用户(不是正式用户)
l.Infof("[handleMobileExists] 场景2.3: 已登录临时用户+手机号存在, 需要合并账号, sourceUserId: %s, targetUserId: %s, authType: %s", currentUserID, userID, currentAuthType)
return l.mergeTempUserToTarget(ctx, session, currentUserID, userID, currentAuthType, currentAuthKey)
}
if currentUserID == userID {
l.Infof("[handleMobileExists] 子分支 2.2: 已登录且与目标同一用户 | userId: %s", userID)
return userID, nil
}
l.Infof("[handleMobileExists] 子分支 2.3: 已登录临时用户+手机号属他人 | 合并 source: %s -> target: %s, authType: %s", currentUserID, userID, currentAuthType)
return l.mergeTempUserToTarget(ctx, session, currentUserID, userID, currentAuthType, currentAuthKey)
}
// mergeTempUserToTarget 合并临时用户到目标用户

View File

@@ -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)
// 发放tokenuserType会根据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)
// 合并完成后生成tokenuserType会根据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
}