package agent import ( "context" "database/sql" "fmt" "os" "time" "ycc-server/app/main/model" "ycc-server/common/ctxdata" "ycc-server/common/xerr" "ycc-server/pkg/lzkit/crypto" "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) { 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) } // 校验验证码(开发环境下跳过验证码校验) if os.Getenv("ENV") != "development" { redisKey := fmt.Sprintf("%s:%s", "agentApply", encryptedMobile) cacheCode, err := l.svcCtx.Redis.Get(redisKey) if err != nil { if errors.Is(err, redis.Nil) { return nil, errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "") } return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "读取验证码失败, %v", err) } if cacheCode != req.Code { return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "") } } // 1. 查询邀请码 inviteCodeModel, err := l.svcCtx.AgentInviteCodeModel.FindOneByCode(l.ctx, req.InviteCode) if err != nil { if errors.Is(err, model.ErrNotFound) { return nil, errors.Wrapf(xerr.NewErrMsg("邀请码不存在"), "") } return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询邀请码失败, %v", err) } // 2. 验证邀请码状态 // 钻石级别的邀请码只能使用一次,使用后立即失效 // 普通级别的邀请码可以无限使用 if inviteCodeModel.Status != 0 { if inviteCodeModel.Status == 1 { return nil, errors.Wrapf(xerr.NewErrMsg("邀请码已使用"), "") } return nil, errors.Wrapf(xerr.NewErrMsg("邀请码已失效"), "") } // 3. 验证邀请码是否过期 if inviteCodeModel.ExpireTime.Valid && inviteCodeModel.ExpireTime.Time.Before(time.Now()) { return nil, errors.Wrapf(xerr.NewErrMsg("邀请码已过期"), "") } // 4. 使用事务处理注册 var userID int64 var agentID int64 var agentLevel int64 err = l.svcCtx.AgentInviteCodeModel.Trans(l.ctx, func(transCtx context.Context, session sqlx.Session) error { // 4.1 检查用户是否已存在 user, findUserErr := l.svcCtx.UserModel.FindOneByMobile(l.ctx, 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 user == nil { // 用户不存在,注册新用户 userID, err = l.svcCtx.UserService.RegisterUser(l.ctx, encryptedMobile) if err != nil { return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "注册用户失败: %v", err) } } else { // 用户已存在,检查是否已是代理 existingAgent, err := l.svcCtx.AgentModel.FindOneByUserId(transCtx, user.Id) if err != nil && !errors.Is(err, model.ErrNotFound) { return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败, %v", err) } if existingAgent != nil { return errors.Wrapf(xerr.NewErrMsg("您已经是代理"), "") } // 如果是临时用户(微信环境下),检查手机号是否已绑定其他微信号,并绑定临时用户到正式用户 // 注意:非微信环境下 claims 为 nil,此逻辑不会执行,直接使用已存在的 user.Id claims, err := ctxdata.GetClaimsFromCtx(l.ctx) if err == nil && claims != nil && claims.UserType == model.UserTypeTemp { userTemp, err := l.svcCtx.UserTempModel.FindOne(l.ctx, claims.UserId) if err != nil { return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询临时用户失败, %v", err) } userAuth, err := l.svcCtx.UserAuthModel.FindOneByUserIdAuthType(l.ctx, user.Id, userTemp.AuthType) if err != nil && !errors.Is(err, model.ErrNotFound) { return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户认证失败, %v", err) } if userAuth != nil && userAuth.AuthKey != userTemp.AuthKey { return errors.Wrapf(xerr.NewErrMsg("该手机号已绑定其他微信号"), "") } // 绑定临时用户到正式用户 err = l.svcCtx.UserService.TempUserBindUser(l.ctx, session, user.Id) if err != nil { return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "绑定用户失败: %v", err) } } userID = user.Id } // 4.2 获取邀请码信息 targetLevel := inviteCodeModel.TargetLevel var parentAgentId int64 = 0 if inviteCodeModel.AgentId.Valid { parentAgentId = inviteCodeModel.AgentId.Int64 } // 4.3 创建代理记录 newAgent := &model.Agent{ 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} } // 4.4 处理上级关系 if parentAgentId > 0 { // 查找上级代理 parentAgent, err := l.svcCtx.AgentModel.FindOne(transCtx, parentAgentId) if err != nil { return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询上级代理失败, %v", err) } // 验证关系是否允许(下级不能比上级等级高) if 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 > 0 { newAgent.TeamLeaderId = sql.NullInt64{Int64: teamLeaderId, Valid: true} } // 先插入代理记录 agentResult, err := l.svcCtx.AgentModel.Insert(transCtx, session, newAgent) if err != nil { return errors.Wrapf(err, "创建代理记录失败") } agentID, _ = agentResult.LastInsertId() newAgent.Id = agentID // 建立关系 relation := &model.AgentRelation{ ParentId: parentAgent.Id, ChildId: agentID, RelationType: 1, // 直接关系 } if _, err := l.svcCtx.AgentRelationModel.Insert(transCtx, session, relation); err != nil { return errors.Wrapf(err, "建立代理关系失败") } } else { // 平台发放的钻石邀请码,独立成团队 if targetLevel == 3 { // 先插入代理记录 agentResult, err := l.svcCtx.AgentModel.Insert(transCtx, session, newAgent) if err != nil { return errors.Wrapf(err, "创建代理记录失败") } agentID, _ = agentResult.LastInsertId() newAgent.Id = agentID // 设置自己为团队首领 newAgent.TeamLeaderId = sql.NullInt64{Int64: agentID, Valid: true} if err := l.svcCtx.AgentModel.UpdateInTransaction(transCtx, session, newAgent); err != nil { return errors.Wrapf(err, "更新团队首领失败") } } else { // 普通/黄金代理,但没有上级(异常情况) agentResult, err := l.svcCtx.AgentModel.Insert(transCtx, session, newAgent) if err != nil { return errors.Wrapf(err, "创建代理记录失败") } agentID, _ = agentResult.LastInsertId() } } // 4.5 初始化钱包 wallet := &model.AgentWallet{ AgentId: agentID, } if _, err := l.svcCtx.AgentWalletModel.Insert(transCtx, session, wallet); err != nil { return errors.Wrapf(err, "初始化钱包失败") } // 4.6 更新邀请码状态 // 钻石级别的邀请码只能使用一次,使用后立即失效 // 普通级别的邀请码可以无限使用,不更新状态 if targetLevel == 3 { // 钻石邀请码:使用后失效 inviteCodeModel.Status = 1 // 已使用(使用后立即失效) } // 记录使用信息(用于统计,普通邀请码可以多次使用) inviteCodeModel.UsedUserId = sql.NullInt64{Int64: userID, Valid: true} inviteCodeModel.UsedAgentId = sql.NullInt64{Int64: 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, "更新邀请码状态失败") } // 4.7 记录邀请码使用历史(用于统计和查询) usage := &model.AgentInviteCodeUsage{ 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, "记录邀请码使用历史失败") } agentLevel = targetLevel return nil }) if err != nil { return nil, err } // 5. 生成并返回token token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal) if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成token失败: %v", err) } now := time.Now().Unix() // 获取等级名称 levelName := "" switch agentLevel { case 1: levelName = "普通" case 2: levelName = "黄金" case 3: levelName = "钻石" } 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, }, nil } // findTeamLeader 查找团队首领(钻石代理) func (l *RegisterByInviteCodeLogic) findTeamLeader(ctx context.Context, agentId int64) (int64, error) { 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 0, err } if len(relations) == 0 { agent, err := l.svcCtx.AgentModel.FindOne(ctx, currentId) if err != nil { return 0, err } if agent.Level == 3 { return agent.Id, nil } return 0, nil } parentAgent, err := l.svcCtx.AgentModel.FindOne(ctx, relations[0].ParentId) if err != nil { return 0, err } if parentAgent.Level == 3 { return parentAgent.Id, nil } currentId = parentAgent.Id depth++ } return 0, nil }