321 lines
11 KiB
Go
321 lines
11 KiB
Go
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 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)
|
|
}
|
|
|
|
// 1. 必须提供邀请码,用户不能自主成为代理
|
|
if req.InviteCode == "" {
|
|
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 int64
|
|
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 {
|
|
// 临时用户,检查手机号是否已绑定其他微信号
|
|
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
|
|
}
|
|
|
|
// 3. 检查是否已是代理
|
|
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 {
|
|
return errors.Wrapf(xerr.NewErrMsg("您已经是代理"), "")
|
|
}
|
|
|
|
// 4. 必须通过邀请码成为代理(没有其他途径)
|
|
// 4.1 查询邀请码
|
|
inviteCodeModel, err := l.svcCtx.AgentInviteCodeModel.FindOneByCode(transCtx, req.InviteCode)
|
|
if err != nil {
|
|
if errors.Is(err, model.ErrNotFound) {
|
|
return errors.Wrapf(xerr.NewErrMsg("邀请码不存在"), "")
|
|
}
|
|
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询邀请码失败, %v", err)
|
|
}
|
|
|
|
// 4.2 验证邀请码状态
|
|
// 钻石级别的邀请码只能使用一次,使用后立即失效
|
|
// 普通级别的邀请码可以无限使用
|
|
if inviteCodeModel.Status != 0 {
|
|
if inviteCodeModel.Status == 1 {
|
|
return errors.Wrapf(xerr.NewErrMsg("邀请码已使用"), "")
|
|
}
|
|
return errors.Wrapf(xerr.NewErrMsg("邀请码已失效"), "")
|
|
}
|
|
|
|
// 4.3 验证邀请码是否过期
|
|
if inviteCodeModel.ExpireTime.Valid && inviteCodeModel.ExpireTime.Time.Before(time.Now()) {
|
|
return errors.Wrapf(xerr.NewErrMsg("邀请码已过期"), "")
|
|
}
|
|
|
|
// 4.4 获取邀请码信息
|
|
targetLevel := inviteCodeModel.TargetLevel
|
|
var parentAgentId int64 = 0
|
|
if inviteCodeModel.AgentId.Valid {
|
|
parentAgentId = inviteCodeModel.AgentId.Int64
|
|
}
|
|
|
|
// 4.5 创建代理记录
|
|
newAgent := &model.Agent{
|
|
UserId: userID,
|
|
Level: targetLevel,
|
|
Mobile: encryptedMobile,
|
|
}
|
|
if req.Region != "" {
|
|
newAgent.Region = sql.NullString{String: req.Region, Valid: true}
|
|
}
|
|
|
|
// 4.6 处理上级关系
|
|
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, "创建代理记录失败")
|
|
}
|
|
_, _ = agentResult.LastInsertId()
|
|
}
|
|
}
|
|
|
|
// 4.7 初始化钱包
|
|
wallet := &model.AgentWallet{
|
|
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.NullInt64{Int64: userID, Valid: true}
|
|
inviteCodeModel.UsedAgentId = sql.NullInt64{Int64: 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{
|
|
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, model.UserTypeNormal)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成token失败: %v", err)
|
|
}
|
|
|
|
now := time.Now().Unix()
|
|
return &types.AgentApplyResp{
|
|
AccessToken: token,
|
|
AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire,
|
|
RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter,
|
|
}, nil
|
|
}
|
|
|
|
// findTeamLeader 查找团队首领(钻石代理)
|
|
func (l *ApplyForAgentLogic) 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
|
|
}
|