From 0f676d3c76535f77f8381d0a00eb4eae01f983f2 Mon Sep 17 00:00:00 2001
From: liangzai <2440983361@qq.com>
Date: Wed, 10 Dec 2025 13:10:39 +0800
Subject: [PATCH] fix
---
.gitignore | 9 +-
API调用失败退款时的代理处理确认.md | 131 --
.../logic/agent/applyforagentlogic.go | 122 +-
.../logic/agent/registerbyinvitecodelogic.go | 19 +-
.../internal/logic/query/queryservicelogic.go | 337 ++-
app/main/api/internal/logic/user/authlogic.go | 4 +-
.../internal/logic/user/bindmobilelogic.go | 10 +-
.../api/internal/logic/user/detaillogic.go | 14 +-
.../api/internal/logic/user/gettokenlogic.go | 4 +-
.../logic/user/mobilecodeloginlogic.go | 2 +-
.../api/internal/logic/user/wxh5authlogic.go | 69 +-
.../internal/logic/user/wxminiauthlogic.go | 68 +-
.../userauthinterceptormiddleware.go | 6 +-
app/main/api/internal/service/userService.go | 49 +-
app/main/api/main.go | 4 +-
distributeNormalAgentBonus配置项说明.md | 101 -
代理系统测试用例清单.md | 448 ----
代理配置表分析和优化建议.md | 774 -------
佣金冻结功能完成总结.md | 136 --
佣金冻结功能实现说明.md | 106 -
删除推广订单功能计划.md | 213 --
定时清理报告系统问题分析.md | 111 -
报告查询链路和代理分配逻辑检查报告.md | 221 --
新代理系统完整文档.md | 1833 -----------------
新代理系统检查清单.md | 506 -----
短链系统实现说明.md | 135 --
解冻任务优化建议.md | 177 --
解冻任务实现方案说明.md | 132 --
退款时代理处理逻辑分析.md | 232 ---
邀请码使用历史功能说明.md | 107 -
邀请链接和二维码生成逻辑说明.md | 211 --
31 files changed, 375 insertions(+), 5916 deletions(-)
delete mode 100644 API调用失败退款时的代理处理确认.md
delete mode 100644 distributeNormalAgentBonus配置项说明.md
delete mode 100644 代理系统测试用例清单.md
delete mode 100644 代理配置表分析和优化建议.md
delete mode 100644 佣金冻结功能完成总结.md
delete mode 100644 佣金冻结功能实现说明.md
delete mode 100644 删除推广订单功能计划.md
delete mode 100644 定时清理报告系统问题分析.md
delete mode 100644 报告查询链路和代理分配逻辑检查报告.md
delete mode 100644 新代理系统完整文档.md
delete mode 100644 新代理系统检查清单.md
delete mode 100644 短链系统实现说明.md
delete mode 100644 解冻任务优化建议.md
delete mode 100644 解冻任务实现方案说明.md
delete mode 100644 退款时代理处理逻辑分析.md
delete mode 100644 邀请码使用历史功能说明.md
delete mode 100644 邀请链接和二维码生成逻辑说明.md
diff --git a/.gitignore b/.gitignore
index 5ac55ea..36ebdcb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,7 +6,6 @@
**/.DS_Store
#deploy data
-
data/*
!data/.gitkeep
@@ -19,6 +18,14 @@ data/*
/tmp/
+# 打包出来的可执行文件
/app/api
+/app/main/api/main
+/app/main/api/debug
+/app/main/api/test
+
+# 文档目录
+documents/*
+!documents/.gitkeep
deploy/script/js
diff --git a/API调用失败退款时的代理处理确认.md b/API调用失败退款时的代理处理确认.md
deleted file mode 100644
index f9822f8..0000000
--- a/API调用失败退款时的代理处理确认.md
+++ /dev/null
@@ -1,131 +0,0 @@
-# API调用失败退款时的代理处理确认
-
-## 执行流程分析
-
-### 关键执行顺序
-
-```
-1. 支付成功 → 创建代理订单(ProcessStatus = 0,未处理)
- ↓
-2. 支付回调 → 发送查询任务
- ↓
-3. 查询任务执行:
- ├─ 创建查询记录(query表,状态为 "pending") ✅ 第77-87行
- ├─ 生成授权书 ✅ 第108-142行
- ├─ 调用API(第164行)⚠️ 可能失败
- │ ├─ 如果成功 → 继续执行
- │ └─ 如果失败 → return handleError(第166行)⚠️ 直接返回
- │
- ├─ [API成功才会执行到这里]
- ├─ 保存响应数据 ✅ 第177-182行
- ├─ 更新查询状态为 "success" ✅ 第184-189行
- └─ 发送代理处理任务 ✅ 第192行(关键!只有到这里才会发送)
-```
-
-### 关键代码位置
-
-**第164-167行**:
-```go
-combinedResponse, err := l.svcCtx.ApiRequestService.ProcessRequests(decryptData, product.Id)
-if err != nil {
- return l.handleError(ctx, err, order, query) // ← 直接返回,不会继续执行
-}
-```
-
-**第192行(发送代理处理任务)**:
-```go
-// 报告生成成功后,发送代理处理异步任务(不阻塞报告流程)
-if asyncErr := l.svcCtx.AsynqService.SendAgentProcessTask(order.Id); asyncErr != nil {
- logx.Errorf("发送代理处理任务失败,订单ID: %d, 错误: %v", order.Id, asyncErr)
-}
-```
-
----
-
-## 确认结果
-
-### ✅ API调用失败时的情况(第166-167行)
-
-**执行流程**:
-1. 支付时创建代理订单(`ProcessStatus = 0`)
-2. API调用失败(第164行)
-3. **直接返回** `handleError`(第166行)
-4. 进入退款流程
-5. **第192行不会执行**(因为已经return了)
-
-**代理状态**:
-- ✅ 代理订单 `ProcessStatus = 0`(未处理)
-- ✅ 代理**没有收到**佣金
-- ✅ 代理**没有收到**返佣
-- ✅ 代理处理任务**没有发送**
-
----
-
-## 业务逻辑分析
-
-### ✅ 符合业务逻辑
-
-**理由**:
-1. **服务未完成交付**
- - API调用失败,意味着无法生成查询报告
- - 用户支付了订单,但服务没有成功交付
-
-2. **用户权益保护**
- - 平台退款给用户是合理的
- - 用户没有获得服务,不应该承担费用
-
-3. **代理收益逻辑**
- - 代理的收益应该基于**服务成功交付**
- - 而不是仅仅因为用户支付了订单
- - 如果服务未成功交付,代理不应该获得收益
-
-4. **当前实现正确**
- - 只有在查询成功后(第184行更新状态为 "success")
- - 才会发送代理处理任务(第192行)
- - API调用失败时,查询未成功,代理任务不会发送
-
----
-
-## 结论
-
-### ✅ 当前处理完全正确
-
-**对于 API 调用失败退款的情况**:
-- ✅ 代理订单未处理(`ProcessStatus = 0`)
-- ✅ 代理没有收到佣金和返佣
-- ✅ 代理处理任务没有发送
-- ✅ **完全符合业务逻辑**
-
-**业务逻辑合理性**:
-- ✅ 代理收益应该在服务成功交付后才发放
-- ✅ API调用失败意味着服务未交付,不应发放收益
-- ✅ 退款给用户是合理的,保护用户权益
-
----
-
-## 不需要修改
-
-当前逻辑已经正确处理了这种情况,**无需修改**。
-
-**关键保护点**:
-1. 代理处理任务只在查询成功后发送(第192行)
-2. API调用失败会直接返回,不会执行到第192行
-3. 代理订单状态保持为0,代理没有收益
-
----
-
-## 需要注意的其他场景
-
-虽然当前场景处理正确,但需要注意以下场景:
-
-### ⚠️ 场景:代理已处理但订单被退款
-
-如果:
-1. API调用成功
-2. 查询状态更新为 "success"
-3. 发送代理处理任务
-4. 代理处理任务执行,发放佣金和返佣(`ProcessStatus = 1`)
-5. **之后**订单被退款(比如管理员手动退款)
-
-这种情况下需要撤销代理收益(需要另外处理,不是当前场景)。
-
diff --git a/app/main/api/internal/logic/agent/applyforagentlogic.go b/app/main/api/internal/logic/agent/applyforagentlogic.go
index 12f46d7..a466f21 100644
--- a/app/main/api/internal/logic/agent/applyforagentlogic.go
+++ b/app/main/api/internal/logic/agent/applyforagentlogic.go
@@ -1,22 +1,22 @@
package agent
import (
- "context"
- "database/sql"
- "fmt"
- "os"
- "time"
- "ycc-server/app/main/model"
- "ycc-server/common/ctxdata"
- "ycc-server/common/globalkey"
- "ycc-server/common/xerr"
- "ycc-server/pkg/lzkit/crypto"
- "strconv"
+ "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"
+ "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"
@@ -50,9 +50,9 @@ func (l *ApplyForAgentLogic) ApplyForAgent(req *types.AgentApplyReq) (resp *type
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密手机号失败: %v", err)
}
- if req.Referrer == "" {
- return nil, errors.Wrapf(xerr.NewErrMsg("请填写邀请信息"), "")
- }
+ if req.Referrer == "" {
+ return nil, errors.Wrapf(xerr.NewErrMsg("请填写邀请信息"), "")
+ }
// 2. 校验验证码(开发环境下跳过验证码校验)
if os.Getenv("ENV") != "development" {
@@ -115,49 +115,49 @@ func (l *ApplyForAgentLogic) ApplyForAgent(req *types.AgentApplyReq) (resp *type
return errors.Wrapf(xerr.NewErrMsg("您已经是代理"), "")
}
- var inviteCodeModel *model.AgentInviteCode
- var parentAgentId string
- var targetLevel int64
+ 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
- }
- }
+ 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{
@@ -279,7 +279,7 @@ func (l *ApplyForAgentLogic) ApplyForAgent(req *types.AgentApplyReq) (resp *type
}
// 6. 生成并返回token
- token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal)
+ 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)
}
diff --git a/app/main/api/internal/logic/agent/registerbyinvitecodelogic.go b/app/main/api/internal/logic/agent/registerbyinvitecodelogic.go
index be625cb..a1dc0eb 100644
--- a/app/main/api/internal/logic/agent/registerbyinvitecodelogic.go
+++ b/app/main/api/internal/logic/agent/registerbyinvitecodelogic.go
@@ -105,10 +105,16 @@ func (l *RegisterByInviteCodeLogic) RegisterByInviteCode(req *types.RegisterByIn
return errors.Wrapf(xerr.NewErrMsg("您已经是代理"), "")
}
- // 如果是临时用户(微信环境下),检查手机号是否已绑定其他微信号,并绑定临时用户到正式用户
- // 注意:非微信环境下 claims 为 nil,此逻辑不会执行,直接使用已存在的 user.Id
+ // 检查用户是否有mobile绑定(没有mobile则不能成为代理)
+ // 如果是临时用户(微信环境下),需要先绑定手机号
claims, err := ctxdata.GetClaimsFromCtx(l.ctx)
- if err == nil && claims != nil && claims.UserType == model.UserTypeTemp {
+ if err == nil && claims != nil {
+ // 获取用户的mobile信息
+ if !user.Mobile.Valid || user.Mobile.String == "" {
+ // 临时用户(无mobile)不能直接成为代理,需要先绑定mobile
+ return errors.Wrapf(xerr.NewErrMsg("请先绑定手机号后再申请成为代理"), "")
+ }
+ // 检查是否已绑定手机号认证(用于确保后续可通过手机号登录)
userAuth, err := l.svcCtx.UserAuthModel.FindOneByUserIdAuthType(l.ctx, user.Id, claims.AuthType)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户认证失败, %v", err)
@@ -116,11 +122,6 @@ func (l *RegisterByInviteCodeLogic) RegisterByInviteCode(req *types.RegisterByIn
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
@@ -263,7 +264,7 @@ func (l *RegisterByInviteCodeLogic) RegisterByInviteCode(req *types.RegisterByIn
}
// 5. 生成并返回token
- token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal)
+ 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)
}
diff --git a/app/main/api/internal/logic/query/queryservicelogic.go b/app/main/api/internal/logic/query/queryservicelogic.go
index eb986f5..4830454 100644
--- a/app/main/api/internal/logic/query/queryservicelogic.go
+++ b/app/main/api/internal/logic/query/queryservicelogic.go
@@ -8,7 +8,6 @@ import (
"os"
"time"
"ycc-server/app/main/api/internal/service"
- "ycc-server/app/main/model"
"ycc-server/common/ctxdata"
"ycc-server/common/xerr"
"ycc-server/pkg/lzkit/crypto"
@@ -60,60 +59,60 @@ var productProcessors = map[string]func(*QueryServiceLogic, *types.QueryServiceR
func (l *QueryServiceLogic) PreprocessLogic(req *types.QueryServiceReq, product string) (*types.QueryServiceResp, error) {
if processor, exists := productProcessors[product]; exists {
- return processor(l, req) // 调用对应的处理函数
+ return processor(l, req) // 璋冪敤瀵瑰簲鐨勫鐞嗗嚱鏁?
}
- return nil, errors.New("未找到相应的处理程序")
+ return nil, errors.New("鏈壘鍒扮浉搴旂殑澶勭悊绋嬪簭")
}
func (l *QueryServiceLogic) ProcessMarriageLogic(req *types.QueryServiceReq) (*types.QueryServiceResp, error) {
- // AES解密
+ // AES瑙e瘑
decryptData, DecryptDataErr := l.DecryptData(req.Data)
if DecryptDataErr != nil {
return nil, DecryptDataErr
}
- // 校验参数
+ // 鏍¢獙鍙傛暟
var data types.MarriageReq
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 瑙e瘑鍚庣殑鏁版嵁鏍煎紡涓嶆纭? %+v", unmarshalErr)
}
if validatorErr := validator.Validate(data); validatorErr != nil {
- return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
+ return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "鏌ヨ鏈嶅姟, 鍙傛暟涓嶆纭? %+v", validatorErr)
}
- // 校验验证码
+ // 鏍¢獙楠岃瘉鐮?
verifyCodeErr := l.VerifyCode(data.Mobile, data.Code)
if verifyCodeErr != nil {
return nil, verifyCodeErr
}
- // 校验三要素
+ // 鏍¢獙涓夎绱?
verifyErr := l.Verify(data.Name, data.IDCard, data.Mobile)
if verifyErr != nil {
return nil, verifyErr
}
- // 缓存
+ // 缂撳瓨
params := map[string]interface{}{
"name": data.Name,
"id_card": data.IDCard,
"mobile": data.Mobile,
}
- userID, userType, err := l.GetOrCreateUser()
- if err != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 处理用户失败: %v", err)
- }
+ userID, err := l.GetOrCreateUser()
+ if err != nil {
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 澶勭悊鐢ㄦ埛澶辫触: %v", err)
+ }
cacheNo, cacheDataErr := l.CacheData(params, "marriage", userID)
if cacheDataErr != nil {
return nil, cacheDataErr
}
- token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, userType)
+ 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)
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 鐢熸垚token澶辫触 : %v", err)
}
- // 获取当前时间戳
+ // 鑾峰彇褰撳墠鏃堕棿鎴?
now := time.Now().Unix()
return &types.QueryServiceResp{
Id: cacheNo,
@@ -123,58 +122,58 @@ func (l *QueryServiceLogic) ProcessMarriageLogic(req *types.QueryServiceReq) (*t
}, nil
}
-// 处理家政服务相关逻辑
+// 澶勭悊瀹舵斂鏈嶅姟鐩稿叧閫昏緫
func (l *QueryServiceLogic) ProcessHomeServiceLogic(req *types.QueryServiceReq) (*types.QueryServiceResp, error) {
- // AES解密
+ // AES瑙e瘑
decryptData, DecryptDataErr := l.DecryptData(req.Data)
if DecryptDataErr != nil {
return nil, DecryptDataErr
}
- // 校验参数
+ // 鏍¢獙鍙傛暟
var data types.HomeServiceReq
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 瑙e瘑鍚庣殑鏁版嵁鏍煎紡涓嶆纭? %+v", unmarshalErr)
}
if validatorErr := validator.Validate(data); validatorErr != nil {
- return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
+ return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "鏌ヨ鏈嶅姟, 鍙傛暟涓嶆纭? %+v", validatorErr)
}
- // 校验验证码
+ // 鏍¢獙楠岃瘉鐮?
verifyCodeErr := l.VerifyCode(data.Mobile, data.Code)
if verifyCodeErr != nil {
return nil, verifyCodeErr
}
- // 校验三要素
+ // 鏍¢獙涓夎绱?
verifyErr := l.Verify(data.Name, data.IDCard, data.Mobile)
if verifyErr != nil {
return nil, verifyErr
}
- // 缓存
+ // 缂撳瓨
params := map[string]interface{}{
"name": data.Name,
"id_card": data.IDCard,
"mobile": data.Mobile,
}
- userID, userType, err := l.GetOrCreateUser()
- if err != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 处理用户失败: %v", err)
- }
+ userID, err := l.GetOrCreateUser()
+ if err != nil {
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 澶勭悊鐢ㄦ埛澶辫触: %v", err)
+ }
cacheNo, cacheDataErr := l.CacheData(params, "homeservice", userID)
if cacheDataErr != nil {
return nil, cacheDataErr
}
- token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, userType)
+ token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID)
if err != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 鐢熸垚token澶辫触 : %d", userID)
}
- // 获取当前时间戳
+ // 鑾峰彇褰撳墠鏃堕棿鎴?
now := time.Now().Unix()
return &types.QueryServiceResp{
Id: cacheNo,
@@ -184,58 +183,58 @@ func (l *QueryServiceLogic) ProcessHomeServiceLogic(req *types.QueryServiceReq)
}, nil
}
-// 处理风险评估相关逻辑
+// 澶勭悊椋庨櫓璇勪及鐩稿叧閫昏緫
func (l *QueryServiceLogic) ProcessRiskAssessmentLogic(req *types.QueryServiceReq) (*types.QueryServiceResp, error) {
- // AES解密
+ // AES瑙e瘑
decryptData, DecryptDataErr := l.DecryptData(req.Data)
if DecryptDataErr != nil {
return nil, DecryptDataErr
}
- // 校验参数
+ // 鏍¢獙鍙傛暟
var data types.RiskAssessmentReq
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 瑙e瘑鍚庣殑鏁版嵁鏍煎紡涓嶆纭? %+v", unmarshalErr)
}
if validatorErr := validator.Validate(data); validatorErr != nil {
- return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
+ return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "鏌ヨ鏈嶅姟, 鍙傛暟涓嶆纭? %+v", validatorErr)
}
- // 校验验证码
+ // 鏍¢獙楠岃瘉鐮?
verifyCodeErr := l.VerifyCode(data.Mobile, data.Code)
if verifyCodeErr != nil {
return nil, verifyCodeErr
}
- // 校验三要素
+ // 鏍¢獙涓夎绱?
verifyErr := l.Verify(data.Name, data.IDCard, data.Mobile)
if verifyErr != nil {
return nil, verifyErr
}
- // 缓存
+ // 缂撳瓨
params := map[string]interface{}{
"name": data.Name,
"id_card": data.IDCard,
"mobile": data.Mobile,
}
- userID, userType, err := l.GetOrCreateUser()
- if err != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 处理用户失败: %v", err)
- }
+ userID, err := l.GetOrCreateUser()
+ if err != nil {
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 澶勭悊鐢ㄦ埛澶辫触: %v", err)
+ }
cacheNo, cacheDataErr := l.CacheData(params, "riskassessment", userID)
if cacheDataErr != nil {
return nil, cacheDataErr
}
- token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, userType)
+ token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID)
if err != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 鐢熸垚token澶辫触 : %d", userID)
}
- // 获取当前时间戳
+ // 鑾峰彇褰撳墠鏃堕棿鎴?
now := time.Now().Unix()
return &types.QueryServiceResp{
Id: cacheNo,
@@ -245,57 +244,57 @@ func (l *QueryServiceLogic) ProcessRiskAssessmentLogic(req *types.QueryServiceRe
}, nil
}
-// 处理公司信息查询相关逻辑
+// 澶勭悊鍏徃淇℃伅鏌ヨ鐩稿叧閫昏緫
func (l *QueryServiceLogic) ProcessCompanyInfoLogic(req *types.QueryServiceReq) (*types.QueryServiceResp, error) {
- // AES解密
+ // AES瑙e瘑
decryptData, DecryptDataErr := l.DecryptData(req.Data)
if DecryptDataErr != nil {
return nil, DecryptDataErr
}
- // 校验参数
+ // 鏍¢獙鍙傛暟
var data types.CompanyInfoReq
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 瑙e瘑鍚庣殑鏁版嵁鏍煎紡涓嶆纭? %+v", unmarshalErr)
}
if validatorErr := validator.Validate(data); validatorErr != nil {
- return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
+ return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "鏌ヨ鏈嶅姟, 鍙傛暟涓嶆纭? %+v", validatorErr)
}
- // 校验验证码
+ // 鏍¢獙楠岃瘉鐮?
verifyCodeErr := l.VerifyCode(data.Mobile, data.Code)
if verifyCodeErr != nil {
return nil, verifyCodeErr
}
- // 校验三要素
+ // 鏍¢獙涓夎绱?
verifyErr := l.Verify(data.Name, data.IDCard, data.Mobile)
if verifyErr != nil {
return nil, verifyErr
}
- // 缓存
+ // 缂撳瓨
params := map[string]interface{}{
"name": data.Name,
"id_card": data.IDCard,
"mobile": data.Mobile,
}
- userID, userType, err := l.GetOrCreateUser()
- if err != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 处理用户失败: %v", err)
- }
+ userID, err := l.GetOrCreateUser()
+ if err != nil {
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 澶勭悊鐢ㄦ埛澶辫触: %v", err)
+ }
cacheNo, cacheDataErr := l.CacheData(params, "companyinfo", userID)
if cacheDataErr != nil {
return nil, cacheDataErr
}
- token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, userType)
+ token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID)
if err != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 鐢熸垚token澶辫触 : %d", userID)
}
- // 获取当前时间戳
+ // 鑾峰彇褰撳墠鏃堕棿鎴?
now := time.Now().Unix()
return &types.QueryServiceResp{
Id: cacheNo,
@@ -305,58 +304,58 @@ func (l *QueryServiceLogic) ProcessCompanyInfoLogic(req *types.QueryServiceReq)
}, nil
}
-// 处理租赁信息查询相关逻辑
+// 澶勭悊绉熻祦淇℃伅鏌ヨ鐩稿叧閫昏緫
func (l *QueryServiceLogic) ProcessRentalInfoLogic(req *types.QueryServiceReq) (*types.QueryServiceResp, error) {
- // AES解密
+ // AES瑙e瘑
decryptData, DecryptDataErr := l.DecryptData(req.Data)
if DecryptDataErr != nil {
return nil, DecryptDataErr
}
- // 校验参数
+ // 鏍¢獙鍙傛暟
var data types.RentalInfoReq
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 瑙e瘑鍚庣殑鏁版嵁鏍煎紡涓嶆纭? %+v", unmarshalErr)
}
if validatorErr := validator.Validate(data); validatorErr != nil {
- return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
+ return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "鏌ヨ鏈嶅姟, 鍙傛暟涓嶆纭? %+v", validatorErr)
}
- // 校验验证码
+ // 鏍¢獙楠岃瘉鐮?
verifyCodeErr := l.VerifyCode(data.Mobile, data.Code)
if verifyCodeErr != nil {
return nil, verifyCodeErr
}
- // 校验三要素
+ // 鏍¢獙涓夎绱?
verifyErr := l.Verify(data.Name, data.IDCard, data.Mobile)
if verifyErr != nil {
return nil, verifyErr
}
- // 缓存
+ // 缂撳瓨
params := map[string]interface{}{
"name": data.Name,
"id_card": data.IDCard,
"mobile": data.Mobile,
}
- userID, userType, err := l.GetOrCreateUser()
- if err != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 处理用户失败: %v", err)
- }
+ userID, err := l.GetOrCreateUser()
+ if err != nil {
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 澶勭悊鐢ㄦ埛澶辫触: %v", err)
+ }
cacheNo, cacheDataErr := l.CacheData(params, "rentalinfo", userID)
if cacheDataErr != nil {
return nil, cacheDataErr
}
- token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, userType)
+ token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID)
if err != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 鐢熸垚token澶辫触 : %d", userID)
}
- // 获取当前时间戳
+ // 鑾峰彇褰撳墠鏃堕棿鎴?
now := time.Now().Unix()
return &types.QueryServiceResp{
Id: cacheNo,
@@ -366,58 +365,58 @@ func (l *QueryServiceLogic) ProcessRentalInfoLogic(req *types.QueryServiceReq) (
}, nil
}
-// 处理贷前背景检查相关逻辑
+// 澶勭悊璐峰墠鑳屾櫙妫€鏌ョ浉鍏抽€昏緫
func (l *QueryServiceLogic) ProcessPreLoanBackgroundCheckLogic(req *types.QueryServiceReq) (*types.QueryServiceResp, error) {
- // AES解密
+ // AES瑙e瘑
decryptData, DecryptDataErr := l.DecryptData(req.Data)
if DecryptDataErr != nil {
return nil, DecryptDataErr
}
- // 校验参数
+ // 鏍¢獙鍙傛暟
var data types.PreLoanBackgroundCheckReq
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 瑙e瘑鍚庣殑鏁版嵁鏍煎紡涓嶆纭? %+v", unmarshalErr)
}
if validatorErr := validator.Validate(data); validatorErr != nil {
- return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
+ return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "鏌ヨ鏈嶅姟, 鍙傛暟涓嶆纭? %+v", validatorErr)
}
- // 校验验证码
+ // 鏍¢獙楠岃瘉鐮?
verifyCodeErr := l.VerifyCode(data.Mobile, data.Code)
if verifyCodeErr != nil {
return nil, verifyCodeErr
}
- // 校验三要素
+ // 鏍¢獙涓夎绱?
verifyErr := l.Verify(data.Name, data.IDCard, data.Mobile)
if verifyErr != nil {
return nil, verifyErr
}
- // 缓存
+ // 缂撳瓨
params := map[string]interface{}{
"name": data.Name,
"id_card": data.IDCard,
"mobile": data.Mobile,
}
- userID, userType, err := l.GetOrCreateUser()
- if err != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 处理用户失败: %v", err)
- }
+ userID, err := l.GetOrCreateUser()
+ if err != nil {
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 澶勭悊鐢ㄦ埛澶辫触: %v", err)
+ }
cacheNo, cacheDataErr := l.CacheData(params, "preloanbackgroundcheck", userID)
if cacheDataErr != nil {
return nil, cacheDataErr
}
- token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, userType)
+ token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID)
if err != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 鐢熸垚token澶辫触 : %d", userID)
}
- // 获取当前时间戳
+ // 鑾峰彇褰撳墠鏃堕棿鎴?
now := time.Now().Unix()
return &types.QueryServiceResp{
Id: cacheNo,
@@ -427,57 +426,57 @@ func (l *QueryServiceLogic) ProcessPreLoanBackgroundCheckLogic(req *types.QueryS
}, nil
}
-// 处理人事背调相关逻辑
+// 澶勭悊浜轰簨鑳岃皟鐩稿叧閫昏緫
func (l *QueryServiceLogic) ProcessBackgroundCheckLogic(req *types.QueryServiceReq) (*types.QueryServiceResp, error) {
- // AES解密
+ // AES瑙e瘑
decryptData, DecryptDataErr := l.DecryptData(req.Data)
if DecryptDataErr != nil {
return nil, DecryptDataErr
}
- // 校验参数
+ // 鏍¢獙鍙傛暟
var data types.BackgroundCheckReq
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 瑙e瘑鍚庣殑鏁版嵁鏍煎紡涓嶆纭? %+v", unmarshalErr)
}
if validatorErr := validator.Validate(data); validatorErr != nil {
- return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
+ return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "鏌ヨ鏈嶅姟, 鍙傛暟涓嶆纭? %+v", validatorErr)
}
- // 校验验证码
+ // 鏍¢獙楠岃瘉鐮?
verifyCodeErr := l.VerifyCode(data.Mobile, data.Code)
if verifyCodeErr != nil {
return nil, verifyCodeErr
}
- // 校验三要素
+ // 鏍¢獙涓夎绱?
verifyErr := l.Verify(data.Name, data.IDCard, data.Mobile)
if verifyErr != nil {
return nil, verifyErr
}
- // 缓存
+ // 缂撳瓨
params := map[string]interface{}{
"name": data.Name,
"id_card": data.IDCard,
"mobile": data.Mobile,
}
- userID, userType, err := l.GetOrCreateUser()
- if err != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 处理用户失败: %v", err)
- }
+ userID, err := l.GetOrCreateUser()
+ if err != nil {
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 澶勭悊鐢ㄦ埛澶辫触: %v", err)
+ }
cacheNo, cacheDataErr := l.CacheData(params, "backgroundcheck", userID)
if cacheDataErr != nil {
return nil, cacheDataErr
}
- token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, userType)
+ token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID)
if err != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 鐢熸垚token澶辫触 : %d", userID)
}
- // 获取当前时间戳
+ // 鑾峰彇褰撳墠鏃堕棿鎴?
now := time.Now().Unix()
return &types.QueryServiceResp{
Id: cacheNo,
@@ -487,55 +486,55 @@ func (l *QueryServiceLogic) ProcessBackgroundCheckLogic(req *types.QueryServiceR
}, nil
}
func (l *QueryServiceLogic) ProcessPersonalDataLogic(req *types.QueryServiceReq) (*types.QueryServiceResp, error) {
- // AES解密
+ // AES瑙e瘑
decryptData, DecryptDataErr := l.DecryptData(req.Data)
if DecryptDataErr != nil {
return nil, DecryptDataErr
}
- // 校验参数
+ // 鏍¢獙鍙傛暟
var data types.PersonalDataReq
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 瑙e瘑鍚庣殑鏁版嵁鏍煎紡涓嶆纭? %+v", unmarshalErr)
}
if validatorErr := validator.Validate(data); validatorErr != nil {
- return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
+ return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "鏌ヨ鏈嶅姟, 鍙傛暟涓嶆纭? %+v", validatorErr)
}
- // 校验验证码
+ // 鏍¢獙楠岃瘉鐮?
verifyCodeErr := l.VerifyCode(data.Mobile, data.Code)
if verifyCodeErr != nil {
return nil, verifyCodeErr
}
- // 校验三要素
+ // 鏍¢獙涓夎绱?
verifyErr := l.Verify(data.Name, data.IDCard, data.Mobile)
if verifyErr != nil {
return nil, verifyErr
}
- // 缓存
+ // 缂撳瓨
params := map[string]interface{}{
"name": data.Name,
"id_card": data.IDCard,
"mobile": data.Mobile,
}
- userID, userType, err := l.GetOrCreateUser()
- if err != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 处理用户失败: %v", err)
- }
+ userID, err := l.GetOrCreateUser()
+ if err != nil {
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 澶勭悊鐢ㄦ埛澶辫触: %v", err)
+ }
cacheNo, cacheDataErr := l.CacheData(params, "personalData", userID)
if cacheDataErr != nil {
return nil, cacheDataErr
}
- token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, userType)
+ token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID)
if err != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 鐢熸垚token澶辫触 : %d", userID)
}
- // 获取当前时间戳
+ // 鑾峰彇褰撳墠鏃堕棿鎴?
now := time.Now().Unix()
return &types.QueryServiceResp{
Id: cacheNo,
@@ -545,55 +544,55 @@ func (l *QueryServiceLogic) ProcessPersonalDataLogic(req *types.QueryServiceReq)
}, nil
}
func (l *QueryServiceLogic) ProcessConsumerFinanceReportLogic(req *types.QueryServiceReq) (*types.QueryServiceResp, error) {
- // AES解密
+ // AES瑙e瘑
decryptData, DecryptDataErr := l.DecryptData(req.Data)
if DecryptDataErr != nil {
return nil, DecryptDataErr
}
- // 校验参数
+ // 鏍¢獙鍙傛暟
var data types.ConsumerFinanceReportReq
if unmarshalErr := json.Unmarshal(decryptData, &data); unmarshalErr != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 解密后的数据格式不正确: %+v", unmarshalErr)
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 瑙e瘑鍚庣殑鏁版嵁鏍煎紡涓嶆纭? %+v", unmarshalErr)
}
if validatorErr := validator.Validate(data); validatorErr != nil {
- return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "查询服务, 参数不正确: %+v", validatorErr)
+ return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.PARAM_VERIFICATION_ERROR, validatorErr.Error()), "鏌ヨ鏈嶅姟, 鍙傛暟涓嶆纭? %+v", validatorErr)
}
- // 校验验证码
+ // 鏍¢獙楠岃瘉鐮?
verifyCodeErr := l.VerifyCode(data.Mobile, data.Code)
if verifyCodeErr != nil {
return nil, verifyCodeErr
}
- // 校验三要素
+ // 鏍¢獙涓夎绱?
verifyErr := l.Verify(data.Name, data.IDCard, data.Mobile)
if verifyErr != nil {
return nil, verifyErr
}
- // 缓存
+ // 缂撳瓨
params := map[string]interface{}{
"name": data.Name,
"id_card": data.IDCard,
"mobile": data.Mobile,
}
- userID, userType, err := l.GetOrCreateUser()
- if err != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 处理用户失败: %v", err)
- }
+ userID, err := l.GetOrCreateUser()
+ if err != nil {
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 澶勭悊鐢ㄦ埛澶辫触: %v", err)
+ }
cacheNo, cacheDataErr := l.CacheData(params, "consumerFinanceReport", userID)
if cacheDataErr != nil {
return nil, cacheDataErr
}
- token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, userType)
+ token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID)
if err != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 生成token失败 : %d", userID)
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 鐢熸垚token澶辫触 : %d", userID)
}
- // 获取当前时间戳
+ // 鑾峰彇褰撳墠鏃堕棿鎴?
now := time.Now().Unix()
return &types.QueryServiceResp{
Id: cacheNo,
@@ -606,43 +605,43 @@ func (l *QueryServiceLogic) DecryptData(data string) ([]byte, error) {
secretKey := l.svcCtx.Config.Encrypt.SecretKey
key, decodeErr := hex.DecodeString(secretKey)
if decodeErr != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "密钥获取失败: %+v", decodeErr)
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "瀵嗛挜鑾峰彇澶辫触: %+v", decodeErr)
}
decryptData, aesDecryptErr := crypto.AesDecrypt(data, key)
if aesDecryptErr != nil || len(decryptData) == 0 {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解密失败: %+v", aesDecryptErr)
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "瑙e瘑澶辫触: %+v", aesDecryptErr)
}
return decryptData, nil
}
-// 校验验证码
+// 鏍¢獙楠岃瘉鐮?
func (l *QueryServiceLogic) VerifyCode(mobile string, code string) error {
- // 开发环境下跳过验证码校验
+ // 寮€鍙戠幆澧冧笅璺宠繃楠岃瘉鐮佹牎楠?
if os.Getenv("ENV") == "development" {
return nil
}
secretKey := l.svcCtx.Config.Encrypt.SecretKey
encryptedMobile, err := crypto.EncryptMobile(mobile, secretKey)
if err != nil {
- return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密手机号失败: %+v", err)
+ return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鍔犲瘑鎵嬫満鍙峰け璐? %+v", err)
}
codeRedisKey := fmt.Sprintf("%s:%s", "query", encryptedMobile)
cacheCode, err := l.svcCtx.Redis.Get(codeRedisKey)
if err != nil {
if errors.Is(err, redis.Nil) {
- return errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "验证码过期: %s", mobile)
+ return errors.Wrapf(xerr.NewErrMsg("楠岃瘉鐮佸凡杩囨湡"), "楠岃瘉鐮佽繃鏈? %s", mobile)
}
- return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "读取验证码redis缓存失败, mobile: %s, err: %+v", mobile, err)
+ return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "璇诲彇楠岃瘉鐮乺edis缂撳瓨澶辫触, mobile: %s, err: %+v", mobile, err)
}
if cacheCode != code {
- return errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "验证码不正确: %s", mobile)
+ return errors.Wrapf(xerr.NewErrMsg("楠岃瘉鐮佷笉姝g‘"), "楠岃瘉鐮佷笉姝g‘: %s", mobile)
}
return nil
}
-// 二、三要素验证
+// 浜屻€佷笁瑕佺礌楠岃瘉
func (l *QueryServiceLogic) Verify(Name string, IDCard string, Mobile string) error {
- // 开发环境下跳过二/三要素验证
+ // 寮€鍙戠幆澧冧笅璺宠繃浜?涓夎绱犻獙璇?
if os.Getenv("ENV") == "development" {
return nil
}
@@ -653,13 +652,13 @@ func (l *QueryServiceLogic) Verify(Name string, IDCard string, Mobile string) er
}
verification, err := l.svcCtx.VerificationService.TwoFactorVerification(twoVerification)
if err != nil {
- return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "二要素验证失败: %v", err)
+ return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "浜岃绱犻獙璇佸け璐? %v", err)
}
if !verification.Passed {
- return errors.Wrapf(xerr.NewErrCodeMsg(xerr.SERVER_COMMON_ERROR, verification.Err.Error()), "二要素验证不通过: %v", err)
+ return errors.Wrapf(xerr.NewErrCodeMsg(xerr.SERVER_COMMON_ERROR, verification.Err.Error()), "浜岃绱犻獙璇佷笉閫氳繃: %v", err)
}
} else {
- // 三要素验证
+ // 涓夎绱犻獙璇?
threeVerification := service.ThreeFactorVerificationRequest{
Name: Name,
IDCard: IDCard,
@@ -667,30 +666,30 @@ func (l *QueryServiceLogic) Verify(Name string, IDCard string, Mobile string) er
}
verification, err := l.svcCtx.VerificationService.ThreeFactorVerification(threeVerification)
if err != nil {
- return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "三要素验证失败: %v", err)
+ return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "涓夎绱犻獙璇佸け璐? %v", err)
}
if !verification.Passed {
- return errors.Wrapf(xerr.NewErrCodeMsg(xerr.SERVER_COMMON_ERROR, verification.Err.Error()), "三要素验证不通过: %v", err)
+ return errors.Wrapf(xerr.NewErrCodeMsg(xerr.SERVER_COMMON_ERROR, verification.Err.Error()), "涓夎绱犻獙璇佷笉閫氳繃: %v", err)
}
}
return nil
}
-// 缓存
+// 缂撳瓨
func (l *QueryServiceLogic) CacheData(params map[string]interface{}, Product string, userID string) (string, error) {
agentIdentifier, _ := l.ctx.Value("agentIdentifier").(string)
secretKey := l.svcCtx.Config.Encrypt.SecretKey
key, decodeErr := hex.DecodeString(secretKey)
if decodeErr != nil {
- return "", errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 获取AES密钥失败: %+v", decodeErr)
+ return "", errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 鑾峰彇AES瀵嗛挜澶辫触: %+v", decodeErr)
}
paramsMarshal, marshalErr := json.Marshal(params)
if marshalErr != nil {
- return "", errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 序列化参数失败: %+v", marshalErr)
+ return "", errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 搴忓垪鍖栧弬鏁板け璐? %+v", marshalErr)
}
encryptParams, aesEncryptErr := crypto.AesEncrypt(paramsMarshal, key)
if aesEncryptErr != nil {
- return "", errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 加密参数失败: %+v", aesEncryptErr)
+ return "", errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 鍔犲瘑鍙傛暟澶辫触: %+v", aesEncryptErr)
}
queryCache := types.QueryCacheLoad{
Params: encryptParams,
@@ -699,7 +698,7 @@ func (l *QueryServiceLogic) CacheData(params map[string]interface{}, Product str
}
jsonData, marshalErr := json.Marshal(queryCache)
if marshalErr != nil {
- return "", errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 序列化参数失败: %+v", marshalErr)
+ return "", errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "鏌ヨ鏈嶅姟, 搴忓垪鍖栧弬鏁板け璐? %+v", marshalErr)
}
outTradeNo := "Q_" + l.svcCtx.AlipayService.GenerateOutTradeNo()
redisKey := fmt.Sprintf(types.QueryCacheKey, userID, outTradeNo)
@@ -710,18 +709,18 @@ func (l *QueryServiceLogic) CacheData(params map[string]interface{}, Product str
return outTradeNo, nil
}
-// GetOrCreateUser 获取或创建用户
-// 1. 如果上下文中已有用户ID,直接返回
-// 2. 如果是代理查询或APP请求,创建新用户
-// 3. 其他情况返回未登录错误
-func (l *QueryServiceLogic) GetOrCreateUser() (string, int64, error) {
- claims, err := ctxdata.GetClaimsFromCtx(l.ctx)
- if err == nil && claims != nil {
- return claims.UserId, claims.UserType, nil
- }
- userID, regErr := l.svcCtx.UserService.RegisterUUIDUser(l.ctx)
- if regErr != nil {
- return "", 0, regErr
- }
- return userID, model.UserTypeTemp, nil
+// GetOrCreateUser 鑾峰彇鎴栧垱寤虹敤鎴?
+// 1. 濡傛灉涓婁笅鏂囦腑宸叉湁鐢ㄦ埛ID锛岀洿鎺ヨ繑鍥?
+// 2. 濡傛灉鏄唬鐞嗘煡璇㈡垨APP璇锋眰锛屽垱寤烘柊鐢ㄦ埛
+// 3. 鍏朵粬鎯呭喌杩斿洖鏈櫥褰曢敊璇?
+func (l *QueryServiceLogic) GetOrCreateUser() (string, error) {
+ claims, err := ctxdata.GetClaimsFromCtx(l.ctx)
+ if err == nil && claims != nil {
+ return claims.UserId, nil
+ }
+ userID, regErr := l.svcCtx.UserService.RegisterUUIDUser(l.ctx)
+ if regErr != nil {
+ return "", regErr
+ }
+ return userID, nil
}
diff --git a/app/main/api/internal/logic/user/authlogic.go b/app/main/api/internal/logic/user/authlogic.go
index d67113e..02753ff 100644
--- a/app/main/api/internal/logic/user/authlogic.go
+++ b/app/main/api/internal/logic/user/authlogic.go
@@ -23,7 +23,7 @@ func NewAuthLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AuthLogic {
}
func (l *AuthLogic) Auth(req *types.AuthReq) (*types.AuthResp, error) {
- var userID string
+ var userID string
var userType int64
var authType string
var authKey string
@@ -88,7 +88,7 @@ func (l *AuthLogic) Auth(req *types.AuthReq) (*types.AuthResp, error) {
return nil, errors.Wrapf(xerr.NewErrMsg("不支持的平台类型"), "platform=%s", req.Platform)
}
- token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, userType)
+ 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)
}
diff --git a/app/main/api/internal/logic/user/bindmobilelogic.go b/app/main/api/internal/logic/user/bindmobilelogic.go
index 9d34f41..5fdb0b7 100644
--- a/app/main/api/internal/logic/user/bindmobilelogic.go
+++ b/app/main/api/internal/logic/user/bindmobilelogic.go
@@ -90,8 +90,8 @@ func (l *BindMobileLogic) BindMobile(req *types.BindMobileReq) (resp *types.Bind
if _, err := l.svcCtx.UserAuthModel.Insert(l.ctx, nil, &model.UserAuth{Id: uuid.NewString(), UserId: finalUserID, AuthType: model.UserAuthTypeMobile, AuthKey: encryptedMobile}); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建手机号认证失败: %v", err)
}
- // 发放正式用户token
- token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, finalUserID, model.UserTypeNormal)
+ // 发放token(userType会根据mobile字段动态计算)
+ token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, finalUserID)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成Token失败: %v", err)
}
@@ -118,7 +118,7 @@ func (l *BindMobileLogic) BindMobile(req *types.BindMobileReq) (resp *types.Bind
}
// 如果当前认证已属于目标手机号用户,直接发放token(无需合并)
if existingAuth != nil && existingAuth.UserId == finalUserID {
- token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, finalUserID, model.UserTypeNormal)
+ token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, finalUserID)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成Token失败: %v", err)
}
@@ -232,8 +232,8 @@ func (l *BindMobileLogic) BindMobile(req *types.BindMobileReq) (resp *types.Bind
return nil, err
}
- // 合并完成后生成并返回正式用户token
- token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, finalUserID, model.UserTypeNormal)
+ // 合并完成后生成token(userType会根据mobile字段动态计算)
+ token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, finalUserID)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成Token失败: %v", err)
}
diff --git a/app/main/api/internal/logic/user/detaillogic.go b/app/main/api/internal/logic/user/detaillogic.go
index c3d71b3..bbcab09 100644
--- a/app/main/api/internal/logic/user/detaillogic.go
+++ b/app/main/api/internal/logic/user/detaillogic.go
@@ -36,17 +36,8 @@ func (l *DetailLogic) Detail() (resp *types.UserInfoResp, err error) {
}
userID := claims.UserId
- userType := claims.UserType
- if userType != model.UserTypeNormal {
- return &types.UserInfoResp{
- UserInfo: types.User{
- Id: userID,
- UserType: userType,
- Mobile: "",
- NickName: "",
- },
- }, nil
- }
+
+ // 无论是临时用户还是正常用户,都需要从数据库中查询用户信息
user, err := l.svcCtx.UserModel.FindOne(l.ctx, userID)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
@@ -54,6 +45,7 @@ func (l *DetailLogic) Detail() (resp *types.UserInfoResp, err error) {
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "用户信息, 数据库查询用户信息失败, %v", err)
}
+
var userInfo types.User
err = copier.Copy(&userInfo, user)
if err != nil {
diff --git a/app/main/api/internal/logic/user/gettokenlogic.go b/app/main/api/internal/logic/user/gettokenlogic.go
index 95146a8..be9bdb9 100644
--- a/app/main/api/internal/logic/user/gettokenlogic.go
+++ b/app/main/api/internal/logic/user/gettokenlogic.go
@@ -2,9 +2,9 @@ package user
import (
"context"
+ "time"
"ycc-server/common/ctxdata"
"ycc-server/common/xerr"
- "time"
"github.com/pkg/errors"
@@ -33,7 +33,7 @@ func (l *GetTokenLogic) GetToken() (resp *types.MobileCodeLoginResp, err error)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "用户信息, %v", err)
}
- token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, claims.UserId, claims.UserType)
+ token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, claims.UserId)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "用户信息, %v", err)
}
diff --git a/app/main/api/internal/logic/user/mobilecodeloginlogic.go b/app/main/api/internal/logic/user/mobilecodeloginlogic.go
index 4701dff..7b78b0b 100644
--- a/app/main/api/internal/logic/user/mobilecodeloginlogic.go
+++ b/app/main/api/internal/logic/user/mobilecodeloginlogic.go
@@ -62,7 +62,7 @@ func (l *MobileCodeLoginLogic) MobileCodeLogin(req *types.MobileCodeLoginReq) (r
return nil, errors.Wrapf(xerr.NewErrMsg("用户不存在"), "手机登录, 用户不存在: %s", encryptedMobile)
}
userID = user.Id
- token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal)
+ token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 生成token失败 : %s", userID)
}
diff --git a/app/main/api/internal/logic/user/wxh5authlogic.go b/app/main/api/internal/logic/user/wxh5authlogic.go
index 417bc1e..66da8f0 100644
--- a/app/main/api/internal/logic/user/wxh5authlogic.go
+++ b/app/main/api/internal/logic/user/wxh5authlogic.go
@@ -1,22 +1,22 @@
package user
import (
- "context"
- "ycc-server/app/main/model"
- "ycc-server/common/xerr"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "time"
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "time"
+ "ycc-server/app/main/model"
+ "ycc-server/common/xerr"
- "github.com/google/uuid"
- "github.com/pkg/errors"
+ "github.com/google/uuid"
+ "github.com/pkg/errors"
- "ycc-server/app/main/api/internal/svc"
- "ycc-server/app/main/api/internal/types"
+ "ycc-server/app/main/api/internal/svc"
+ "ycc-server/app/main/api/internal/types"
- "github.com/zeromicro/go-zero/core/logx"
+ "github.com/zeromicro/go-zero/core/logx"
)
type WxH5AuthLogic struct {
@@ -47,29 +47,28 @@ func (l *WxH5AuthLogic) WxH5Auth(req *types.WXH5AuthReq) (resp *types.WXH5AuthRe
}
// Step 3: 处理用户信息
- var userID string
- var userType int64
- if userAuth != nil {
- // 已存在用户,直接登录
- userID = userAuth.UserId
- userType = model.UserTypeNormal
- } else {
- user := &model.User{Id: uuid.NewString()}
- _, err := l.svcCtx.UserModel.Insert(l.ctx, nil, user)
- if err != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建用户失败: %v", err)
- }
- ua := &model.UserAuth{Id: uuid.NewString(), UserId: user.Id, AuthType: model.UserAuthTypeWxh5OpenID, AuthKey: accessTokenResp.Openid}
- _, err = l.svcCtx.UserAuthModel.Insert(l.ctx, nil, ua)
- if err != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建用户授权失败: %v", err)
- }
- userID = user.Id
- userType = model.UserTypeTemp
- }
+ var userID string
+ if userAuth != nil {
+ // 已存在用户,直接登录
+ userID = userAuth.UserId
+ } else {
+ // 新用户创建为临时用户(没有mobile)
+ user := &model.User{Id: uuid.NewString()}
+ _, err := l.svcCtx.UserModel.Insert(l.ctx, nil, user)
+ if err != nil {
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建用户失败: %v", err)
+ }
+ ua := &model.UserAuth{Id: uuid.NewString(), UserId: user.Id, AuthType: model.UserAuthTypeWxh5OpenID, AuthKey: accessTokenResp.Openid}
+ _, err = l.svcCtx.UserAuthModel.Insert(l.ctx, nil, ua)
+ if err != nil {
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建用户授权失败: %v", err)
+ }
+ userID = user.Id
+ l.Infof("Created new weixin user: userID=%s, openid=%s", userID, accessTokenResp.Openid)
+ }
- // Step 4: 生成JWT Token
- token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, userType)
+ // Step 4: 生成JWT Token(动态计算userType)
+ token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成JWT token失败: %v", err)
}
diff --git a/app/main/api/internal/logic/user/wxminiauthlogic.go b/app/main/api/internal/logic/user/wxminiauthlogic.go
index d94842a..89f68d5 100644
--- a/app/main/api/internal/logic/user/wxminiauthlogic.go
+++ b/app/main/api/internal/logic/user/wxminiauthlogic.go
@@ -1,21 +1,21 @@
package user
import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "time"
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "time"
- "ycc-server/app/main/api/internal/svc"
- "ycc-server/app/main/api/internal/types"
- "ycc-server/app/main/model"
- "ycc-server/common/xerr"
+ "ycc-server/app/main/api/internal/svc"
+ "ycc-server/app/main/api/internal/types"
+ "ycc-server/app/main/model"
+ "ycc-server/common/xerr"
- "github.com/google/uuid"
- "github.com/pkg/errors"
- "github.com/zeromicro/go-zero/core/logx"
+ "github.com/google/uuid"
+ "github.com/pkg/errors"
+ "github.com/zeromicro/go-zero/core/logx"
)
type WxMiniAuthLogic struct {
@@ -46,29 +46,27 @@ func (l *WxMiniAuthLogic) WxMiniAuth(req *types.WXMiniAuthReq) (resp *types.WXMi
}
// 3. 处理用户信息
- var userID string
- var userType int64
- if userAuth != nil {
- // 已存在用户,直接登录
- userID = userAuth.UserId
- userType = model.UserTypeNormal
- } else {
- user := &model.User{Id: uuid.NewString()}
- _, err := l.svcCtx.UserModel.Insert(l.ctx, nil, user)
- if err != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建用户失败: %v", err)
- }
- ua := &model.UserAuth{Id: uuid.NewString(), UserId: user.Id, AuthType: model.UserAuthTypeWxMiniOpenID, AuthKey: sessionKeyResp.Openid}
- _, err = l.svcCtx.UserAuthModel.Insert(l.ctx, nil, ua)
- if err != nil {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建用户授权失败: %v", err)
- }
- userID = user.Id
- userType = model.UserTypeTemp
- }
+ var userID string
+ if userAuth != nil {
+ // 已存在用户,直接登录
+ userID = userAuth.UserId
+ } else {
+ // 新用户创建为临时用户(没有mobile)
+ user := &model.User{Id: uuid.NewString()}
+ _, err := l.svcCtx.UserModel.Insert(l.ctx, nil, user)
+ if err != nil {
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建用户失败: %v", err)
+ }
+ ua := &model.UserAuth{Id: uuid.NewString(), UserId: user.Id, AuthType: model.UserAuthTypeWxMiniOpenID, AuthKey: sessionKeyResp.Openid}
+ _, err = l.svcCtx.UserAuthModel.Insert(l.ctx, nil, ua)
+ if err != nil {
+ return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建用户授权失败: %v", err)
+ }
+ userID = user.Id
+ }
- // 4. 生成JWT Token
- token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, userType)
+ // 4. 生成JWT Token(动态计算userType)
+ token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成JWT Token失败: %v", err)
}
diff --git a/app/main/api/internal/middleware/userauthinterceptormiddleware.go b/app/main/api/internal/middleware/userauthinterceptormiddleware.go
index 0f05d1c..730f1ab 100644
--- a/app/main/api/internal/middleware/userauthinterceptormiddleware.go
+++ b/app/main/api/internal/middleware/userauthinterceptormiddleware.go
@@ -1,10 +1,10 @@
package middleware
import (
+ "net/http"
"ycc-server/app/main/model"
"ycc-server/common/ctxdata"
"ycc-server/common/xerr"
- "net/http"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/rest/httpx"
@@ -24,8 +24,10 @@ func (m *UserAuthInterceptorMiddleware) Handle(next http.HandlerFunc) http.Handl
httpx.Error(w, errors.Wrapf(xerr.NewErrCode(ErrCodeUnauthorized), "token解析失败: %v", err))
return
}
+ // 检查用户是否绑定了mobile(没有mobile表示是临时用户,不允许访问需要认证的接口)
+ // 注:临时用户现在基于 mobile 字段判断,而不是 UserType
if claims.UserType == model.UserTypeTemp {
- httpx.Error(w, errors.Wrapf(xerr.NewErrCode(xerr.USER_NEED_BIND_MOBILE), "token解析失败: %v", err))
+ httpx.Error(w, errors.Wrapf(xerr.NewErrCode(xerr.USER_NEED_BIND_MOBILE), "请先绑定手机号: %v", err))
return
}
next(w, r)
diff --git a/app/main/api/internal/service/userService.go b/app/main/api/internal/service/userService.go
index fb37bbd..8003657 100644
--- a/app/main/api/internal/service/userService.go
+++ b/app/main/api/internal/service/userService.go
@@ -63,17 +63,38 @@ func (s *UserService) RegisterUUIDUser(ctx context.Context) (string, error) {
return userId, nil
}
-// GeneralUserToken 生成用户token
-func (s *UserService) GeneralUserToken(ctx context.Context, userID string, userType int64) (string, error) {
+// GetUserType 根据user.Mobile字段动态计算用户类型
+// 如果有mobile,则为正式用户(UserTypeNormal),否则为临时用户(UserTypeTemp)
+func (s *UserService) GetUserType(ctx context.Context, userID string) (int64, error) {
+ user, err := s.userModel.FindOne(ctx, userID)
+ if err != nil {
+ return 0, err
+ }
+ if user.Mobile.Valid && user.Mobile.String != "" {
+ return model.UserTypeNormal, nil
+ }
+ return model.UserTypeTemp, nil
+}
+
+// GeneralUserToken 生成用户token(动态计算userType)
+func (s *UserService) GeneralUserToken(ctx context.Context, userID string) (string, error) {
platform, err := ctxdata.GetPlatformFromCtx(ctx)
if err != nil {
return "", err
}
+ // 动态计算userType
+ userType, err := s.GetUserType(ctx, userID)
+ if err != nil {
+ return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取用户信息失败: %v", err)
+ }
+
var isAgent int64
var agentID string
var authType string
var authKey string
+
+ // 只有正式用户(有mobile)才可能是代理
if userType == model.UserTypeNormal {
agent, err := s.agentModel.FindOneByUserId(ctx, userID)
if err != nil && !errors.Is(err, model.ErrNotFound) {
@@ -89,6 +110,7 @@ func (s *UserService) GeneralUserToken(ctx context.Context, userID string, userT
authKey = userAuth.AuthKey
}
} else {
+ // 临时用户获取其他平台的auth信息
platAuthType := s.getAuthTypeByPlatform(platform)
ua, err := s.userAuthModel.FindOneByUserIdAuthType(ctx, userID, platAuthType)
if err == nil && ua != nil {
@@ -159,12 +181,16 @@ func (s *UserService) RegisterUser(ctx context.Context, mobile string) (string,
return userId, nil
}
- // 双重判断是否已经注册
- if claims.UserType == model.UserTypeNormal {
+ // 双重判断是否已经注册(根据mobile判断,而不是userType)
+ currentUser, err := s.userModel.FindOne(ctx, claims.UserId)
+ if err != nil && !errors.Is(err, model.ErrNotFound) {
+ return "", err
+ }
+ if currentUser != nil && currentUser.Mobile.Valid && currentUser.Mobile.String != "" {
return "", errors.New("用户已注册")
}
var userId string
- // 临时转正式注册
+ // 临时用户绑定mobile转正式注册
err = s.userModel.Trans(ctx, func(ctx context.Context, session sqlx.Session) error {
user := &model.User{Id: uuid.NewString(), Mobile: sql.NullString{String: mobile, Valid: true}}
if _, userInsertErr := s.userModel.Insert(ctx, session, user); userInsertErr != nil {
@@ -187,17 +213,26 @@ func (s *UserService) RegisterUser(ctx context.Context, mobile string) (string,
return userId, nil
}
-// TempUserBindUser 临时用户绑定用户
+// TempUserBindUser 临时用户绑定用户(添加mobile使其变为正式用户)
func (s *UserService) TempUserBindUser(ctx context.Context, session sqlx.Session, normalUserID string) error {
claims, err := ctxdata.GetClaimsFromCtx(ctx)
if err != nil && !errors.Is(err, ctxdata.ErrNoInCtx) {
return err
}
- if claims == nil || claims.UserType != model.UserTypeTemp {
+ if claims == nil {
return errors.New("无临时用户")
}
+ // 检查当前用户是否已经绑定了mobile(根据mobile判断,而不是userType)
+ tempUser, err := s.userModel.FindOne(ctx, claims.UserId)
+ if err != nil && !errors.Is(err, model.ErrNotFound) {
+ return err
+ }
+ if tempUser != nil && tempUser.Mobile.Valid && tempUser.Mobile.String != "" {
+ return errors.New("临时用户已注册")
+ }
+
existingAuth, err := s.userAuthModel.FindOneByAuthTypeAuthKey(ctx, claims.AuthType, claims.AuthKey)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return err
diff --git a/app/main/api/main.go b/app/main/api/main.go
index 3d993ec..4c17ca1 100644
--- a/app/main/api/main.go
+++ b/app/main/api/main.go
@@ -61,7 +61,7 @@ func main() {
defer server.Stop()
handler.RegisterHandlers(server, svcContext)
-
+
// 自动注册API到数据库
apiRegistry := service.NewApiRegistryService(svcContext.AdminApiModel)
routes := server.Routes()
@@ -70,7 +70,7 @@ func main() {
} else {
logx.Infof("API注册成功,共注册 %d 个路由", len(routes))
}
-
+
fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
server.Start()
}
diff --git a/distributeNormalAgentBonus配置项说明.md b/distributeNormalAgentBonus配置项说明.md
deleted file mode 100644
index c7beaa0..0000000
--- a/distributeNormalAgentBonus配置项说明.md
+++ /dev/null
@@ -1,101 +0,0 @@
-# distributeNormalAgentBonus 函数使用的配置项说明
-
-## 函数位置
-`ycc-proxy-server/app/main/api/internal/service/agentService.go:254-478`
-
-## 使用的配置项列表
-
-### 1. `direct_parent_amount_normal`
-- **配置键**:`direct_parent_amount_normal`
-- **类型**:浮点数(float64)
-- **默认值**:2.0元
-- **使用位置**:第376行
-- **用途**:普通代理给直接上级普通代理的返佣金额
-- **使用场景**:
- - 当直接上级是普通代理时使用
- - 无论后续层级有多少普通代理,这部分金额只给推广人的直接上级
-- **示例**:等级加成6元,给直接上级普通代理2元
-
-### 2. `direct_parent_amount_gold`
-- **配置键**:`direct_parent_amount_gold`
-- **类型**:浮点数(float64)
-- **默认值**:3.0元
-- **使用位置**:第326行
-- **用途**:普通代理给直接上级黄金代理的返佣金额
-- **使用场景**:
- - 当直接上级是黄金代理时使用
- - 这部分金额给直接上级黄金代理,剩余部分继续向上分配给钻石上级
-- **示例**:等级加成6元,给直接上级黄金代理3元,剩余3元给钻石上级
-
-### 3. `max_gold_rebate_amount`
-- **配置键**:`max_gold_rebate_amount`
-- **类型**:浮点数(float64)
-- **默认值**:3.0元
-- **使用位置**:第492行
-- **用途**:普通代理给黄金上级的最大返佣金额
-- **使用场景**:
- - 当直接上级是普通代理,且只有黄金上级(没有钻石上级)时使用
- - 即使剩余金额超过这个值,也只能给黄金上级最多这个金额,超出部分归平台
-- **示例**:剩余4元,最大限额3元,则给黄金3元,剩余1元归平台
-
-### 4. `level_2_bonus`
-- **配置键**:`level_2_bonus`
-- **类型**:整数(int64),从配置读取时转换为浮点数计算
-- **默认值**:3元
-- **使用位置**:第438行(通过 `getLevelBonus(ctx, 2)` 调用)
-- **用途**:黄金代理的等级加成
-- **使用场景**:
- - 用于计算等级加成的差(普通等级加成 - 黄金等级加成)
- - 当直接上级是普通代理,且上级链中有黄金和钻石时,用于计算给黄金的返佣金额
-- **计算逻辑**:
- - 等级加成差 = 普通等级加成(6元)- 黄金等级加成(3元)= 3元
- - 给黄金的金额 = 等级加成差(3元)- 已给普通的部分(2元)= 1元
-
-### 5. `level_1_bonus`
-- **配置键**:`level_1_bonus`
-- **类型**:整数(int64)
-- **默认值**:6元
-- **使用位置**:通过 `amount` 参数传入(在 `AgentProcess` 中通过 `getLevelBonus(ctx, 1)` 获取)
-- **用途**:普通代理的等级加成
-- **说明**:
- - 不在 `distributeNormalAgentBonus` 函数中直接读取
- - 作为 `amount` 参数传入函数
- - 这是需要分配的总额
-
-## 配置项使用总结表
-
-| 配置键 | 默认值 | 使用场景 | 代码行号 |
-|--------|--------|---------|---------|
-| `direct_parent_amount_normal` | 2.0元 | 直接上级是普通代理时,给直接上级的返佣金额 | 376 |
-| `direct_parent_amount_gold` | 3.0元 | 直接上级是黄金代理时,给直接上级的返佣金额 | 326 |
-| `max_gold_rebate_amount` | 3.0元 | 只有黄金上级(无钻石)时,给黄金的最大返佣金额 | 492 |
-| `level_2_bonus` | 3元 | 黄金代理等级加成,用于计算等级加成差 | 438 |
-| `level_1_bonus` | 6元 | 普通代理等级加成,通过参数传入 | - |
-
-## 配置项在代码中的调用关系
-
-```
-AgentProcess (处理订单)
- ↓
- 调用 getLevelBonus(ctx, agent.Level) 获取普通代理等级加成
- ↓
- 传入 distributeNormalAgentBonus(amount=6元, ...)
- ↓
- 在函数内使用:
- 1. getRebateConfigFloat("direct_parent_amount_normal", 2.0) # 第376行
- 2. getRebateConfigFloat("direct_parent_amount_gold", 3.0) # 第326行
- 3. getRebateConfigFloat("max_gold_rebate_amount", 3.0) # 第492行
- 4. getLevelBonus(ctx, 2) -> 读取 level_2_bonus # 第438行
-```
-
-## 注意事项
-
-1. **所有配置项都支持动态配置**:如果配置不存在,会使用默认值
-2. **配置读取失败会记录日志**:确保配置问题时可以追踪
-3. **配置值的类型转换**:
- - 返佣配置直接使用浮点数(支持小数)
- - 等级加成从配置读取时为整数,计算时转换为浮点数
-4. **配置项命名规范**:
- - 返佣配置使用描述性名称:`direct_parent_amount_normal`、`direct_parent_amount_gold`、`max_gold_rebate_amount`
- - 等级加成配置使用数字后缀:`level_1_bonus`、`level_2_bonus`
-
diff --git a/代理系统测试用例清单.md b/代理系统测试用例清单.md
deleted file mode 100644
index fb0890c..0000000
--- a/代理系统测试用例清单.md
+++ /dev/null
@@ -1,448 +0,0 @@
-# 代理系统测试用例清单
-
-## 一、邀请下级代理测试
-
-### 1.1 钻石代理邀请下级
-- [ ] **钻石邀请普通代理**
- - 预期:建立上下级关系,普通代理的 team_leader_id 指向钻石代理
- - 验证:关系类型为直接关系,团队首领正确
-
-- [ ] **钻石邀请黄金代理**
- - 预期:建立上下级关系,黄金代理的 team_leader_id 指向钻石代理
- - 验证:关系类型为直接关系,团队首领正确
-
-- [ ] **钻石邀请钻石代理(不允许)**
- - 预期:拒绝,提示"代理等级不能高于上级代理"
- - 验证:邀请失败,无关系建立
-
-### 1.2 黄金代理邀请下级
-- [ ] **黄金邀请普通代理**
- - 预期:建立上下级关系,普通代理的 team_leader_id 指向上级钻石代理
- - 验证:关系类型为直接关系,团队首领正确(向上查找钻石)
-
-- [ ] **黄金邀请黄金代理(不允许)**
- - 预期:拒绝,提示"代理等级不能高于上级代理"
- - 验证:邀请失败,无关系建立
-
-- [ ] **黄金邀请钻石代理(不允许)**
- - 预期:拒绝,提示"代理等级不能高于上级代理"
- - 验证:邀请失败,无关系建立
-
-### 1.3 普通代理邀请下级
-- [ ] **普通邀请普通代理**
- - 预期:建立上下级关系,普通代理的 team_leader_id 向上查找钻石/黄金
- - 验证:关系类型为直接关系,团队首领正确
-
-- [ ] **普通邀请黄金代理(不允许)**
- - 预期:拒绝,提示"代理等级不能高于上级代理"
- - 验证:邀请失败,无关系建立
-
-- [ ] **普通邀请钻石代理(不允许)**
- - 预期:拒绝,提示"代理等级不能高于上级代理"
- - 验证:邀请失败,无关系建立
-
-### 1.4 多层级邀请测试
-- [ ] **钻石 → 黄金 → 普通(3层)**
- - 预期:3层关系链,所有代理的 team_leader_id 指向钻石
- - 验证:关系链正确,团队首领一致
-
-- [ ] **钻石 → 普通 → 普通 → 普通(4层)**
- - 预期:4层关系链,所有代理的 team_leader_id 指向钻石
- - 验证:关系链正确,团队首领一致
-
-- [ ] **钻石 → 普通 → 普通 → 普通 → 普通(5层)**
- - 预期:5层关系链,所有代理的 team_leader_id 指向钻石
- - 验证:关系链正确,团队首领一致
-
----
-
-## 二、推广报告收益测试(等级加成返佣)
-
-### 2.1 钻石代理推广报告
-- [ ] **钻石代理自己推广的报告**
- - 等级加成:0元
- - 预期:无等级加成返佣,全部收益归自己
- - 验证:agent_rebate 表无记录,代理收益 = 设定价格 - 基础底价 - 提价成本
-
-### 2.2 黄金代理推广报告
-- [ ] **黄金代理(上级是钻石)推广报告**
- - 等级加成:3元
- - 预期:3元全部返佣给钻石上级
- - 验证:agent_rebate 表记录正确,钻石上级钱包增加3元
-
-- [ ] **黄金代理(无上级/上级不是钻石)推广报告**
- - 等级加成:3元
- - 预期:返佣归平台(异常情况)
- - 验证:agent_rebate 表无记录
-
-### 2.3 普通代理推广报告(等级加成6元)
-
-#### 2.3.1 直接上级是钻石
-- [ ] **普通代理(上级是钻石)推广报告**
- - 等级加成:6元
- - 预期:6元全部返佣给钻石上级
- - 验证:agent_rebate 表记录正确,钻石上级钱包增加6元
-
-#### 2.3.2 直接上级是黄金
-- [ ] **普通代理(上级是黄金,黄金上级是钻石)推广报告**
- - 等级加成:6元
- - 预期:
- - 3元给黄金上级(配置:normal_to_gold_rebate)
- - 3元给钻石上级(上上级)
- - 验证:agent_rebate 表有2条记录,金额分配正确
-
-- [ ] **普通代理(上级是黄金,无钻石上级)推广报告**
- - 等级加成:6元
- - 预期:3元给黄金上级,剩余3元归平台
- - 验证:agent_rebate 表只有1条记录(3元给黄金),剩余归平台
-
-#### 2.3.3 直接上级是普通(多层普通代理)
-- [ ] **普通 → 普通 → 钻石(3层)**
- - 等级加成:6元
- - 预期:
- - 2元给直接上级普通(配置:normal_to_normal_rebate)
- - 4元给钻石上级(跳过中间普通,直接给钻石)
- - 验证:agent_rebate 表有2条记录,金额分配正确
-
-- [ ] **普通 → 普通 → 普通 → 钻石(4层)**
- - 等级加成:6元
- - 预期:
- - 2元给直接上级普通
- - 4元给钻石上级(跳过中间所有普通代理)
- - 验证:agent_rebate 表有2条记录,金额分配正确
-
-- [ ] **普通 → 普通 → 黄金(无钻石,3层)**
- - 等级加成:6元
- - 预期:
- - 2元给直接上级普通
- - 3元给黄金上级(配置:normal_to_gold_rebate_max)
- - 1元归平台(超出部分)
- - 验证:agent_rebate 表有2条记录,金额分配正确,剩余归平台
-
-- [ ] **普通 → 普通 → 普通(全部是普通,无钻石/黄金)**
- - 等级加成:6元
- - 预期:
- - 2元给直接上级普通
- - 4元归平台(无钻石/黄金上级)
- - 验证:agent_rebate 表只有1条记录(2元),剩余归平台
-
----
-
-## 三、升级代理收益测试
-
-### 3.1 自主付费升级
-
-#### 3.1.1 普通 → 黄金(199元)
-- [ ] **普通代理升级为黄金(上级是钻石)**
- - 升级费用:199元
- - 返佣:139元给原直接上级
- - 预期:
- - 原直接上级(钻石)钱包增加139元
- - 升级后不脱离关系(钻石 > 黄金)
- - 仍属于原团队
- - 验证:升级成功,返佣记录在 agent_upgrade 表,钱包余额正确
-
-- [ ] **普通代理升级为黄金(上级是黄金)**
- - 升级费用:199元
- - 返佣:139元给原直接上级(黄金)
- - 预期:
- - 原直接上级(黄金)钱包增加139元
- - 升级后脱离关系(同级不能作为上下级)
- - 保留团队关系(向上查找钻石)
- - 验证:升级成功,关系脱离,团队首领正确
-
-- [ ] **普通代理升级为黄金(上级是普通)**
- - 升级费用:199元
- - 返佣:139元给原直接上级(普通)
- - 预期:
- - 原直接上级(普通)钱包增加139元
- - 升级后脱离关系(下级等级高于上级)
- - 保留团队关系(向上查找钻石/黄金)
- - 验证:升级成功,关系脱离,团队首领正确
-
-#### 3.1.2 普通 → 钻石(980元)
-- [ ] **普通代理升级为钻石(上级是钻石)**
- - 升级费用:980元
- - 返佣:680元给原直接上级(钻石)
- - 预期:
- - 原直接上级(钻石)钱包增加680元
- - 升级后脱离关系(同级不能作为上下级)
- - 独立成为新团队,team_leader_id = 自己
- - 所有下级跟随到新团队
- - 验证:升级成功,独立成团队,下级团队首领更新
-
-- [ ] **普通代理升级为钻石(上级是黄金)**
- - 升级费用:980元
- - 返佣:680元给原直接上级(黄金)
- - 预期:
- - 原直接上级(黄金)钱包增加680元
- - 升级后脱离关系(下级等级高于上级)
- - 独立成为新团队,team_leader_id = 自己
- - 所有下级跟随到新团队
- - 验证:升级成功,独立成团队,下级团队首领更新
-
-- [ ] **普通代理升级为钻石(上级是普通)**
- - 升级费用:980元
- - 返佣:680元给原直接上级(普通)
- - 预期:
- - 原直接上级(普通)钱包增加680元
- - 升级后脱离关系(下级等级高于上级)
- - 独立成为新团队,team_leader_id = 自己
- - 所有下级跟随到新团队
- - 验证:升级成功,独立成团队,下级团队首领更新
-
-#### 3.1.3 黄金 → 钻石(980元)
-- [ ] **黄金代理升级为钻石(上级是钻石)**
- - 升级费用:980元
- - 返佣:680元给原直接上级(钻石)
- - 预期:
- - 原直接上级(钻石)钱包增加680元
- - 升级后脱离关系(同级不能作为上下级)
- - 独立成为新团队,team_leader_id = 自己
- - 所有下级跟随到新团队
- - 验证:升级成功,独立成团队,下级团队首领更新
-
-- [ ] **黄金代理升级为钻石(无上级)**
- - 升级费用:980元
- - 返佣:无
- - 预期:
- - 独立成为新团队,team_leader_id = 自己
- - 所有下级跟随到新团队
- - 验证:升级成功,独立成团队,下级团队首领更新
-
-### 3.2 钻石升级下级(免费)
-- [ ] **钻石升级下级(普通 → 黄金)**
- - 升级费用:免费
- - 返佣:无
- - 预期:
- - 被升级代理无需付费
- - 升级后根据原上级等级决定是否脱离关系
- - 保留团队关系
- - 验证:升级成功,费用为0,关系处理正确
-
----
-
-## 四、升级后团队转移测试
-
-### 4.1 普通 → 黄金升级(保留团队)
-
-#### 4.1.1 上级是钻石(不脱离)
-- [ ] **普通 → 黄金(上级是钻石),有下级**
- - 预期:
- - 不脱离关系
- - 保留原团队(team_leader_id 不变)
- - 所有下级(直接+间接)的 team_leader_id 不变
- - 验证:关系保留,所有下级团队首领不变
-
-#### 4.1.2 上级是黄金(脱离关系)
-- [ ] **普通 → 黄金(上级是黄金),有下级**
- - 预期:
- - 脱离直接上下级关系
- - 保留团队关系(向上查找钻石)
- - 所有下级(直接+间接)的 team_leader_id 不变
- - 验证:关系脱离(RelationType=2),所有下级团队首领不变
-
-#### 4.1.3 上级是普通(脱离关系)
-- [ ] **普通 → 黄金(上级是普通),有下级**
- - 预期:
- - 脱离直接上下级关系
- - 保留团队关系(向上查找钻石/黄金)
- - 所有下级(直接+间接)的 team_leader_id 不变
- - 验证:关系脱离,所有下级团队首领不变
-
-### 4.2 升级为钻石(独立成新团队)
-
-#### 4.2.1 普通 → 钻石
-- [ ] **普通 → 钻石(上级是钻石),有下级**
- - 预期:
- - 脱离关系
- - 独立成新团队(team_leader_id = 自己)
- - 所有直接下级的 team_leader_id 更新为自己
- - 所有间接下级的 team_leader_id 更新为自己(递归)
- - 验证:
- - 升级代理的 team_leader_id = 自己
- - 所有下级(直接+间接)的 team_leader_id = 升级代理ID
- - 下级数量统计正确
-
-- [ ] **普通 → 钻石(上级是黄金),有下级(2层)**
- - 预期:
- - 脱离关系
- - 独立成新团队
- - 直接下级跟随
- - 间接下级跟随
- - 验证:所有下级团队首领更新为新钻石
-
-- [ ] **普通 → 钻石(上级是普通),有下级(3层以上)**
- - 预期:
- - 脱离关系
- - 独立成新团队
- - 所有层级的下级都跟随
- - 验证:所有下级团队首领更新为新钻石
-
-#### 4.2.2 黄金 → 钻石
-- [ ] **黄金 → 钻石(上级是钻石),有下级**
- - 预期:
- - 脱离关系
- - 独立成新团队
- - 所有下级跟随
- - 验证:所有下级团队首领更新为新钻石
-
-### 4.3 复杂团队转移场景
-
-#### 4.3.1 多层级团队
-- [ ] **钻石A → 黄金B → 普通C → 普通D,B升级为钻石**
- - 预期:
- - B独立成新团队
- - C和D的 team_leader_id 更新为B
- - A的团队:只剩自己
- - B的团队:B、C、D
- - 验证:团队划分正确,关系链正确
-
-#### 4.3.2 跨团队转移
-- [ ] **钻石A → 普通B → 普通C,C升级为钻石**
- - 预期:
- - C独立成新团队
- - C无下级,团队只有C自己
- - A的团队:A、B
- - 验证:团队划分正确
-
-#### 4.3.3 深度层级转移
-- [ ] **钻石A → 普通B → 普通C → 普通D → 普通E,C升级为钻石**
- - 预期:
- - C独立成新团队
- - D和E的 team_leader_id 更新为C
- - A的团队:A、B
- - C的团队:C、D、E
- - 验证:团队划分正确,所有层级更新正确
-
----
-
-## 五、综合测试场景
-
-### 5.1 完整业务流程
-- [ ] **创建团队 → 邀请下级 → 推广报告 → 收益分配 → 升级 → 团队转移**
- - 步骤:
- 1. 创建钻石代理A
- 2. A邀请黄金代理B
- 3. B邀请普通代理C
- 4. C邀请普通代理D
- 5. D推广报告,验证收益分配
- 6. C升级为黄金,验证关系变化
- 7. B升级为钻石,验证团队转移
- - 验证:每个步骤的数据正确
-
-### 5.2 收益统计测试
-- [ ] **查询代理收益统计(包含佣金和返佣)**
- - 验证:agent_wallet 表的 Balance 和 TotalEarnings 正确
-
-- [ ] **查询下级列表和统计**
- - 验证:下级数量、团队规模统计正确
-
-### 5.3 边界情况测试
-- [ ] **钻石代理无下级时升级(边界情况)**
- - 验证:独立成团队,team_leader_id = 自己
-
-- [ ] **普通代理无上级时升级**
- - 验证:独立成团队,无返佣
-
-- [ ] **多层普通代理链,无钻石/黄金上级**
- - 验证:收益分配正确(部分归平台)
-
----
-
-## 六、数据验证检查点
-
-### 6.1 关系表验证
-- [ ] agent_relation 表的关系类型正确(1=直接关系,2=已脱离)
-- [ ] 脱离关系时,DetachReason 和 DetachTime 正确记录
-
-### 6.2 钱包验证
-- [ ] agent_wallet 表的 Balance(可用余额)正确
-- [ ] agent_wallet 表的 FrozenBalance(冻结余额)正确(如有)
-- [ ] agent_wallet 表的 TotalEarnings(累计收益)正确
-
-### 6.3 返佣记录验证
-- [ ] agent_rebate 表的记录完整(推广报告返佣)
-- [ ] agent_upgrade 表的返佣记录正确(升级返佣)
-- [ ] 返佣金额计算正确
-
-### 6.4 团队验证
-- [ ] agent 表的 team_leader_id 正确指向钻石代理
-- [ ] 升级后所有下级 team_leader_id 更新正确
-
-### 6.5 订单和佣金验证
-- [ ] agent_order 表记录完整
-- [ ] agent_commission 表记录完整
-- [ ] 佣金金额计算正确
-
----
-
-## 七、测试数据准备建议
-
-### 7.1 创建测试代理账号
-建议准备以下测试账号(可用不同手机号):
-
-1. **钻石代理**:
- - 钻石A(团队首领,无上级)
- - 钻石B(团队首领,无上级)
-
-2. **黄金代理**:
- - 黄金A(上级:钻石A)
- - 黄金B(上级:钻石A)
- - 黄金C(上级:钻石B)
-
-3. **普通代理**:
- - 普通A(上级:钻石A)
- - 普通B(上级:黄金A)
- - 普通C(上级:普通B)
- - 普通D(上级:普通C)
- - 普通E(上级:普通D)
-
-### 7.2 测试产品配置
-- [ ] 确保有测试产品配置(agent_product_config 表)
-- [ ] 配置基础底价、提价阈值、提价手续费比例
-
-### 7.3 测试返佣配置
-- [ ] normal_to_normal_rebate(默认2元)
-- [ ] normal_to_gold_rebate(默认3元)
-- [ ] normal_to_gold_rebate_max(默认3元)
-
----
-
-## 八、测试执行顺序建议
-
-1. **第一阶段**:基础功能测试
- - 邀请下级(各种组合)
- - 验证关系建立
- - 验证团队首领
-
-2. **第二阶段**:收益分配测试
- - 推广报告
- - 收益计算
- - 返佣分配
-
-3. **第三阶段**:升级功能测试
- - 自主付费升级
- - 钻石升级下级
- - 升级返佣
-
-4. **第四阶段**:团队转移测试
- - 普通→黄金升级(保留团队)
- - 升级为钻石(独立成团队)
- - 复杂场景测试
-
-5. **第五阶段**:综合测试
- - 完整业务流程
- - 边界情况
- - 数据一致性验证
-
----
-
-## 注意事项
-
-1. **开发环境**:测试时确保使用开发环境(ENV=development),可跳过验证码校验
-2. **数据清理**:每次测试后建议清理测试数据,避免相互影响
-3. **事务验证**:注意验证事务的一致性,确保要么全部成功,要么全部回滚
-4. **并发测试**:如有需要,可进行并发场景测试(多代理同时升级等)
-5. **日志记录**:测试过程中查看日志,确保业务流程正确
-
diff --git a/代理配置表分析和优化建议.md b/代理配置表分析和优化建议.md
deleted file mode 100644
index 168ba80..0000000
--- a/代理配置表分析和优化建议.md
+++ /dev/null
@@ -1,774 +0,0 @@
-# 代理配置表分析和优化建议
-
-## 一、当前配置表结构分析
-
-### 1.1 数据库表结构
-
-**表名**: `agent_config`
-
-```sql
-CREATE TABLE `agent_config` (
- `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
- `config_key` varchar(100) NOT NULL COMMENT '配置键(唯一)',
- `config_value` varchar(500) NOT NULL COMMENT '配置值',
- `config_type` varchar(50) NOT NULL COMMENT '配置类型:price=价格,bonus=等级加成,upgrade=升级费用,rebate=返佣,tax=税费',
- `description` varchar(500) DEFAULT NULL COMMENT '配置描述',
- `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
- `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- `delete_time` datetime DEFAULT NULL,
- `del_state` tinyint NOT NULL DEFAULT 0,
- `version` bigint NOT NULL DEFAULT 0,
- PRIMARY KEY (`id`),
- UNIQUE KEY `uk_config_key` (`config_key`),
- KEY `idx_config_type` (`config_type`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-```
-
-### 1.2 配置表设计评估
-
-**优点**:
-✅ **键值对存储灵活**:使用 `config_key` 和 `config_value` 的键值对模式,易于扩展新配置项
-✅ **类型分类清晰**:`config_type` 字段将配置分为 price、bonus、upgrade、rebate、tax 等类型,便于管理
-✅ **唯一索引合理**:`config_key` 的唯一索引确保配置键不重复
-✅ **版本控制**:包含 `version` 字段支持乐观锁,适合配置更新场景
-✅ **软删除支持**:支持软删除,保留历史配置记录
-
-**缺点/问题**:
-❌ **配置值类型限制**:`config_value` 为 `varchar(500)`,所有值都存储为字符串,需要在使用时转换
-❌ **缺少验证机制**:数据库层面无法验证配置值的格式和范围
-❌ **配置值长度限制**:对于复杂配置(如JSON对象),500字符可能不够
-
----
-
-## 二、当前配置项清单
-
-### 2.1 SQL初始化脚本中的配置项
-
-根据 `agent_system_migration.sql` 文件,当前初始化的配置项:
-
-| 配置键 | 配置值 | 类型 | 说明 |
-| ------------------------------- | ------- | ------- | ------------------------ |
-| `base_price` | 0.00 | price | 系统基础底价 |
-| `system_max_price` | 9999.99 | price | 系统价格上限 |
-| `price_threshold` | 0.00 | price | 提价标准阈值 |
-| `price_fee_rate` | 0.0000 | price | 提价手续费比例 |
-| `level_bonus_normal` | 6.00 | bonus | 普通代理等级加成 |
-| `level_bonus_gold` | 3.00 | bonus | 黄金代理等级加成 |
-| `level_bonus_diamond` | 0.00 | bonus | 钻石代理等级加成 |
-| `upgrade_fee_normal_to_gold` | 199.00 | upgrade | 普通→黄金升级费用 |
-| `upgrade_fee_to_diamond` | 980.00 | upgrade | 升级为钻石费用 |
-| `upgrade_rebate_normal_to_gold` | 139.00 | upgrade | 普通→黄金返佣金额 |
-| `upgrade_rebate_to_diamond` | 680.00 | upgrade | 升级为钻石返佣金额 |
-| `direct_parent_amount_diamond` | 6.00 | rebate | 直接上级是钻石的返佣金额 |
-| `direct_parent_amount_gold` | 3.00 | rebate | 直接上级是黄金的返佣金额 |
-| `direct_parent_amount_normal` | 2.00 | rebate | 直接上级是普通的返佣金额 |
-| `max_gold_rebate_amount` | 3.00 | rebate | 黄金代理最大返佣金额 |
-| `tax_rate` | 0.0600 | tax | 提现税率(6%) |
-
-**共15个配置项**
-
-### 2.2 代码中使用的配置键
-
-#### 2.2.1 AgentService 中使用的配置键
-
-- `base_price` - 基础底价 ✅
-- `price_threshold` - 提价标准阈值 ✅
-- `price_fee_rate` - 提价手续费比例 ✅
-- `level_bonus` - **硬编码,未从配置表读取** ❌
-
-#### 2.2.2 AdminGetAgentConfigLogic 中使用的配置键
-
-- `level_1_bonus` - 普通代理等级加成 ❌ **配置键不匹配**
-- `level_2_bonus` - 黄金代理等级加成 ❌ **配置键不匹配**
-- `level_3_bonus` - 钻石代理等级加成 ❌ **配置键不匹配**
-- `upgrade_to_gold_fee` - 升级为黄金费用 ❌ **配置键不匹配**
-- `upgrade_to_diamond_fee` - 升级为钻石费用 ❌ **配置键不匹配**
-- `upgrade_to_gold_rebate` - 升级为黄金返佣 ❌ **配置键不匹配**
-- `upgrade_to_diamond_rebate` - 升级为钻石返佣 ❌ **配置键不匹配**
-- `tax_rate` - 税率 ✅
-- `tax_exemption_amount` - 免税额度 ❌ **配置项缺失**
-
----
-
-## 三、发现的问题
-
-### 3.1 🔴 严重问题:价格配置不应在系统配置表中
-
-**问题描述**:
-价格相关配置(底价、上限、提价阈值、手续费比例)应该按产品配置,而不是全局系统配置。当前这些配置同时存在于 `agent_config` 和 `agent_product_config` 表中,但代码只从系统配置表读取,没有实现产品级别的配置覆盖。
-
-**问题影响**:
-- ❌ 不同产品无法设置不同的底价和价格策略
-- ❌ 代码中只读取系统配置,忽略了产品配置(`AgentService.AgentProcess`、`PaymentLogic`)
-- ❌ 产品配置表虽然存在,但在订单处理逻辑中未被使用
-
-**应该移除的系统配置项**:
-- `base_price` - 应该只在产品配置表中
-- `system_max_price` - 应该只在产品配置表中
-- `price_threshold` - 应该只在产品配置表中(可选)
-- `price_fee_rate` - 应该只在产品配置表中(可选)
-
-**正确的设计**:
-- ✅ 所有价格相关配置都应该在 `agent_product_config` 表中按产品配置
-- ✅ 系统配置表只保留全局配置(等级加成、升级费用、税费等)
-- ✅ 代码应该优先从产品配置读取,如果产品未配置,则使用默认值
-
-### 3.2 🔴 严重问题:配置键命名不一致
-
-**问题描述**:
-SQL初始化脚本和代码逻辑中使用的配置键命名不一致,导致配置无法正确读取。
-
-**具体情况**:
-
-| 配置项 | SQL初始化脚本 | 代码逻辑期望 | 状态 |
-| ---------------- | ------------------------------- | --------------------------- | -------- |
-| 普通代理等级加成 | `level_bonus_normal` | `level_1_bonus` | ❌ 不匹配 |
-| 黄金代理等级加成 | `level_bonus_gold` | `level_2_bonus` | ❌ 不匹配 |
-| 钻石代理等级加成 | `level_bonus_diamond` | `level_3_bonus` | ❌ 不匹配 |
-| 升级为黄金费用 | `upgrade_fee_normal_to_gold` | `upgrade_to_gold_fee` | ❌ 不匹配 |
-| 升级为钻石费用 | `upgrade_fee_to_diamond` | `upgrade_to_diamond_fee` | ❌ 不匹配 |
-| 升级为黄金返佣 | `upgrade_rebate_normal_to_gold` | `upgrade_to_gold_rebate` | ❌ 不匹配 |
-| 升级为钻石返佣 | `upgrade_rebate_to_diamond` | `upgrade_to_diamond_rebate` | ❌ 不匹配 |
-
-**影响**:
-- 后台管理系统无法正确读取和显示配置值
-- 配置更新可能无法正常工作
-
-### 3.3 🔴 严重问题:代码中硬编码等级加成
-
-**问题位置**:
-```go
-// agentService.go:144-155
-func (s *AgentService) getLevelBonus(level int64) int64 {
- switch level {
- case 1: // 普通
- return 6 // ❌ 硬编码,应该从配置表读取
- case 2: // 黄金
- return 3 // ❌ 硬编码,应该从配置表读取
- case 3: // 钻石
- return 0 // ❌ 硬编码,应该从配置表读取
- default:
- return 0
- }
-}
-```
-
-**问题影响**:
-- 等级加成值无法通过后台配置动态调整
-- 必须修改代码才能更改等级加成
-- 违反了配置化的设计原则
-
-### 3.4 🟡 中等问题:缺少配置项
-
-**缺失的配置项**:
-1. `tax_exemption_amount` - 免税额度(前端接口需要,但SQL初始化脚本中未包含)
-2. `upgrade_fee_gold_to_diamond` - 黄金→钻石升级费用(如果独立配置)
-
-### 3.5 🟡 中等问题:配置键命名规范不统一
-
-**当前问题**:
-- 部分使用下划线分隔:`level_bonus_normal`
-- 部分使用数字后缀:`level_1_bonus`
-- 部分使用驼峰式:`upgrade_fee_normal_to_gold`
-
-**建议**:统一命名规范
-
-### 3.6 🟢 轻微问题:配置值存储方式
-
-**当前方式**:所有配置值以字符串形式存储,需要在使用时转换
-- `strconv.ParseFloat()` 转换为浮点数
-- `strconv.ParseInt()` 转换为整数
-
-**影响**:每次读取都需要类型转换,性能影响较小,但容易出错
-
----
-
-## 四、配置使用情况分析
-
-### 4.1 实际使用的配置项
-
-**在业务逻辑中使用的配置**:
-1. ✅ `base_price` - 订单处理时使用
-2. ✅ `price_threshold` - 计算提价成本时使用
-3. ✅ `price_fee_rate` - 计算提价成本时使用
-4. ❌ `level_bonus_*` - **未使用,代码中硬编码**
-
-**在后台管理中使用的配置**:
-1. ❌ `level_1_bonus` - 读取配置(但键名不匹配)
-2. ❌ `level_2_bonus` - 读取配置(但键名不匹配)
-3. ❌ `level_3_bonus` - 读取配置(但键名不匹配)
-4. ❌ `upgrade_to_gold_fee` - 读取配置(但键名不匹配)
-5. ❌ `upgrade_to_diamond_fee` - 读取配置(但键名不匹配)
-6. ❌ `upgrade_to_gold_rebate` - 读取配置(但键名不匹配)
-7. ❌ `upgrade_to_diamond_rebate` - 读取配置(但键名不匹配)
-8. ✅ `tax_rate` - 读取配置
-9. ❌ `tax_exemption_amount` - 读取配置(但配置项缺失)
-
-### 4.2 未使用的配置项
-
-以下配置项在SQL中初始化了,但在代码中**似乎未使用**:
-- `direct_parent_amount_diamond` - 直接上级是钻石的返佣金额
-- `direct_parent_amount_gold` - 直接上级是黄金的返佣金额
-- `direct_parent_amount_normal` - 直接上级是普通的返佣金额
-- `max_gold_rebate_amount` - 黄金代理最大返佣金额
-
-**说明**:这些配置项可能被硬编码在 `distributeNormalAgentBonus` 等函数中,需要确认是否需要配置化。
-
----
-
-## 五、优化建议
-
-### 5.1 🔴 立即修复:移除系统配置表中的价格配置
-
-**问题**:
-价格相关配置(`base_price`、`system_max_price`、`price_threshold`、`price_fee_rate`)应该完全由产品配置表管理,系统配置表中不应该存在这些配置。
-
-**修复步骤**:
-
-1. **从系统配置表中删除价格相关配置**:
-
-```sql
--- 删除系统配置表中的价格相关配置
-DELETE FROM `agent_config` WHERE `config_key` IN (
- 'base_price',
- 'system_max_price',
- 'price_threshold',
- 'price_fee_rate'
-);
-```
-
-2. **修改订单处理逻辑,从产品配置表读取**:
-
-需要修改的文件:
-- `app/main/api/internal/service/agentService.go` - `AgentProcess` 方法
-- `app/main/api/internal/logic/pay/paymentlogic.go` - 创建订单时的价格计算
-
-**示例代码修改**(`agentService.go`):
-
-```go
-// AgentProcess 处理代理订单(新系统)
-func (s *AgentService) AgentProcess(ctx context.Context, order *model.Order) error {
- // ... 前面的代码不变 ...
-
- // 4. 获取产品配置(优先使用产品配置,如果不存在则使用默认值)
- productConfig, err := s.AgentProductConfigModel.FindOneByProductId(ctx, order.ProductId)
- if err != nil && !errors.Is(err, model.ErrNotFound) {
- return errors.Wrapf(err, "查询产品配置失败, productId: %d", order.ProductId)
- }
-
- // 使用产品配置的底价,如果产品未配置则使用默认值0
- basePrice := 0.0
- if productConfig != nil {
- basePrice = productConfig.BasePrice
- }
-
- // ... 使用basePrice计算实际底价 ...
-
- // 6.2 计算提价成本(使用产品配置)
- priceThreshold := 0.0
- priceFeeRate := 0.0
- if productConfig != nil {
- if productConfig.PriceThreshold.Valid {
- priceThreshold = productConfig.PriceThreshold.Float64
- }
- if productConfig.PriceFeeRate.Valid {
- priceFeeRate = productConfig.PriceFeeRate.Float64
- }
- }
- priceCost := s.calculatePriceCost(agentOrder.SetPrice, priceThreshold, priceFeeRate)
-
- // ... 后续代码 ...
-}
-```
-
-3. **更新后台管理系统**:
-
-- 移除系统配置页面中的价格相关配置项
-- 确保产品配置页面可以正确配置这些价格参数
-
-### 5.2 🔴 立即修复:统一配置键命名
-
-**方案一:修改SQL初始化脚本,使用代码期望的键名**
-
-```sql
--- 修改等级加成配置键
-UPDATE `agent_config` SET `config_key` = 'level_1_bonus' WHERE `config_key` = 'level_bonus_normal';
-UPDATE `agent_config` SET `config_key` = 'level_2_bonus' WHERE `config_key` = 'level_bonus_gold';
-UPDATE `agent_config` SET `config_key` = 'level_3_bonus' WHERE `config_key` = 'level_bonus_diamond';
-
--- 修改升级费用配置键
-UPDATE `agent_config` SET `config_key` = 'upgrade_to_gold_fee' WHERE `config_key` = 'upgrade_fee_normal_to_gold';
-UPDATE `agent_config` SET `config_key` = 'upgrade_to_diamond_fee' WHERE `config_key` = 'upgrade_fee_to_diamond';
-
--- 修改升级返佣配置键
-UPDATE `agent_config` SET `config_key` = 'upgrade_to_gold_rebate' WHERE `config_key` = 'upgrade_rebate_normal_to_gold';
-UPDATE `agent_config` SET `config_key` = 'upgrade_to_diamond_rebate' WHERE `config_key` = 'upgrade_rebate_to_diamond';
-```
-
-**方案二:修改代码逻辑,使用SQL中的键名**
-
-需要修改的文件:
-- `app/main/api/internal/logic/admin_agent/admingetagentconfiglogic.go`
-- `app/main/api/internal/logic/admin_agent/adminupdateagentconfiglogic.go`
-
-**推荐方案一**,因为:
-- 代码中的命名更清晰(`level_1_bonus` 比 `level_bonus_normal` 更直观)
-- 数字后缀与等级数字(1/2/3)对应,易于理解
-
-### 5.3 🔴 立即修复:从配置表读取等级加成
-
-**修改 `agentService.go` 中的 `getLevelBonus` 函数**:
-
-```go
-// getLevelBonus 获取等级加成(从配置表读取)
-func (s *AgentService) getLevelBonus(ctx context.Context, level int64) (int64, error) {
- var configKey string
- switch level {
- case 1:
- configKey = "level_1_bonus"
- case 2:
- configKey = "level_2_bonus"
- case 3:
- configKey = "level_3_bonus"
- default:
- return 0, nil
- }
-
- bonus, err := s.getConfigFloat(ctx, configKey)
- if err != nil {
- // 配置不存在时返回默认值
- switch level {
- case 1:
- return 6, nil
- case 2:
- return 3, nil
- case 3:
- return 0, nil
- }
- return 0, nil
- }
- return int64(bonus), nil
-}
-```
-
-**修改调用处**:
-```go
-// agentService.go:108
-levelBonus, err := s.getLevelBonus(ctx, agent.Level)
-if err != nil {
- return errors.Wrapf(err, "获取等级加成配置失败")
-}
-actualBasePrice := basePrice + float64(levelBonus)
-```
-
-### 5.4 🟡 补充缺失的配置项
-
-**添加 `tax_exemption_amount` 配置**:
-
-```sql
-INSERT INTO `agent_config` (`config_key`, `config_value`, `config_type`, `description`)
-VALUES ('tax_exemption_amount', '0.00', 'tax', '提现免税额度(元,默认0)');
-```
-
-### 5.5 🟡 规范化配置键命名
-
-**建议统一使用以下命名规范**:
-
-```
-{类型}_{编号/级别}_{属性}
-```
-
-**示例**:
-- ✅ `level_1_bonus` - 等级1的加成
-- ✅ `level_2_bonus` - 等级2的加成
-- ✅ `level_3_bonus` - 等级3的加成
-- ✅ `upgrade_to_gold_fee` - 升级到黄金的费用
-- ✅ `upgrade_to_diamond_fee` - 升级到钻石的费用
-- ✅ `upgrade_to_gold_rebate` - 升级到黄金的返佣
-- ✅ `upgrade_to_diamond_rebate` - 升级到钻石的返佣
-
-### 5.6 🟢 优化建议:添加配置验证
-
-**在更新配置时添加验证逻辑**:
-
-```go
-// 验证配置值的合理性
-func validateConfigValue(key string, value float64) error {
- switch key {
- case "tax_rate":
- if value < 0 || value > 1 {
- return errors.New("税率必须在0-1之间")
- }
- case "price_fee_rate":
- if value < 0 || value > 1 {
- return errors.New("提价费率必须在0-1之间")
- }
- case "base_price", "system_max_price":
- if value < 0 {
- return errors.New("价格配置不能为负数")
- }
- }
- return nil
-}
-```
-
----
-
-## 六、配置项完整清单(修正后)
-
-### 6.1 价格相关配置(应移除,改为产品配置)
-
-**⚠️ 重要说明**:以下配置项应该从系统配置表(`agent_config`)中移除,改为在产品配置表(`agent_product_config`)中按产品配置。
-
-| 配置键 | 说明 | 配置位置 |
-| ---------------------- | ----------------------------- | ----------------------------- |
-| ~~`base_price`~~ | ~~系统基础底价~~ | ✅ 应在 `agent_product_config` |
-| ~~`system_max_price`~~ | ~~系统价格上限~~ | ✅ 应在 `agent_product_config` |
-| ~~`price_threshold`~~ | ~~提价标准阈值~~ | ✅ 应在 `agent_product_config` |
-| ~~`price_fee_rate`~~ | ~~提价手续费比例(0-1之间)~~ | ✅ 应在 `agent_product_config` |
-
-**产品配置表结构**(`agent_product_config`):
-- `base_price` - 产品基础底价(必填)
-- `system_max_price` - 产品价格上限(必填)
-- `price_threshold` - 提价标准阈值(可选,NULL时表示不设阈值)
-- `price_fee_rate` - 提价手续费比例(可选,NULL时表示不收费)
-
-### 6.2 等级加成配置(bonus类型)
-
-| 配置键 | 默认值 | 说明 |
-| --------------- | ------ | ---------------------- |
-| `level_1_bonus` | 6.00 | 普通代理等级加成(元) |
-| `level_2_bonus` | 3.00 | 黄金代理等级加成(元) |
-| `level_3_bonus` | 0.00 | 钻石代理等级加成(元) |
-
-### 6.3 升级费用配置(upgrade类型)
-
-| 配置键 | 默认值 | 说明 |
-| --------------------------- | ------ | ------------------------ |
-| `upgrade_to_gold_fee` | 199.00 | 普通→黄金升级费用(元) |
-| `upgrade_to_diamond_fee` | 980.00 | 升级为钻石费用(元) |
-| `upgrade_to_gold_rebate` | 139.00 | 普通→黄金返佣金额(元) |
-| `upgrade_to_diamond_rebate` | 680.00 | 升级为钻石返佣金额(元) |
-
-**注意**:`gold_to_diamond` 的费用和返佣可以通过计算得出:
-- 费用:`upgrade_to_diamond_fee - upgrade_to_gold_fee = 980 - 199 = 781元`
-- 返佣:可以通过前端或后端计算,不单独存储
-
-### 6.4 返佣规则配置(rebate类型)
-
-| 配置键 | 默认值 | 说明 |
-| ------------------------------ | ------ | ------------------------------ |
-| `direct_parent_amount_diamond` | 6.00 | 直接上级是钻石的返佣金额(元) |
-| `direct_parent_amount_gold` | 3.00 | 直接上级是黄金的返佣金额(元) |
-| `direct_parent_amount_normal` | 2.00 | 直接上级是普通的返佣金额(元) |
-| `max_gold_rebate_amount` | 3.00 | 黄金代理最大返佣金额(元) |
-
-**建议**:这些配置项目前在代码中硬编码,建议配置化。
-
-### 6.5 税费配置(tax类型)
-
-| 配置键 | 默认值 | 说明 |
-| ---------------------- | ------ | ---------------------- |
-| `tax_rate` | 0.0600 | 提现税率(6%,即0.06) |
-| `tax_exemption_amount` | 0.00 | 免税额度(元,默认0) |
-
----
-
-## 七、配置表的合理性评估
-
-### 7.1 表结构设计
-
-| 评估项 | 评分 | 说明 |
-| ------------ | ----- | ---------------------------- |
-| **灵活性** | ⭐⭐⭐⭐⭐ | 键值对设计非常灵活,易于扩展 |
-| **可维护性** | ⭐⭐⭐⭐ | 类型分类清晰,便于管理 |
-| **性能** | ⭐⭐⭐⭐ | 唯一索引优化查询,缓存支持 |
-| **类型安全** | ⭐⭐⭐ | 所有值都是字符串,需要转换 |
-| **验证机制** | ⭐⭐ | 缺少数据库层面的验证 |
-
-### 7.2 总体评价
-
-**设计优点**:
-✅ 采用键值对存储,扩展性强
-✅ 类型分类清晰,便于管理
-✅ 支持版本控制和软删除
-
-**存在的问题**:
-❌ **价格配置应该在产品配置表,而不是系统配置表**(严重设计问题)
-❌ 配置键命名不一致(严重)
-❌ 部分配置硬编码(严重)
-❌ 缺少部分配置项(中等)
-❌ 缺少配置验证机制(中等)
-
-**总体评分**:⭐⭐⭐(3/5)
-
-**结论**:配置表的设计思路基本合理,但存在**价格配置位置设计错误**的严重问题。价格相关配置应该完全由产品配置表管理,系统配置表只应该包含全局配置(等级加成、升级费用、税费等)。修复这些问题后,配置表设计将更加完善和符合项目规范。
-
----
-
-## 八、修复后的完整配置SQL
-
-```sql
--- ============================================
--- 代理系统配置初始化(修正版)
--- ============================================
-
--- 删除价格相关配置(应该在产品配置表中)
-DELETE FROM `agent_config` WHERE `config_key` IN (
- 'base_price',
- 'system_max_price',
- 'price_threshold',
- 'price_fee_rate'
-);
-
--- 删除旧的不一致配置(如果存在)
-DELETE FROM `agent_config` WHERE `config_key` IN (
- 'level_bonus_normal',
- 'level_bonus_gold',
- 'level_bonus_diamond',
- 'upgrade_fee_normal_to_gold',
- 'upgrade_fee_to_diamond',
- 'upgrade_rebate_normal_to_gold',
- 'upgrade_rebate_to_diamond'
-);
-
--- 插入修正后的配置项
-INSERT INTO `agent_config` (`config_key`, `config_value`, `config_type`, `description`) VALUES
--- 注意:价格相关配置(base_price, system_max_price, price_threshold, price_fee_rate)
--- 已从系统配置表中移除,改为在产品配置表(agent_product_config)中按产品配置
-
--- 等级加成配置(修正键名)
-('level_1_bonus', '6.00', 'bonus', '普通代理等级加成(6元)'),
-('level_2_bonus', '3.00', 'bonus', '黄金代理等级加成(3元)'),
-('level_3_bonus', '0.00', 'bonus', '钻石代理等级加成(0元)'),
-
--- 升级费用配置(修正键名)
-('upgrade_to_gold_fee', '199.00', 'upgrade', '普通→黄金升级费用(199元)'),
-('upgrade_to_diamond_fee', '980.00', 'upgrade', '升级为钻石费用(980元)'),
-
--- 升级返佣配置(修正键名)
-('upgrade_to_gold_rebate', '139.00', 'upgrade', '普通→黄金返佣金额(139元)'),
-('upgrade_to_diamond_rebate', '680.00', 'upgrade', '升级为钻石返佣金额(680元)'),
-
--- 返佣规则配置
-('direct_parent_amount_diamond', '6.00', 'rebate', '直接上级是钻石的返佣金额(6元)'),
-('direct_parent_amount_gold', '3.00', 'rebate', '直接上级是黄金的返佣金额(3元)'),
-('direct_parent_amount_normal', '2.00', 'rebate', '直接上级是普通的返佣金额(2元)'),
-('max_gold_rebate_amount', '3.00', 'rebate', '黄金代理最大返佣金额(3元)'),
-
--- 税费配置
-('tax_rate', '0.0600', 'tax', '提现税率(6%,即0.06)'),
-('tax_exemption_amount', '0.00', 'tax', '提现免税额度(元,默认0)')
-ON DUPLICATE KEY UPDATE
- `config_value` = VALUES(`config_value`),
- `update_time` = NOW();
-```
-
----
-
-## 九、建议的改进方案
-
-### 方案A:保持当前键值对设计(推荐)
-
-**优点**:
-- 灵活性强,易于扩展
-- 不需要修改表结构
-- 符合项目当前架构
-
-**需要做的修改**:
-1. 统一配置键命名
-2. 修复代码硬编码问题
-3. 补充缺失的配置项
-4. 添加配置验证逻辑
-
-### 方案B:改为结构化JSON配置(可选)
-
-**如果配置项继续增长,可以考虑**:
-- 将相关配置组合成JSON对象存储在 `config_value` 中
-- 例如:`level_bonus_config` 存储 `{"1": 6, "2": 3, "3": 0}`
-
-**缺点**:
-- 需要修改所有读取配置的代码
-- 不利于单个配置项的独立更新
-
-**建议**:当前配置项数量不多(约15个),保持键值对设计更合适。
-
----
-
-## 十、总结
-
-### 当前状态
-- ✅ 配置表设计思路合理
-- ❌ **价格配置设计错误**(应该在产品配置表,不在系统配置表)
-- ❌ 配置键命名不一致(需要立即修复)
-- ❌ 部分配置硬编码(需要立即修复)
-- ⚠️ 缺少部分配置项(需要补充)
-
-### 修复优先级
-1. **P0(紧急)**:移除系统配置表中的价格配置,改为完全由产品配置表管理
-2. **P0(紧急)**:修改订单处理逻辑,从产品配置表读取价格参数
-3. **P0(紧急)**:统一配置键命名,修复不一致问题
-4. **P0(紧急)**:修改代码,从配置表读取等级加成,移除硬编码
-5. **P1(重要)**:补充缺失的配置项(`tax_exemption_amount`)
-6. **P2(建议)**:添加配置验证逻辑
-7. **P2(建议)**:将返佣规则配置化(当前硬编码)
-
-### 是否符合项目
-
-**配置表设计**:✅ 基本符合,但价格配置的位置设计错误
-
-**存在的问题**:
-1. ❌ **价格配置应该在产品配置表中,而不是系统配置表**(严重设计问题)
-2. ❌ 配置键命名不一致
-3. ❌ 部分配置硬编码
-4. ⚠️ 缺少部分配置项
-
-**结论**:需要修复上述问题后才能完全符合项目的配置化设计理念。**最重要的是将价格配置从系统配置表移除,改为完全由产品配置表管理。**
-
----
-
-## 十一、产品配置表使用规范
-
-### 11.1 产品配置表结构
-
-**表名**: `agent_product_config`
-
-每个产品都应该有对应的配置记录,包含以下字段:
-
-| 字段 | 类型 | 必填 | 说明 |
-| ------------------ | ------------- | ---- | ----------------------------------------- |
-| `product_id` | bigint | ✅ 是 | 产品ID(唯一) |
-| `product_name` | varchar(100) | ✅ 是 | 产品名称 |
-| `base_price` | decimal(10,2) | ✅ 是 | 产品基础底价 |
-| `system_max_price` | decimal(10,2) | ✅ 是 | 产品价格上限 |
-| `price_threshold` | decimal(10,2) | ❌ 否 | 提价标准阈值(NULL表示不设阈值) |
-| `price_fee_rate` | decimal(5,4) | ❌ 否 | 提价手续费比例(NULL表示不收费,0-1之间) |
-
-### 11.2 配置优先级和默认值
-
-**规则**:
-1. 所有价格参数必须从产品配置表读取
-2. 如果产品配置记录不存在,应该报错或使用合理的默认值(建议报错,强制每个产品必须配置)
-3. 可选字段(`price_threshold`、`price_fee_rate`)如果为NULL,表示不启用该功能
- - `price_threshold = NULL` → 不设提价阈值
- - `price_fee_rate = NULL` → 不收取提价手续费
-
-### 11.3 代码修改示例
-
-#### 修改 AgentService.AgentProcess 方法
-
-```go
-// AgentProcess 处理代理订单(新系统)
-func (s *AgentService) AgentProcess(ctx context.Context, order *model.Order) error {
- // 1-3. 前面的代码不变...
-
- // 4. 获取产品配置(必须存在)
- productConfig, err := s.AgentProductConfigModel.FindOneByProductId(ctx, order.ProductId)
- if err != nil {
- if errors.Is(err, model.ErrNotFound) {
- return errors.Wrapf(err, "产品配置不存在, productId: %d,请先在后台配置产品价格参数", order.ProductId)
- }
- return errors.Wrapf(err, "查询产品配置失败, productId: %d", order.ProductId)
- }
-
- // 使用产品配置的底价
- basePrice := productConfig.BasePrice
-
- // 6. 使用事务处理订单
- return s.AgentWalletModel.Trans(ctx, func(transCtx context.Context, session sqlx.Session) error {
- // 6.1 计算实际底价和代理收益
- levelBonus, err := s.getLevelBonus(ctx, agent.Level)
- if err != nil {
- return errors.Wrapf(err, "获取等级加成配置失败")
- }
- actualBasePrice := basePrice + float64(levelBonus)
-
- // 6.2 计算提价成本(使用产品配置)
- priceThreshold := 0.0
- priceFeeRate := 0.0
- if productConfig.PriceThreshold.Valid {
- priceThreshold = productConfig.PriceThreshold.Float64
- }
- if productConfig.PriceFeeRate.Valid {
- priceFeeRate = productConfig.PriceFeeRate.Float64
- }
- priceCost := s.calculatePriceCost(agentOrder.SetPrice, priceThreshold, priceFeeRate)
-
- // 6.3 计算代理收益
- agentProfit := agentOrder.SetPrice - actualBasePrice - priceCost
-
- // ... 后续代码不变 ...
- })
-}
-```
-
-#### 修改 PaymentLogic 创建订单时的价格计算
-
-```go
-// 如果是代理推广订单,创建完整的代理订单记录
-if data.AgentIdentifier != "" && agentLinkModel != nil {
- // ... 获取代理信息 ...
-
- // 获取产品配置(必须存在)
- productConfig, err := l.svcCtx.AgentProductConfigModel.FindOneByProductId(l.ctx, product.Id)
- if err != nil {
- if errors.Is(err, model.ErrNotFound) {
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
- "生成订单失败,产品配置不存在, productId: %d,请先在后台配置产品价格参数", product.Id)
- }
- return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
- "生成订单, 查询产品配置失败: %+v", err)
- }
-
- // 使用产品配置的底价
- basePrice := productConfig.BasePrice
-
- // 计算实际底价(基础底价+等级加成)
- levelBonus, _ := l.getLevelBonus(agent.Level)
- actualBasePrice := basePrice + float64(levelBonus)
-
- // 计算提价成本(使用产品配置)
- priceThreshold := 0.0
- priceFeeRate := 0.0
- if productConfig.PriceThreshold.Valid {
- priceThreshold = productConfig.PriceThreshold.Float64
- }
- if productConfig.PriceFeeRate.Valid {
- priceFeeRate = productConfig.PriceFeeRate.Float64
- }
-
- priceCost := 0.0
- if agentLinkModel.SetPrice > priceThreshold {
- priceCost = (agentLinkModel.SetPrice - priceThreshold) * priceFeeRate
- }
-
- // 计算代理收益
- agentProfit := agentLinkModel.SetPrice - actualBasePrice - priceCost
-
- // ... 创建代理订单记录 ...
-}
-```
-
-### 11.4 需要修改的文件清单
-
-需要修改以支持产品配置的代码文件:
-
-1. **`app/main/api/internal/service/agentService.go`**
- - `AgentProcess()` 方法:从产品配置表读取价格参数
-
-2. **`app/main/api/internal/logic/pay/paymentlogic.go`**
- - 创建订单时的价格计算:从产品配置表读取价格参数
-
-3. **`app/main/api/internal/logic/agent/getagentproductconfiglogic.go`**
- - 移除从系统配置表读取价格参数的逻辑(只保留从产品配置表读取)
-
-4. **`app/main/api/internal/logic/admin_agent/admingetagentconfiglogic.go`**
- - 移除价格相关配置项的返回
-
-5. **`app/main/api/internal/logic/admin_agent/adminupdateagentconfiglogic.go`**
- - 移除价格相关配置项的更新逻辑
-
-6. **后台管理系统前端**
- - 系统配置页面:移除价格相关配置项
- - 产品配置页面:确保可以正确配置所有价格参数
-
diff --git a/佣金冻结功能完成总结.md b/佣金冻结功能完成总结.md
deleted file mode 100644
index d184eaa..0000000
--- a/佣金冻结功能完成总结.md
+++ /dev/null
@@ -1,136 +0,0 @@
-# 佣金冻结功能实现完成总结
-
-## ✅ 已完成的工作
-
-### 1. 数据库表
-- ✅ 创建了 `agent_freeze_task` 表(`deploy/sql/agent_freeze_task_migration.sql`)
-- ✅ 表结构包含:代理ID、订单ID、佣金ID、冻结金额、订单单价、冻结比例、状态、冻结时间、解冻时间等
-
-### 2. 核心业务逻辑
-- ✅ 修改了 `giveAgentCommission` 函数(`agentService.go`)
- - 增加订单单价参数
- - 实现冻结逻辑:订单单价 >= 100元时,冻结订单单价的10%(可配置)
- - 创建冻结任务记录
- - 更新钱包余额和冻结余额
-
-### 3. 异步任务系统
-- ✅ 创建了解冻任务处理器(`queue/unfreezeCommission.go`)
-- ✅ 添加了 `SendUnfreezeTask` 方法到 `AsynqService`
-- ✅ 在 `agentProcess.go` 中,代理处理成功后自动发送解冻任务
-- ✅ 注册了解冻任务路由
-
-### 4. 配置支持
-- ✅ 冻结比例从配置表 `agent_config` 读取,配置键为 `commission_freeze_ratio`
-
-### 5. ServiceContext 更新
-- ✅ 添加了 `AgentFreezeTaskModel` 字段
-- ✅ 初始化了 `agentFreezeTaskModel`
-- ✅ 传递给 `AgentService`
-- ✅ 添加到返回的 `ServiceContext`
-
-## 📋 功能说明
-
-### 触发条件
-- 订单单价 >= 配置阈值(默认100元,可通过 `commission_freeze_threshold` 配置)
-
-### 冻结规则
-- 冻结阈值:从配置表读取(配置键:`commission_freeze_threshold`,默认100元)
-- 冻结比例:从配置表读取(配置键:`commission_freeze_ratio`,默认10%)
-- 冻结金额 = 订单单价 × 冻结比例
-- 冻结金额不能超过佣金金额
-- 例如:订单单价140元,佣金134元,冻结14元(140 × 10%),实际到账120元(134 - 14)
-
-### 冻结时长
-- 从配置表读取(配置键:`commission_freeze_days`,默认30天)
-- 注意:配置只在创建任务时读取,已创建的任务不受后续配置修改影响
-
-### 解冻机制
-- 通过异步任务延迟执行
-- 1个月后自动解冻
-- 解冻时:`FrozenBalance -= 冻结金额`,`Balance += 冻结金额`
-
-### 数据持久化
-- 冻结任务记录在 `agent_freeze_task` 表中
-- 保证任务的一致性和持久化
-- 支持任务重试和状态追踪
-
-## 🔧 还需要完成的工作
-
-### 1. 添加配置项
-在 `agent_config` 表中添加配置:
-```sql
--- 冻结阈值(订单单价达到此金额才触发冻结,默认100元)
-INSERT INTO `agent_config` (`config_key`, `config_value`, `config_type`, `description`)
-VALUES ('commission_freeze_threshold', '100', 'rebate', '佣金冻结阈值(订单单价达到此金额才触发冻结,单位:元)');
-
--- 冻结比例(默认10%)
-INSERT INTO `agent_config` (`config_key`, `config_value`, `config_type`, `description`)
-VALUES ('commission_freeze_ratio', '0.1', 'rebate', '佣金冻结比例(例如:0.1表示10%)');
-
--- 解冻天数(默认30天,即1个月)
-INSERT INTO `agent_config` (`config_key`, `config_value`, `config_type`, `description`)
-VALUES ('commission_freeze_days', '30', 'rebate', '佣金冻结解冻天数(单位:天,例如:30表示30天后解冻)');
-```
-
-### 2. 运行SQL创建表(如果还没运行)
-```bash
-mysql -u用户名 -p数据库名 < deploy/sql/agent_freeze_task_migration.sql
-```
-
-## 📝 代码文件清单
-
-### 新增文件
-1. `deploy/sql/agent_freeze_task_migration.sql` - 冻结任务表SQL
-2. `app/main/api/internal/queue/unfreezeCommission.go` - 解冻任务处理器
-3. `佣金冻结功能实现说明.md` - 实现说明文档
-
-### 修改文件
-1. `app/main/api/internal/service/agentService.go` - 修改佣金发放逻辑
-2. `app/main/api/internal/service/asynqService.go` - 添加发送解冻任务方法
-3. `app/main/api/internal/queue/agentProcess.go` - 添加发送解冻任务逻辑
-4. `app/main/api/internal/queue/routes.go` - 注册解冻任务路由
-5. `app/main/api/internal/types/taskname.go` - 添加解冻任务类型
-6. `app/main/api/internal/types/payload.go` - 添加解冻任务负载类型
-7. `app/main/api/internal/svc/servicecontext.go` - 添加 AgentFreezeTaskModel
-
-## 🎯 功能流程
-
-```
-订单支付成功
- ↓
-代理处理(AgentProcess)
- ↓
-发放佣金(giveAgentCommission)
- ↓
-判断:订单单价 >= 100元?
- ├─ 是 → 计算冻结金额(订单单价 × 10%)
- │ 创建冻结任务记录
- │ 更新钱包:Balance += (佣金-冻结金额), FrozenBalance += 冻结金额
- │ 发送解冻异步任务(延迟配置天数)
- │
- └─ 否 → 正常发放佣金,不冻结
- ↓
-配置天数后
- ↓
-解冻任务执行(UnfreezeCommissionHandler)
- ↓
-更新冻结任务状态为已解冻
- ↓
-更新钱包:FrozenBalance -= 冻结金额, Balance += 冻结金额
-```
-
-## ✅ 代码检查
-- ✅ 所有文件已通过编译检查
-- ✅ 没有 lint 错误
-- ✅ ServiceContext 已正确更新
-- ✅ 异步任务路由已注册
-
-## 🚀 下一步
-1. 运行SQL创建表(如果还没运行)
-2. 添加配置项到 `agent_config` 表
-3. 测试功能:
- - 测试订单单价 < 100元:不应冻结
- - 测试订单单价 >= 100元:应冻结订单单价的10%
- - 测试冻结金额超过佣金:应冻结全部佣金
- - 测试解冻任务:在配置的天数后应自动解冻
-
diff --git a/佣金冻结功能实现说明.md b/佣金冻结功能实现说明.md
deleted file mode 100644
index d9ec35c..0000000
--- a/佣金冻结功能实现说明.md
+++ /dev/null
@@ -1,106 +0,0 @@
-# 佣金冻结功能实现说明
-
-## 功能需求
-当订单单价 >= 配置阈值(默认100元)时,抽取订单单价的配置比例(默认10%)放到冻结余额,冻结配置天数(默认30天)后自动解冻。所有配置项都可在配置表中修改,但配置只在创建任务时读取,已创建的任务不受后续配置修改影响。
-
-## 实现步骤
-
-### 1. 运行SQL创建冻结任务表
-```bash
-# 执行SQL文件
-mysql -u用户名 -p数据库名 < deploy/sql/agent_freeze_task_migration.sql
-```
-
-### 2. 生成Model
-运行代码生成工具生成 `AgentFreezeTaskModel`:
-```bash
-# 在 ycc-proxy-server 目录下运行
-goctl model mysql datasource -url="数据库连接字符串" -table="agent_freeze_task" -dir="./app/main/model" -cache
-```
-
-### 3. 更新ServiceContext
-在 `app/main/api/internal/svc/servicecontext.go` 中:
-
-#### 3.1 添加字段
-```go
-AgentFreezeTaskModel model.AgentFreezeTaskModel
-```
-
-#### 3.2 初始化Model
-在 `NewServiceContext` 函数中添加:
-```go
-agentFreezeTaskModel := model.NewAgentFreezeTaskModel(db, cacheConf)
-```
-
-#### 3.3 传递给AgentService
-修改 `NewAgentService` 调用,添加参数:
-```go
-agentService := service.NewAgentService(c, orderModel, agentModel, agentWalletModel,
- agentRelationModel, agentLinkModel, agentOrderModel, agentCommissionModel, agentRebateModel,
- agentUpgradeModel, agentWithdrawalModel, agentConfigModel, agentProductConfigModel,
- agentRealNameModel, agentWithdrawalTaxModel, agentFreezeTaskModel) // 添加这个参数
-```
-
-#### 3.4 添加到ServiceContext返回
-在返回的 `ServiceContext` 结构体中添加:
-```go
-AgentFreezeTaskModel: agentFreezeTaskModel,
-```
-
-### 4. 添加配置项
-在 `agent_config` 表中添加配置:
-```sql
--- 冻结阈值(订单单价达到此金额才触发冻结,默认100元)
-INSERT INTO `agent_config` (`config_key`, `config_value`, `config_type`, `description`)
-VALUES ('commission_freeze_threshold', '100', 'rebate', '佣金冻结阈值(订单单价达到此金额才触发冻结,单位:元)');
-
--- 冻结比例(默认10%)
-INSERT INTO `agent_config` (`config_key`, `config_value`, `config_type`, `description`)
-VALUES ('commission_freeze_ratio', '0.1', 'rebate', '佣金冻结比例(例如:0.1表示10%)');
-
--- 解冻天数(默认30天,即1个月)
-INSERT INTO `agent_config` (`config_key`, `config_value`, `config_type`, `description`)
-VALUES ('commission_freeze_days', '30', 'rebate', '佣金冻结解冻天数(单位:天,例如:30表示30天后解冻)');
-```
-
-### 5. 代码已完成的修改
-
-#### 5.1 已修改的文件
-- ✅ `deploy/sql/agent_freeze_task_migration.sql` - 创建冻结任务表
-- ✅ `app/main/api/internal/service/agentService.go` - 修改 `giveAgentCommission` 函数,增加冻结逻辑
-- ✅ `app/main/api/internal/service/asynqService.go` - 添加 `SendUnfreezeTask` 方法
-- ✅ `app/main/api/internal/queue/unfreezeCommission.go` - 创建解冻任务处理器
-- ✅ `app/main/api/internal/queue/routes.go` - 注册解冻任务路由
-- ✅ `app/main/api/internal/queue/agentProcess.go` - 在代理处理成功后发送解冻任务
-- ✅ `app/main/api/internal/types/taskname.go` - 添加解冻任务类型
-- ✅ `app/main/api/internal/types/payload.go` - 添加解冻任务负载类型
-
-#### 5.2 核心逻辑说明
-
-**冻结逻辑**(在 `giveAgentCommission` 中):
-1. 判断订单单价是否 >= 100元
-2. 如果是,从配置表读取冻结比例(默认10%)
-3. 计算冻结金额 = 订单单价 × 冻结比例(不超过佣金金额)
-4. 创建冻结任务记录(状态=待解冻,解冻时间=从配置读取的天数后)
-5. 更新钱包:`Balance += (佣金金额 - 冻结金额)`,`FrozenBalance += 冻结金额`
-
-**解冻逻辑**(在 `unfreezeCommission.go` 中):
-1. 查询冻结任务
-2. 检查解冻时间是否已到
-3. 更新冻结任务状态为已解冻
-4. 更新钱包:`FrozenBalance -= 冻结金额`,`Balance += 冻结金额`
-
-### 6. 注意事项
-
-1. **Model生成**:必须先运行SQL并生成Model,否则代码无法编译
-2. **配置项**:需要在 `agent_config` 表中添加 `commission_freeze_ratio` 配置项
-3. **异步任务**:解冻任务通过 asynq 延迟执行,确保在配置的天数后自动解冻
-4. **数据一致性**:所有操作都在事务中执行,保证数据一致性
-
-### 7. 测试建议
-
-1. 测试订单单价 < 100元:不应冻结
-2. 测试订单单价 >= 100元:应冻结订单单价的10%
-3. 测试冻结金额超过佣金:应冻结全部佣金
-4. 测试解冻任务:在配置的天数后应自动解冻
-
diff --git a/删除推广订单功能计划.md b/删除推广订单功能计划.md
deleted file mode 100644
index b9e05a2..0000000
--- a/删除推广订单功能计划.md
+++ /dev/null
@@ -1,213 +0,0 @@
-# 删除推广订单功能计划
-
-## 目标
-删除后台以及后端中promotion(推广订单)相关功能,但**保留推广数据分析和推广链接管理**功能。
-
-## 功能区分
-
-### ✅ 需要保留的功能
-1. **推广链接管理**
- - 创建推广链接
- - 更新推广链接
- - 删除推广链接
- - 获取推广链接列表
- - 获取推广链接详情
- - 记录链接点击
-
-2. **推广数据分析**
- - 获取推广总统计
- - 获取推广历史记录
-
-### ❌ 需要删除的功能
-1. **推广订单功能**
- - AdminPromotionOrder 模型及相关代码
- - 订单管理中的 IsPromotion 字段
- - 订单创建/更新/删除中的推广订单逻辑
- - 前端订单管理中的推广订单字段
-
----
-
-## 详细删除计划
-
-### 阶段一:后端代码清理
-
-#### 1.1 删除推广订单模型文件
-- [ ] 删除 `ycc-proxy-server/app/main/model/adminPromotionOrderModel.go`
-- [ ] 删除 `ycc-proxy-server/app/main/model/adminPromotionOrderModel_gen.go`
-- [ ] 确认 `ycc-proxy-server/deploy/script/gen_models.ps1` 中 `admin_promotion_order` 已被注释(第57行,已确认已注释)
-
-#### 1.2 从 ServiceContext 中移除推广订单模型
-- [ ] 从 `ycc-proxy-server/app/main/api/internal/svc/servicecontext.go` 中删除:
- - `AdminPromotionOrderModel` 字段声明
- - `AdminPromotionOrderModel` 初始化代码
-
-#### 1.3 修改订单管理相关逻辑
-
-**文件:`ycc-proxy-server/app/main/api/internal/logic/admin_order/admincreateorderlogic.go`**
-- [ ] 删除 `IsPromotion` 字段处理逻辑(第75-87行)
-- [ ] 删除 `AdminPromotionOrderModel` 的引用
-
-**文件:`ycc-proxy-server/app/main/api/internal/logic/admin_order/adminupdateorderlogic.go`**
-- [ ] 删除推广订单状态处理逻辑(第79-101行)
-- [ ] 删除 `AdminPromotionOrderModel` 的引用
-
-**文件:`ycc-proxy-server/app/main/api/internal/logic/admin_order/admindeleteorderlogic.go`**
-- [ ] 删除删除推广订单记录的逻辑(第44-51行)
-- [ ] 删除 `AdminPromotionOrderModel` 的引用
-
-**文件:`ycc-proxy-server/app/main/api/internal/logic/admin_order/admingetorderlistlogic.go`**
-- [ ] 删除 `IsPromotion` 查询条件(第57-59行)
-- [ ] 删除判断是否为推广订单的逻辑(第277-280行)
-- [ ] 删除返回结果中的 `IsPromotion` 字段赋值
-
-**文件:`ycc-proxy-server/app/main/api/internal/logic/admin_order/admingetorderdetaillogic.go`**
-- [ ] 删除判断是否为推广订单的逻辑(第43-47行)
-- [ ] 删除返回结果中的 `IsPromotion` 字段赋值(第121行)
-
-#### 1.4 修改 API 定义文件
-
-**文件:`ycc-proxy-server/app/main/api/desc/admin/order.api`**
-- [ ] 从 `AdminGetOrderListReq` 中删除 `IsPromotion` 字段(第56行)
-- [ ] 从 `AdminGetOrderListResp` 的 `OrderItem` 中删除 `IsPromotion` 字段(第83行)
-- [ ] 从 `AdminGetOrderDetailResp` 中删除 `IsPromotion` 字段(第105行)
-- [ ] 从 `AdminCreateOrderReq` 中删除 `IsPromotion` 字段(第119行)
-- [ ] 从 `AdminUpdateOrderReq` 中删除 `IsPromotion` 字段(第137行)
-
-#### 1.5 修改 Types 定义
-
-**文件:`ycc-proxy-server/app/main/api/internal/types/types.go`**
-- [ ] 从 `AdminGetOrderListReq` 中删除 `IsPromotion` 字段
-- [ ] 从 `AdminGetOrderListResp` 的 `OrderItem` 中删除 `IsPromotion` 字段
-- [ ] 从 `AdminGetOrderDetailResp` 中删除 `IsPromotion` 字段
-- [ ] 从 `AdminCreateOrderReq` 中删除 `IsPromotion` 字段
-- [ ] 从 `AdminUpdateOrderResp` 中删除 `IsPromotion` 字段
-
-#### 1.6 重新生成代码
-- [ ] 运行 `goctl api go -api desc/admin/order.api -dir . -style gozero`
-- [ ] 检查生成的代码是否正确
-
----
-
-### 阶段二:前端代码清理
-
-#### 2.1 修改订单管理页面
-
-**文件:`ycc-proxy-admin/apps/web-antd/src/views/order/order/data.ts`**
-- [ ] 删除推广订单列定义(第110-121行)
-- [ ] 删除表单中的推广订单字段(第224-225行)
-
-**文件:`ycc-proxy-admin/apps/web-antd/src/api/order/order.ts`**
-- [ ] 从接口类型定义中删除 `is_promotion` 字段(第19行)
-
-#### 2.2 检查其他前端文件
-- [ ] 搜索前端代码中是否还有其他地方引用了 `is_promotion` 或 `IsPromotion`
-- [ ] 删除所有相关引用
-
----
-
-### 阶段三:数据库清理(可选)
-
-#### 3.1 数据库表处理
-- [ ] 确认是否删除 `admin_promotion_order` 表
- - 如果保留历史数据,可以保留表但不使用
- - 如果需要完全清理,可以执行删除表的SQL
-
-#### 3.2 数据库迁移脚本(如需要)
-- [ ] 创建迁移脚本,删除 `admin_promotion_order` 表(如果决定删除)
-
----
-
-### 阶段四:验证和测试
-
-#### 4.1 功能验证
-- [ ] 验证推广链接管理功能正常
-- [ ] 验证推广数据分析功能正常
-- [ ] 验证订单创建功能正常(不再有推广订单选项)
-- [ ] 验证订单更新功能正常(不再有推广订单选项)
-- [ ] 验证订单列表查询功能正常(不再显示推广订单字段)
-- [ ] 验证订单详情查看功能正常(不再显示推广订单字段)
-
-#### 4.2 代码检查
-- [ ] 运行 `go build` 确保后端代码编译通过
-- [ ] 运行前端构建确保前端代码正常
-- [ ] 检查是否有编译错误或警告
-- [ ] 检查是否有未使用的导入
-
----
-
-## 注意事项
-
-1. **保留的功能不受影响**
- - 推广链接管理(`AdminPromotionLink`)完全保留
- - 推广数据分析(`AdminPromotionLinkStatsTotal`, `AdminPromotionLinkStatsHistory`)完全保留
- - 记录链接点击功能完全保留
-
-2. **数据库表处理**
- - `admin_promotion_order` 表可以保留(用于历史数据),但不再使用
- - 或者完全删除该表(需要确认是否有历史数据需要保留)
-
-3. **代码生成**
- - 修改 `.api` 文件后需要重新生成代码
- - 确保生成的代码与手动修改的代码一致
-
-4. **向后兼容**
- - 如果前端已经部署,需要确保前端和后端同时更新
- - 或者先更新后端,保持向后兼容一段时间
-
----
-
-## 执行顺序建议
-
-1. **第一步**:修改后端 API 定义文件(`.api` 文件)
-2. **第二步**:重新生成后端代码
-3. **第三步**:手动修改后端逻辑代码(删除推广订单相关逻辑)
-4. **第四步**:修改前端代码
-5. **第五步**:测试验证
-6. **第六步**:清理数据库(如需要)
-
----
-
-## 文件清单
-
-### 需要修改的后端文件
-1. `ycc-proxy-server/app/main/api/desc/admin/order.api`
-2. `ycc-proxy-server/app/main/api/internal/types/types.go`
-3. `ycc-proxy-server/app/main/api/internal/logic/admin_order/admincreateorderlogic.go`
-4. `ycc-proxy-server/app/main/api/internal/logic/admin_order/adminupdateorderlogic.go`
-5. `ycc-proxy-server/app/main/api/internal/logic/admin_order/admindeleteorderlogic.go`
-6. `ycc-proxy-server/app/main/api/internal/logic/admin_order/admingetorderlistlogic.go`
-7. `ycc-proxy-server/app/main/api/internal/logic/admin_order/admingetorderdetaillogic.go`
-8. `ycc-proxy-server/app/main/api/internal/svc/servicecontext.go`
-9. `ycc-proxy-server/app/main/model/vars.go`(如需要)
-
-### 需要删除的后端文件
-1. `ycc-proxy-server/app/main/model/adminPromotionOrderModel.go`
-2. `ycc-proxy-server/app/main/model/adminPromotionOrderModel_gen.go`
-
-### 需要修改的前端文件
-1. `ycc-proxy-admin/apps/web-antd/src/views/order/order/data.ts`
-2. `ycc-proxy-admin/apps/web-antd/src/api/order/order.ts`
-
-### 需要保留的文件(推广链接和数据分析)
-1. `ycc-proxy-server/app/main/api/desc/admin/promotion.api` ✅
-2. `ycc-proxy-server/app/main/api/internal/logic/admin_promotion/*` ✅
-3. `ycc-proxy-server/app/main/api/internal/handler/admin_promotion/*` ✅
-4. `ycc-proxy-server/app/main/model/adminPromotionLinkModel*` ✅
-5. `ycc-proxy-server/app/main/model/adminPromotionLinkStats*` ✅
-6. `ycc-proxy-server/app/main/api/internal/service/adminPromotionLinkStatsService.go` ✅
-7. `ycc-proxy-admin/apps/web-antd/src/views/promotion/*` ✅
-8. `ycc-proxy-admin/apps/web-antd/src/api/promotion/*` ✅
-
----
-
-## 完成标志
-
-- [ ] 所有后端代码修改完成
-- [ ] 所有前端代码修改完成
-- [ ] 代码编译通过
-- [ ] 功能测试通过
-- [ ] 推广链接管理功能正常
-- [ ] 推广数据分析功能正常
-- [ ] 订单管理功能正常(无推广订单相关功能)
-- [ ] 无编译错误和警告
-
diff --git a/定时清理报告系统问题分析.md b/定时清理报告系统问题分析.md
deleted file mode 100644
index 0f2961d..0000000
--- a/定时清理报告系统问题分析.md
+++ /dev/null
@@ -1,111 +0,0 @@
-# 定时清理报告系统问题分析
-
-## 🔴 严重问题
-
-### 1. **重复插入清理日志记录(第129-137行)**
-**问题**:每批次都插入新的清理日志记录,导致同一个清理任务产生多条日志记录。
-
-**影响**:
-- 数据冗余,同一个清理任务会有多条日志
-- 无法准确统计单次清理的总影响行数
-- 日志记录混乱
-
-**当前代码**:
-```go
-// 4. 保存清理日志(每批次都记录)
-cleanupLogInsertResult, err := l.svcCtx.QueryCleanupLogModel.Insert(ctx, session, cleanupLog)
-```
-
-**应该**:先创建一条日志记录,然后只更新 `AffectedRows` 字段,或者只在最后插入一次。
-
-### 2. **大事务问题(第96行)**
-**问题**:整个清理过程在一个大事务中,如果数据量很大,会导致:
-- 事务持续时间过长(可能几小时)
-- 锁等待时间过长
-- 可能导致事务超时
-- 数据库连接占用时间过长
-- 如果中途失败,所有操作回滚,已处理的数据无法恢复
-
-**影响**:
-- 数据库性能下降
-- 可能导致其他操作阻塞
-- 数据量大时可能失败
-
-**建议**:每个批次使用独立事务,或者使用小事务分批提交。
-
-### 3. **缺少超时控制**
-**问题**:没有为整个清理任务设置超时,如果数据量很大,可能会运行很长时间。
-
-**影响**:
-- 任务可能无限期运行
-- 无法及时响应系统关闭信号
-
-**建议**:添加超时控制,例如最多运行1小时。
-
-### 4. **查询条件缺少排序(第100-103行)**
-**问题**:查询条件没有排序,可能导致每次查询的结果不一致。
-
-**影响**:
-- 可能导致某些数据被重复处理或遗漏
-- 无法保证处理顺序
-
-**建议**:添加 `OrderBy("id ASC")` 或 `OrderBy("create_time ASC")`。
-
-## 🟡 中等问题
-
-### 5. **缺少进度监控**
-**问题**:没有记录处理进度,如果任务中断,无法知道处理到哪里了。
-
-**影响**:
-- 任务中断后无法恢复
-- 无法监控处理进度
-
-**建议**:记录已处理的批次数和最后处理的ID。
-
-### 6. **错误处理不够完善**
-**问题**:如果某个批次失败,整个事务回滚,但已经处理的数据无法恢复。
-
-**影响**:
-- 部分数据可能已经处理,但失败后全部回滚
-- 无法知道哪些数据已经处理过
-
-**建议**:每个批次使用独立事务,失败时只回滚当前批次。
-
-### 7. **缺少优雅关闭支持**
-**问题**:如果服务关闭,正在处理的任务可能被中断。
-
-**影响**:
-- 数据可能处于不一致状态
-- 无法记录已处理的进度
-
-**建议**:检查 context 是否被取消,优雅退出。
-
-## 🟢 小问题
-
-### 8. **日志信息不够详细**
-**问题**:日志信息可以更详细,便于排查问题。
-
-**建议**:记录批次处理情况、处理时间等。
-
-### 9. **配置验证不足**
-**问题**:没有验证配置值的合理性(如批次大小、保留天数)。
-
-**建议**:添加配置验证,确保配置值在合理范围内。
-
-## 修复建议优先级
-
-### 🔴 高优先级(必须修复)
-1. **修复重复插入日志问题** - 影响数据准确性
-2. **修复大事务问题** - 影响性能和可靠性
-3. **添加查询排序** - 影响数据一致性
-
-### 🟡 中优先级(建议修复)
-4. **添加超时控制** - 提高可靠性
-5. **改进错误处理** - 提高容错性
-6. **添加进度监控** - 便于排查问题
-
-### 🟢 低优先级(可选)
-7. **优雅关闭支持** - 如果服务很少重启可以不做
-8. **更详细的日志** - 便于排查问题
-9. **配置验证** - 提高健壮性
-
diff --git a/报告查询链路和代理分配逻辑检查报告.md b/报告查询链路和代理分配逻辑检查报告.md
deleted file mode 100644
index 051f13a..0000000
--- a/报告查询链路和代理分配逻辑检查报告.md
+++ /dev/null
@@ -1,221 +0,0 @@
-# 报告查询链路和代理分配逻辑检查报告
-
-## 一、报告查询链路检查
-
-### 1.1 整体流程
-
-```
-用户支付订单
- ↓
-支付回调(支付宝/微信)
- ↓
-更新订单状态为 "paid"
- ↓
-发送异步任务 SendQueryTask(order.Id)
- ↓
-异步任务处理 PaySuccessNotifyUserHandler.ProcessTask
- ├─ 创建报告记录(query表)
- ├─ 生成授权书
- ├─ 调用API请求服务获取报告数据
- ├─ 更新报告状态为 "success"
- ├─ 调用代理处理 AgentService.AgentProcess
- └─ 删除Redis缓存
-```
-
-### 1.2 链路检查结果
-
-✅ **链路基本通顺**,但存在以下问题:
-
-#### ✅ 问题1:代理处理失败时的处理逻辑 - 已修复
-
-**原问题**:
-- 代理处理在报告生成成功后同步执行
-- 如果代理处理失败,会触发 `handleError`,但报告已经生成成功
-
-**修复方案**:
-1. ✅ 创建了独立的代理处理异步任务 Handler(`app/main/api/internal/queue/agentProcess.go`)
-2. ✅ 修改 `paySuccessNotify.go` 改为发送异步任务,不再阻塞报告流程
-3. ✅ 代理处理失败时只记录日志,不影响报告使用
-4. ✅ 保留了手动重试接口供管理员使用
-
-**修复后的代码**:
-```go
-// 报告生成成功后,发送代理处理异步任务(不阻塞报告流程)
-if asyncErr := l.svcCtx.AsynqService.SendAgentProcessTask(order.Id); asyncErr != nil {
- // 代理处理任务发送失败,只记录日志,不影响报告流程
- logx.Errorf("发送代理处理任务失败,订单ID: %d, 错误: %v", order.Id, asyncErr)
-}
-```
-
-### 1.3 支付回调链路
-
-✅ **支付回调链路正常**:
-- 支付宝回调:`AlipayCallbackLogic.handleQueryOrderPayment`
-- 微信回调:`WechatPayCallbackLogic.handleQueryOrderPayment`
-- 支付成功后正确发送异步任务
-
----
-
-## 二、代理分配逻辑检查
-
-### 2.1 代理收益计算
-
-✅ **代理收益计算逻辑正确**
-
-**计算公式**:
-```go
-实际底价 = 基础底价 + 等级加成
-提价成本 = (设定价格 - 提价阈值) × 提价手续费比例(当设定价格 > 提价阈值时)
-代理收益 = 设定价格 - 实际底价 - 提价成本
-```
-
-**位置**:`app/main/api/internal/service/agentService.go:131-132`
-
-**验证**:
-- ✅ 使用产品配置的底价(从 `agent_product_config` 表读取)
-- ✅ 等级加成从配置表读取(支持动态配置)
-- ✅ 提价成本计算逻辑正确
-
-### 2.2 等级加成返佣分配
-
-#### 2.2.1 黄金代理(等级加成3元)
-
-✅ **实现正确**
-- 全部给钻石上级
-- 找不到钻石上级时,返佣归平台
-
-**位置**:`app/main/api/internal/service/agentService.go:233-244`
-
-#### 2.2.2 普通代理(等级加成6元)
-
-✅ **已按照新规则修复**
-
-**新规则**(已确认并实现):
-1. **直接上级是钻石**:等级加成全部给钻石上级
-2. **直接上级是黄金**:一部分给黄金上级(配置:`normal_to_gold_rebate`,默认3元),剩余给钻石上级
-3. **直接上级是普通**:
- - 一部分给直接上级普通(配置:`normal_to_normal_rebate`,默认2元)
- - 剩余金额:
- - 有钻石上级:剩余全部给钻石上级
- - 只有黄金上级:最多给黄金上级(配置:`normal_to_gold_rebate_max`,默认3元),超出归平台
- - 都没有:全部归平台
-
-**代码实现**(`app/main/api/internal/service/agentService.go:254-368`):
-- ✅ 按照新规则完全重写
-- ✅ 支持从配置表读取返佣金额(如果配置不存在使用默认值)
-- ✅ 跳过多层普通代理,直接查找钻石/黄金上级
-
-**配置项**:
-- `normal_to_normal_rebate`:普通代理给直接上级普通的金额(默认2元)
-- `normal_to_gold_rebate`:普通代理给直接上级黄金的金额(默认3元)
-- `normal_to_gold_rebate_max`:普通代理给黄金上级的最大金额(默认3元)
-
-### 2.3 代理收益分配流程
-
-✅ **分配流程正确**
-
-**流程**:
-1. 检查是否是代理订单
-2. 检查订单是否已处理(防重复)
-3. 获取代理信息和产品配置
-4. 使用事务处理(保证原子性):
- - 计算代理收益
- - 更新代理订单状态
- - 发放代理佣金到代理钱包
- - 分配等级加成返佣给上级链
-
-**位置**:`app/main/api/internal/service/agentService.go:76-156`
-
-### 2.4 事务处理和错误处理
-
-✅ **事务处理正确**
-
-**验证**:
-- ✅ 使用 `AgentWalletModel.Trans` 事务处理
-- ✅ 所有数据库操作在同一事务中
-- ✅ 任何步骤失败都会回滚
-
-**位置**:`app/main/api/internal/service/agentService.go:109`
-
-### 2.5 防重复处理
-
-✅ **防重复处理正确**
-
-**验证**:
-- ✅ 检查 `agent_order.ProcessStatus == 1` 判断是否已处理
-- ✅ 已处理的订单直接返回,不重复处理
-
-**位置**:`app/main/api/internal/service/agentService.go:87-91`
-
----
-
-## 三、其他发现
-
-### 3.1 重试机制
-
-✅ **已有手动重试接口**
-
-**位置**:
-- 接口:`POST /api/v1/admin/order/retry-agent-process/:id`
-- Logic:`app/main/api/internal/logic/admin_order/adminretryagentprocesslogic.go`
-
-**功能**:
-- 管理员可以手动触发代理处理重试
-- 可以处理代理处理失败的情况
-
-### 3.2 代理处理失败时的错误处理
-
-**当前行为**:
-- 代理处理失败会触发 `handleError`
-- `handleError` 会尝试退款(如果报告状态为 pending)
-- 但报告已经生成成功,可能不会触发退款
-
-**建议**:
-- 代理处理失败不应该触发退款(因为报告已经成功生成)
-- 应该记录错误日志,供管理员手动重试
-
----
-
-## 四、总结和建议
-
-### 4.1 需要确认的问题
-
-1. **普通代理等级加成返佣分配规则**
- - 文档和代码实现不一致
- - 需要确认正确的业务规则
-
-### 4.2 建议改进
-
-1. **代理处理失败的处理**
- - 建议将代理处理改为独立的异步任务,或至少确保失败不影响报告使用
- - 失败时只记录日志,不触发退款
-
-2. **文档更新**
- - 根据最终确认的业务规则,更新文档或代码
-
-3. **日志增强**
- - 在代理处理失败时记录更详细的日志,方便排查问题
-
-### 4.3 整体评价
-
-- ✅ 报告查询链路基本通顺
-- ✅ 代理收益计算逻辑正确
-- ✅ 事务处理和防重复处理正确
-- ⚠️ 普通代理等级加成返佣分配规则需要确认
-- ⚠️ 代理处理失败时的处理逻辑可以优化
-
----
-
-## 五、代码位置索引
-
-### 报告查询链路
-- 支付回调:`app/main/api/internal/logic/pay/alipaycallbacklogic.go:56-106`
-- 异步任务:`app/main/api/internal/queue/paySuccessNotify.go:35-178`
-- 报告查询:`app/main/api/internal/logic/query/querylistlogic.go`
-
-### 代理分配逻辑
-- 代理处理入口:`app/main/api/internal/service/agentService.go:76-156`
-- 收益计算:`app/main/api/internal/service/agentService.go:131-132`
-- 等级加成返佣:`app/main/api/internal/service/agentService.go:226-328`
-- 普通代理返佣分配:`app/main/api/internal/service/agentService.go:254-328`
-
diff --git a/新代理系统完整文档.md b/新代理系统完整文档.md
deleted file mode 100644
index 457468c..0000000
--- a/新代理系统完整文档.md
+++ /dev/null
@@ -1,1833 +0,0 @@
-# 新代理系统完整文档
-
-## 文档说明
-
-本文档详细记录了新代理系统的完整链路、业务逻辑、数据表结构、API接口和配置说明,用于后续系统调整和维护。
-
-**文档版本**: v1.0
-**最后更新**: 2024年
-**维护人员**: 开发团队
-
----
-
-## 目录
-
-1. [系统概述](#一系统概述)
-2. [数据表结构](#二数据表结构)
-3. [核心业务规则](#三核心业务规则)
-4. [完整链路流程](#四完整链路流程)
-5. [关键代码逻辑](#五关键代码逻辑)
-6. [API接口列表](#六api接口列表)
-7. [系统配置说明](#七系统配置说明)
-8. [数据流转图](#八数据流转图)
-
----
-
-## 一、系统概述
-
-### 1.1 系统定位
-
-新代理系统是一个三级代理分销系统,**用户只能通过邀请码成为代理**,支持邀请码管理、推广、收益分配、升级和提现等完整业务流程。
-
-### 1.2 核心特性
-
-- **三级代理体系**:普通(Level 1) → 黄金(Level 2) → 钻石(Level 3)
-- **团队结构管理**:钻石代理作为团队首领,管理整个团队
-- **邀请码机制**:**用户不能自主成为代理,只能通过邀请码成为代理**
- - 平台发放钻石邀请码:管理员生成,用户使用后成为钻石代理(**只能使用一次,使用后立即失效**)
- - 代理发放邀请码:代理生成,用户使用后成为该代理的下级(**普通邀请码可以无限使用**)
- - 邀请链接/二维码:代理生成,用户通过链接注册成为该代理的下级
-- **灵活的价格体系**:代理可自定义推广价格,系统自动计算收益
-- **智能收益分配**:自动计算代理收益和上级返佣
-- **升级机制**:支持自主付费升级和钻石代理升级下级
-- **税费管理**:月度累计提现,自动计算税费
-- **实名认证**:提现前必须完成实名认证(三要素核验,无需审核)
-
-### 1.3 技术架构
-
-- **框架**: Go-Zero
-- **数据库**: MySQL
-- **缓存**: Redis
-- **支付**: 支付宝、微信支付
-- **加密**: AES加密(手机号、身份证号)
-
----
-
-## 二、数据表结构
-
-### 2.1 核心数据表
-
-#### 2.1.1 agent(代理基本信息表)
-
-| 字段 | 类型 | 说明 |
-| -------------- | ------------ | -------------------------------- |
-| id | bigint | 主键ID |
-| user_id | bigint | 用户ID(唯一) |
-| level | tinyint | 代理等级:1=普通,2=黄金,3=钻石 |
-| region | varchar(50) | 区域(可选) |
-| mobile | varchar(50) | 手机号(加密) |
-| wechat_id | varchar(100) | 微信号(可选) |
-| team_leader_id | bigint | 团队首领ID(钻石代理的ID) |
-| create_time | datetime | 创建时间 |
-| update_time | datetime | 更新时间 |
-| delete_time | datetime | 删除时间 |
-| del_state | tinyint | 删除状态:0=未删除,1=已删除 |
-| version | bigint | 版本号(乐观锁) |
-
-**索引**:
-- PRIMARY KEY (`id`)
-- UNIQUE KEY `uk_user_id` (`user_id`)
-- KEY `idx_mobile` (`mobile`)
-- KEY `idx_level` (`level`)
-- KEY `idx_team_leader_id` (`team_leader_id`)
-
-#### 2.1.2 agent_wallet(代理钱包表)
-
-| 字段 | 类型 | 说明 |
-| ---------------- | ------------- | -------------- |
-| id | bigint | 主键ID |
-| agent_id | bigint | 代理ID(唯一) |
-| balance | decimal(10,2) | 可用余额 |
-| frozen_balance | decimal(10,2) | 冻结余额 |
-| total_earnings | decimal(10,2) | 累计收益 |
-| withdrawn_amount | decimal(10,2) | 已提现金额 |
-| create_time | datetime | 创建时间 |
-| update_time | datetime | 更新时间 |
-| delete_time | datetime | 删除时间 |
-| del_state | tinyint | 删除状态 |
-| version | bigint | 版本号 |
-
-#### 2.1.4 agent_relation(代理关系表)
-
-| 字段 | 类型 | 说明 |
-| ------------- | ------------ | ------------------------------ |
-| id | bigint | 主键ID |
-| parent_id | bigint | 上级代理ID |
-| child_id | bigint | 下级代理ID |
-| relation_type | tinyint | 关系类型:1=直接关系,2=已脱离 |
-| detach_reason | varchar(200) | 脱离原因 |
-| detach_time | datetime | 脱离时间 |
-| create_time | datetime | 创建时间 |
-| update_time | datetime | 更新时间 |
-| delete_time | datetime | 删除时间 |
-| del_state | tinyint | 删除状态 |
-| version | bigint | 版本号 |
-
-**关系类型说明**:
-- `relation_type = 1`: 直接关系(正常上下级)
-- `relation_type = 2`: 已脱离(升级后脱离直接关系)
-
-**唯一约束**:
-- UNIQUE KEY `uk_parent_child_type` (`parent_id`, `child_id`, `relation_type`)
-
-#### 2.1.5 agent_link(推广链接表)
-
-| 字段 | 类型 | 说明 |
-| ----------------- | ------------- | ----------------------------- |
-| id | bigint | 主键ID |
-| agent_id | bigint | 代理ID |
-| user_id | bigint | 用户ID |
-| product_id | bigint | 产品ID |
-| link_identifier | varchar(200) | 链接标识(加密) |
-| set_price | decimal(10,2) | 设定价格 |
-| actual_base_price | decimal(10,2) | 实际底价(基础底价+等级加成) |
-| create_time | datetime | 创建时间 |
-| update_time | datetime | 更新时间 |
-| delete_time | datetime | 删除时间 |
-| del_state | tinyint | 删除状态 |
-| version | bigint | 版本号 |
-
-**唯一约束**:
-- UNIQUE KEY `uk_agent_product_price` (`agent_id`, `product_id`, `set_price`, `del_state`)
-
-#### 2.1.6 agent_order(代理订单表)
-
-| 字段 | 类型 | 说明 |
-| ----------------- | ------------- | ------------------------------------------ |
-| id | bigint | 主键ID |
-| agent_id | bigint | 代理ID |
-| order_id | bigint | 订单ID |
-| product_id | bigint | 产品ID |
-| order_amount | decimal(10,2) | 订单金额(SetPrice) |
-| set_price | decimal(10,2) | 设定价格 |
-| actual_base_price | decimal(10,2) | 实际底价 |
-| price_cost | decimal(10,2) | 提价成本 |
-| agent_profit | decimal(10,2) | 代理收益 |
-| process_status | tinyint | 处理状态:0=待处理,1=处理成功,2=处理失败 |
-| process_time | datetime | 处理时间 |
-| process_remark | varchar(500) | 处理备注 |
-| create_time | datetime | 创建时间 |
-| update_time | datetime | 更新时间 |
-| delete_time | datetime | 删除时间 |
-| del_state | tinyint | 删除状态 |
-| version | bigint | 版本号 |
-
-**处理状态说明**:
-- `process_status = 0`: 待处理(订单创建后)
-- `process_status = 1`: 处理成功(收益已分配)
-- `process_status = 2`: 处理失败
-
-#### 2.1.7 agent_commission(代理佣金表)
-
-| 字段 | 类型 | 说明 |
-| ----------- | ------------- | -------------------- |
-| id | bigint | 主键ID |
-| agent_id | bigint | 代理ID |
-| order_id | bigint | 订单ID |
-| product_id | bigint | 产品ID |
-| amount | decimal(10,2) | 佣金金额(代理收益) |
-| status | tinyint | 状态:1=已发放 |
-| create_time | datetime | 创建时间 |
-| update_time | datetime | 更新时间 |
-| delete_time | datetime | 删除时间 |
-| del_state | tinyint | 删除状态 |
-| version | bigint | 版本号 |
-
-#### 2.1.8 agent_rebate(代理返佣表)
-
-| 字段 | 类型 | 说明 |
-| --------------- | ------------- | -------------------------------------------------------- |
-| id | bigint | 主键ID |
-| agent_id | bigint | 获得返佣的代理ID |
-| source_agent_id | bigint | 来源代理ID(产生订单的代理) |
-| order_id | bigint | 订单ID |
-| product_id | bigint | 产品ID |
-| rebate_type | tinyint | 返佣类型:1=直接上级返佣,2=钻石上级返佣,3=黄金上级返佣 |
-| level_bonus | decimal(10,2) | 等级加成金额 |
-| rebate_amount | decimal(10,2) | 返佣金额 |
-| status | tinyint | 状态:1=已发放 |
-| create_time | datetime | 创建时间 |
-| update_time | datetime | 更新时间 |
-| delete_time | datetime | 删除时间 |
-| del_state | tinyint | 删除状态 |
-| version | bigint | 版本号 |
-
-**返佣类型说明**:
-- `rebate_type = 1`: 直接上级返佣(普通代理的等级加成给直接上级)
-- `rebate_type = 2`: 钻石上级返佣(给钻石上级的返佣)
-- `rebate_type = 3`: 黄金上级返佣(给黄金上级的返佣)
-
-#### 2.1.9 agent_upgrade(代理升级表)
-
-| 字段 | 类型 | 说明 |
-| ----------------- | ------------- | ------------------------------------ |
-| id | bigint | 主键ID |
-| agent_id | bigint | 代理ID |
-| from_level | tinyint | 原等级 |
-| to_level | tinyint | 目标等级 |
-| upgrade_type | tinyint | 升级类型:1=自主付费,2=钻石升级下级 |
-| upgrade_fee | decimal(10,2) | 升级费用 |
-| rebate_amount | decimal(10,2) | 返佣金额(给原直接上级) |
-| rebate_agent_id | bigint | 返佣代理ID(原直接上级) |
-| operator_agent_id | bigint | 操作代理ID(钻石升级下级时使用) |
-| order_no | varchar(100) | 支付订单号 |
-| status | tinyint | 状态:1=待处理,2=已完成,3=已失败 |
-| remark | varchar(500) | 备注 |
-| create_time | datetime | 创建时间 |
-| update_time | datetime | 更新时间 |
-| delete_time | datetime | 删除时间 |
-| del_state | tinyint | 删除状态 |
-| version | bigint | 版本号 |
-
-**升级类型说明**:
-- `upgrade_type = 1`: 自主付费升级(代理自己付费)
-- `upgrade_type = 2`: 钻石升级下级(钻石代理操作,免费)
-
-#### 2.1.10 agent_withdrawal(代理提现表)
-
-| 字段 | 类型 | 说明 |
-| ------------- | ------------- | ------------------------------------------------------------------------ |
-| id | bigint | 主键ID |
-| agent_id | bigint | 代理ID |
-| withdraw_no | varchar(100) | 提现单号(唯一) |
-| payee_account | varchar(100) | 收款账户(支付宝账号) |
-| payee_name | varchar(100) | 收款人姓名 |
-| amount | decimal(10,2) | 提现金额 |
-| tax_amount | decimal(10,2) | 税费金额 |
-| actual_amount | decimal(10,2) | 实际到账金额 |
-| status | tinyint | 状态:1=待审核,2=审核通过,3=审核拒绝,4=提现中,5=提现成功,6=提现失败 |
-| remark | varchar(500) | 备注 |
-| create_time | datetime | 创建时间 |
-| update_time | datetime | 更新时间 |
-| delete_time | datetime | 删除时间 |
-| del_state | tinyint | 删除状态 |
-| version | bigint | 版本号 |
-
-**状态说明**:
-- `status = 1`: 待审核(代理申请后)
-- `status = 2`: 审核通过(管理员审核通过,准备转账)
-- `status = 3`: 审核拒绝(管理员拒绝)
-- `status = 4`: 提现中(已调用支付宝转账接口,处理中)
-- `status = 5`: 提现成功(转账成功)
-- `status = 6`: 提现失败(转账失败)
-
-#### 2.1.11 agent_withdrawal_tax(代理提现扣税表)
-
-| 字段 | 类型 | 说明 |
-| ----------------- | ------------- | ------------------------------ |
-| id | bigint | 主键ID |
-| agent_id | bigint | 代理ID |
-| withdrawal_id | bigint | 提现记录ID |
-| year_month | bigint | 年月(格式:YYYYMM,如202401) |
-| withdrawal_amount | decimal(10,2) | 提现金额 |
-| taxable_amount | decimal(10,2) | 应税金额 |
-| tax_rate | decimal(5,4) | 税率 |
-| tax_amount | decimal(10,2) | 税费金额 |
-| actual_amount | decimal(10,2) | 实际到账金额 |
-| tax_status | tinyint | 扣税状态:1=待扣税,2=已扣税 |
-| tax_time | datetime | 扣税时间 |
-| remark | varchar(500) | 备注 |
-| create_time | datetime | 创建时间 |
-| update_time | datetime | 更新时间 |
-| delete_time | datetime | 删除时间 |
-| del_state | tinyint | 删除状态 |
-| version | bigint | 版本号 |
-
-**扣税状态说明**:
-- `tax_status = 1`: 待扣税(提现申请后)
-- `tax_status = 2`: 已扣税(提现成功后)
-
-#### 2.1.12 agent_config(代理系统配置表)
-
-| 字段 | 类型 | 说明 |
-| ------------ | ------------ | ----------------------------------------------------------------------------- |
-| id | bigint | 主键ID |
-| config_key | varchar(100) | 配置键(唯一) |
-| config_value | varchar(500) | 配置值 |
-| config_type | varchar(50) | 配置类型:price=价格,bonus=等级加成,upgrade=升级费用,rebate=返佣,tax=税费 |
-| description | varchar(500) | 配置描述 |
-| create_time | datetime | 创建时间 |
-| update_time | datetime | 更新时间 |
-| delete_time | datetime | 删除时间 |
-| del_state | tinyint | 删除状态 |
-| version | bigint | 版本号 |
-
-**配置键列表**:
-- `base_price`: 基础底价
-- `system_max_price`: 系统价格上限
-- `price_threshold`: 提价标准阈值
-- `price_fee_rate`: 提价手续费比例
-- `level_1_bonus`: 普通代理等级加成(6元)
-- `level_2_bonus`: 黄金代理等级加成(3元)
-- `level_3_bonus`: 钻石代理等级加成(0元)
-- `upgrade_to_gold_fee`: 升级为黄金费用(199元)
-- `upgrade_to_diamond_fee`: 升级为钻石费用(980元)
-- `upgrade_to_gold_rebate`: 升级为黄金返佣(139元)
-- `upgrade_to_diamond_rebate`: 升级为钻石返佣(680元)
-- `tax_rate`: 税率(默认0.06,即6%)
-- `tax_exemption_amount`: 免税额度(默认0)
-
-#### 2.1.13 agent_product_config(代理产品配置表)
-
-| 字段 | 类型 | 说明 |
-| ---------------- | ------------- | ------------------------------------ |
-| id | bigint | 主键ID |
-| product_id | bigint | 产品ID(唯一) |
-| product_name | varchar(100) | 产品名称 |
-| base_price | decimal(10,2) | 基础底价(可覆盖系统配置) |
-| system_max_price | decimal(10,2) | 系统价格上限(可覆盖系统配置) |
-| price_threshold | decimal(10,2) | 提价标准阈值(可选,覆盖系统配置) |
-| price_fee_rate | decimal(5,4) | 提价手续费比例(可选,覆盖系统配置) |
-| create_time | datetime | 创建时间 |
-| update_time | datetime | 更新时间 |
-| delete_time | datetime | 删除时间 |
-| del_state | tinyint | 删除状态 |
-| version | bigint | 版本号 |
-
-#### 2.1.14 agent_real_name(代理实名认证表)
-
-| 字段 | 类型 | 说明 |
-| ----------- | ----------- | ---------------------------------------------- |
-| id | bigint | 主键ID |
-| agent_id | bigint | 代理ID(唯一) |
-| name | varchar(50) | 真实姓名 |
-| id_card | varchar(50) | 身份证号(加密) |
-| mobile | varchar(50) | 手机号(加密) |
-| verify_time | datetime | 验证时间(三要素验证通过时间,NULL表示未验证) |
-| create_time | datetime | 创建时间 |
-| update_time | datetime | 更新时间 |
-| delete_time | datetime | 删除时间 |
-| del_state | tinyint | 删除状态 |
-| version | bigint | 版本号 |
-
-**验证说明**:
-- `verify_time IS NULL`: 未验证(未通过三要素核验或未提交)
-- `verify_time IS NOT NULL`: 已通过(三要素核验通过,可以提现)
-
-**重要**:实名认证改为三要素核验,无需人工审核。提交实名认证信息后,系统自动进行三要素核验(姓名、身份证号、手机号),核验通过后自动设置 `verify_time`,无需管理员审核。
-
-### 2.2 数据表关系图
-
-```
-agent (代理表)
-├── agent_wallet (钱包表) 1:1
-├── agent_link (推广链接表) 1:N
-├── agent_order (代理订单表) 1:N
-├── agent_relation (关系表) 1:N (作为parent_id)
-│ └── agent_relation (关系表) 1:N (作为child_id)
-├── agent_commission (佣金表) 1:N
-├── agent_rebate (返佣表) 1:N (作为agent_id)
-│ └── agent_rebate (返佣表) 1:N (作为source_agent_id)
-├── agent_upgrade (升级表) 1:N
-├── agent_withdrawal (提现表) 1:N
-│ └── agent_withdrawal_tax (扣税表) 1:N
-└── agent_real_name (实名认证表) 1:1
-```
-
----
-
-## 三、核心业务规则
-
-### 3.1 代理等级体系
-
-#### 3.1.1 等级定义
-
-| 等级 | 数值 | 名称 | 说明 |
-| ------- | ---- | -------- | ---------------------------------- |
-| Level 1 | 1 | 普通代理 | 初始等级,所有新代理默认等级 |
-| Level 2 | 2 | 黄金代理 | 中级等级,可通过付费或钻石升级获得 |
-| Level 3 | 3 | 钻石代理 | 最高等级,团队首领,可通过付费获得 |
-
-#### 3.1.2 等级加成规则
-
-| 等级 | 等级加成 | 说明 |
-| -------- | -------- | ------------------------- |
-| 普通代理 | +6元 | 实际底价 = 基础底价 + 6元 |
-| 黄金代理 | +3元 | 实际底价 = 基础底价 + 3元 |
-| 钻石代理 | +0元 | 实际底价 = 基础底价 |
-
-**计算公式**:
-```
-实际底价 = 基础底价 + 等级加成
-```
-
-### 3.2 团队结构规则
-
-#### 3.2.1 团队定义
-
-- **团队**: 由一个钻石代理作为首领,及其所有下级代理组成的层级关系链
-- **团队首领**: 必须是钻石代理,每个团队有且仅有一个首领
-- **团队关系**: 通过 `team_leader_id` 字段关联,所有团队成员指向同一个钻石代理
-
-#### 3.2.2 上下级关系约束
-
-**核心原则**:
-1. **下级不能比上级等级高**: 下级等级必须 ≤ 上级等级
-2. **同级不能作为上下级**(除了普通代理): 黄金和钻石不能作为同级上下级
-
-**允许的关系**:
-- 普通 → 普通 ✓(同级普通允许)
-- 黄金 → 普通 ✓(上级等级高于下级)
-- 钻石 → 普通 ✓(上级等级高于下级)
-- 钻石 → 黄金 ✓(上级等级高于下级,允许建立关系)
-
-**禁止的关系**:
-- 普通 → 黄金 ✗(下级等级高于上级)
-- 普通 → 钻石 ✗(下级等级高于上级)
-- 黄金 → 黄金 ✗(同级不能作为上下级)
-- 钻石 → 钻石 ✗(同级不能作为上下级)
-- 黄金 → 钻石 ✗(下级等级高于上级)
-
-**升级后脱离关系的规则**:
-- 下级等级 > 上级等级 → 脱离关系
-- 同级(黄金或钻石)→ 脱离关系
-- 普通代理同级 → 不脱离关系(允许保持)
-
-#### 3.2.3 升级规则
-
-**核心规则**: 代理升级后,其所有下级(直接+间接)会跟随该代理。
-
-**升级场景**:
-
-1. **普通 → 黄金**:
- - 升级后根据上级等级决定是否脱离关系:
- * 如果上级是普通代理:必须脱离(下级等级高于上级)
- * 如果上级是黄金代理:必须脱离(同级不能作为上下级)
- * 如果上级是钻石代理:不脱离(钻石等级高于黄金,允许保持关系)
- - 保留团队关系(通过团队首领钻石代理)
- - 仍属于原团队
- - 所有下级(直接+间接)继续跟随该代理
-
-2. **黄金 → 钻石**:
- - 独立成为新团队
- - 成为新团队的首领(`team_leader_id = 自己`)
- - 所有下级(直接+间接)跟随该代理到新团队
-
-3. **普通 → 钻石**:
- - 独立成为新团队
- - 成为新团队的首领
- - 所有下级(直接+间接)跟随该代理到新团队
-
-#### 3.2.4 升级方法和费用规则
-
-**升级方式**:
-
-1. **钻石代理升级下级**:
- - 钻石代理可以将下级的普通代理升级为黄金代理
- - 升级方式: 钻石代理操作,无需被升级代理付费
- - 限制: 只能升级普通代理为黄金代理
-
-2. **代理自主付费升级**:
- - 普通代理可以付费升级为黄金代理(199元)
- - 普通代理可以付费升级为钻石代理(980元)
- - 黄金代理可以付费升级为钻石代理(980元)
-
-**升级费用和返佣规则**:
-
-| 升级类型 | 升级费用 | 直接上级返佣 | 说明 |
-| ------------------------- | -------- | ------------ | -------------------------------- |
-| 普通→黄金 | 199元 | 139元 | 付费后立即返佣给直接上级 |
-| 普通→钻石 | 980元 | 680元 | 付费后立即返佣给直接上级 |
-| 黄金→钻石 | 980元 | 680元 | 付费后立即返佣给直接上级 |
-| 钻石升级下级(普通→黄金) | 免费 | 无 | 钻石代理操作,被升级代理无需付费 |
-
-**重要规则**:
-- ✅ **返佣给原直接上级**: 即使升级后脱离直接上下级关系,返佣仍然给原直接上级
-- ✅ **返佣时机**: 付费成功后立即返佣,然后执行升级操作
-- ✅ **升级流程**: 付费 → 返佣给直接上级 → 升级 → 根据情况脱离关系
-
-### 3.3 价格体系规则
-
-#### 3.3.1 价格计算公式
-
-```
-实际底价 = 基础底价 + 等级加成
-代理收益 = 设定价格 - 实际底价 - 提价成本
-提价成本 = (设定价格 - 提价阈值) × 提价手续费比例(当设定价格 > 提价阈值时)
-```
-
-#### 3.3.2 价格约束
-
-- **最低价格**: 实际底价(基础底价 + 等级加成)
-- **最高价格**: 系统价格上限(或产品配置的价格上限)
-- **价格范围**: `实际底价 ≤ 设定价格 ≤ 系统价格上限`
-
-#### 3.3.3 提价成本计算
-
-- **当设定价格 ≤ 提价阈值**: 提价成本 = 0
-- **当设定价格 > 提价阈值**: 提价成本 = (设定价格 - 提价阈值) × 提价手续费比例
-
-### 3.4 收益分配规则
-
-#### 3.4.1 代理收益
-
-代理收益 = 设定价格 - 实际底价 - 提价成本
-
-**分配流程**:
-1. 订单支付成功后,触发 `AgentService.AgentProcess`
-2. 计算代理收益并发放到代理钱包
-3. 创建 `agent_commission` 记录
-4. 更新 `agent_wallet` 余额和累计收益
-
-#### 3.4.2 等级加成返佣分配
-
-**核心原则**:
-- **基础底价(BasePrice)**:无论哪个等级,全部归平台(固定收入)
-- **等级加成部分**:作为返佣分配给上级链
-
-**分配规则总结表**:
-
-| 代理等级 | 等级加成 | 直接上级 | 等级加成返佣分配规则 |
-| -------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------ |
-| 钻石 | 0元 | 无 | 无返佣分配(加成为0) |
-| 黄金 | 3元 | 钻石 | 3元全部给钻石上级 |
-| 普通 | 6元 | 钻石 | 6元全部给钻石上级 |
-| 普通 | 6元 | 黄金 | 3元给黄金上级,3元给钻石上级(上上级) |
-| 普通 | 6元 | 普通 | 2元给直接上级普通,剩余4元:
- 有钻石:4元全部给钻石
- 只有黄金:3元给黄金,1元归平台
- 都没有:4元归平台 |
-
-**详细规则说明**:
-
-1. **普通代理(等级加成,例如6元)**:
- - **直接上级是钻石**:等级加成全部给钻石上级
- - **直接上级是黄金**:一部分给黄金上级(配置:`normal_to_gold_rebate`,默认3元),剩余给钻石上级(上上级)
- - **直接上级是普通**:
- - 一部分给直接上级普通(配置:`normal_to_normal_rebate`,默认2元)
- - 剩余金额:
- - 有钻石上级:剩余全部给钻石上级
- - 只有黄金上级:最多给黄金上级(配置:`normal_to_gold_rebate_max`,默认3元),超出部分归平台
- - 都没有:全部归平台
- - **注意**:如果在团队上下级链路中这中间有很多层普通代理,给直接上级的金额只给推广人的直接上级,对于中间的会直接跳过,然后到黄金/钻石代理
-
-2. **黄金代理(等级加成,例如3元)**:
- - 全部给钻石上级(如有)
- - 如果找不到钻石上级,归平台
-
-3. **钻石代理(等级加成0元)**:
- - 无返佣分配
-
-**配置项说明**:
-- 返佣金额的具体取值从配置表读取,支持动态配置
-- 配置键:
- - `normal_to_normal_rebate`:普通代理给直接上级普通的金额(默认2元)
- - `normal_to_gold_rebate`:普通代理给直接上级黄金的金额(默认3元)
- - `normal_to_gold_rebate_max`:普通代理给黄金上级的最大金额(默认3元)
-- 如果配置不存在,使用默认值
-
-#### 3.4.3 返佣记录
-
-所有返佣都会记录到 `agent_rebate` 表,包含:
-- 获得返佣的代理ID
-- 来源代理ID(产生订单的代理)
-- 返佣类型(1=直接上级,2=钻石上级,3=黄金上级)
-- 返佣金额
-- 等级加成金额
-
-### 3.5 提现规则
-
-#### 3.5.1 提现条件
-
-1. **实名认证**: 必须完成实名认证且三要素核验已通过(`verify_time` 不为空)
-2. **余额充足**: 钱包余额 >= 提现金额
-3. **账户信息**: 必须提供支付宝账号和收款人姓名
-
-#### 3.5.2 税费计算规则
-
-**计算公式**:
-```
-本月累计提现金额 = 查询本月所有已提现金额之和
-剩余免税额度 = 免税额度 - 本月累计提现金额
-
-如果 本次提现金额 <= 剩余免税额度:
- 应税金额 = 0
- 税费 = 0
-否则:
- 应税金额 = 本次提现金额 - 剩余免税额度
- 税费 = 应税金额 × 税率
-
-实际到账金额 = 提现金额 - 税费
-```
-
-**示例**:
-- 免税额度: 1000元
-- 税率: 6%
-- 本月已提现: 800元
-- 本次提现: 500元
-
-计算:
-- 剩余免税额度 = 1000 - 800 = 200元
-- 本次提现500元 > 剩余免税额度200元
-- 应税金额 = 500 - 200 = 300元
-- 税费 = 300 × 6% = 18元
-- 实际到账 = 500 - 18 = 482元
-
-#### 3.5.3 提现流程
-
-1. **代理申请提现**:
- - 验证实名认证状态
- - 验证余额
- - 计算税费
- - 冻结余额
- - 创建提现记录和扣税记录
-
-2. **管理员审核**:
- - 审核通过: 调用支付宝转账接口
- - 审核拒绝: 解冻余额,返回余额
-
-3. **转账处理**:
- - 转账成功: 解冻并扣除余额,更新扣税状态
- - 转账失败: 解冻余额,返回余额
- - 处理中: 保持提现中状态,后续轮询更新
-
----
-
-## 四、完整链路流程
-
-### 4.1 通过邀请码成为代理链路
-
-**重要规则**:用户不能自主成为代理,只能通过邀请码成为代理。成为代理的唯一途径包括:
-1. 平台发放钻石邀请码
-2. 代理发放邀请码
-3. 代理邀请链接/二维码
-
-```
-┌─────────────────────────────────────────────────────────────┐
-│ 1. 用户通过邀请码申请成为代理 │
-│ API: POST /api/v1/agent/apply │
-│ Logic: ApplyForAgentLogic │
-│ - 必须提供邀请码(必填) │
-│ - 如果没有邀请码,直接拒绝 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 2. 验证手机验证码 │
-│ - 从Redis读取验证码 │
-│ - 验证码校验 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 3. 用户注册/绑定(如需要) │
-│ - 检查用户是否存在 │
-│ - 不存在则注册新用户 │
-│ - 临时用户则绑定为正式用户 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 4. 检查是否已是代理 │
-│ - 检查是否已是代理(agent表) │
-│ - 如果已是代理,直接拒绝 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 5. 验证邀请码 │
-│ - 查询邀请码是否存在 │
-│ - 验证邀请码状态(未使用、未过期) │
-│ - 获取邀请码信息(目标等级、发放代理ID) │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 6. 创建代理记录 │
-│ - 根据邀请码的target_level设置代理等级 │
-│ - 如果是代理发放的邀请码,建立上下级关系 │
-│ - 如果是平台发放的钻石邀请码,独立成团队 │
-│ - 初始化钱包 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 7. 更新邀请码状态 │
-│ - **钻石邀请码**:状态更新为"已使用"(status=1),使用后立即失效│
-│ - **普通邀请码**:不更新状态,保持未使用(status=0),可继续使用│
-│ - 记录使用用户ID和代理ID(用于统计) │
-│ - 记录使用时间 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 8. 生成并返回Token │
-│ - 生成JWT Token │
-│ - 返回给前端 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 8. 完成(无需审核,直接成为代理) │
-│ - 代理记录已创建 │
-│ - 钱包已初始化 │
-│ - 关系已建立(如有上级) │
-│ - 团队首领已设置 │
-│ - 微信号和区域为可选字段 │
-└─────────────────────────────────────────────────────────────┘
-```
-
-### 4.2 推广链接生成链路
-
-```
-┌─────────────────────────────────────────────────────────────┐
-│ 1. 代理生成推广链接 │
-│ API: POST /api/v1/agent/link/generate │
-│ Logic: GeneratingLinkLogic │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 2. 获取代理信息 │
-│ - 从Token获取用户ID │
-│ - 查询 agent 表 │
-│ - 验证代理身份 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 3. 获取系统配置 │
-│ - base_price(基础底价) │
-│ - system_max_price(系统价格上限) │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 4. 计算实际底价 │
-│ 实际底价 = 基础底价 + 等级加成 │
-│ - 普通代理: +6元 │
-│ - 黄金代理: +3元 │
-│ - 钻石代理: +0元 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 5. 验证设定价格范围 │
-│ - 实际底价 ≤ 设定价格 ≤ 系统价格上限 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 6. 检查是否已存在相同链接 │
-│ - 查询 agent_link 表 │
-│ - 条件: agent_id + product_id + set_price + del_state=0 │
-│ - 如果存在,直接返回已有链接 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 7. 生成推广链接标识 │
-│ - 构建 AgentIdentifier 结构 │
-│ { AgentID, ProductID, SetPrice } │
-│ - JSON序列化 │
-│ - AES加密生成 LinkIdentifier │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 8. 保存推广链接 │
-│ - 插入 agent_link 表 │
-│ - 记录 agent_id, product_id, link_identifier │
-│ - 记录 set_price, actual_base_price │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 9. 返回加密后的 LinkIdentifier │
-│ - 前端使用此标识生成推广链接 │
-└─────────────────────────────────────────────────────────────┘
-```
-
-### 4.3 订单处理与收益分配链路
-
-```
-┌─────────────────────────────────────────────────────────────┐
-│ 1. 用户通过推广链接下单 │
-│ API: POST /api/v1/pay/payment │
-│ Logic: PaymentLogic │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 2. 解析推广链接标识 │
-│ - 解密 LinkIdentifier │
-│ - 解析 AgentIdentifier │
-│ - 获取 AgentID, ProductID, SetPrice │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 3. 查询推广链接信息 │
-│ - 查询 agent_link 表 │
-│ - 验证链接有效性 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 4. 创建订单记录 │
-│ - 插入 order 表 │
-│ - 记录订单金额(SetPrice) │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 5. 创建代理订单记录 │
-│ - 插入 agent_order 表 │
-│ - OrderAmount = SetPrice │
-│ - SetPrice = 设定价格 │
-│ - ActualBasePrice = 实际底价(基础底价+等级加成) │
-│ - PriceCost = 提价成本 │
-│ - AgentProfit = 代理收益(SetPrice - ActualBasePrice - PriceCost)│
-│ - ProcessStatus = 0(待处理) │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 6. 用户支付订单 │
-│ - 调用支付宝/微信支付接口 │
-│ - 生成支付订单 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 7. 支付成功回调 │
-│ - 支付宝/微信支付回调 │
-│ - 验证签名 │
-│ - 更新订单状态 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 8. 触发代理订单处理 │
-│ Service: AgentService.AgentProcess │
-│ - 检查是否是代理订单 │
-│ - 检查订单是否已处理(防重复) │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 9. 获取代理信息和系统配置 │
-│ - 查询 agent 表 │
-│ - 获取代理等级 │
-│ - 获取系统配置(base_price等) │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 10. 使用事务处理订单(开始事务) │
-│ - 计算实际底价和代理收益 │
-│ - 计算提价成本 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 11. 更新代理订单状态 │
-│ - ProcessStatus = 1(处理成功) │
-│ - ProcessTime = 当前时间 │
-│ - ProcessRemark = "处理成功" │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 12. 发放代理佣金 │
-│ - 创建 agent_commission 记录 │
-│ - 更新 agent_wallet │
-│ Balance += AgentProfit │
-│ TotalEarnings += AgentProfit │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 13. 分配等级加成返佣 │
-│ - 根据代理等级分配返佣 │
-│ - 普通代理(6元): 给直接上级3元,剩余给钻石/黄金上级 │
-│ - 黄金代理(3元): 全部给钻石上级 │
-│ - 钻石代理(0元): 无返佣 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 14. 记录返佣 │
-│ - 创建 agent_rebate 记录 │
-│ - 更新上级钱包余额 │
-│ - 记录返佣类型和金额 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 15. 提交事务(完成) │
-└─────────────────────────────────────────────────────────────┘
-```
-
-### 4.4 代理升级链路
-
-#### 4.4.1 自主付费升级链路
-
-```
-┌─────────────────────────────────────────────────────────────┐
-│ 1. 代理申请升级 │
-│ API: POST /api/v1/agent/upgrade/apply │
-│ Logic: ApplyUpgradeLogic │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 2. 验证升级条件 │
-│ - 验证当前等级和目标等级 │
-│ - 普通→黄金: ✓ │
-│ - 普通→钻石: ✓ │
-│ - 黄金→钻石: ✓ │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 3. 计算升级费用和返佣 │
-│ - 普通→黄金: 199元,返佣139元 │
-│ - 普通→钻石: 980元,返佣680元 │
-│ - 黄金→钻石: 980元,返佣680元 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 4. 查找原直接上级 │
-│ - 查询 agent_relation 表 │
-│ - RelationType = 1(直接关系) │
-│ - 用于返佣 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 5. 创建升级记录 │
-│ - 插入 agent_upgrade 表 │
-│ - UpgradeType = 1(自主付费) │
-│ - Status = 1(待处理) │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 6. 返回升级ID和订单号 │
-│ - 用于支付 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 7. 用户支付升级费用 │
-│ - 调用支付接口 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 8. 支付成功回调 │
-│ - 触发升级处理 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 9. 执行升级操作 │
-│ Service: AgentService.ProcessUpgrade │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 10. 返佣给原直接上级(开始事务) │
-│ - 更新上级钱包余额 │
-│ - Balance += RebateAmount │
-│ - TotalEarnings += RebateAmount │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 11. 更新代理等级 │
-│ - agent.Level = toLevel │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 12. 检查是否需要脱离直接上级关系 │
-│ - 检查升级后等级是否高于上级 │
-│ - 检查是否同级(黄金/钻石,普通代理同级除外) │
-└─────────────────────────────────────────────────────────────┘
- ↓
- ┌───────────────────────────────┐
- │ 需要脱离关系 │
- └───────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 13. 脱离直接上级关系 │
-│ - 更新 agent_relation 表 │
-│ - RelationType = 2(已脱离) │
-│ - DetachReason = "upgrade" │
-│ - DetachTime = 当前时间 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 14. 更新团队首领 │
-│ - 如果升级为钻石: team_leader_id = 自己 │
-│ - 如果升级为黄金: 查找上级链中的钻石代理 │
-│ - 更新所有下级的 team_leader_id │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 15. 更新升级记录状态 │
-│ - Status = 2(已完成) │
-│ - Remark = "升级成功" │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 16. 提交事务(完成) │
-└─────────────────────────────────────────────────────────────┘
-```
-
-#### 4.4.2 钻石升级下级链路
-
-```
-┌─────────────────────────────────────────────────────────────┐
-│ 1. 钻石代理升级下级 │
-│ API: POST /api/v1/agent/upgrade/subordinate │
-│ Logic: UpgradeSubordinateLogic │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 2. 验证权限 │
-│ - 必须是钻石代理(Level = 3) │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 3. 验证下级等级 │
-│ - 只能是普通代理(Level = 1) │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 4. 验证关系 │
-│ - 必须是直接下级 │
-│ - 查询 agent_relation 表 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 5. 验证目标等级 │
-│ - 只能升级为黄金(toLevel = 2) │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 6. 创建升级记录 │
-│ - 插入 agent_upgrade 表 │
-│ - UpgradeType = 2(钻石升级下级) │
-│ - UpgradeFee = 0(免费) │
-│ - RebateAmount = 0(无返佣) │
-│ - OperatorAgentId = 操作者代理ID │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 7. 执行升级操作 │
-│ Service: AgentService.ProcessUpgrade │
-│ - 更新下级等级为黄金 │
-│ - 脱离直接上级关系(如需要) │
-│ - 更新团队首领(保持原团队首领) │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 8. 更新升级记录状态 │
-│ - Status = 2(已完成) │
-│ - Remark = "钻石代理升级下级成功" │
-└─────────────────────────────────────────────────────────────┘
-```
-
-### 4.5 提现链路
-
-```
-┌─────────────────────────────────────────────────────────────┐
-│ 1. 代理申请提现 │
-│ API: POST /api/v1/agent/withdrawal/apply │
-│ Logic: ApplyWithdrawalLogic │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 2. 验证实名认证 │
-│ - 查询 agent_real_name 表 │
-│ - verify_time 必须不为空(三要素核验已通过) │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 3. 验证提现金额 │
-│ - Amount > 0 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 4. 验证钱包余额 │
-│ - Balance >= Amount │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 5. 计算税费 │
-│ - 查询本月累计提现金额 │
-│ - 计算剩余免税额度 │
-│ - 计算应税金额 │
-│ - 计算税费 = 应税金额 × 税率 │
-│ - 计算实际到账金额 = 提现金额 - 税费 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 6. 生成提现单号 │
-│ - 格式: WD + timestamp + agentId │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 7. 使用事务处理提现申请(开始事务) │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 8. 冻结余额 │
-│ - FrozenBalance += Amount │
-│ - Balance -= Amount │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 9. 创建提现记录 │
-│ - 插入 agent_withdrawal 表 │
-│ - Status = 1(待审核) │
-│ - 记录 PayeeAccount, PayeeName │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 10. 创建扣税记录 │
-│ - 插入 agent_withdrawal_tax 表 │
-│ - TaxStatus = 1(待扣税) │
-│ - 记录 YearMonth, TaxableAmount, TaxAmount │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 11. 提交事务(完成) │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 12. 管理员审核提现 │
-│ API: POST /api/v1/admin/agent/withdrawal/audit │
-│ Logic: AdminAuditWithdrawalLogic │
-└─────────────────────────────────────────────────────────────┘
- ↓
- ┌───────────────────────────────┐
- │ 审核通过(Status=2) │
- └───────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 13. 更新提现状态为提现中 │
-│ - Status = 4(提现中) │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 14. 调用支付宝转账接口 │
-│ - AliTransfer(account, name, amount, remark, outBizNo) │
-└─────────────────────────────────────────────────────────────┘
- ↓
- ┌───────────────┬───────────────┬───────────────┐
- │ 转账成功 │ 转账失败 │ 处理中 │
- │ (SUCCESS) │ (FAIL) │ (DEALING) │
- └───────────────┴───────────────┴───────────────┘
- ↓ ↓ ↓
- ┌───────────────┐ ┌───────────────┐ ┌───────────────┐
- │ 15. 更新状态 │ │ 15. 更新状态 │ │ 15. 保持状态 │
- │ Status = 5 │ │ Status = 6 │ │ Status = 4 │
- │ (提现成功) │ │ (提现失败) │ │ (提现中) │
- └───────────────┘ └───────────────┘ └───────────────┘
- ↓ ↓
- ┌───────────────┐ ┌───────────────┐
- │ 16. 解冻并扣 │ │ 16. 解冻余额 │
- │ 除余额 │ │ 返回余额 │
- │ - FrozenBalance│ │ - FrozenBalance│
- │ -= Amount │ │ -= Amount │
- │ - WithdrawnAmount│ │ - Balance │
- │ += Amount │ │ += Amount │
- └───────────────┘ └───────────────┘
- ↓
- ┌───────────────┐
- │ 17. 更新扣税 │
- │ 状态 │
- │ TaxStatus = 2 │
- │ (已扣税) │
- └───────────────┘
-```
-
-### 4.6 实名认证链路
-
-```
-┌─────────────────────────────────────────────────────────────┐
-│ 1. 代理提交实名认证信息 │
-│ API: POST /api/v1/agent/real_name │
-│ Logic: RealNameAuthLogic │
-│ - 姓名、身份证号、手机号 │
-│ - 验证码(手机号验证码) │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 2. 验证代理身份 │
-│ - 从Token获取用户ID │
-│ - 查询 agent 表 │
-│ - 验证手机号是否匹配 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 3. 验证手机验证码 │
-│ - 从Redis读取验证码 │
-│ - 验证码校验 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 4. 三要素核验(自动) │
-│ - 调用 VerificationService.ThreeFactorVerification │
-│ - 核验姓名、身份证号、手机号是否一致 │
-│ - 核验通过:继续流程 │
-│ - 核验失败:返回错误 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 5. 加密敏感信息 │
-│ - 加密身份证号 │
-│ - 加密手机号 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 6. 保存实名认证记录(开始事务) │
-│ - 检查是否已有记录 │
-│ - 如有记录:更新姓名、身份证号、手机号、verify_time │
-│ - 如无记录:创建新记录,设置 verify_time = 当前时间 │
-│ - verify_time 不为空表示已通过三要素核验 │
-└─────────────────────────────────────────────────────────────┘
- ↓
-┌─────────────────────────────────────────────────────────────┐
-│ 7. 提交事务(完成) │
-│ - 返回成功,实名认证完成 │
-│ - 可以申请提现 │
-└─────────────────────────────────────────────────────────────┘
-```
-
-**重要说明**:
-- 实名认证改为三要素核验,无需人工审核
-- 核验通过后自动设置 `verify_time`,无需管理员操作
-- `verify_time` 不为空即表示已通过认证,可以提现
-
----
-
-## 五、关键代码逻辑
-
-### 5.1 AgentService 核心方法
-
-#### 5.1.1 AgentProcess(订单处理)
-
-**文件**: `app/main/api/internal/service/agentService.go`
-
-**功能**: 处理代理订单,分配收益和返佣
-
-**核心逻辑**:
-```go
-func (s *AgentService) AgentProcess(ctx context.Context, order *model.Order) error {
- // 1. 检查是否是代理订单
- agentOrder, err := s.AgentOrderModel.FindOneByOrderId(ctx, order.Id)
- if err != nil {
- if errors.Is(err, model.ErrNotFound) {
- return nil // 不是代理订单
- }
- return err
- }
-
- // 2. 检查订单是否已处理(防重复)
- if agentOrder.ProcessStatus == 1 {
- return nil
- }
-
- // 3. 获取代理信息和系统配置
- agent, err := s.AgentModel.FindOne(ctx, agentOrder.AgentId)
- basePrice, err := s.getConfigFloat(ctx, "base_price")
-
- // 4. 使用事务处理
- return s.AgentWalletModel.Trans(ctx, func(transCtx context.Context, session sqlx.Session) error {
- // 4.1 计算实际底价和代理收益
- levelBonus := s.getLevelBonus(agent.Level)
- actualBasePrice := basePrice + float64(levelBonus)
- priceCost := s.calculatePriceCost(agentOrder.SetPrice, priceThreshold, priceFeeRate)
- agentProfit := agentOrder.SetPrice - actualBasePrice - priceCost
-
- // 4.2 更新代理订单状态
- agentOrder.ProcessStatus = 1
- s.AgentOrderModel.UpdateWithVersion(transCtx, session, agentOrder)
-
- // 4.3 发放代理佣金
- s.giveAgentCommission(transCtx, session, agentOrder.AgentId, order.Id, order.ProductId, agentProfit)
-
- // 4.4 分配等级加成返佣
- if levelBonus > 0 {
- s.distributeLevelBonus(transCtx, session, agent, order.Id, order.ProductId, float64(levelBonus), levelBonus)
- }
-
- return nil
- })
-}
-```
-
-#### 5.1.2 distributeLevelBonus(等级加成返佣分配)
-
-**功能**: 根据代理等级分配等级加成返佣给上级链
-
-**核心逻辑**:
-```go
-func (s *AgentService) distributeLevelBonus(ctx context.Context, session sqlx.Session, agent *model.Agent, orderId, productId int64, levelBonus float64, levelBonusInt int64) error {
- // 钻石代理:等级加成为0,无返佣分配
- if agent.Level == 3 {
- return nil
- }
-
- // 黄金代理:等级加成3元,全部给钻石上级
- if agent.Level == 2 {
- diamondParent, err := s.findDiamondParent(ctx, agent.Id)
- if diamondParent != nil {
-100002 return s.giveRebate(ctx, session, diamondParent.Id, agent.Id, orderId, productId, levelBonus, levelBonusInt, 2)
- }
- return nil
- }
-
- // 普通代理:等级加成6元,按规则分配给上级链
- if agent.Level == 1 {
- return s.distributeNormalAgentBonus(ctx, session, agent, orderId, productId, levelBonus, levelBonusInt)
- }
-
- return nil
-}
-```
-
-#### 5.1.3 ProcessUpgrade(代理升级处理)
-
-**功能**: 处理代理升级,包括返佣、关系脱离、团队首领更新
-
-**核心逻辑**:
-```go
-func (s *AgentService) ProcessUpgrade(ctx context.Context, agentId, toLevel int64, upgradeType int64, upgradeFee, rebateAmount float64, orderNo string, operatorAgentId int64) error {
- return s.AgentWalletModel.Trans(ctx, func(transCtx context.Context, session sqlx.Session) error {
- // 1. 获取代理信息
- agent, err := s.AgentModel.FindOne(transCtx, agentId)
-
- // 2. 如果是自主付费升级,处理返佣
- if upgradeType == 1 {
- parent, err := s.findDirectParent(transCtx, agentId)
- if parent != nil && rebateAmount > 0 {
- s.giveRebateForUpgrade(transCtx, session, parent.Id, agentId, rebateAmount)
- }
- }
-
- // 3. 更新代理等级
- agent.Level = toLevel
-
- // 4. 检查是否需要脱离直接上级关系
- needDetach, err := s.needDetachFromParent(transCtx, agent, toLevel)
- if needDetach {
- s.detachFromParent(transCtx, session, agentId)
- }
-
- // 5. 如果升级为钻石,独立成新团队
- if toLevel == 3 {
- agent.TeamLeaderId = sql.NullInt64{Int64: agentId, Valid: true}
- s.updateChildrenTeamLeader(transCtx, session, agentId, agentId)
- } else {
- // 更新团队首领(查找上级链中的钻石代理)
- teamLeaderId, _ := s.findTeamLeaderId(transCtx, agentId)
- if teamLeaderId > 0 {
- agent.TeamLeaderId = sql.NullInt64{Int64: teamLeaderId, Valid: true}
- }
- }
-
- // 6. 更新代理记录
- s.AgentModel.UpdateWithVersion(transCtx, session, agent)
-
- return nil
- })
-}
-```
-
-#### 5.1.4 calculateTax(税费计算)
-
-**功能**: 计算提现税费,基于月度累计和免税额度
-
-**核心逻辑**:
-```go
-func calculateTax(ctx context.Context, agentId int64, amount float64, yearMonth int64) (*TaxInfo, error) {
- // 1. 获取税率配置(默认6%)
- taxRate := 0.06
- config, _ := AgentConfigModel.FindOneByConfigKey(ctx, "tax_rate")
- if config != nil {
- taxRate = parseFloat(config.ConfigValue)
- }
-
- // 2. 查询本月已提现金额
- taxRecords, _ := AgentWithdrawalTaxModel.FindAll(...)
- monthlyTotal := sum(taxRecords.WithdrawalAmount)
-
- // 3. 获取免税额度配置(默认0)
- exemptionAmount := 0.0
- exemptionConfig, _ := AgentConfigModel.FindOneByConfigKey(ctx, "tax_exemption_amount")
- if exemptionConfig != nil {
- exemptionAmount = parseFloat(exemptionConfig.ConfigValue)
- }
-
- // 4. 计算应税金额
- remainingExemption := exemptionAmount - monthlyTotal
- taxableAmount := amount
- if remainingExemption > 0 {
- if amount <= remainingExemption {
- taxableAmount = 0 // 完全免税
- } else {
- taxableAmount = amount - remainingExemption // 部分免税
- }
- }
-
- // 5. 计算税费和实际到账金额
- taxAmount := taxableAmount * taxRate
- actualAmount := amount - taxAmount
-
- return &TaxInfo{
- TaxableAmount: taxableAmount,
- TaxRate: taxRate,
- TaxAmount: taxAmount,
- ActualAmount: actualAmount,
- }, nil
-}
-```
-
----
-
-## 六、API接口列表
-
-### 6.1 前端接口(agent.api)
-
-#### 6.1.1 公开接口(无需登录)
-
-| 接口 | 方法 | 路径 | 说明 |
-| ---------------------- | ---- | --------------------- | -------------------------------------------- |
-| 获取推广链接数据 | GET | `/api/v1/agent/link` | 根据LinkIdentifier获取推广链接信息 |
-| 通过邀请码申请成为代理 | POST | `/api/v1/agent/apply` | 用户通过邀请码申请成为代理(必须提供邀请码) |
-
-#### 6.1.2 需要登录的接口
-
-| 接口 | 方法 | 路径 | 说明 |
-| -------------------- | ------- | ----------------------------------- | ------------------------------------------------------ |
-| ~~查询代理申请状态~~ | ~~GET~~ | ~~`/api/v1/agent/audit/status`~~ | ~~查询当前用户的代理申请审核状态(已废弃:无需审核)~~ |
-| 查看代理信息 | GET | `/api/v1/agent/info` | 获取当前代理的详细信息 |
-| 生成推广链接 | POST | `/api/v1/agent/generating_link` | 生成产品推广链接 |
-| 获取产品配置 | GET | `/api/v1/agent/product_config` | 获取代理可配置的产品价格信息 |
-| 获取团队统计 | GET | `/api/v1/agent/team/statistics` | 获取团队统计数据 |
-| 获取下级列表 | GET | `/api/v1/agent/subordinate/list` | 分页查询直接下级列表 |
-| 获取收益信息 | GET | `/api/v1/agent/revenue` | 获取钱包余额和收益信息 |
-| 获取佣金记录 | GET | `/api/v1/agent/commission/list` | 分页查询佣金记录 |
-| 获取返佣记录 | GET | `/api/v1/agent/rebate/list` | 分页查询返佣记录 |
-| 获取升级记录 | GET | `/api/v1/agent/upgrade/list` | 分页查询升级记录 |
-| 申请升级 | POST | `/api/v1/agent/upgrade/apply` | 自主付费升级 |
-| 钻石升级下级 | POST | `/api/v1/agent/upgrade/subordinate` | 钻石代理升级下级 |
-| 获取提现列表 | GET | `/api/v1/agent/withdrawal/list` | 分页查询提现记录 |
-| 申请提现 | POST | `/api/v1/agent/withdrawal/apply` | 申请提现 |
-| 实名认证 | POST | `/api/v1/agent/real_name` | 提交实名认证信息(三要素核验,自动通过) |
-
-### 6.2 后台管理接口(admin_agent.api)
-
-| 接口 | 方法 | 路径 | 说明 |
-| -------------------- | -------- | ------------------------------------------- | ------------------------------------------------------------ |
-| 代理分页查询 | GET | `/api/v1/admin/agent/list` | 分页查询代理列表 |
-| ~~代理审核~~ | ~~POST~~ | ~~`/api/v1/admin/agent/audit`~~ | ~~审核代理申请(已废弃:通过邀请码直接成为代理,无需审核)~~ |
-| 推广链接分页查询 | GET | `/api/v1/admin/agent/link/list` | 分页查询推广链接 |
-| 代理订单分页查询 | GET | `/api/v1/admin/agent/order/list` | 分页查询代理订单 |
-| 代理佣金分页查询 | GET | `/api/v1/admin/agent/commission/list` | 分页查询佣金记录 |
-| 代理返佣分页查询 | GET | `/api/v1/admin/agent/rebate/list` | 分页查询返佣记录 |
-| 代理升级记录分页查询 | GET | `/api/v1/admin/agent/upgrade/list` | 分页查询升级记录 |
-| 代理提现分页查询 | GET | `/api/v1/admin/agent/withdrawal/list` | 分页查询提现记录 |
-| 代理提现审核 | POST | `/api/v1/admin/agent/withdrawal/audit` | 审核提现申请 |
-| 代理实名认证分页查询 | GET | `/api/v1/admin/agent/real_name/list` | 分页查询实名认证记录 |
-| ~~代理实名认证审核~~ | ~~POST~~ | ~~`/api/v1/admin/agent/real_name/audit`~~ | ~~审核实名认证(已废弃:改为三要素核验,无需审核)~~ |
-| 系统配置查询 | GET | `/api/v1/admin/agent/config` | 查询系统配置 |
-| 系统配置更新 | POST | `/api/v1/admin/agent/config/update` | 更新系统配置 |
-| 产品配置分页查询 | GET | `/api/v1/admin/agent/product_config/list` | 分页查询产品配置 |
-| 产品配置更新 | POST | `/api/v1/admin/agent/product_config/update` | 更新产品配置 |
-
----
-
-## 七、系统配置说明
-
-### 7.1 系统配置项(agent_config表)
-
-| 配置键 | 配置类型 | 默认值 | 说明 |
-| --------------------------- | -------- | ------ | ------------------------------- |
-| `base_price` | price | - | 基础底价(系统默认) |
-| `system_max_price` | price | - | 系统价格上限 |
-| `price_threshold` | price | - | 提价标准阈值 |
-| `price_fee_rate` | price | - | 提价手续费比例(0-1之间的小数) |
-| `level_1_bonus` | bonus | 6 | 普通代理等级加成(元) |
-| `level_2_bonus` | bonus | 3 | 黄金代理等级加成(元) |
-| `level_3_bonus` | bonus | 0 | 钻石代理等级加成(元) |
-| `upgrade_to_gold_fee` | upgrade | 199 | 升级为黄金费用(元) |
-| `upgrade_to_diamond_fee` | upgrade | 980 | 升级为钻石费用(元) |
-| `upgrade_to_gold_rebate` | rebate | 139 | 升级为黄金返佣(元) |
-| `upgrade_to_diamond_rebate` | rebate | 680 | 升级为钻石返佣(元) |
-| `tax_rate` | tax | 0.06 | 税率(默认6%,即0.06) |
-| `tax_exemption_amount` | tax | 0 | 免税额度(元,默认0) |
-
-### 7.2 产品配置项(agent_product_config表)
-
-每个产品可以单独配置以下参数,覆盖系统默认配置:
-
-| 字段 | 说明 |
-| ------------------ | ---------------------------------------- |
-| `base_price` | 产品基础底价(覆盖系统配置) |
-| `system_max_price` | 产品价格上限(覆盖系统配置) |
-| `price_threshold` | 产品提价标准阈值(可选,覆盖系统配置) |
-| `price_fee_rate` | 产品提价手续费比例(可选,覆盖系统配置) |
-
-### 7.3 配置优先级
-
-1. **产品配置** > **系统配置**
- - 如果产品配置了 `base_price`,使用产品配置
- - 如果产品未配置,使用系统配置
-
-2. **系统配置** > **代码默认值**
- - 如果系统配置存在,使用系统配置
- - 如果系统配置不存在,使用代码中的默认值
-
----
-
-## 八、数据流转图
-
-### 8.1 订单处理数据流
-
-```
-用户下单
- ↓
-创建 order 记录
- ↓
-创建 agent_order 记录(ProcessStatus=0)
- ↓
-用户支付
- ↓
-支付成功回调
- ↓
-调用 AgentService.AgentProcess
- ↓
-┌─────────────────────────────────────┐
-│ 事务开始 │
-│ 1. 计算实际底价 = 基础底价 + 等级加成 │
-│ 2. 计算提价成本 │
-│ 3. 计算代理收益 │
-│ 4. 更新 agent_order (ProcessStatus=1) │
-│ 5. 创建 agent_commission 记录 │
-│ 6. 更新 agent_wallet (Balance, TotalEarnings) │
-│ 7. 分配等级加成返佣 │
-│ - 创建 agent_rebate 记录 │
-│ - 更新上级 agent_wallet │
-│ 事务提交 │
-└─────────────────────────────────────┘
-```
-
-### 8.2 升级处理数据流
-
-```
-代理申请升级
- ↓
-创建 agent_upgrade 记录(Status=1)
- ↓
-用户支付升级费用
- ↓
-支付成功回调
- ↓
-调用 AgentService.ProcessUpgrade
- ↓
-┌─────────────────────────────────────┐
-│ 事务开始 │
-│ 1. 返佣给原直接上级(如需要) │
-│ - 更新上级 agent_wallet │
-│ 2. 更新 agent.Level │
-│ 3. 检查是否需要脱离关系 │
-│ - 更新 agent_relation (RelationType=2) │
-│ 4. 更新团队首领 │
-│ - 更新 agent.TeamLeaderId │
-│ - 更新所有下级的 TeamLeaderId │
-│ 5. 更新 agent_upgrade (Status=2) │
-│ 事务提交 │
-└─────────────────────────────────────┘
-```
-
-### 8.3 提现处理数据流
-
-```
-代理申请提现
- ↓
-验证实名认证和余额
- ↓
-计算税费
- ↓
-┌─────────────────────────────────────┐
-│ 事务开始 │
-│ 1. 冻结余额 │
-│ - agent_wallet.FrozenBalance += Amount │
-│ - agent_wallet.Balance -= Amount │
-│ 2. 创建 agent_withdrawal 记录 │
-│ (Status=1, 待审核) │
-│ 3. 创建 agent_withdrawal_tax 记录 │
-│ (TaxStatus=1, 待扣税) │
-│ 事务提交 │
-└─────────────────────────────────────┘
- ↓
-管理员审核
- ↓
-审核通过(Status=2)
- ↓
-调用支付宝转账接口
- ↓
-转账成功/失败/处理中
- ↓
-┌─────────────────────────────────────┐
-│ 事务开始 │
-│ 1. 更新 agent_withdrawal.Status │
-│ 2. 解冻并扣除余额(成功) │
-│ - agent_wallet.FrozenBalance -= Amount │
-│ - agent_wallet.WithdrawnAmount += Amount │
-│ 3. 更新 agent_withdrawal_tax.TaxStatus │
-│ 事务提交 │
-└─────────────────────────────────────┘
-```
-
----
-
-## 九、关键算法说明
-
-### 9.1 团队统计递归算法
-
-```go
-func getTeamMembers(agentId int64) []int64 {
- teamMembers := []int64{agentId} // 包括自己
-
- var collectChildren func(int64)
- collectChildren = func(parentId int64) {
- // 查找直接下级(relation_type=1)
- relations := findDirectChildren(parentId)
- for _, relation := range relations {
- teamMembers = append(teamMembers, relation.ChildId)
- collectChildren(relation.ChildId) // 递归
- }
- }
-
- collectChildren(agentId)
- return teamMembers
-}
-```
-
-### 9.2 查找上级链算法
-
-```go
-// 查找直接上级
-func findDirectParent(agentId int64) *Agent {
- relation := findRelation(childId=agentId, relationType=1)
- return findAgent(relation.ParentId)
-}
-
-// 查找钻石上级(向上递归)
-func findDiamondParent(agentId int64) *Agent {
- currentId := agentId
- maxDepth := 100
- depth := 0
-
- for depth < maxDepth {
- parent := findDirectParent(currentId)
- if parent == nil {
- return nil
- }
- if parent.Level == 3 { // 钻石
- return parent
- }
- currentId = parent.Id
- depth++
- }
- return nil
-}
-```
-
-### 9.3 价格计算算法
-
-```go
-// 计算实际底价
-func calculateActualBasePrice(agentLevel int64, basePrice float64) float64 {
- levelBonus := getLevelBonus(agentLevel) // 6, 3, 0
- return basePrice + float64(levelBonus)
-}
-
-// 计算提价成本
-func calculatePriceCost(setPrice, priceThreshold, priceFeeRate float64) float64 {
- if setPrice <= priceThreshold {
- return 0
- }
- return (setPrice - priceThreshold) * priceFeeRate
-}
-
-// 计算代理收益
-func calculateAgentProfit(setPrice, actualBasePrice, priceCost float64) float64 {
- return setPrice - actualBasePrice - priceCost
-}
-```
-
----
-
-## 十、注意事项和最佳实践
-
-### 10.1 数据一致性
-
-1. **事务使用**: 所有涉及多表更新的操作必须使用事务
- - 订单处理(agent_order, agent_commission, agent_wallet, agent_rebate)
- - 升级处理(agent, agent_relation, agent_wallet, agent_upgrade)
- - 提现处理(agent_withdrawal, agent_withdrawal_tax, agent_wallet)
-
-2. **乐观锁**: 所有更新操作使用 `version` 字段进行乐观锁控制
- - 使用 `UpdateWithVersion` 方法
- - 更新失败时重试或提示用户
-
-3. **防重复处理**:
- - 订单处理前检查 `agent_order.ProcessStatus`
- - 升级处理前检查 `agent_upgrade.Status`
-
-### 10.2 性能优化
-
-1. **批量查询**: 避免N+1查询问题
- - 列表查询时批量获取产品名称、代理信息等
-
-2. **索引使用**:
- - `agent_relation`: `idx_parent_relation`, `idx_child_relation`
- - `agent_rebate`: `idx_order_rebate_type`
- - `agent_order`: `idx_process_status`
-
-3. **递归深度限制**:
- - 查找上级链时设置最大深度(如100层)
- - 防止无限循环
-
-### 10.3 错误处理
-
-1. **配置缺失**: 配置项不存在时使用默认值
- - 税率默认6%
- - 等级加成使用代码中的固定值
-
-2. **关系异常**:
- - 找不到上级时,返佣归平台
- - 升级时找不到直接上级,跳过返佣
-
-3. **余额不足**:
- - 提现前验证余额
- - 使用事务确保余额扣减的原子性
-
-### 10.4 安全考虑
-
-1. **数据加密**:
- - 手机号、身份证号使用AES加密存储
- - 推广链接标识加密传输
-
-2. **权限控制**:
- - 代理只能查看自己的数据
- - 管理员需要认证和授权
-
-3. **金额精度**:
- - 使用 `decimal(10,2)` 类型存储金额
- - 计算时注意浮点数精度问题
-
----
-
-## 十一、常见问题解答
-
-### 11.1 升级后关系脱离规则
-
-**Q**: 普通代理升级为黄金后什么情况下会脱离直接上级关系?
-
-**A**: 根据关系约束规则:
-- **下级不能比上级等级高**:如果直接上级是普通代理,升级后黄金等级高于普通,必须脱离
-- **同级不能作为上下级(除了普通代理)**:如果直接上级是黄金代理,升级后同级,必须脱离
-- **钻石可以拥有黄金下级**:如果直接上级是钻石代理,升级后钻石等级高于黄金,不脱离关系,保持直接下级关系
-
-### 11.2 返佣分配规则
-
-**Q**: 普通代理的6元等级加成如何分配?
-
-**A**: 按优先级分配:
-1. 给直接上级(根据上级等级:钻石6元,黄金3元,普通2元)
-2. 剩余金额给钻石上级(如有)
-3. 如果无钻石上级,给黄金上级(最多3元)
-4. 都没有,剩余归平台
-
-### 11.3 团队归属
-
-**Q**: 升级后团队归属如何变化?
-
-**A**:
-- 普通→黄金:仍属于原团队(通过team_leader_id指向原钻石代理)
-- 黄金→钻石:独立成新团队(team_leader_id指向自己)
-- 普通→钻石:独立成新团队(team_leader_id指向自己)
-
-### 11.4 税费计算
-
-**Q**: 税费如何计算?
-
-**A**:
-1. 查询本月累计提现金额
-2. 计算剩余免税额度 = 免税额度 - 本月累计
-3. 如果本次提现 <= 剩余免税额度,免税
-4. 否则,应税金额 = 本次提现 - 剩余免税额度
-5. 税费 = 应税金额 × 税率
-
----
-
-## 十二、后续优化建议
-
-### 12.1 功能扩展
-
-1. **多级返佣**: 支持更多层级的返佣分配
-2. **业绩统计**: 增加团队业绩统计和排行榜
-3. **消息通知**: 收益到账、升级成功等消息推送
-
-### 12.2 性能优化
-
-1. **缓存策略**:
- - 系统配置缓存
- - 团队统计数据缓存
- - 代理信息缓存
-
-2. **异步处理**:
- - 订单处理异步化
- - 团队统计异步计算
-
-### 12.3 监控和日志
-
-1. **关键操作日志**:
- - 订单处理日志
- - 升级操作日志
- - 提现操作日志
-
-2. **性能监控**:
- - 订单处理耗时
- - 数据库查询耗时
- - 接口响应时间
-
----
-
-## 附录
-
-### A. 数据表索引说明
-
-详见 `deploy/sql/agent_system_migration.sql` 文件中的索引定义。
-
-### B. API接口详细定义
-
-详见 `app/main/api/desc/front/agent.api` 和 `app/main/api/desc/admin/admin_agent.api` 文件。
-
-### C. 代码文件清单
-
-- **Service层**: `app/main/api/internal/service/agentService.go`
-- **Logic层**: `app/main/api/internal/logic/agent/*.go`
-- **Model层**: `app/main/model/agent*.go`
-- **API定义**: `app/main/api/desc/front/agent.api`, `app/main/api/desc/admin/admin_agent.api`
-
----
-
-**文档结束**
\ No newline at end of file
diff --git a/新代理系统检查清单.md b/新代理系统检查清单.md
deleted file mode 100644
index 449f137..0000000
--- a/新代理系统检查清单.md
+++ /dev/null
@@ -1,506 +0,0 @@
-# 新代理系统完整检查清单
-
-## 检查说明
-
-本文档提供新代理系统的完整检查方案,按照业务流程顺序组织,确保系统逻辑正确性和完整性。
-
-**检查原则**:
-1. 按照业务流程顺序检查(从注册到提现)
-2. 先检查核心链路,再检查辅助功能
-3. 每个检查点包含:功能点、关键逻辑、预期结果
-4. 重点关注数据一致性、事务完整性、边界条件
-
----
-
-## 一、基础数据检查
-
-### 1.1 数据表结构
-- [ ] 确认所有新表已创建(参考 `deploy/sql/agent_system_migration.sql`)
-- [ ] 检查表结构是否与文档一致
-- [ ] 验证索引是否正确创建
-- [ ] 确认所有表都有 `id`, `create_time`, `update_time`, `delete_time`, `del_state`, `version` 字段
-
-**关键表**:
-- `agent` - 代理基本信息
-- `agent_wallet` - 代理钱包
-- `agent_relation` - 代理关系
-- `agent_link` - 推广链接
-- `agent_order` - 代理订单
-- `agent_commission` - 代理佣金
-- `agent_rebate` - 代理返佣
-- `agent_upgrade` - 代理升级
-- `agent_withdrawal` - 代理提现
-- `agent_withdrawal_tax` - 提现扣税
-- `agent_config` - 系统配置
-- `agent_product_config` - 产品配置
-- `agent_real_name` - 实名认证
-- `agent_invite_code` - 邀请码
-
-### 1.2 系统配置初始化
-- [ ] 检查 `agent_config` 表是否有基础配置数据
-- [ ] 验证关键配置项是否存在:
- - `base_price` - 基础底价
- - `system_max_price` - 系统价格上限
- - `level_1_bonus` - 普通代理等级加成(6元)
- - `level_2_bonus` - 黄金代理等级加成(3元)
- - `level_3_bonus` - 钻石代理等级加成(0元)
- - `upgrade_to_gold_fee` - 升级为黄金费用(199元)
- - `upgrade_to_diamond_fee` - 升级为钻石费用(980元)
- - `tax_rate` - 税率(0.06)
- - `tax_exemption_amount` - 免税额度
-
----
-
-## 二、核心业务流程检查
-
-### 2.1 通过邀请码成为代理链路
-
-**入口**:`POST /api/v1/agent/apply` 或 `POST /api/v1/agent/register/invite`
-
-**检查点**:
-
-#### 2.1.1 邀请码验证
-- [ ] **文件**:`app/main/api/internal/logic/agent/applyforagentlogic.go`
-- [ ] 验证邀请码必填(没有邀请码直接拒绝)
-- [ ] 验证邀请码存在性(`agent_invite_code` 表)
-- [ ] 验证邀请码状态(`status=0` 未使用)
-- [ ] 验证邀请码是否过期(`expire_time`)
-- [ ] 验证邀请码使用后状态更新为已使用(`status=1`)
-
-#### 2.1.2 用户注册/绑定
-- [ ] 检查用户是否存在(通过手机号查询)
-- [ ] 不存在则注册新用户
-- [ ] 临时用户则绑定为正式用户
-- [ ] 验证手机号加密存储
-
-#### 2.1.3 代理记录创建
-- [ ] 检查是否已是代理(防止重复)
-- [ ] 根据邀请码的 `target_level` 设置代理等级
-- [ ] 创建 `agent` 记录
-- [ ] 初始化 `agent_wallet`(余额为0)
-- [ ] 可选字段处理:`region`, `wechat_id` 可以为空
-
-#### 2.1.4 关系建立
-- [ ] **代理发放的邀请码**:
- - [ ] 验证上级代理存在
- - [ ] 验证关系是否允许(下级等级不能高于上级)
- - [ ] 创建 `agent_relation` 记录(`relation_type=1` 直接关系)
- - [ ] 设置 `team_leader_id`(查找上级链中的钻石代理)
-- [ ] **平台发放的钻石邀请码**:
- - [ ] 独立成团队(`team_leader_id = 自己`)
-
-#### 2.1.5 邀请码状态更新
-- [ ] 更新邀请码状态为已使用(`status=1`)
-- [ ] 记录使用用户ID和代理ID
-- [ ] 记录使用时间
-
-#### 2.1.6 Token生成
-- [ ] 生成JWT Token
-- [ ] 返回给前端
-
-**测试场景**:
-1. 正常流程:使用有效邀请码注册
-2. 边界条件:邀请码不存在、已使用、已过期
-3. 重复注册:已是代理的用户再次申请
-4. 关系验证:下级等级高于上级的情况
-
----
-
-### 2.2 推广链接生成链路
-
-**入口**:`POST /api/v1/agent/generating_link`
-
-**检查点**:
-
-#### 2.2.1 代理身份验证
-- [ ] **文件**:`app/main/api/internal/logic/agent/generatinglinklogic.go`
-- [ ] 从Token获取用户ID
-- [ ] 查询 `agent` 表验证代理身份
-- [ ] 非代理用户拒绝
-
-#### 2.2.2 价格计算
-- [ ] 获取系统配置(`base_price`, `system_max_price`)
-- [ ] 计算实际底价 = 基础底价 + 等级加成
- - 普通代理:+6元
- - 黄金代理:+3元
- - 钻石代理:+0元
-- [ ] 验证设定价格范围:`实际底价 ≤ 设定价格 ≤ 系统价格上限`
-
-#### 2.2.3 链接生成
-- [ ] 检查是否已存在相同链接(`agent_id + product_id + set_price`)
-- [ ] 构建 `AgentIdentifier` 结构(`AgentID`, `ProductID`, `SetPrice`)
-- [ ] JSON序列化并AES加密生成 `LinkIdentifier`
-- [ ] 保存到 `agent_link` 表
-- [ ] 记录 `set_price` 和 `actual_base_price`
-
-**测试场景**:
-1. 正常生成:有效代理生成推广链接
-2. 价格验证:设定价格低于实际底价或高于系统上限
-3. 重复链接:相同代理、产品、价格的链接复用
-
----
-
-### 2.3 订单处理与收益分配链路
-
-**入口**:`POST /api/v1/pay/payment` → 支付回调 → `AgentService.AgentProcess`
-
-**检查点**:
-
-#### 2.3.1 订单创建
-- [ ] **文件**:`app/main/api/internal/logic/pay/paymentlogic.go`
-- [ ] 解析推广链接标识(解密 `LinkIdentifier`)
-- [ ] 查询 `agent_link` 表验证链接有效性
-- [ ] 创建 `order` 记录(订单金额 = SetPrice)
-- [ ] 创建 `agent_order` 记录:
- - `order_amount` = SetPrice
- - `set_price` = 设定价格
- - `actual_base_price` = 实际底价(基础底价+等级加成)
- - `price_cost` = 提价成本
- - `agent_profit` = 代理收益(SetPrice - ActualBasePrice - PriceCost)
- - `process_status` = 0(待处理)
-
-#### 2.3.2 支付成功处理
-- [ ] 支付回调验证签名
-- [ ] 更新订单状态
-- [ ] 触发 `AgentService.AgentProcess`
-
-#### 2.3.3 代理订单处理
-- [ ] **文件**:`app/main/api/internal/service/agentService.go` - `AgentProcess`
-- [ ] 检查是否是代理订单(查询 `agent_order` 表)
-- [ ] 检查订单是否已处理(`process_status=1` 防重复)
-- [ ] 获取代理信息和系统配置
-- [ ] 使用事务处理:
- - 更新 `agent_order` 状态为处理成功
- - 创建 `agent_commission` 记录
- - 更新 `agent_wallet`(`balance += agent_profit`, `total_earnings += agent_profit`)
-
-#### 2.3.4 等级加成返佣分配
-- [ ] **文件**:`app/main/api/internal/service/agentService.go` - `distributeLevelBonus`
-- [ ] **普通代理(6元)**:
- - [ ] 给直接上级(最多3元)
- - [ ] 剩余给钻石上级(如有)
- - [ ] 如果无钻石上级,给黄金上级(最多3元)
-- [ ] **黄金代理(3元)**:
- - [ ] 全部给钻石上级(如有)
-- [ ] **钻石代理(0元)**:
- - [ ] 无返佣
-- [ ] 创建 `agent_rebate` 记录
-- [ ] 更新上级钱包余额
-
-**测试场景**:
-1. 正常订单:普通代理订单,验证收益和返佣分配
-2. 防重复:已处理订单再次处理
-3. 返佣分配:不同等级代理的返佣分配逻辑
-4. 无上级:没有上级代理时的返佣处理
-
----
-
-### 2.4 代理升级链路
-
-#### 2.4.1 自主付费升级
-
-**入口**:`POST /api/v1/agent/upgrade/apply`
-
-**检查点**:
-- [ ] **文件**:`app/main/api/internal/logic/agent/applyupgradelogic.go`
-- [ ] 验证升级条件(普通→黄金、普通→钻石、黄金→钻石)
-- [ ] 计算升级费用和返佣
-- [ ] 查找原直接上级(用于返佣)
-- [ ] 创建 `agent_upgrade` 记录(`status=1` 待处理)
-- [ ] 支付成功后调用 `AgentService.ProcessUpgrade`
-- [ ] **升级处理**:
- - [ ] 返佣给原直接上级(如需要)
- - [ ] 更新代理等级
- - [ ] 检查是否需要脱离直接上级关系
- - [ ] 更新团队首领(升级为钻石时独立成团队)
- - [ ] 更新所有下级的 `team_leader_id`
-
-#### 2.4.2 钻石升级下级
-
-**入口**:`POST /api/v1/agent/upgrade/subordinate`
-
-**检查点**:
-- [ ] **文件**:`app/main/api/internal/logic/agent/upgradesubordinatelogic.go`
-- [ ] 验证权限(必须是钻石代理)
-- [ ] 验证下级等级(只能是普通代理)
-- [ ] 验证关系(必须是直接下级)
-- [ ] 验证目标等级(只能升级为黄金)
-- [ ] 创建升级记录(`upgrade_type=2`,`upgrade_fee=0`)
-- [ ] 执行升级操作
-
-**测试场景**:
-1. 正常升级:普通→黄金,验证返佣和关系脱离
-2. 升级为钻石:验证团队独立
-3. 钻石升级下级:验证权限和限制
-4. 关系脱离:升级后等级高于上级的情况
-
----
-
-### 2.5 实名认证链路
-
-**入口**:`POST /api/v1/agent/real_name`
-
-**检查点**:
-- [ ] **文件**:`app/main/api/internal/logic/agent/realnameauthlogic.go`
-- [ ] 验证代理身份
-- [ ] 验证手机号是否匹配
-- [ ] 验证手机验证码
-- [ ] 三要素核验(姓名、身份证号、手机号)
-- [ ] 加密身份证号和手机号
-- [ ] 保存实名认证记录(`verify_time` 不为空表示已通过)
-- [ ] 无需人工审核,核验通过即生效
-
-**测试场景**:
-1. 正常认证:三要素核验通过
-2. 核验失败:三要素不匹配
-3. 手机号不匹配:与代理注册手机号不一致
-
----
-
-### 2.6 提现链路
-
-**入口**:`POST /api/v1/agent/withdrawal/apply`
-
-**检查点**:
-
-#### 2.6.1 提现申请
-- [ ] **文件**:`app/main/api/internal/logic/agent/applywithdrawallogic.go`
-- [ ] 验证实名认证(`verify_time` 不为空)
-- [ ] 验证提现金额(> 0)
-- [ ] 验证钱包余额(`balance >= amount`)
-- [ ] 计算税费:
- - 查询本月累计提现金额
- - 计算剩余免税额度
- - 计算应税金额和税费
-- [ ] 冻结余额(`frozen_balance += amount`, `balance -= amount`)
-- [ ] 创建 `agent_withdrawal` 记录(`status=1` 待审核)
-- [ ] 创建 `agent_withdrawal_tax` 记录(`tax_status=1` 待扣税)
-
-#### 2.6.2 提现审核
-- [ ] **文件**:`app/main/api/internal/logic/admin_agent/adminauditwithdrawallogic.go`
-- [ ] 审核通过:调用支付宝转账接口
-- [ ] 审核拒绝:解冻余额
-- [ ] 转账成功:解冻并扣除余额,更新扣税状态
-- [ ] 转账失败:解冻余额
-
-**测试场景**:
-1. 正常提现:实名认证通过,余额充足
-2. 税费计算:验证月度累计和免税额度
-3. 余额不足:提现金额大于余额
-4. 未实名:未完成实名认证
-
----
-
-## 三、查询接口检查
-
-### 3.1 代理信息查询
-- [ ] **文件**:`app/main/api/internal/logic/agent/getagentinfologic.go`
-- [ ] 查询代理基本信息
-- [ ] 查询实名认证状态(`verify_time` 不为空)
-- [ ] 处理可选字段(`region`, `wechat_id`)
-
-### 3.2 收益信息查询
-- [ ] **文件**:`app/main/api/internal/logic/agent/getrevenueinfologic.go`
-- [ ] 查询钱包余额和累计收益
-- [ ] 返回 `balance`, `frozen_balance`, `total_earnings`, `withdrawn_amount`
-
-### 3.3 佣金记录查询
-- [ ] **文件**:`app/main/api/internal/logic/agent/getcommissionlistlogic.go`
-- [ ] 分页查询佣金记录
-- [ ] 关联查询产品名称
-
-### 3.4 返佣记录查询
-- [ ] **文件**:`app/main/api/internal/logic/agent/getrebatelistlogic.go`
-- [ ] 分页查询返佣记录
-- [ ] 显示返佣类型和金额
-
-### 3.5 团队统计查询
-- [ ] **文件**:`app/main/api/internal/logic/agent/getteamstatisticslogic.go`
-- [ ] 递归查询所有下级(直接+间接)
-- [ ] 统计总人数、按等级统计
-- [ ] 权限检查(只能查看自己团队)
-
-### 3.6 下级列表查询
-- [ ] **文件**:`app/main/api/internal/logic/agent/getsubordinatelistlogic.go`
-- [ ] 分页查询直接下级
-- [ ] 显示下级等级和信息
-
----
-
-## 四、邀请码管理检查
-
-### 4.1 生成邀请码
-- [ ] **文件**:`app/main/api/internal/logic/agent/generateinvitecodelogic.go`
-- [ ] 验证代理身份
-- [ ] 生成唯一邀请码
-- [ ] 设置目标等级(默认1=普通)
-- [ ] 保存到 `agent_invite_code` 表
-
-### 4.2 邀请码列表
-- [ ] **文件**:`app/main/api/internal/logic/agent/getinvitecodelistlogic.go`
-- [ ] 查询代理生成的邀请码
-- [ ] 显示状态(未使用、已使用、已失效)
-
-### 4.3 邀请链接
-- [ ] **文件**:`app/main/api/internal/logic/agent/getinvitelinklogic.go`
-- [ ] 生成邀请链接和二维码
-- [ ] 链接包含邀请码信息
-
-### 4.4 后台生成钻石邀请码
-- [ ] **文件**:`app/main/api/internal/logic/admin_agent/admingeneratediamondinvitecodelogic.go`
-- [ ] 管理员生成钻石邀请码
-- [ ] 目标等级为3(钻石)
-- [ ] 无发放代理ID(平台发放)
-
----
-
-## 五、后台管理接口检查
-
-### 5.1 代理列表查询
-- [ ] **文件**:`app/main/api/internal/logic/admin_agent/admingetagentlistlogic.go`
-- [ ] 分页查询代理列表
-- [ ] 显示代理等级、实名状态
-- [ ] 处理可选字段(`region`, `wechat_id`)
-
-### 5.2 代理订单查询
-- [ ] **文件**:`app/main/api/internal/logic/admin_agent/admingetagentorderlistlogic.go`
-- [ ] 分页查询代理订单
-- [ ] 显示订单金额、代理收益、处理状态
-
-### 5.3 系统配置管理
-- [ ] **文件**:`app/main/api/internal/logic/admin_agent/admingetagentconfiglogic.go`
-- [ ] 查询系统配置
-- [ ] **文件**:`app/main/api/internal/logic/admin_agent/adminupdateagentconfiglogic.go`
-- [ ] 更新系统配置
-
-### 5.4 产品配置管理
-- [ ] **文件**:`app/main/api/internal/logic/admin_agent/admingetagentproductconfiglistlogic.go`
-- [ ] 查询产品配置列表
-- [ ] **文件**:`app/main/api/internal/logic/admin_agent/adminupdateagentproductconfiglogic.go`
-- [ ] 更新产品配置
-
----
-
-## 六、关键业务逻辑验证
-
-### 6.1 关系约束验证
-- [ ] 下级等级不能高于上级
-- [ ] 同级不能作为上下级(除了普通代理)
-- [ ] 钻石可以拥有黄金下级(钻石等级高于黄金)
-- [ ] 升级后关系脱离逻辑(根据上级等级判断)
-
-### 6.2 团队首领逻辑
-- [ ] 钻石代理独立成团队(`team_leader_id = 自己`)
-- [ ] 普通/黄金代理指向上级链中的钻石代理
-- [ ] 升级为钻石时,所有下级跟随到新团队
-
-### 6.3 价格计算逻辑
-- [ ] 实际底价 = 基础底价 + 等级加成
-- [ ] 提价成本计算(当设定价格 > 提价阈值时)
-- [ ] 代理收益 = 设定价格 - 实际底价 - 提价成本
-
-### 6.4 返佣分配逻辑
-- [ ] 普通代理6元:直接上级3元,剩余给钻石/黄金上级
-- [ ] 黄金代理3元:全部给钻石上级
-- [ ] 钻石代理0元:无返佣
-
-### 6.5 税费计算逻辑
-- [ ] 月度累计提现金额查询
-- [ ] 剩余免税额度计算
-- [ ] 应税金额和税费计算
-
----
-
-## 七、数据一致性检查
-
-### 7.1 事务完整性
-- [ ] 代理注册:用户创建、代理创建、钱包初始化、关系建立、邀请码更新在同一事务
-- [ ] 订单处理:订单状态更新、佣金发放、返佣分配在同一事务
-- [ ] 升级处理:等级更新、关系脱离、团队首领更新在同一事务
-- [ ] 提现申请:余额冻结、提现记录、扣税记录在同一事务
-
-### 7.2 乐观锁
-- [ ] 所有更新操作使用 `version` 字段
-- [ ] 使用 `UpdateWithVersion` 方法
-- [ ] 更新失败时正确处理
-
-### 7.3 防重复处理
-- [ ] 订单处理前检查 `process_status`
-- [ ] 升级处理前检查 `status`
-- [ ] 邀请码使用后立即更新状态
-
----
-
-## 八、边界条件和异常处理
-
-### 8.1 边界条件
-- [ ] 邀请码不存在、已使用、已过期
-- [ ] 已是代理的用户再次申请
-- [ ] 下级等级高于上级的情况
-- [ ] 设定价格低于实际底价或高于系统上限
-- [ ] 余额不足的提现申请
-- [ ] 未完成实名认证的提现申请
-
-### 8.2 异常处理
-- [ ] 数据库操作失败的回滚
-- [ ] 三要素核验失败的处理
-- [ ] 支付回调失败的处理
-- [ ] 转账失败的处理
-
----
-
-## 九、性能和安全检查
-
-### 9.1 性能优化
-- [ ] 批量查询避免N+1问题
-- [ ] 索引使用(`agent_relation`, `agent_rebate`, `agent_order`)
-- [ ] 递归深度限制(查找上级链时)
-
-### 9.2 安全考虑
-- [ ] 手机号、身份证号加密存储
-- [ ] 推广链接标识加密传输
-- [ ] 权限控制(代理只能查看自己的数据)
-- [ ] 金额精度(使用 `decimal` 类型)
-
----
-
-## 十、测试建议
-
-### 10.1 单元测试
-- [ ] 价格计算函数
-- [ ] 税费计算函数
-- [ ] 返佣分配函数
-- [ ] 关系验证函数
-
-### 10.2 集成测试
-- [ ] 完整注册流程
-- [ ] 完整订单处理流程
-- [ ] 完整升级流程
-- [ ] 完整提现流程
-
-### 10.3 压力测试
-- [ ] 并发订单处理
-- [ ] 并发提现申请
-- [ ] 大量下级查询
-
----
-
-## 检查顺序建议
-
-1. **第一阶段**:基础数据检查(1.1, 1.2)
-2. **第二阶段**:核心业务流程(2.1, 2.2, 2.3)
-3. **第三阶段**:升级和实名认证(2.4, 2.5)
-4. **第四阶段**:提现流程(2.6)
-5. **第五阶段**:查询接口(三、四)
-6. **第六阶段**:后台管理(五)
-7. **第七阶段**:业务逻辑验证(六、七、八)
-8. **第八阶段**:性能和安全(九)
-
----
-
-**检查完成后,建议**:
-1. 记录发现的问题
-2. 修复问题后重新检查
-3. 编写测试用例覆盖关键流程
-4. 进行端到端测试验证
-
diff --git a/短链系统实现说明.md b/短链系统实现说明.md
deleted file mode 100644
index 5101bd7..0000000
--- a/短链系统实现说明.md
+++ /dev/null
@@ -1,135 +0,0 @@
-# 短链系统实现说明
-
-## 一、功能说明
-
-短链系统用于生成推广链接和邀请链接的短链,格式为:`https://推广域名/s/{shortCode}`
-
-### 支持两种类型
-1. **推广报告(promotion)**:类型值为1
- - 用于推广产品查询服务
- - 前端传入目标地址,如:`/agent/promotionInquire/{linkIdentifier}`
-
-2. **邀请好友(invite)**:类型值为2
- - 用于邀请用户成为代理
- - 前端传入目标地址,如:`/register?invite_code=XXXXX`
-
-### 工作流程
-1. 前端调用接口时传入 `target_path`(目标地址)
-2. 后端生成6位随机短链标识
-3. 短链存储在 `agent_short_link` 表中,包含类型和目标地址
-4. 返回短链URL:`https://推广域名/s/{shortCode}`
-5. 用户访问短链时,根据 `target_path` 重定向到对应页面
-
-## 二、执行步骤
-
-### 1. 执行SQL创建表
-
-```bash
-# 执行SQL文件创建短链表
-mysql -u root -p your_database < deploy/sql/add_agent_short_link_table.sql
-```
-
-或者直接在数据库客户端执行 `deploy/sql/add_agent_short_link_table.sql` 文件。
-
-### 2. 生成Model
-
-使用goctl工具生成Model代码:
-
-```bash
-# Windows PowerShell
-cd ycc-proxy-server
-goctl model mysql datasource -url="ycc:5vg67b3UNHu8@tcp(127.0.0.1:21001)/ycc" -table="agent_short_link" -dir="./app/main/model" --home="./deploy/template" -cache=true --style=goZero
-
-# Linux/Mac
-cd ycc-proxy-server
-goctl model mysql datasource -url="ycc:5vg67b3UNHu8@tcp(127.0.0.1:21001)/ycc" -table="agent_short_link" -dir="./app/main/model" --home="./deploy/template" -cache=true --style=goZero
-```
-
-### 3. 代码已实现
-
-代码已经实现完成,生成Model后即可使用。
-
-### 4. 配置推广域名
-
-在配置文件中设置推广域名:
-
-```yaml
-# main.yaml 或 main.dev.yaml
-Promotion:
- PromotionDomain: "https://promo.example.com" # 推广域名
-```
-
-## 三、接口说明
-
-### 短链重定向接口
-
-- **路径**: `/s/{shortCode}`
-- **方法**: `GET`
-- **说明**: 不需要 `/api/v1` 前缀,直接放在根路径
-- **功能**: 根据短链标识查询对应的推广链接,重定向到推广页面
-
-### 生成推广链接接口
-
-- **路径**: `/api/v1/agent/generating_link`
-- **方法**: `POST`
-- **请求参数**:
- ```json
- {
- "product_id": 1,
- "set_price": 10.00,
- "target_path": "/agent/promotionInquire/{linkIdentifier}" // 前端传入目标地址
- }
- ```
-- **返回**:
- ```json
- {
- "link_identifier": "加密的链接标识",
- "full_link": "https://推广域名/s/xxxxxx"
- }
- ```
-
-### 生成邀请链接接口
-
-- **路径**: `/api/v1/agent/invite_link`
-- **方法**: `GET`
-- **请求参数**:
- - `invite_code`: 邀请码
- - `target_path`: 目标地址(可选,默认:`/register?invite_code=xxx`)
-- **返回**:
- ```json
- {
- "invite_link": "https://推广域名/s/xxxxxx"
- }
- ```
-
-## 四、数据库表结构
-
-### agent_short_link 表
-
-| 字段 | 类型 | 说明 |
-|------|------|------|
-| id | bigint | 主键ID |
-| type | tinyint | 类型:1=推广报告,2=邀请好友 |
-| link_id | bigint | 推广链接ID(关联agent_link表,仅推广报告使用) |
-| invite_code_id | bigint | 邀请码ID(关联agent_invite_code表,仅邀请好友使用) |
-| link_identifier | varchar(200) | 推广链接标识(加密,仅推广报告使用) |
-| invite_code | varchar(50) | 邀请码(仅邀请好友使用) |
-| short_code | varchar(20) | 短链标识(6位随机字符串) |
-| target_path | varchar(500) | 目标地址(前端传入,如:/agent/promotionInquire/xxx) |
-| promotion_domain | varchar(200) | 推广域名 |
-| create_time | datetime | 创建时间 |
-| update_time | datetime | 更新时间 |
-| delete_time | datetime | 删除时间 |
-| del_state | tinyint | 删除状态:0=未删除,1=已删除 |
-| version | bigint | 版本号(乐观锁) |
-
-## 五、注意事项
-
-1. 短链标识是6位随机字符串(大小写字母+数字)
-2. 同一推广链接或邀请码同一类型只会生成一个短链(通过唯一索引保证)
-3. 短链重定向使用 `target_path`(相对路径),域名切换由服务器配置处理
-4. 如果推广域名未配置,生成短链时会返回空字符串
-5. **前端必须传入 `target_path` 参数**,后端只负责确定推广域名并生成短链
-6. 推广报告类型:前端传入 `/agent/promotionInquire/{linkIdentifier}`
-7. 邀请好友类型:前端传入 `/register?invite_code=XXXXX`(如果不传则使用默认值)
-
diff --git a/解冻任务优化建议.md b/解冻任务优化建议.md
deleted file mode 100644
index bad524c..0000000
--- a/解冻任务优化建议.md
+++ /dev/null
@@ -1,177 +0,0 @@
-# 解冻任务优化建议
-
-## 当前实现分析
-
-### 优点
-✅ 使用信号量控制并发,避免数据库压力过大
-✅ 使用事务和乐观锁保证数据一致性
-✅ 双重检查防止重复处理
-✅ 错误处理不中断整体流程
-
-### 可改进点
-
-## 1. ⚠️ 超时控制(重要)
-
-**问题**:如果某个任务处理时间过长(比如数据库慢查询),会阻塞整个扫描流程。
-
-**建议**:为每个任务添加超时控制
-
-```go
-// 为每个任务设置30秒超时
-taskCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
-defer cancel()
-
-err := l.svcCtx.AgentFreezeTaskModel.Trans(taskCtx, func(transCtx context.Context, session sqlx.Session) error {
- // ... 处理逻辑
-})
-```
-
-## 2. 📊 批次大小限制(可选)
-
-**问题**:如果任务量非常大(比如几千个),一次性查询所有会占用大量内存。
-
-**建议**:如果任务量超过一定数量,可以分批处理
-
-```go
-const maxBatchSize = 1000 // 每次最多查询1000个
-if len(freezeTasks) > maxBatchSize {
- logx.Warnf("任务数量过多(%d),本次只处理前%d个,剩余将在下次扫描处理", len(freezeTasks), maxBatchSize)
- freezeTasks = freezeTasks[:maxBatchSize]
-}
-```
-
-## 3. 🔄 错误分类处理
-
-**问题**:所有错误都统一处理,没有区分临时错误和永久错误。
-
-**建议**:区分错误类型,临时错误可以重试,永久错误跳过
-
-```go
-if err != nil {
- // 判断错误类型
- if isTemporaryError(err) {
- // 临时错误,记录但继续处理其他任务
- logx.Errorf("解冻任务临时失败,将在下次扫描重试: freezeTaskId=%d, err=%v", task.Id, err)
- } else {
- // 永久错误(如数据异常),记录详细日志
- logx.Errorf("解冻任务永久失败: freezeTaskId=%d, err=%v", task.Id, err)
- }
-}
-```
-
-## 4. ⏱️ 处理时间监控
-
-**问题**:缺少处理时间统计,无法评估性能。
-
-**建议**:记录处理时间,便于监控和优化
-
-```go
-startTime := time.Now()
-// ... 处理逻辑
-duration := time.Since(startTime)
-logx.Infof("解冻任务处理耗时: freezeTaskId=%d, duration=%v", task.Id, duration)
-```
-
-## 5. 🛡️ 优雅关闭支持
-
-**问题**:如果服务关闭,正在处理的任务可能被中断。
-
-**建议**:检查 context 是否被取消
-
-```go
-select {
-case <-ctx.Done():
- logx.Infof("扫描任务被取消,已处理: 成功=%d, 失败=%d", successCount, failCount)
- return ctx.Err()
-default:
- // 继续处理
-}
-```
-
-## 6. 📈 延迟统计
-
-**问题**:无法知道任务延迟了多久才被处理。
-
-**建议**:记录延迟时间,便于监控
-
-```go
-delay := time.Since(currentTask.UnfreezeTime)
-if delay > 1*time.Hour {
- logx.Warnf("解冻任务延迟处理: freezeTaskId=%d, 延迟=%v", task.Id, delay)
-}
-```
-
-## 7. 🔍 数据库连接池监控
-
-**问题**:并发处理时可能耗尽数据库连接池。
-
-**建议**:监控连接池使用情况,如果连接数不足,降低并发数
-
-```go
-// 可以根据数据库连接池情况动态调整并发数
-maxConcurrency := 2
-if dbConnPoolAvailable < 5 {
- maxConcurrency = 1 // 连接池紧张时降低并发
-}
-```
-
-## 8. 🎯 幂等性增强
-
-**问题**:虽然有乐观锁,但可以进一步优化。
-
-**建议**:添加更多的幂等性检查
-
-```go
-// 检查是否已经解冻过(通过 actual_unfreeze_time)
-if currentTask.ActualUnfreezeTime.Valid {
- logx.Infof("任务已解冻,跳过: freezeTaskId=%d", task.Id)
- return nil
-}
-```
-
-## 9. 📝 更详细的日志
-
-**问题**:日志信息可以更详细,便于排查问题。
-
-**建议**:添加更多上下文信息
-
-```go
-logx.Infof("解冻任务详情: freezeTaskId=%d, agentId=%d, amount=%.2f, orderPrice=%.2f, freezeTime=%v, unfreezeTime=%v, delay=%v",
- task.Id, currentTask.AgentId, currentTask.FreezeAmount, currentTask.OrderPrice,
- currentTask.FreezeTime, currentTask.UnfreezeTime, delay)
-```
-
-## 10. 🔧 配置化并发数
-
-**问题**:并发数硬编码为2,不够灵活。
-
-**建议**:从配置表读取并发数
-
-```go
-// 从配置表读取并发数,默认2
-maxConcurrency, err := l.svcCtx.AgentConfigModel.FindOneByConfigKey(ctx, "unfreeze_max_concurrency")
-if err != nil || maxConcurrency == nil {
- maxConcurrency = 2 // 默认值
-}
-```
-
-## 优先级建议
-
-### 🔴 高优先级(建议立即实现)
-1. **超时控制** - 防止任务卡死
-2. **错误分类处理** - 提高可靠性
-
-### 🟡 中优先级(建议后续优化)
-3. **处理时间监控** - 便于性能优化
-4. **延迟统计** - 便于监控
-5. **更详细的日志** - 便于排查问题
-
-### 🟢 低优先级(可选)
-6. **批次大小限制** - 如果任务量不大可以不做
-7. **优雅关闭** - 如果服务很少重启可以不做
-8. **配置化并发数** - 如果并发数不需要调整可以不做
-
-## 实施建议
-
-建议先实现**超时控制**和**错误分类处理**,这两个对可靠性影响最大。其他优化可以根据实际运行情况逐步添加。
-
diff --git a/解冻任务实现方案说明.md b/解冻任务实现方案说明.md
deleted file mode 100644
index 0beb0d3..0000000
--- a/解冻任务实现方案说明.md
+++ /dev/null
@@ -1,132 +0,0 @@
-# 解冻任务实现方案说明
-
-## 方案对比
-
-### 方案1:Asynq 延迟任务(已实现但未使用)
-**优点:**
-- ✅ 精确到秒级执行
-- ✅ 自动重试机制
-- ✅ 无需额外调度器
-
-**缺点:**
-- ❌ 依赖 Redis 持久化,Redis 数据丢失会导致任务丢失
-- ❌ 系统长时间停机可能导致延迟任务过期
-- ❌ 需要补偿机制
-
-### 方案2:定时任务扫描(✅ 已采用)
-**优点:**
-- ✅ **数据持久化在数据库,更可靠**(核心优势)
-- ✅ **系统停机后重启,定时任务会继续扫描并处理**(核心优势)
-- ✅ 可以批量处理,效率高
-- ✅ 已有定时任务基础设施
-- ✅ 不依赖 Redis 持久化
-
-**缺点:**
-- ⚠️ 执行时间不够精确(取决于扫描频率,如每5分钟扫描一次)
-- ⚠️ 需要处理并发扫描(已通过乐观锁解决)
-
-## 最终选择:定时任务扫描方案
-
-### 选择理由
-1. **金融场景,可靠性优先**:涉及资金解冻,必须保证任务不丢失
-2. **解冻时间允许延迟**:解冻时间可以有一定的延迟(比如几分钟内都可以接受)
-3. **已有基础设施**:项目中已有定时任务实现(`cleanQueryData.go`)
-4. **数据库表已设计好**:`status` 和 `unfreeze_time` 字段支持扫描查询
-
-## 实现细节
-
-### 1. 定时任务配置
-- **执行频率**:每2小时执行一次(`0 */2 * * *`)- 节省性能
-- **任务类型**:`MsgUnfreezeCommissionScan`
-- **处理器**:`UnfreezeCommissionScanHandler`
-- **批次大小**:每次最多处理2个任务,避免并发太多
-
-### 2. 扫描逻辑
-```go
-// 查询条件:
-// - status = 1(待解冻)
-// - unfreeze_time <= 当前时间
-// - del_state = 0(未删除)
-// - 按 unfreeze_time 升序排序
-```
-
-### 3. 并发安全
-- 使用**乐观锁**(`version` 字段)确保并发安全
-- 每个任务在事务中处理,确保原子性
-- 双重检查:查询后再次检查状态,防止并发处理
-
-### 4. 错误处理
-- 单个任务失败不影响其他任务
-- 记录详细的错误日志
-- 失败的任务会在下次扫描时重试
-
-### 5. 性能优化
-- 使用数据库索引优化查询(`idx_status` 和 `idx_unfreeze_time`)
-- **扫描频率**:每2小时扫描一次,减少数据库查询压力
-- **查询所有任务**:每次扫描找到所有需要解冻的任务(不限制数量)
-- **并发控制**:使用信号量(Semaphore)限制最多同时处理2个任务
-- **批量处理**:所有任务都会处理,但通过并发控制避免同时处理太多,节省性能
-
-## 代码文件
-
-### 新增文件
-- `app/main/api/internal/queue/unfreezeCommissionScan.go` - 定时扫描处理器
-
-### 修改文件
-- `app/main/api/internal/queue/routes.go` - 注册定时任务
-- `app/main/api/internal/queue/agentProcess.go` - 移除发送延迟任务的逻辑
-- `app/main/api/internal/types/taskname.go` - 添加任务类型常量
-
-### 保留文件(备用)
-- `app/main/api/internal/queue/unfreezeCommission.go` - 延迟任务处理器(保留作为备用)
-- `app/main/api/internal/service/asynqService.go` - `SendUnfreezeTask` 方法(保留作为备用)
-
-## 执行流程
-
-```
-定时任务启动(每2小时)
- ↓
-扫描数据库:status=1 AND unfreeze_time <= 当前时间
- ↓
-查询所有需要解冻的任务(不限制数量)
- ↓
-并发处理(使用信号量限制最多同时2个)
- ├─ 任务1(goroutine 1)
- ├─ 任务2(goroutine 2)
- ├─ 任务3(等待,直到前2个完成)
- ├─ 任务4(等待,直到前2个完成)
- └─ ...(以此类推,两个两个处理)
- ↓
-每个任务使用事务 + 乐观锁处理
- ↓
-更新任务状态:status = 2(已解冻)
- ↓
-更新钱包:FrozenBalance -= 冻结金额, Balance += 冻结金额
- ↓
-等待所有任务完成
- ↓
-记录日志
-```
-
-## 监控建议
-
-1. **监控扫描任务执行情况**
- - 检查定时任务是否正常执行
- - 监控每次扫描找到的任务数量
- - 监控成功/失败数量
-
-2. **监控解冻延迟**
- - 记录 `actual_unfreeze_time - unfreeze_time` 的差值
- - 如果延迟超过10分钟,需要检查定时任务是否正常
-
-3. **监控异常情况**
- - 冻结余额不足的情况(数据异常)
- - 任务状态异常的情况
-
-## 后续优化建议
-
-1. **可配置扫描频率**:将扫描频率(当前5分钟)配置到配置表
-2. **批次大小限制**:如果任务量很大,可以限制每次处理的数量
-3. **告警机制**:如果连续多次扫描都失败,发送告警
-4. **补偿机制**:提供手动触发扫描的接口,用于紧急情况
-
diff --git a/退款时代理处理逻辑分析.md b/退款时代理处理逻辑分析.md
deleted file mode 100644
index c1be031..0000000
--- a/退款时代理处理逻辑分析.md
+++ /dev/null
@@ -1,232 +0,0 @@
-# 退款时代理处理逻辑分析
-
-## 当前流程分析
-
-### 1. 订单支付成功后的流程
-
-```
-支付成功
- ↓
-支付回调(支付宝/微信)
- ↓
-更新订单状态为 "paid"
- ↓
-发送异步任务 SendQueryTask(order.Id)
- ↓
-PaySuccessNotifyUserHandler.ProcessTask
- ├─ 创建查询记录(query表,状态为 "pending")
- ├─ 生成授权书
- ├─ 调用API请求服务(第164行)← 可能失败
- ├─ 如果API成功:
- │ ├─ 更新查询状态为 "success"
- │ └─ 发送代理处理任务 SendAgentProcessTask(第192行)
- └─ 如果API失败:
- └─ handleError → 退款 → 更新订单状态为 "refunded"
-```
-
-### 2. 代理订单创建时机
-
-**在支付时创建**(`paymentlogic.go:316-327`):
-- 代理订单在用户支付时创建
-- `ProcessStatus = 0`(待处理)
-- 此时还没有发放佣金和返佣
-
-### 3. 代理处理任务执行时机
-
-**只在查询成功后发送**(`paySuccessNotify.go:192`):
-```go
-// 报告生成成功后,发送代理处理异步任务(不阻塞报告流程)
-if asyncErr := l.svcCtx.AsynqService.SendAgentProcessTask(order.Id); asyncErr != nil {
- // 代理处理任务发送失败,只记录日志,不影响报告流程
- logx.Errorf("发送代理处理任务失败,订单ID: %d, 错误: %v", order.Id, asyncErr)
-}
-```
-
-### 4. API调用失败时的处理(第164-167行)
-
-```go
-combinedResponse, err := l.svcCtx.ApiRequestService.ProcessRequests(decryptData, product.Id)
-if err != nil {
- return l.handleError(ctx, err, order, query)
-}
-```
-
-**handleError 的处理**(第206-257行):
-1. 删除Redis缓存
-2. 更新查询状态为 `failed`
-3. **退款**
-4. 更新订单状态为 `refunded`
-
-**关键点**:此时代理处理任务**还没有发送**(因为查询还没成功)
-
----
-
-## 问题分析
-
-### ✅ 情况1:API调用失败,退款(正常情况)
-
-**流程**:
-1. 支付成功,创建代理订单(`ProcessStatus = 0`)
-2. 调用API失败(第164-167行)
-3. 进入 `handleError`,退款
-4. 更新订单状态为 `refunded`
-5. **代理处理任务还没发送**(查询未成功)
-
-**代理状态**:
-- ✅ 代理订单 `ProcessStatus = 0`(未处理)
-- ✅ 代理**没有收到**佣金
-- ✅ 代理**没有收到**返佣
-- ✅ **处理正确**
-
-### ⚠️ 情况2:查询成功但订单被退款(边界情况)
-
-**可能的场景**:
-1. 支付成功,创建代理订单(`ProcessStatus = 0`)
-2. 调用API成功,查询状态更新为 `success`
-3. 发送代理处理任务(第192行)
-4. **但在代理处理任务执行前**,订单被退款(比如管理员手动退款)
-
-**代理处理任务的保护机制**(`agentProcess.go:43-46`):
-```go
-// 检查订单状态
-if order.Status != "paid" {
- logx.Infof("代理处理任务跳过,订单未支付: orderID=%d, status=%s", payload.OrderID, order.Status)
- return nil // 订单未支付,不处理,不重试
-}
-```
-
-**代理状态**:
-- ✅ 如果订单状态是 `refunded`,代理处理任务会跳过
-- ✅ 代理订单 `ProcessStatus` 仍然是 0
-- ✅ 代理**没有收到**佣金和返佣
-- ✅ **处理正确**
-
-### ⚠️ 情况3:代理已处理但订单被退款(需要处理)
-
-**可能的场景**:
-1. 支付成功,创建代理订单(`ProcessStatus = 0`)
-2. 调用API成功,查询状态更新为 `success`
-3. 发送代理处理任务
-4. 代理处理任务执行,发放佣金和返佣(`ProcessStatus = 1`)
-5. **之后**订单被退款(比如管理员手动退款)
-
-**当前问题**:
-- ❌ **代理已经收到佣金和返佣**
-- ❌ **没有撤销代理收益的逻辑**
-- ❌ **退款回调中也没有处理代理订单的逻辑**
-
----
-
-## 发现的问题
-
-### 问题1:退款回调中缺少代理订单处理
-
-**当前退款回调逻辑**(`wechatpayrefundcallbacklogic.go` 和 `alipayrefundcallbacklogic.go`):
-- ✅ 只更新订单状态和退款记录
-- ❌ **没有检查代理订单**
-- ❌ **没有撤销代理收益**
-
-### 问题2:管理员手动退款时缺少代理订单处理
-
-**当前管理员退款逻辑**(`adminrefundorderlogic.go`):
-- ✅ 创建退款记录,更新订单状态
-- ❌ **没有检查代理订单**
-- ❌ **没有撤销代理收益**
-
----
-
-## 建议的解决方案
-
-### 方案1:在退款回调中处理代理订单(推荐)
-
-在退款成功回调中,检查代理订单并撤销收益:
-
-```go
-// 在 handleQueryOrderRefund 中添加代理订单处理
-if status == refunddomestic.STATUS_SUCCESS {
- // 更新订单状态
- order.Status = orderStatus
- // ...
-
- // 检查并处理代理订单
- agentOrder, err := l.svcCtx.AgentOrderModel.FindOneByOrderId(ctx, order.Id)
- if err == nil && agentOrder.ProcessStatus == 1 {
- // 代理订单已处理,需要撤销收益
- err = l.svcCtx.AgentService.CancelAgentCommission(ctx, order.Id)
- if err != nil {
- logx.Errorf("撤销代理收益失败,订单ID: %d, 错误: %v", order.Id, err)
- // 不阻断退款流程,只记录日志
- }
- }
-}
-```
-
-### 方案2:在管理员退款时处理代理订单
-
-在 `AdminRefundOrderLogic` 中添加代理订单检查和处理。
-
-### 方案3:在代理处理任务中增加订单状态检查(已有保护)
-
-当前已有保护机制(`agentProcess.go:43-46`),如果订单状态不是 `paid`,会跳过处理。
-
----
-
-## 当前处理是否正确?
-
-### ✅ 对于 API 调用失败的情况
-
-**完全正确**:
-- 如果API调用失败(第164-167行),会进入退款流程
-- 此时代理处理任务还没发送(因为查询未成功)
-- 代理订单 `ProcessStatus = 0`,代理没有收到收益
-- **处理正确,无需修改**
-
-### ⚠️ 对于已处理代理订单的退款情况
-
-**存在问题**:
-- 如果代理订单已经处理(`ProcessStatus = 1`),代理已收到佣金和返佣
-- 此时订单退款,**没有撤销代理收益的逻辑**
-- 这会导致:
- - 用户收到退款
- - 但代理仍然保留佣金和返佣
- - **资金不一致**
-
----
-
-## 建议修改
-
-### 1. 在退款回调中添加代理订单检查
-
-### 2. 在管理员退款中添加代理订单检查
-
-### 3. 创建撤销代理收益的方法
-
-```go
-// CancelAgentCommission 撤销代理收益(订单退款时调用)
-func (s *AgentService) CancelAgentCommission(ctx context.Context, orderId int64) error {
- // 1. 查找代理订单
- // 2. 检查是否已处理
- // 3. 撤销佣金(从钱包扣除)
- // 4. 撤销返佣(从上级钱包扣除)
- // 5. 更新代理订单状态
- // 6. 创建撤销记录
-}
-```
-
----
-
-## 总结
-
-### 当前情况(API调用失败退款)
-
-✅ **处理正确**:
-- API调用失败时,代理处理任务还没发送
-- 代理订单未处理,代理没有收益
-- **无需修改**
-
-### 需要补充的场景
-
-⚠️ **需要处理**:
-- 代理订单已处理(`ProcessStatus = 1`)后订单退款的情况
-- 需要在退款回调和管理员退款中添加撤销代理收益的逻辑
-
diff --git a/邀请码使用历史功能说明.md b/邀请码使用历史功能说明.md
deleted file mode 100644
index ce2c789..0000000
--- a/邀请码使用历史功能说明.md
+++ /dev/null
@@ -1,107 +0,0 @@
-# 邀请码使用历史功能说明
-
-## 问题描述
-
-当前系统中,`agent_invite_code` 表只记录了最后一次使用情况(`used_user_id`, `used_agent_id`, `used_time`)。对于普通邀请码(可以无限使用),每次使用都会覆盖之前的记录,导致:
-
-1. **无法统计**:无法统计每个邀请码总共邀请了多少代理
-2. **无法查询**:无法查询某个代理是通过哪个邀请码成为代理的
-3. **历史丢失**:无法保留完整的使用历史记录
-
-## 解决方案
-
-### 1. 创建使用历史表
-
-创建 `agent_invite_code_usage` 表,记录每次邀请码使用的详细信息。
-
-### 2. 在 agent 表中添加字段
-
-在 `agent` 表中添加 `invite_code_id` 字段,便于直接查询代理是通过哪个邀请码成为的。
-
-## 实施步骤
-
-### 第一步:执行 SQL 迁移
-
-```bash
-# 执行 SQL 文件
-mysql -u root -p your_database < deploy/sql/add_invite_code_usage_table.sql
-```
-
-### 第二步:生成 Model
-
-```bash
-# 生成 AgentInviteCodeUsage Model
-goctl model mysql datasource -url="root:password@tcp(localhost:3306)/database" \
- -table="agent_invite_code_usage" \
- -dir="app/main/model" \
- -cache=true \
- --style=goZero \
- --home="./deploy/template"
-```
-
-### 第三步:更新 ServiceContext
-
-在 `app/main/api/internal/svc/servicecontext.go` 中添加:
-
-```go
-AgentInviteCodeUsageModel model.AgentInviteCodeUsageModel
-```
-
-并在 `NewServiceContext` 中初始化:
-
-```go
-AgentInviteCodeUsageModel: model.NewAgentInviteCodeUsageModel(db, cacheConf),
-```
-
-### 第四步:更新逻辑代码
-
-更新以下文件,在使用邀请码时记录使用历史:
-
-1. `app/main/api/internal/logic/agent/registerbyinvitecodelogic.go`
-2. `app/main/api/internal/logic/agent/applyforagentlogic.go`
-
-### 第五步:更新 Agent Model(可选)
-
-如果 agent 表添加了 `invite_code_id` 字段,需要重新生成 Agent Model 或手动更新。
-
-## 功能说明
-
-### 统计功能
-
-- **统计邀请码邀请数量**:通过 `agent_invite_code_usage` 表,可以统计每个邀请码邀请了多少代理
-- **查询代理来源**:可以通过 `agent.invite_code_id` 或 `agent_invite_code_usage.agent_id` 查询代理是通过哪个邀请码成为的
-
-### 数据完整性
-
-- **保留完整历史**:每次使用邀请码都会记录一条使用历史,不会丢失
-- **支持多次使用**:普通邀请码可以多次使用,每次使用都会记录
-
-## API 查询示例
-
-### 查询某个邀请码邀请了多少代理
-
-```sql
-SELECT COUNT(*) FROM agent_invite_code_usage
-WHERE invite_code_id = ? AND del_state = 0;
-```
-
-### 查询某个代理是通过哪个邀请码成为的
-
-```sql
--- 方式1:通过 agent 表(如果添加了 invite_code_id 字段)
-SELECT invite_code_id FROM agent WHERE id = ?;
-
--- 方式2:通过使用历史表
-SELECT invite_code_id, code FROM agent_invite_code_usage
-WHERE agent_id = ? AND del_state = 0
-ORDER BY used_time DESC LIMIT 1;
-```
-
-### 查询某个邀请码的所有使用记录
-
-```sql
-SELECT * FROM agent_invite_code_usage
-WHERE invite_code_id = ? AND del_state = 0
-ORDER BY used_time DESC;
-```
-
diff --git a/邀请链接和二维码生成逻辑说明.md b/邀请链接和二维码生成逻辑说明.md
deleted file mode 100644
index 1864d0f..0000000
--- a/邀请链接和二维码生成逻辑说明.md
+++ /dev/null
@@ -1,211 +0,0 @@
-# 邀请链接和二维码生成逻辑说明
-
-## 一、邀请链接生成逻辑
-
-### 1. API 端点
-- **路径**: `GET /agent/invite_link`
-- **处理器**: `GetInviteLinkHandler`
-- **逻辑**: `GetInviteLinkLogic`
-
-### 2. 生成流程
-
-#### 步骤 1: 验证代理身份
-```go
-// 获取当前用户ID
-userID := ctxdata.GetUidFromCtx(ctx)
-
-// 查询代理信息
-agent := AgentModel.FindOneByUserId(userID)
-```
-
-#### 步骤 2: 生成邀请码
-- 生成一个8位随机邀请码(使用 `tool.Krand(8, tool.KC_RAND_KIND_ALL)`)
-- 检查邀请码是否已存在(最多重试10次)
-- 创建邀请码记录:
- - `agent_id`: 当前代理ID
- - `target_level`: 1(普通代理)
- - `status`: 0(未使用)
- - `expire_time`: NULL(不过期)
- - `remark`: "邀请链接生成"
-
-#### 步骤 3: 构建邀请链接
-```go
-frontendDomain := "https://example.com" // TODO: 需要配置
-inviteLink := fmt.Sprintf("%s/register?invite_code=%s", frontendDomain, inviteCode)
-```
-
-#### 步骤 4: 生成二维码URL
-```go
-qrCodeUrl := fmt.Sprintf("%s/api/v1/image/qrcode?type=invitation&content=%s", frontendDomain, inviteLink)
-```
-
-### 3. 当前问题
-- ❌ `frontendDomain` 硬编码为 `"https://example.com"`,需要配置化
-- ❌ 二维码API (`/api/v1/image/qrcode`) 可能还未实现
-
-## 二、二维码生成逻辑
-
-### 1. 两种生成方式
-
-#### 方式 A: 前端生成(当前使用)
-- **位置**: `ycc-proxy-webview/src/components/QRcode.vue`
-- **库**: `qrcode` (npm)
-- **逻辑**:
- ```javascript
- // 如果提供了后端返回的 qrCodeUrl,优先使用
- if (mode === "invitation" && qrCodeUrl) {
- // 加载后端生成的二维码图片
- qrImg.src = qrCodeUrl;
- } else {
- // 前端生成二维码
- QRCode.toDataURL(url, { width: 150, margin: 0 });
- }
- ```
-
-#### 方式 B: 后端生成(已实现服务但可能缺少API端点)
-- **服务**: `ImageService.ProcessImageWithQRCode()`
-- **功能**:
- - 支持两种类型:`promote`(推广)和 `invitation`(邀请)
- - 加载背景图片(`static/images/yq_qrcode_1.png` 或 `tg_qrcode_1.png`)
- - 生成二维码并合成到背景图上
- - 返回 PNG 格式图片
-
-### 2. 二维码海报生成流程
-
-#### 推广海报(promote模式)
-- **背景图**: `static/images/tg_qrcode_1.png` - `tg_qrcode_8.jpg`(8张轮播图)
-- **二维码位置**: 左下角
- - 尺寸: 280px
- - 位置: X=192px, Y=距离底部190px
-- **用途**: 推广产品查询服务
-
-#### 邀请海报(invitation模式)
-- **背景图**: `static/images/yq_qrcode_1.png`
-- **二维码位置**: 中间偏上
- - 尺寸: 360px
- - 位置: 水平居中,垂直位置Y=555px
-- **用途**: 邀请好友成为下级代理
-
-### 3. 前端海报合成逻辑
-
-```javascript
-// 1. 加载海报背景图
-posterImg.src = posterImages[index];
-
-// 2. 生成或加载二维码
-if (mode === "invitation" && qrCodeUrl) {
- // 使用后端返回的二维码
- qrImg.src = qrCodeUrl;
-} else {
- // 前端生成二维码
- QRCode.toDataURL(url);
-}
-
-// 3. 在Canvas上绘制
-ctx.drawImage(posterImg, 0, 0); // 背景
-ctx.drawImage(qrCodeImg, x, y, size, size); // 二维码
-```
-
-## 三、数据流程
-
-### 邀请链接流程
-
-```
-用户点击"生成邀请链接"
- ↓
-前端调用 GET /agent/invite_link
- ↓
-后端逻辑 (GetInviteLinkLogic):
- 1. 验证代理身份
- 2. 生成新的邀请码(8位随机,不过期)
- 3. 保存到 agent_invite_code 表
- 4. 构建邀请链接: https://domain/register?invite_code=XXXXX
- 5. 构建二维码URL: https://domain/api/v1/image/qrcode?type=invitation&content=...
- ↓
-返回 { invite_link, qr_code_url }
- ↓
-前端显示链接和二维码
-```
-
-### 二维码使用流程
-
-#### 邀请模式 (invitation)
-```
-后端返回 qrCodeUrl
- ↓
-前端 QRcode 组件加载二维码图片
- ↓
-如果加载成功: 直接使用后端生成的二维码海报
-如果加载失败: 降级到前端生成二维码
- ↓
-合成到邀请海报背景图 (yq_qrcode_1.png)
- ↓
-用户可以保存或分享海报
-```
-
-#### 推广模式 (promote)
-```
-前端生成二维码 (使用 qrcode 库)
- ↓
-合成到推广海报背景图 (tg_qrcode_1.png - 8.png)
- ↓
-用户可以在多张海报中切换
- ↓
-用户可以保存或分享海报
-```
-
-## 四、相关数据库表
-
-### agent_invite_code
-- 存储邀请码信息
-- 每个邀请链接对应一个邀请码记录
-- 备注:`"邀请链接生成"` 表示这是通过链接生成的
-
-### agent_invite_code_usage
-- 存储邀请码使用历史
-- 记录每个代理是通过哪个邀请码成为的
-- 支持统计和查询
-
-## 五、待完善的问题
-
-### 1. 前端域名配置
-- ❌ 当前硬编码为 `"https://example.com"`
-- ✅ 应该从配置文件读取
-
-### 2. 二维码API实现
-- ❓ `/api/v1/image/qrcode` 端点是否存在?
-- ✅ `ImageService.ProcessImageWithQRCode()` 已实现
-- ❓ 是否需要创建对应的 Handler 和路由?
-
-### 3. 邀请码复用
-- 当前每次调用都生成新的邀请码
-- 是否应该复用已有的邀请码?
-
-## 六、链接格式对比
-
-### 邀请链接(成为代理)
-```
-格式: https://domain/register?invite_code=XXXXX
-参数: invite_code (8位邀请码)
-用途: 用户通过此链接注册成为代理
-```
-
-### 推广链接(推广产品)
-```
-格式: https://domain/agent/promotionInquire/{linkIdentifier}
-参数: linkIdentifier (加密的JSON字符串,包含agent_id, product_id, set_price)
-用途: 用户通过此链接查询产品
-```
-
-## 七、文件位置
-
-### 后端
-- **邀请链接逻辑**: `app/main/api/internal/logic/agent/getinvitelinklogic.go`
-- **二维码服务**: `app/main/api/internal/service/imageService.go`
-- **推广链接逻辑**: `app/main/api/internal/logic/agent/generatinglinklogic.go`
-
-### 前端
-- **二维码组件**: `ycc-proxy-webview/src/components/QRcode.vue`
-- **邀请页面**: `ycc-proxy-webview/src/views/Invitation.vue`
-- **推广查询页**: `ycc-proxy-webview/src/views/PromotionInquire.vue`
-