Files
ycc-proxy-server/app/main/api/internal/logic/agent/applyforagentlogic.go
2026-02-09 11:12:49 +08:00

365 lines
12 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 ApplyForAgentLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewApplyForAgentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ApplyForAgentLogic {
return &ApplyForAgentLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ApplyForAgentLogic) ApplyForAgent(req *types.AgentApplyReq) (resp *types.AgentApplyResp, err error) {
claims, err := ctxdata.GetClaimsFromCtx(l.ctx)
if err != nil && !errors.Is(err, ctxdata.ErrNoInCtx) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "代理申请失败, %v", err)
}
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 req.Referrer == "" {
return nil, errors.Wrapf(xerr.NewErrMsg("请填写邀请信息"), "")
}
// 2. 校验验证码(开发环境下跳过验证码校验)
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("验证码不正确"), "")
}
}
var userID string
transErr := l.svcCtx.AgentModel.Trans(l.ctx, func(transCtx context.Context, session sqlx.Session) error {
// 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 {
// 用户不存在,注册新用户
if claims != nil && claims.UserType == model.UserTypeNormal {
return errors.Wrapf(xerr.NewErrMsg("当前用户已注册,请输入注册的手机号"), "")
}
userID, err = l.svcCtx.UserService.RegisterUser(l.ctx, encryptedMobile)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "注册用户失败: %v", err)
}
} else {
// 用户已存在
if claims != nil && claims.UserType == model.UserTypeTemp {
// 临时用户,检查手机号是否已绑定其他微信号
userAuth, findUserAuthErr := l.svcCtx.UserAuthModel.FindOneByUserIdAuthType(l.ctx, user.Id, claims.AuthType)
if findUserAuthErr != nil && !errors.Is(findUserAuthErr, model.ErrNotFound) {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户认证失败, %v", findUserAuthErr)
}
if userAuth != nil && userAuth.AuthKey != claims.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
}
// 3. 检查是否已是代理H5 已注册过代理时,小程序绑定手机后视为自动登录,此处直接返回成功)
existingAgent, err := l.svcCtx.AgentModel.FindOneByUserId(transCtx, userID)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败, %v", err)
}
if existingAgent != nil {
// 已是代理(如 H5 已注册):不报错,事务外会统一发放 token 并返回成功,视为自动登录
return nil
}
var inviteCodeModel *model.AgentInviteCode
var parentAgentId string
var targetLevel int64
inviteCodeModel, err = l.svcCtx.AgentInviteCodeModel.FindOneByCode(transCtx, req.Referrer)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询邀请码失败, %v", err)
}
if inviteCodeModel != nil {
if inviteCodeModel.Status != 0 {
if inviteCodeModel.Status == 1 {
return errors.Wrapf(xerr.NewErrMsg("邀请码已使用"), "")
}
return errors.Wrapf(xerr.NewErrMsg("邀请码已失效"), "")
}
if inviteCodeModel.ExpireTime.Valid && inviteCodeModel.ExpireTime.Time.Before(time.Now()) {
return errors.Wrapf(xerr.NewErrMsg("邀请码已过期"), "")
}
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 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
}
}
// 4.5 创建代理记录
newAgent := &model.Agent{
Id: uuid.NewString(),
UserId: userID,
Level: targetLevel,
Mobile: encryptedMobile,
}
if req.Region != "" {
newAgent.Region = sql.NullString{String: req.Region, Valid: true}
}
// 4.6 处理上级关系
if parentAgentId != "" {
// 代理发放的邀请码,成为该代理的下级
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 != "" {
newAgent.TeamLeaderId = sql.NullString{String: teamLeaderId, Valid: true}
}
newAgent.AgentCode = 0
_, err = l.svcCtx.AgentModel.Insert(transCtx, session, newAgent)
if err != nil {
return errors.Wrapf(err, "创建代理记录失败")
}
// 已设置newAgent.Id为UUID
// 建立关系
relation := &model.AgentRelation{
Id: uuid.NewString(),
ParentId: parentAgent.Id,
ChildId: newAgent.Id,
RelationType: 1,
}
if _, err := l.svcCtx.AgentRelationModel.Insert(transCtx, session, relation); err != nil {
return errors.Wrapf(err, "建立代理关系失败")
}
} else {
// 平台发放的钻石邀请码,独立成团队
if targetLevel == 3 {
newAgent.AgentCode = 0
_, err = l.svcCtx.AgentModel.Insert(transCtx, session, newAgent)
if err != nil {
return errors.Wrapf(err, "创建代理记录失败")
}
// 设置自己为团队首领
newAgent.TeamLeaderId = sql.NullString{String: newAgent.Id, Valid: true}
if err := l.svcCtx.AgentModel.UpdateInTransaction(transCtx, session, newAgent); err != nil {
return errors.Wrapf(err, "更新团队首领失败")
}
} else {
newAgent.AgentCode = 0
_, err = l.svcCtx.AgentModel.Insert(transCtx, session, newAgent)
if err != nil {
return errors.Wrapf(err, "创建代理记录失败")
}
}
}
// 4.7 初始化钱包
wallet := &model.AgentWallet{
Id: uuid.NewString(),
AgentId: newAgent.Id,
}
if _, err := l.svcCtx.AgentWalletModel.Insert(transCtx, session, wallet); err != nil {
return errors.Wrapf(err, "初始化钱包失败")
}
// 4.8 更新邀请码状态
// 钻石级别的邀请码只能使用一次,使用后立即失效
// 普通级别的邀请码可以无限使用,不更新状态
if targetLevel == 3 {
// 钻石邀请码:使用后失效
inviteCodeModel.Status = 1 // 已使用(使用后立即失效)
}
// 记录使用信息(用于统计,普通邀请码可以多次使用)
inviteCodeModel.UsedUserId = sql.NullString{String: userID, Valid: true}
if inviteCodeModel != nil {
inviteCodeModel.UsedAgentId = sql.NullString{String: newAgent.Id, 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.9 记录邀请码使用历史(用于统计和查询)
usage := &model.AgentInviteCodeUsage{
Id: uuid.NewString(),
InviteCodeId: inviteCodeModel.Id,
Code: inviteCodeModel.Code,
UserId: userID,
AgentId: newAgent.Id,
AgentLevel: targetLevel,
UsedTime: time.Now(),
}
if _, err := l.svcCtx.AgentInviteCodeUsageModel.Insert(transCtx, session, usage); err != nil {
return errors.Wrapf(err, "记录邀请码使用历史失败")
}
return nil
})
if transErr != nil {
return nil, transErr
}
// 6. 生成并返回token
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)
}
now := time.Now().Unix()
agent, _ := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
var code int64
if agent != nil {
code = agent.AgentCode
}
return &types.AgentApplyResp{
AccessToken: token,
AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire,
RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter,
AgentCode: code,
}, nil
}
func (l *ApplyForAgentLogic) 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 *ApplyForAgentLogic) 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
}
// findTeamLeader 查找团队首领(钻石代理)
func (l *ApplyForAgentLogic) findTeamLeader(ctx context.Context, agentId string) (string, 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 "", err
}
if len(relations) == 0 {
agent, err := l.svcCtx.AgentModel.FindOne(ctx, currentId)
if err != nil {
return "", err
}
if agent.Level == 3 {
return agent.Id, nil
}
return "", nil
}
parentAgent, err := l.svcCtx.AgentModel.FindOne(ctx, relations[0].ParentId)
if err != nil {
return "", err
}
if parentAgent.Level == 3 {
return parentAgent.Id, nil
}
currentId = parentAgent.Id
depth++
}
return "", nil
}