Files
ycc-proxy-server/app/main/api/internal/logic/agent/registerbyinvitecodelogic.go
2025-12-12 14:24:45 +08:00

699 lines
32 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 (
"context"
"database/sql"
"fmt"
"os"
"strconv"
"time"
"ycc-server/app/main/model"
"ycc-server/common/ctxdata"
"ycc-server/common/globalkey"
"ycc-server/common/xerr"
"ycc-server/pkg/lzkit/crypto"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/stores/redis"
"github.com/zeromicro/go-zero/core/stores/sqlx"
"ycc-server/app/main/api/internal/svc"
"ycc-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,
}
}
func (l *RegisterByInviteCodeLogic) RegisterByInviteCode(req *types.RegisterByInviteCodeReq) (resp *types.RegisterByInviteCodeResp, err error) {
l.Infof("[RegisterByInviteCode] 开始处理代理注册请求, mobile: %s, referrer: %s", req.Mobile, req.Referrer)
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)
// 校验验证码(开发环境下跳过验证码校验)
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, authKey: %s", claims.UserId, claims.AuthType, claims.UserType, 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, mobile: %s", claims.UserId, currentUser.Mobile.String)
// 当前用户是正式用户,检查是否已是代理
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, requestMobile: %s", claims.UserId, currentUser.Mobile.String, encryptedMobile)
return nil, errors.Wrapf(xerr.NewErrMsg("请输入当前账号的手机号码"), "")
}
l.Infof("[RegisterByInviteCode] 前置检查通过, 正式用户手机号匹配")
} else {
l.Infof("[RegisterByInviteCode] 当前用户是临时用户或无手机号, userId: %s", claims.UserId)
}
}
// 验证邀请码是否有效
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] 找到目标用户, userId: %s", targetUser.Id)
} else {
l.Infof("[RegisterByInviteCode] 目标用户不存在, 将创建新用户")
}
// 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 {
// 场景1: 手机号不存在
l.Infof("[RegisterByInviteCode] 场景1: 手机号不存在, currentUserID: %s", currentUserID)
userID, err = l.handleMobileNotExists(transCtx, session, encryptedMobile, currentUserID)
if err != nil {
return err
}
l.Infof("[RegisterByInviteCode] 场景1处理完成, userID: %s", userID)
} else {
// 场景2: 手机号已存在
l.Infof("[RegisterByInviteCode] 场景2: 手机号已存在, targetUserId: %s, currentUserID: %s", targetUser.Id, currentUserID)
userID, err = l.handleMobileExists(transCtx, session, targetUser, currentUserID, currentAuthType, currentAuthKey)
if err != nil {
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] 事务处理失败: %v", 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 == "" {
// 场景1.1: 未登录 + 手机号不存在 -> 创建新用户
l.Infof("[handleMobileNotExists] 场景1.1: 未登录+手机号不存在, 创建新用户")
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 认证
if _, err := l.svcCtx.UserAuthModel.Insert(ctx, session, &model.UserAuth{
Id: uuid.NewString(),
UserId: newUser.Id,
AuthType: model.UserAuthTypeMobile,
AuthKey: encryptedMobile,
}); err != nil {
return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建手机号认证失败: %v", err)
}
l.Infof("[handleMobileNotExists] 手机号认证创建成功, 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
}
}
// 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)
// 检查目标用户是否已是代理
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 == "" {
// 场景2.1: 未登录 + 手机号存在 -> 直接使用目标用户(验证码已确认身份)
l.Infof("[handleMobileExists] 场景2.1: 未登录+手机号存在, 直接使用目标用户, userId: %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)
}
}
// 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
}