diff --git a/app/main/api/internal/logic/agent/registerbyinvitecodelogic.go b/app/main/api/internal/logic/agent/registerbyinvitecodelogic.go index e8d3b70..f865532 100644 --- a/app/main/api/internal/logic/agent/registerbyinvitecodelogic.go +++ b/app/main/api/internal/logic/agent/registerbyinvitecodelogic.go @@ -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] 未登录状态(claims为nil,将按未登录流程处理)") + l.Infof("[RegisterByInviteCode] 未登录状态(claims 为 nil,将按未登录流程处理)") } // 前置检查:如果当前用户是正式用户(有手机号),进行拦截检查 @@ -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 合并临时用户到目标用户 diff --git a/app/main/api/internal/logic/user/bindmobilelogic.go b/app/main/api/internal/logic/user/bindmobilelogic.go index 07dc622..ef9b474 100644 --- a/app/main/api/internal/logic/user/bindmobilelogic.go +++ b/app/main/api/internal/logic/user/bindmobilelogic.go @@ -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 }