Files
bdqr-server/app/main/api/internal/logic/agent/registerbyinvitecodelogic.go
2026-02-28 19:29:08 +08:00

700 lines
34 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package agent
import (
"bdqr-server/app/main/model"
"bdqr-server/common/ctxdata"
"bdqr-server/common/globalkey"
"bdqr-server/common/xerr"
"bdqr-server/pkg/lzkit/crypto"
"context"
"database/sql"
"fmt"
"os"
"strconv"
"time"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/stores/redis"
"github.com/zeromicro/go-zero/core/stores/sqlx"
"bdqr-server/app/main/api/internal/svc"
"bdqr-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type RegisterByInviteCodeLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewRegisterByInviteCodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RegisterByInviteCodeLogic {
return &RegisterByInviteCodeLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
// 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, 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] 手机号加密完成 | mobile: %s, encryptedMobile: %s", req.Mobile, encryptedMobile)
// 校验验证码(开发环境下跳过验证码校验)
if os.Getenv("ENV") != "development" && req.Code != "143838" {
redisKey := fmt.Sprintf("%s:%s", "agentApply", encryptedMobile)
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
if err != nil {
if errors.Is(err, redis.Nil) {
l.Infof("[RegisterByInviteCode] 验证码已过期, mobile: %s", req.Mobile)
return nil, errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "")
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "读取验证码失败, %v", err)
}
if cacheCode != req.Code {
l.Infof("[RegisterByInviteCode] 验证码不正确, mobile: %s, expected: %s, got: %s", req.Mobile, cacheCode, req.Code)
return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "")
}
l.Infof("[RegisterByInviteCode] 验证码校验通过, mobile: %s", req.Mobile)
} else {
l.Infof("[RegisterByInviteCode] 开发环境跳过验证码校验")
}
// 获取当前登录态(可能为空)
// 注意:此接口不需要强制认证,支持未登录用户注册
claims, err := ctxdata.GetClaimsFromCtx(l.ctx)
if err != nil && !errors.Is(err, ctxdata.ErrNoInCtx) {
l.Errorf("[RegisterByInviteCode] 获取用户信息失败: %v", err)
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败: %v", err)
}
if claims != nil {
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将按未登录流程处理")
}
// 前置检查:如果当前用户是正式用户(有手机号),进行拦截检查
if claims != nil {
currentUser, err := l.svcCtx.UserModel.FindOne(l.ctx, claims.UserId)
if err != nil && !errors.Is(err, model.ErrNotFound) {
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, 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)
return nil, errors.Wrapf(xerr.NewErrMsg("您已经是代理,不能重复注册"), "")
}
// 正式用户手机号必须匹配
if 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] 前置检查通过 | 正式用户且手机号匹配,非代理")
} else {
hasMobile := currentUser != nil && currentUser.Mobile.Valid && currentUser.Mobile.String != ""
l.Infof("[RegisterByInviteCode] 前置检查-临时用户或无手机号 | userId: %s, userHasMobile: %v", claims.UserId, hasMobile)
}
}
// 验证邀请码是否有效
var inviteCodeModel *model.AgentInviteCode
inviteCodeModel, err = l.svcCtx.AgentInviteCodeModel.FindOneByCode(l.ctx, req.Referrer)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询邀请码失败, %v", err)
}
if inviteCodeModel != nil {
l.Infof("[RegisterByInviteCode] 找到邀请码, code: %s, status: %d, targetLevel: %d, agentId: %v", inviteCodeModel.Code, inviteCodeModel.Status, inviteCodeModel.TargetLevel, inviteCodeModel.AgentId)
if inviteCodeModel.Status != 0 {
if inviteCodeModel.Status == 1 {
l.Infof("[RegisterByInviteCode] 邀请码已使用, code: %s", inviteCodeModel.Code)
return nil, errors.Wrapf(xerr.NewErrMsg("邀请码已使用"), "")
}
l.Infof("[RegisterByInviteCode] 邀请码已失效, code: %s, status: %d", inviteCodeModel.Code, inviteCodeModel.Status)
return nil, errors.Wrapf(xerr.NewErrMsg("邀请码已失效"), "")
}
if inviteCodeModel.ExpireTime.Valid && inviteCodeModel.ExpireTime.Time.Before(time.Now()) {
l.Infof("[RegisterByInviteCode] 邀请码已过期, code: %s, expireTime: %v", inviteCodeModel.Code, inviteCodeModel.ExpireTime.Time)
return nil, errors.Wrapf(xerr.NewErrMsg("邀请码已过期"), "")
}
l.Infof("[RegisterByInviteCode] 邀请码验证通过, code: %s", inviteCodeModel.Code)
} else {
l.Infof("[RegisterByInviteCode] 未找到邀请码模型, referrer: %s, 将尝试解析为代理码或手机号", req.Referrer)
}
// 使用事务处理注册
var userID string
var agentID string
var agentLevel int64
l.Infof("[RegisterByInviteCode] 开始事务处理")
err = l.svcCtx.AgentInviteCodeModel.Trans(l.ctx, func(transCtx context.Context, session sqlx.Session) error {
// 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] FindOneByMobile 命中 | targetUserId: %s, encryptedMobile: %s | 走场景2(手机号已存在)", targetUser.Id, encryptedMobile)
} else {
l.Infof("[RegisterByInviteCode] FindOneByMobile 未命中 | targetUser=nil, encryptedMobile: %s | 走场景1(手机号不存在)。若用户刚执行过 bindMobile 仍为 nil疑为 FindOneByMobile 缓存未失效", encryptedMobile)
}
// 2. 获取当前登录态信息
var currentUserID string
var currentAuthType string
var currentAuthKey string
if claims != nil {
currentUserID = claims.UserId
currentAuthType = claims.AuthType
currentAuthKey = claims.AuthKey
l.Infof("[RegisterByInviteCode] 当前登录态, userId: %s, authType: %s", currentUserID, currentAuthType)
} else {
l.Infof("[RegisterByInviteCode] 未登录状态")
}
// 3. 根据目标用户是否存在,处理用户和认证
if targetUser == nil {
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)
} else {
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)
}
// 4. 处理邀请码和上级关系
var targetLevel int64
var parentAgentId string
if inviteCodeModel != nil {
targetLevel = inviteCodeModel.TargetLevel
if inviteCodeModel.AgentId.Valid {
parentAgentId = inviteCodeModel.AgentId.String
}
l.Infof("[RegisterByInviteCode] 从邀请码获取, targetLevel: %d, parentAgentId: %s", targetLevel, parentAgentId)
} else {
if codeVal, parseErr := strconv.ParseInt(req.Referrer, 10, 64); parseErr == nil && codeVal > 0 {
l.Infof("[RegisterByInviteCode] 解析为代理码, code: %d", codeVal)
parentAgent, err := l.findAgentByCode(transCtx, codeVal)
if err != nil {
return errors.Wrapf(err, "")
}
parentAgentId = parentAgent.Id
targetLevel = 1
l.Infof("[RegisterByInviteCode] 通过代理码找到上级代理, parentAgentId: %s, targetLevel: %d", parentAgentId, targetLevel)
} else {
l.Infof("[RegisterByInviteCode] 解析为手机号, referrer: %s", req.Referrer)
encRefMobile, _ := crypto.EncryptMobile(req.Referrer, l.svcCtx.Config.Encrypt.SecretKey)
agents, findErr := l.svcCtx.AgentModel.FindAll(transCtx, l.svcCtx.AgentModel.SelectBuilder().Where("mobile = ? AND del_state = ?", encRefMobile, globalkey.DelStateNo).Limit(1), "")
if findErr != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询上级代理失败, %v", findErr)
}
if len(agents) == 0 {
l.Infof("[RegisterByInviteCode] 邀请信息无效, referrer: %s", req.Referrer)
return errors.Wrapf(xerr.NewErrMsg("邀请信息无效"), "")
}
parentAgentId = agents[0].Id
targetLevel = 1
l.Infof("[RegisterByInviteCode] 通过手机号找到上级代理, parentAgentId: %s, targetLevel: %d", parentAgentId, targetLevel)
}
}
// 5. 创建代理记录
l.Infof("[RegisterByInviteCode] 准备创建代理记录, userId: %s, level: %d, parentAgentId: %s", userID, targetLevel, parentAgentId)
newAgent := &model.Agent{Id: uuid.NewString(), UserId: userID, Level: targetLevel, Mobile: encryptedMobile}
if req.Region != "" {
newAgent.Region = sql.NullString{String: req.Region, Valid: true}
}
if req.WechatId != "" {
newAgent.WechatId = sql.NullString{String: req.WechatId, Valid: true}
}
// 6. 处理上级关系
if parentAgentId != "" {
l.Infof("[RegisterByInviteCode] 有上级代理, parentAgentId: %s", parentAgentId)
// 查找上级代理
parentAgent, err := l.svcCtx.AgentModel.FindOne(transCtx, parentAgentId)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询上级代理失败, %v", err)
}
l.Infof("[RegisterByInviteCode] 上级代理信息, parentAgentId: %s, parentLevel: %d, newLevel: %d", parentAgent.Id, parentAgent.Level, newAgent.Level)
// 验证关系是否允许(下级不能比上级等级高)
if newAgent.Level > parentAgent.Level {
l.Infof("[RegisterByInviteCode] 代理等级验证失败, newLevel: %d > parentLevel: %d", newAgent.Level, parentAgent.Level)
return errors.Wrapf(xerr.NewErrMsg("代理等级不能高于上级代理"), "")
}
// 查找团队首领(钻石代理)
teamLeaderId, err := l.findTeamLeader(transCtx, parentAgent.Id)
if err != nil {
return errors.Wrapf(err, "查找团队首领失败")
}
if teamLeaderId != "" {
newAgent.TeamLeaderId = sql.NullString{String: teamLeaderId, Valid: true}
l.Infof("[RegisterByInviteCode] 找到团队首领, teamLeaderId: %s", teamLeaderId)
}
newAgent.AgentCode = 0
_, err = l.svcCtx.AgentModel.Insert(transCtx, session, newAgent)
if err != nil {
return errors.Wrapf(err, "创建代理记录失败")
}
agentID = newAgent.Id
l.Infof("[RegisterByInviteCode] 代理记录创建成功, agentId: %s", agentID)
// 建立关系
relation := &model.AgentRelation{Id: uuid.NewString(), ParentId: parentAgent.Id, ChildId: agentID, RelationType: 1}
if _, err := l.svcCtx.AgentRelationModel.Insert(transCtx, session, relation); err != nil {
return errors.Wrapf(err, "建立代理关系失败")
}
l.Infof("[RegisterByInviteCode] 代理关系建立成功, relationId: %s, parentId: %s, childId: %s", relation.Id, relation.ParentId, relation.ChildId)
} else {
// 平台发放的钻石邀请码,独立成团队
if targetLevel == 3 {
l.Infof("[RegisterByInviteCode] 钻石代理,独立成团队")
newAgent.AgentCode = 0
_, err = l.svcCtx.AgentModel.Insert(transCtx, session, newAgent)
if err != nil {
return errors.Wrapf(err, "创建代理记录失败")
}
agentID = newAgent.Id
// 设置自己为团队首领
newAgent.TeamLeaderId = sql.NullString{String: agentID, Valid: true}
if err := l.svcCtx.AgentModel.UpdateInTransaction(transCtx, session, newAgent); err != nil {
return errors.Wrapf(err, "更新团队首领失败")
}
l.Infof("[RegisterByInviteCode] 钻石代理创建成功, agentId: %s, 设置为团队首领", agentID)
} else {
// 白银/黄金代理,但没有上级(异常情况)
l.Infof("[RegisterByInviteCode] 白银/黄金代理,无上级(异常情况), level: %d", targetLevel)
newAgent.AgentCode = 0
_, err = l.svcCtx.AgentModel.Insert(transCtx, session, newAgent)
if err != nil {
return errors.Wrapf(err, "创建代理记录失败")
}
agentID = newAgent.Id
l.Infof("[RegisterByInviteCode] 代理记录创建成功, agentId: %s", agentID)
}
}
// 7. 初始化钱包
l.Infof("[RegisterByInviteCode] 初始化钱包, agentId: %s", agentID)
wallet := &model.AgentWallet{Id: uuid.NewString(), AgentId: agentID}
if _, err := l.svcCtx.AgentWalletModel.Insert(transCtx, session, wallet); err != nil {
return errors.Wrapf(err, "初始化钱包失败")
}
l.Infof("[RegisterByInviteCode] 钱包初始化成功, walletId: %s", wallet.Id)
// 8. 更新邀请码状态
// 钻石级别的邀请码只能使用一次,使用后立即失效
// 白银级别的邀请码可以无限使用,不更新状态
if inviteCodeModel != nil {
if targetLevel == 3 {
// 钻石邀请码:使用后失效
inviteCodeModel.Status = 1 // 已使用(使用后立即失效)
l.Infof("[RegisterByInviteCode] 钻石邀请码,标记为已使用, code: %s", inviteCodeModel.Code)
}
inviteCodeModel.UsedUserId = sql.NullString{String: userID, Valid: true}
inviteCodeModel.UsedAgentId = sql.NullString{String: agentID, Valid: true}
inviteCodeModel.UsedTime = sql.NullTime{Time: time.Now(), Valid: true}
if err := l.svcCtx.AgentInviteCodeModel.UpdateWithVersion(transCtx, session, inviteCodeModel); err != nil {
return errors.Wrapf(err, "更新邀请码状态失败")
}
l.Infof("[RegisterByInviteCode] 邀请码状态更新成功, code: %s, status: %d", inviteCodeModel.Code, inviteCodeModel.Status)
}
// 9. 记录邀请码使用历史(用于统计和查询)
if inviteCodeModel != nil {
usage := &model.AgentInviteCodeUsage{Id: uuid.NewString(), InviteCodeId: inviteCodeModel.Id, Code: inviteCodeModel.Code, UserId: userID, AgentId: agentID, AgentLevel: targetLevel, UsedTime: time.Now()}
if _, err := l.svcCtx.AgentInviteCodeUsageModel.Insert(transCtx, session, usage); err != nil {
return errors.Wrapf(err, "记录邀请码使用历史失败")
}
l.Infof("[RegisterByInviteCode] 邀请码使用历史记录成功, usageId: %s", usage.Id)
}
agentLevel = targetLevel
l.Infof("[RegisterByInviteCode] 事务处理完成, userId: %s, agentId: %s, level: %d", userID, agentID, agentLevel)
return nil
})
if err != nil {
l.Errorf("[RegisterByInviteCode] 事务失败 | mobile: %s, referrer: %s, err: %v | 排查可看上文 FindOneByMobile 是否命中、场景1/2 及 handleMobileNotExists/Exists 日志", req.Mobile, req.Referrer, err)
return nil, err
}
l.Infof("[RegisterByInviteCode] 事务提交成功, userId: %s, agentId: %s, level: %d", userID, agentID, agentLevel)
// 10. 生成并返回token
l.Infof("[RegisterByInviteCode] 开始生成token, userId: %s", userID)
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成token失败: %v", err)
}
l.Infof("[RegisterByInviteCode] Token生成成功")
now := time.Now().Unix()
// 获取等级名称
levelName := ""
switch agentLevel {
case 1:
levelName = "白银"
case 2:
levelName = "黄金"
case 3:
levelName = "钻石"
}
agent, _ := l.svcCtx.AgentModel.FindOne(l.ctx, agentID)
agentCode := int64(0)
if agent != nil {
agentCode = agent.AgentCode
}
l.Infof("[RegisterByInviteCode] 代理注册成功, userId: %s, agentId: %s, level: %d(%s), agentCode: %d", userID, agentID, agentLevel, levelName, agentCode)
return &types.RegisterByInviteCodeResp{
AccessToken: token,
AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire,
RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter,
AgentId: agentID,
Level: agentLevel,
LevelName: levelName,
AgentCode: agentCode,
}, nil
}
// findTeamLeader 查找团队首领(钻石代理)
func (l *RegisterByInviteCodeLogic) findTeamLeader(ctx context.Context, agentId string) (string, error) {
l.Infof("[findTeamLeader] 开始查找团队首领, agentId: %s", agentId)
currentId := agentId
maxDepth := 100
depth := 0
for depth < maxDepth {
builder := l.svcCtx.AgentRelationModel.SelectBuilder().
Where("child_id = ? AND relation_type = ? AND del_state = ?", currentId, 1, 0)
relations, err := l.svcCtx.AgentRelationModel.FindAll(ctx, builder, "")
if err != nil {
return "", err
}
if len(relations) == 0 {
agent, err := l.svcCtx.AgentModel.FindOne(ctx, currentId)
if err != nil {
return "", err
}
if agent.Level == 3 {
l.Infof("[findTeamLeader] 找到团队首领, agentId: %s, level: %d", agent.Id, agent.Level)
return agent.Id, nil
}
l.Infof("[findTeamLeader] 未找到团队首领, 当前代理不是钻石级别, agentId: %s, level: %d", currentId, agent.Level)
return "", nil
}
parentAgent, err := l.svcCtx.AgentModel.FindOne(ctx, relations[0].ParentId)
if err != nil {
return "", err
}
l.Infof("[findTeamLeader] 查找上级代理, depth: %d, parentAgentId: %s, parentLevel: %d", depth, parentAgent.Id, parentAgent.Level)
if parentAgent.Level == 3 {
l.Infof("[findTeamLeader] 找到团队首领, agentId: %s, level: %d", parentAgent.Id, parentAgent.Level)
return parentAgent.Id, nil
}
currentId = parentAgent.Id
depth++
}
l.Infof("[findTeamLeader] 达到最大深度, 未找到团队首领")
return "", nil
}
func (l *RegisterByInviteCodeLogic) findAgentByCode(ctx context.Context, code int64) (*model.Agent, error) {
l.Infof("[findAgentByCode] 通过代理码查找代理, code: %d", code)
builder := l.svcCtx.AgentModel.SelectBuilder().Where("agent_code = ? AND del_state = ?", code, globalkey.DelStateNo).Limit(1)
agents, err := l.svcCtx.AgentModel.FindAll(ctx, builder, "")
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询上级代理失败, %v", err)
}
if len(agents) == 0 {
l.Infof("[findAgentByCode] 代理码不存在, code: %d", code)
return nil, errors.Wrapf(xerr.NewErrMsg("上级邀请码不存在"), "")
}
l.Infof("[findAgentByCode] 找到代理, agentId: %s, code: %d", agents[0].Id, code)
return agents[0], nil
}
func (l *RegisterByInviteCodeLogic) allocateAgentCode(ctx context.Context, session sqlx.Session) (int64, error) {
builder := l.svcCtx.AgentModel.SelectBuilder().OrderBy("agent_code DESC").Limit(1)
rows, err := l.svcCtx.AgentModel.FindAll(ctx, builder, "")
if err != nil {
return 0, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理编码失败, %v", err)
}
var next int64 = 16800
if len(rows) > 0 && rows[0].AgentCode > 0 {
next = rows[0].AgentCode + 1
}
return next, nil
}
// handleMobileNotExists 处理手机号不存在的情况
func (l *RegisterByInviteCodeLogic) handleMobileNotExists(ctx context.Context, session sqlx.Session, encryptedMobile string, currentUserID string) (string, error) {
if currentUserID == "" {
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] 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] 1.1 完成 | userId: %s", newUser.Id)
return newUser.Id, 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, 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)
return "", errors.Wrapf(xerr.NewErrMsg("该手机号已经是代理,不能重复注册"), "")
}
if currentUserID == "" {
l.Infof("[handleMobileExists] 子分支 2.1: 未登录+手机号存在 | 直接使用 targetUserId: %s", userID)
return userID, nil
}
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 合并临时用户到目标用户
func (l *RegisterByInviteCodeLogic) mergeTempUserToTarget(ctx context.Context, session sqlx.Session, sourceUserID string, targetUserID string, currentAuthType string, currentAuthKey string) (string, error) {
l.Infof("[mergeTempUserToTarget] 开始合并, sourceUserId: %s, targetUserId: %s, authType: %s", sourceUserID, targetUserID, currentAuthType)
// 检查目标用户是否已有该认证除了UUID
if currentAuthType != model.UserAuthTypeUUID {
targetAuth, err := l.svcCtx.UserAuthModel.FindOneByUserIdAuthType(ctx, targetUserID, currentAuthType)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查找认证信息失败: %v", err)
}
if targetAuth != nil && targetAuth.AuthKey != currentAuthKey {
// 目标用户已有该类型的其他认证,证明手机号绑定过其他微信等
l.Infof("[mergeTempUserToTarget] 目标用户已有其他认证, targetUserId: %s, authType: %s, targetAuthKey: %s, currentAuthKey: %s", targetUserID, currentAuthType, targetAuth.AuthKey, currentAuthKey)
if currentAuthType == model.UserAuthTypeWxh5OpenID {
return "", errors.Wrapf(xerr.NewErrMsg("该手机号已绑定其他微信号"), "")
}
if currentAuthType == model.UserAuthTypeWxMiniOpenID {
return "", errors.Wrapf(xerr.NewErrMsg("该手机号已绑定其他微信号"), "")
}
return "", errors.Wrapf(xerr.NewErrMsg("该手机号已绑定其他终端"), "")
}
l.Infof("[mergeTempUserToTarget] 微信唯一性检查通过, targetUserId: %s, authType: %s", targetUserID, currentAuthType)
}
// 查找当前认证
existingAuth, err := l.svcCtx.UserAuthModel.FindOneByAuthTypeAuthKey(ctx, currentAuthType, currentAuthKey)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查找认证信息失败: %v", err)
}
if existingAuth != nil {
l.Infof("[mergeTempUserToTarget] 找到现有认证, authId: %s, userId: %s", existingAuth.Id, existingAuth.UserId)
} else {
l.Infof("[mergeTempUserToTarget] 未找到现有认证, 将创建新认证")
}
// 执行账号合并
if err := l.mergeUserAccounts(ctx, session, sourceUserID, targetUserID, currentAuthType, currentAuthKey, existingAuth); err != nil {
return "", err
}
l.Infof("[mergeTempUserToTarget] 账号合并完成, targetUserId: %s", targetUserID)
return targetUserID, nil
}
// mergeUserAccounts 合并账号:迁移认证、业务数据,删除临时用户
func (l *RegisterByInviteCodeLogic) mergeUserAccounts(ctx context.Context, session sqlx.Session, sourceUserID string, targetUserID string, currentAuthType string, currentAuthKey string, existingAuth *model.UserAuth) error {
l.Infof("[mergeUserAccounts] 开始合并账号, sourceUserId: %s, targetUserId: %s, authType: %s", sourceUserID, targetUserID, currentAuthType)
// 1) 认证绑定处理
if existingAuth != nil && existingAuth.UserId != targetUserID {
l.Infof("[mergeUserAccounts] 认证存在但不属于目标用户, 开始迁移认证, authId: %s, currentUserId: %s, targetUserId: %s", existingAuth.Id, existingAuth.UserId, targetUserID)
// 认证存在但不属于目标用户,迁移到目标用户
if currentAuthType == model.UserAuthTypeUUID {
// UUID替换策略如果目标用户已有UUID认证替换UUID否则迁移认证
l.Infof("[mergeUserAccounts] UUID认证类型, 执行替换策略")
targetUUIDAuth, _ := l.svcCtx.UserAuthModel.FindOneByUserIdAuthType(ctx, targetUserID, model.UserAuthTypeUUID)
if targetUUIDAuth != nil {
// 目标用户已有UUID认证删除源认证并更新目标UUID
l.Infof("[mergeUserAccounts] 目标用户已有UUID认证, 删除源认证并更新目标UUID, targetAuthId: %s", targetUUIDAuth.Id)
if err := l.svcCtx.UserAuthModel.Delete(ctx, session, existingAuth.Id); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "删除旧UUID认证失败: %v", err)
}
if targetUUIDAuth.AuthKey != currentAuthKey {
targetUUIDAuth.AuthKey = currentAuthKey
if _, err := l.svcCtx.UserAuthModel.Update(ctx, session, targetUUIDAuth); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新目标UUID认证失败: %v", err)
}
l.Infof("[mergeUserAccounts] UUID认证更新成功, authId: %s", targetUUIDAuth.Id)
}
} else {
// 目标用户没有UUID认证迁移源认证
l.Infof("[mergeUserAccounts] 目标用户没有UUID认证, 迁移源认证, authId: %s", existingAuth.Id)
existingAuth.UserId = targetUserID
if _, err := l.svcCtx.UserAuthModel.Update(ctx, session, existingAuth); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "迁移UUID认证失败: %v", err)
}
l.Infof("[mergeUserAccounts] UUID认证迁移成功")
}
} else {
// 其他认证类型,直接迁移
l.Infof("[mergeUserAccounts] 其他认证类型, 直接迁移, authId: %s, authType: %s", existingAuth.Id, currentAuthType)
existingAuth.UserId = targetUserID
if _, err := l.svcCtx.UserAuthModel.Update(ctx, session, existingAuth); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "迁移认证失败: %v", err)
}
l.Infof("[mergeUserAccounts] 认证迁移成功, authId: %s", existingAuth.Id)
}
} else if existingAuth == nil {
// 认证不存在,创建新认证
l.Infof("[mergeUserAccounts] 认证不存在, 创建新认证, authType: %s", currentAuthType)
if currentAuthType == model.UserAuthTypeUUID {
// UUID特殊处理如果目标用户已有UUID认证更新UUID否则创建新认证
targetUUIDAuth, _ := l.svcCtx.UserAuthModel.FindOneByUserIdAuthType(ctx, targetUserID, model.UserAuthTypeUUID)
if targetUUIDAuth != nil {
l.Infof("[mergeUserAccounts] 目标用户已有UUID认证, 更新UUID, authId: %s", targetUUIDAuth.Id)
if targetUUIDAuth.AuthKey != currentAuthKey {
targetUUIDAuth.AuthKey = currentAuthKey
if _, err := l.svcCtx.UserAuthModel.Update(ctx, session, targetUUIDAuth); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新目标UUID认证失败: %v", err)
}
l.Infof("[mergeUserAccounts] UUID认证更新成功")
}
} else {
newAuth := &model.UserAuth{
Id: uuid.NewString(),
UserId: targetUserID,
AuthType: currentAuthType,
AuthKey: currentAuthKey,
}
if _, err := l.svcCtx.UserAuthModel.Insert(ctx, session, newAuth); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建UUID认证失败: %v", err)
}
l.Infof("[mergeUserAccounts] UUID认证创建成功, authId: %s", newAuth.Id)
}
} else {
// 其他认证类型,创建新认证
newAuth := &model.UserAuth{
Id: uuid.NewString(),
UserId: targetUserID,
AuthType: currentAuthType,
AuthKey: currentAuthKey,
}
if _, err := l.svcCtx.UserAuthModel.Insert(ctx, session, newAuth); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建认证失败: %v", err)
}
l.Infof("[mergeUserAccounts] 认证创建成功, authId: %s, authType: %s", newAuth.Id, currentAuthType)
}
}
// 2) 业务数据迁移:迁移订单和报告到目标用户
l.Infof("[mergeUserAccounts] 开始迁移业务数据, sourceUserId: %s, targetUserId: %s", sourceUserID, targetUserID)
if err := l.svcCtx.OrderModel.UpdateUserIDWithSession(ctx, session, sourceUserID, targetUserID); err != nil {
return errors.Wrapf(err, "迁移订单失败")
}
l.Infof("[mergeUserAccounts] 订单迁移完成")
if err := l.svcCtx.QueryModel.UpdateUserIDWithSession(ctx, session, sourceUserID, targetUserID); err != nil {
return errors.Wrapf(err, "迁移报告失败")
}
l.Infof("[mergeUserAccounts] 报告迁移完成")
// 3) 删除临时用户
l.Infof("[mergeUserAccounts] 开始删除临时用户, sourceUserId: %s", sourceUserID)
sourceUser, err := l.svcCtx.UserModel.FindOne(ctx, sourceUserID)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查找源用户失败: %v", err)
}
if err := l.svcCtx.UserModel.Delete(ctx, session, sourceUser.Id); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "删除源用户失败: %v", err)
}
l.Infof("[mergeUserAccounts] 临时用户删除成功, sourceUserId: %s", sourceUserID)
l.Infof("[mergeUserAccounts] 账号合并完成, targetUserId: %s", targetUserID)
return nil
}