Files
ycc-proxy-server/app/main/api/internal/logic/agent/registerbyinvitecodelogic.go

363 lines
12 KiB
Go
Raw Normal View History

2025-11-27 13:09:54 +08:00
package agent
import (
"context"
"database/sql"
"fmt"
2025-12-02 19:57:10 +08:00
"os"
2025-12-09 18:55:28 +08:00
"strconv"
2025-11-27 13:09:54 +08:00
"time"
"ycc-server/app/main/model"
2025-12-02 21:25:00 +08:00
"ycc-server/common/ctxdata"
2025-12-09 18:55:28 +08:00
"ycc-server/common/globalkey"
2025-11-27 13:09:54 +08:00
"ycc-server/common/xerr"
"ycc-server/pkg/lzkit/crypto"
2025-12-09 18:55:28 +08:00
"github.com/google/uuid"
2025-11-27 13:09:54 +08:00
"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)
}
2025-12-02 19:57:10 +08:00
// 校验验证码(开发环境下跳过验证码校验)
if os.Getenv("ENV") != "development" {
2025-11-27 13:09:54 +08:00
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("验证码不正确"), "")
}
}
2025-12-09 18:55:28 +08:00
var inviteCodeModel *model.AgentInviteCode
inviteCodeModel, err = l.svcCtx.AgentInviteCodeModel.FindOneByCode(l.ctx, req.Referrer)
if err != nil && !errors.Is(err, model.ErrNotFound) {
2025-11-27 13:09:54 +08:00
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询邀请码失败, %v", err)
}
2025-12-09 18:55:28 +08:00
if inviteCodeModel != nil {
if inviteCodeModel.Status != 0 {
if inviteCodeModel.Status == 1 {
return nil, errors.Wrapf(xerr.NewErrMsg("邀请码已使用"), "")
}
return nil, errors.Wrapf(xerr.NewErrMsg("邀请码已失效"), "")
}
if inviteCodeModel.ExpireTime.Valid && inviteCodeModel.ExpireTime.Time.Before(time.Now()) {
return nil, errors.Wrapf(xerr.NewErrMsg("邀请码已过期"), "")
2025-11-27 13:09:54 +08:00
}
}
// 4. 使用事务处理注册
2025-12-09 18:55:28 +08:00
var userID string
var agentID string
2025-11-27 13:09:54 +08:00
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("您已经是代理"), "")
}
2025-12-02 21:25:00 +08:00
// 如果是临时用户(微信环境下),检查手机号是否已绑定其他微信号,并绑定临时用户到正式用户
// 注意:非微信环境下 claims 为 nil此逻辑不会执行直接使用已存在的 user.Id
claims, err := ctxdata.GetClaimsFromCtx(l.ctx)
if err == nil && claims != nil && claims.UserType == model.UserTypeTemp {
2025-12-09 18:55:28 +08:00
userAuth, err := l.svcCtx.UserAuthModel.FindOneByUserIdAuthType(l.ctx, user.Id, claims.AuthType)
2025-12-02 21:25:00 +08:00
if err != nil && !errors.Is(err, model.ErrNotFound) {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户认证失败, %v", err)
}
2025-12-09 18:55:28 +08:00
if userAuth != nil && userAuth.AuthKey != claims.AuthKey {
2025-12-02 21:25:00 +08:00
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)
}
}
2025-11-27 13:09:54 +08:00
userID = user.Id
}
2025-12-09 18:55:28 +08:00
var targetLevel int64
var parentAgentId string
if inviteCodeModel != nil {
targetLevel = inviteCodeModel.TargetLevel
if inviteCodeModel.AgentId.Valid {
parentAgentId = inviteCodeModel.AgentId.String
}
} else {
if codeVal, parseErr := strconv.ParseInt(req.Referrer, 10, 64); parseErr == nil && codeVal > 0 {
parentAgent, err := l.findAgentByCode(transCtx, codeVal)
if err != nil {
return errors.Wrapf(err, "")
}
parentAgentId = parentAgent.Id
targetLevel = 1
} else {
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 {
return errors.Wrapf(xerr.NewErrMsg("邀请信息无效"), "")
}
parentAgentId = agents[0].Id
targetLevel = 1
}
2025-11-27 13:09:54 +08:00
}
// 4.3 创建代理记录
2025-12-09 18:55:28 +08:00
newAgent := &model.Agent{Id: uuid.NewString(), UserId: userID, Level: targetLevel, Mobile: encryptedMobile}
2025-11-27 13:09:54 +08:00
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 处理上级关系
2025-12-09 18:55:28 +08:00
if parentAgentId != "" {
2025-11-27 13:09:54 +08:00
// 查找上级代理
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, "查找团队首领失败")
}
2025-12-09 18:55:28 +08:00
if teamLeaderId != "" {
newAgent.TeamLeaderId = sql.NullString{String: teamLeaderId, Valid: true}
2025-11-27 13:09:54 +08:00
}
2025-12-09 18:55:28 +08:00
newAgent.AgentCode = 0
_, err = l.svcCtx.AgentModel.Insert(transCtx, session, newAgent)
2025-11-27 13:09:54 +08:00
if err != nil {
return errors.Wrapf(err, "创建代理记录失败")
}
2025-12-09 18:55:28 +08:00
agentID = newAgent.Id
2025-11-27 13:09:54 +08:00
// 建立关系
2025-12-09 18:55:28 +08:00
relation := &model.AgentRelation{Id: uuid.NewString(), ParentId: parentAgent.Id, ChildId: agentID, RelationType: 1}
2025-11-27 13:09:54 +08:00
if _, err := l.svcCtx.AgentRelationModel.Insert(transCtx, session, relation); err != nil {
return errors.Wrapf(err, "建立代理关系失败")
}
} else {
// 平台发放的钻石邀请码,独立成团队
if targetLevel == 3 {
2025-12-09 18:55:28 +08:00
newAgent.AgentCode = 0
_, err = l.svcCtx.AgentModel.Insert(transCtx, session, newAgent)
2025-11-27 13:09:54 +08:00
if err != nil {
return errors.Wrapf(err, "创建代理记录失败")
}
2025-12-09 18:55:28 +08:00
agentID = newAgent.Id
2025-11-27 13:09:54 +08:00
// 设置自己为团队首领
2025-12-09 18:55:28 +08:00
newAgent.TeamLeaderId = sql.NullString{String: agentID, Valid: true}
2025-12-02 19:57:10 +08:00
if err := l.svcCtx.AgentModel.UpdateInTransaction(transCtx, session, newAgent); err != nil {
2025-11-27 13:09:54 +08:00
return errors.Wrapf(err, "更新团队首领失败")
}
} else {
// 普通/黄金代理,但没有上级(异常情况)
2025-12-09 18:55:28 +08:00
newAgent.AgentCode = 0
_, err = l.svcCtx.AgentModel.Insert(transCtx, session, newAgent)
2025-11-27 13:09:54 +08:00
if err != nil {
return errors.Wrapf(err, "创建代理记录失败")
}
2025-12-09 18:55:28 +08:00
agentID = newAgent.Id
2025-11-27 13:09:54 +08:00
}
}
// 4.5 初始化钱包
2025-12-09 18:55:28 +08:00
wallet := &model.AgentWallet{Id: uuid.NewString(), AgentId: agentID}
2025-11-27 13:09:54 +08:00
if _, err := l.svcCtx.AgentWalletModel.Insert(transCtx, session, wallet); err != nil {
return errors.Wrapf(err, "初始化钱包失败")
}
// 4.6 更新邀请码状态
// 钻石级别的邀请码只能使用一次,使用后立即失效
// 普通级别的邀请码可以无限使用,不更新状态
if targetLevel == 3 {
// 钻石邀请码:使用后失效
inviteCodeModel.Status = 1 // 已使用(使用后立即失效)
}
2025-12-09 18:55:28 +08:00
if inviteCodeModel != nil {
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, "更新邀请码状态失败")
}
2025-11-27 13:09:54 +08:00
}
2025-12-02 19:57:10 +08:00
// 4.7 记录邀请码使用历史(用于统计和查询)
2025-12-09 18:55:28 +08:00
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, "记录邀请码使用历史失败")
}
2025-12-02 19:57:10 +08:00
}
2025-11-27 13:09:54 +08:00
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 = "钻石"
}
2025-12-09 18:55:28 +08:00
agent, _ := l.svcCtx.AgentModel.FindOne(l.ctx, agentID)
2025-11-27 13:09:54 +08:00
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,
2025-12-09 18:55:28 +08:00
AgentCode: func() int64 {
if agent != nil {
return agent.AgentCode
}
return 0
}(),
2025-11-27 13:09:54 +08:00
}, nil
}
// findTeamLeader 查找团队首领(钻石代理)
2025-12-09 18:55:28 +08:00
func (l *RegisterByInviteCodeLogic) findTeamLeader(ctx context.Context, agentId string) (string, error) {
2025-11-27 13:09:54 +08:00
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 {
2025-12-09 18:55:28 +08:00
return "", err
2025-11-27 13:09:54 +08:00
}
if len(relations) == 0 {
agent, err := l.svcCtx.AgentModel.FindOne(ctx, currentId)
if err != nil {
2025-12-09 18:55:28 +08:00
return "", err
2025-11-27 13:09:54 +08:00
}
if agent.Level == 3 {
return agent.Id, nil
}
2025-12-09 18:55:28 +08:00
return "", nil
2025-11-27 13:09:54 +08:00
}
parentAgent, err := l.svcCtx.AgentModel.FindOne(ctx, relations[0].ParentId)
if err != nil {
2025-12-09 18:55:28 +08:00
return "", err
2025-11-27 13:09:54 +08:00
}
if parentAgent.Level == 3 {
return parentAgent.Id, nil
}
currentId = parentAgent.Id
depth++
}
2025-12-09 18:55:28 +08:00
return "", nil
}
func (l *RegisterByInviteCodeLogic) findAgentByCode(ctx context.Context, code int64) (*model.Agent, error) {
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 {
return nil, errors.Wrapf(xerr.NewErrMsg("上级邀请码不存在"), "")
}
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
2025-11-27 13:09:54 +08:00
}