This commit is contained in:
235
docs/COMMISSION_DOCS_INDEX.md
Normal file
235
docs/COMMISSION_DOCS_INDEX.md
Normal file
@@ -0,0 +1,235 @@
|
||||
# 差价佣金文档索引
|
||||
|
||||
本索引汇总了所有关于差价佣金计算和分配的文档,帮助快速定位所需信息。
|
||||
|
||||
## 📚 文档列表
|
||||
|
||||
### 1. **commission-search-result.md** (237行)
|
||||
**内容**: 差价佣金计算和分配逻辑的完整搜索结果
|
||||
|
||||
**包含内容**:
|
||||
- ✅ 差价佣金的计算方式(公式、流程、示例)
|
||||
- ✅ 差价佣金的发放条件(触发条件、发放流程、入账步骤)
|
||||
- ✅ 差价佣金与订单状态的关系(状态定义、转换关系、关键字段)
|
||||
- ✅ 关键代码位置总结(表格形式)
|
||||
- ✅ 相关常量定义
|
||||
|
||||
**适合场景**: 需要快速了解差价佣金的完整逻辑
|
||||
|
||||
**快速导航**:
|
||||
- 差价佣金计算公式 → 第一部分 1.1
|
||||
- 计算流程详解 → 第一部分 1.2
|
||||
- 触发条件 → 第二部分 2.1
|
||||
- 发放流程 → 第二部分 2.2
|
||||
- 佣金入账 → 第二部分 2.3
|
||||
|
||||
---
|
||||
|
||||
### 2. **commission-flow-diagram.md** (319行)
|
||||
**内容**: 差价佣金计算的可视化流程图
|
||||
|
||||
**包含内容**:
|
||||
- ✅ 订单支付到佣金入账的完整流程(ASCII流程图)
|
||||
- ✅ 成本价差佣金计算详细流程(分步骤流程图)
|
||||
- ✅ 佣金入账流程(5个步骤)
|
||||
- ✅ 订单状态转换图
|
||||
- ✅ 佣金记录状态转换图
|
||||
- ✅ 代理链佣金分配示例(实际案例)
|
||||
|
||||
**适合场景**: 需要理解整个流程的全貌,或向他人解释流程
|
||||
|
||||
**快速导航**:
|
||||
- 完整流程 → 第一部分
|
||||
- 计算细节 → 第二部分
|
||||
- 入账步骤 → 第三部分
|
||||
- 状态转换 → 第四、五部分
|
||||
- 实际案例 → 第六部分
|
||||
|
||||
---
|
||||
|
||||
### 3. **commission-quick-reference.md** (283行)
|
||||
**内容**: 差价佣金的快速参考指南
|
||||
|
||||
**包含内容**:
|
||||
- ✅ 关键文件位置表(8个关键功能)
|
||||
- ✅ 关键常量速查(任务类型、佣金来源、状态常量)
|
||||
- ✅ 关键数据库表(5个核心表)
|
||||
- ✅ 常见问题快速查询(10个Q&A)
|
||||
- ✅ 调试技巧(6个SQL查询示例)
|
||||
- ✅ 性能优化建议(3个优化方向)
|
||||
- ✅ 常见错误排查(3个常见错误)
|
||||
|
||||
**适合场景**: 开发过程中快速查询信息,或调试问题
|
||||
|
||||
**快速导航**:
|
||||
- 找代码位置 → 快速查询表 1
|
||||
- 找常量值 → 快速查询表 2
|
||||
- 找数据库表 → 快速查询表 3
|
||||
- 常见问题 → 常见问题快速查询
|
||||
- 调试SQL → 调试技巧
|
||||
- 性能问题 → 性能优化建议
|
||||
- 错误处理 → 常见错误排查
|
||||
|
||||
---
|
||||
|
||||
### 4. **commission-package-model.md** (351行)
|
||||
**内容**: 套餐与佣金业务模型(原有文档)
|
||||
|
||||
**包含内容**:
|
||||
- ✅ 核心概念(两种佣金类型、实体关系)
|
||||
- ✅ 套餐模型详解
|
||||
- ✅ 差价佣金规则(计算规则、关键区分)
|
||||
- ✅ 一次性佣金规则(触发条件、链式分配、流程)
|
||||
- ✅ 梯度佣金规则
|
||||
- ✅ 约束规则(6个约束)
|
||||
- ✅ 操作流程(理想的线性流程)
|
||||
|
||||
**适合场景**: 需要理解完整的业务规则和约束
|
||||
|
||||
**快速导航**:
|
||||
- 两种佣金类型 → 第一部分 1.1
|
||||
- 差价佣金规则 → 第三部分
|
||||
- 一次性佣金规则 → 第四部分
|
||||
- 约束规则 → 第六部分
|
||||
|
||||
---
|
||||
|
||||
## 🎯 使用指南
|
||||
|
||||
### 场景1: 我是新开发者,想快速了解差价佣金
|
||||
|
||||
**推荐阅读顺序**:
|
||||
1. 先读 `commission-flow-diagram.md` 的第一部分(了解整体流程)
|
||||
2. 再读 `commission-search-result.md` 的第一部分(理解计算方式)
|
||||
3. 最后读 `commission-quick-reference.md` 的常见问题(掌握细节)
|
||||
|
||||
**预计时间**: 30分钟
|
||||
|
||||
---
|
||||
|
||||
### 场景2: 我需要修改佣金计算逻辑
|
||||
|
||||
**推荐阅读顺序**:
|
||||
1. 先读 `commission-search-result.md` 的第一部分(理解当前逻辑)
|
||||
2. 查看 `commission-quick-reference.md` 的关键文件位置(定位代码)
|
||||
3. 查看 `commission-flow-diagram.md` 的第二部分(理解计算细节)
|
||||
4. 打开代码文件进行修改
|
||||
|
||||
**关键文件**: `internal/service/commission_calculation/service.go:121-225`
|
||||
|
||||
---
|
||||
|
||||
### 场景3: 我需要调试佣金计算问题
|
||||
|
||||
**推荐阅读顺序**:
|
||||
1. 先读 `commission-quick-reference.md` 的调试技巧(获取SQL查询)
|
||||
2. 执行SQL查询获取数据
|
||||
3. 对比 `commission-flow-diagram.md` 的第六部分(验证计算结果)
|
||||
4. 查看 `commission-quick-reference.md` 的常见错误排查(定位问题)
|
||||
|
||||
**关键SQL**: 见 `commission-quick-reference.md` 的调试技巧部分
|
||||
|
||||
---
|
||||
|
||||
### 场景4: 我需要向产品经理解释佣金逻辑
|
||||
|
||||
**推荐阅读顺序**:
|
||||
1. 先读 `commission-package-model.md` 的第三部分(业务规则)
|
||||
2. 使用 `commission-flow-diagram.md` 的第六部分(实际案例)
|
||||
3. 使用 `commission-flow-diagram.md` 的流程图(可视化展示)
|
||||
|
||||
**关键资源**: `commission-flow-diagram.md` 的第六部分(代理链佣金分配示例)
|
||||
|
||||
---
|
||||
|
||||
### 场景5: 我需要验证佣金计算的正确性
|
||||
|
||||
**推荐阅读顺序**:
|
||||
1. 先读 `commission-quick-reference.md` 的Q10(验证方法)
|
||||
2. 执行SQL查询获取数据
|
||||
3. 按照验证步骤进行检查
|
||||
4. 如有问题,查看常见错误排查
|
||||
|
||||
**关键内容**: `commission-quick-reference.md` 的Q10
|
||||
|
||||
---
|
||||
|
||||
## 📊 文档对比表
|
||||
|
||||
| 文档 | 长度 | 类型 | 适合场景 | 重点 |
|
||||
|------|------|------|---------|------|
|
||||
| commission-search-result.md | 237行 | 参考 | 快速了解逻辑 | 代码位置、计算方式 |
|
||||
| commission-flow-diagram.md | 319行 | 可视化 | 理解流程 | 流程图、状态转换 |
|
||||
| commission-quick-reference.md | 283行 | 工具 | 开发调试 | 常见问题、SQL查询 |
|
||||
| commission-package-model.md | 351行 | 规范 | 业务理解 | 业务规则、约束 |
|
||||
|
||||
---
|
||||
|
||||
## 🔗 关键代码位置速查
|
||||
|
||||
| 功能 | 文件 | 行号 | 文档位置 |
|
||||
|------|------|------|---------|
|
||||
| 差价佣金计算 | `internal/service/commission_calculation/service.go` | 121-225 | search-result 1.2 |
|
||||
| 佣金入账 | `internal/service/commission_calculation/service.go` | 641-687 | search-result 2.3 |
|
||||
| 佣金计算触发 | `internal/service/order/service.go` | 2132-2150 | search-result 2.1 |
|
||||
| 订单支付完成 | `internal/service/order/service.go` | 1431-1623 | search-result 2.1 |
|
||||
| 异步任务处理 | `internal/task/commission_calculation.go` | 40-62 | search-result 2.2 |
|
||||
|
||||
---
|
||||
|
||||
## 💡 常见问题速查
|
||||
|
||||
| 问题 | 答案位置 |
|
||||
|------|---------|
|
||||
| 差价佣金什么时候计算? | quick-reference Q1 |
|
||||
| 差价佣金的计算公式是什么? | quick-reference Q2 / search-result 1.1 |
|
||||
| 如何查询某个订单的佣金记录? | quick-reference Q3 |
|
||||
| 佣金什么时候入账? | quick-reference Q4 |
|
||||
| 链路断裂是什么意思? | quick-reference Q5 |
|
||||
| 如何追踪佣金计算过程? | quick-reference Q6 |
|
||||
| 代理钱包余额如何更新? | quick-reference Q7 |
|
||||
| 如何处理佣金计算失败? | quick-reference Q8 |
|
||||
| 一个订单产生多少条佣金记录? | quick-reference Q9 |
|
||||
| 如何验证佣金计算的正确性? | quick-reference Q10 |
|
||||
|
||||
---
|
||||
|
||||
## 📝 文档维护
|
||||
|
||||
### 最后更新时间
|
||||
- commission-search-result.md: 2025-04-11
|
||||
- commission-flow-diagram.md: 2025-04-11
|
||||
- commission-quick-reference.md: 2025-04-11
|
||||
- commission-package-model.md: 2025-02-03
|
||||
|
||||
### 更新日志
|
||||
- 2025-04-11: 新增三份文档(search-result, flow-diagram, quick-reference)
|
||||
- 2025-02-03: 原有 commission-package-model.md
|
||||
|
||||
### 如何贡献
|
||||
如果发现文档有误或需要补充,请:
|
||||
1. 提交 Issue 描述问题
|
||||
2. 或直接提交 PR 修改文档
|
||||
3. 确保修改后的文档保持一致性
|
||||
|
||||
---
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
**第一次接触差价佣金?**
|
||||
|
||||
1. 花5分钟看 `commission-flow-diagram.md` 的第一部分
|
||||
2. 花10分钟看 `commission-search-result.md` 的第一部分
|
||||
3. 需要时查阅 `commission-quick-reference.md`
|
||||
|
||||
**预计总时间**: 15分钟快速入门
|
||||
|
||||
---
|
||||
|
||||
## 📞 获取帮助
|
||||
|
||||
- **代码问题**: 查看 `commission-quick-reference.md` 的常见错误排查
|
||||
- **业务问题**: 查看 `commission-package-model.md` 的约束规则
|
||||
- **流程问题**: 查看 `commission-flow-diagram.md` 的流程图
|
||||
- **其他问题**: 查看 `commission-quick-reference.md` 的常见问题
|
||||
|
||||
250
docs/ORDER_STATUS_INDEX.md
Normal file
250
docs/ORDER_STATUS_INDEX.md
Normal file
@@ -0,0 +1,250 @@
|
||||
# 订单状态与佣金系统文档索引
|
||||
|
||||
本索引汇总了关于订单状态、冻结状态和差价佣金的所有文档和代码位置。
|
||||
|
||||
## 📚 文档列表
|
||||
|
||||
### 1. [order_status_commission_analysis.md](order_status_commission_analysis.md)
|
||||
**完整的系统分析报告** - 适合深入理解系统设计
|
||||
|
||||
**包含内容**:
|
||||
- ✅ 订单状态定义(支付状态、佣金状态)
|
||||
- ✅ 订单状态流转逻辑(3种支付方式)
|
||||
- ✅ 订单超时自动取消机制
|
||||
- ✅ 佣金系统与订单的关系
|
||||
- ✅ 钱包冻结与提现流程(3阶段)
|
||||
- ✅ 关键数据模型说明
|
||||
- ✅ 完整流程图
|
||||
- ✅ 业务规则总结
|
||||
- ✅ 文件索引表
|
||||
|
||||
**适用场景**:
|
||||
- 需要全面理解系统设计
|
||||
- 进行系统架构评审
|
||||
- 编写相关功能文档
|
||||
|
||||
---
|
||||
|
||||
### 2. [order_status_quick_reference.md](order_status_quick_reference.md)
|
||||
**快速参考指南** - 适合日常开发查询
|
||||
|
||||
**包含内容**:
|
||||
- ✅ 订单支付状态速查表
|
||||
- ✅ 订单佣金状态速查表
|
||||
- ✅ 订单创建流程速查
|
||||
- ✅ 钱包冻结状态速查表
|
||||
- ✅ 提现流程速查(3阶段)
|
||||
- ✅ 佣金类型速查
|
||||
- ✅ 关键代码位置速查
|
||||
- ✅ 常用常量速查
|
||||
- ✅ 常见问题解答
|
||||
- ✅ 数据库查询示例
|
||||
- ✅ 业务规则速查
|
||||
|
||||
**适用场景**:
|
||||
- 快速查询状态值
|
||||
- 查找代码位置
|
||||
- 解答常见问题
|
||||
- 编写 SQL 查询
|
||||
|
||||
---
|
||||
|
||||
### 3. [order_status_search_summary.md](order_status_search_summary.md)
|
||||
**搜索结果总结** - 适合了解核心发现
|
||||
|
||||
**包含内容**:
|
||||
- ✅ 搜索范围说明
|
||||
- ✅ 5个核心发现
|
||||
- ✅ 关键代码位置索引
|
||||
- ✅ 重要业务规则
|
||||
- ✅ 数据流向图
|
||||
- ✅ 生成的文档说明
|
||||
- ✅ 关键发现总结
|
||||
- ✅ 注意事项
|
||||
- ✅ 最佳实践
|
||||
|
||||
**适用场景**:
|
||||
- 快速了解系统概况
|
||||
- 查找关键代码位置
|
||||
- 了解业务规则
|
||||
- 学习最佳实践
|
||||
|
||||
---
|
||||
|
||||
## 🔍 核心概念速查
|
||||
|
||||
### 订单状态
|
||||
| 状态 | 值 | 说明 |
|
||||
|------|-----|------|
|
||||
| 待支付 | 1 | 微信/支付宝支付时的初始状态 |
|
||||
| 已支付 | 2 | 线下/钱包支付或支付回调后的状态 |
|
||||
| 已取消 | 3 | 待支付订单超时30分钟后的状态 |
|
||||
| 已退款 | 4 | 退款操作后的状态 |
|
||||
|
||||
### 佣金状态
|
||||
| 状态 | 值 | 说明 |
|
||||
|------|-----|------|
|
||||
| 待计算 | 1 | 订单创建时的初始状态 |
|
||||
| 已计算 | 2 | 佣金计算完成后的状态 |
|
||||
|
||||
### 钱包冻结
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| balance | 总余额 |
|
||||
| frozen_balance | 冻结余额(用于提现) |
|
||||
| 可用余额 | balance - frozen_balance |
|
||||
|
||||
---
|
||||
|
||||
## 📍 关键代码位置
|
||||
|
||||
### 订单相关
|
||||
```
|
||||
internal/model/order.go
|
||||
├─ 第 98-104 行:支付状态常量
|
||||
├─ 第 106-110 行:佣金状态常量
|
||||
└─ 第 9-138 行:订单模型定义
|
||||
|
||||
internal/service/order/service.go
|
||||
├─ 第 114-354 行:CreateLegacy(已废弃)
|
||||
├─ 第 359-674 行:CreateAdminOrder(后台订单创建)
|
||||
├─ 第 679-928 行:CreateH5Order(H5订单创建)
|
||||
└─ 第 1314-1385 行:订单超时自动取消
|
||||
```
|
||||
|
||||
### 佣金相关
|
||||
```
|
||||
internal/service/commission_calculation/service.go
|
||||
├─ 第 74-119 行:佣金计算主逻辑
|
||||
└─ 第 121-230 行:成本价差佣金计算
|
||||
|
||||
internal/model/commission.go
|
||||
└─ 第 9-46 行:佣金记录模型
|
||||
```
|
||||
|
||||
### 提现相关
|
||||
```
|
||||
internal/service/shop_commission/service.go
|
||||
└─ 第 517-649 行:代理发起提现申请
|
||||
|
||||
internal/service/commission_withdrawal/service.go
|
||||
├─ 第 145-256 行:平台审核通过
|
||||
└─ 第 258-310 行:平台审核拒绝
|
||||
```
|
||||
|
||||
### 钱包相关
|
||||
```
|
||||
internal/model/agent_wallet.go
|
||||
├─ 第 9-30 行:代理钱包模型
|
||||
└─ 第 37-94 行:钱包交易记录模型
|
||||
|
||||
pkg/constants/wallet.go
|
||||
├─ 第 17-22 行:钱包状态常量
|
||||
├─ 第 83-91 行:交易状态常量
|
||||
└─ 第 1-234 行:所有钱包相关常量
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 常见任务指南
|
||||
|
||||
### 任务 1:查询订单状态
|
||||
**文档**:order_status_quick_reference.md - 订单支付状态速查表
|
||||
**代码**:internal/model/order.go:98-104
|
||||
|
||||
### 任务 2:理解佣金计算
|
||||
**文档**:order_status_commission_analysis.md - 第三章
|
||||
**代码**:internal/service/commission_calculation/service.go:74-230
|
||||
|
||||
### 任务 3:实现提现功能
|
||||
**文档**:order_status_commission_analysis.md - 第四章
|
||||
**代码**:
|
||||
- 发起申请:internal/service/shop_commission/service.go:517-649
|
||||
- 审核通过:internal/service/commission_withdrawal/service.go:145-256
|
||||
- 审核拒绝:internal/service/commission_withdrawal/service.go:258-310
|
||||
|
||||
### 任务 4:查询可提现金额
|
||||
**文档**:order_status_quick_reference.md - 数据库查询速查
|
||||
**SQL**:
|
||||
```sql
|
||||
SELECT balance - frozen_balance as available_balance
|
||||
FROM tb_agent_wallet
|
||||
WHERE shop_id = ? AND wallet_type = 'commission';
|
||||
```
|
||||
|
||||
### 任务 5:处理订单超时
|
||||
**文档**:order_status_commission_analysis.md - 第二章
|
||||
**代码**:internal/service/order/service.go:1314-1385
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 重要注意事项
|
||||
|
||||
1. **订单没有冻结状态**
|
||||
- 冻结状态存在于代理钱包,不在订单
|
||||
- 订单只有支付状态和佣金状态
|
||||
|
||||
2. **订单超时不计算佣金**
|
||||
- 取消的订单佣金状态保持为"待计算"
|
||||
- 不会入队佣金计算任务
|
||||
|
||||
3. **代购订单不计算一次性佣金**
|
||||
- 一次性佣金仅对非代购订单触发
|
||||
- 仅在首次购买时触发
|
||||
|
||||
4. **提现流程中的冻结机制**
|
||||
- 提现申请时冻结余额
|
||||
- 审核通过时从冻结余额直接扣款
|
||||
- 审核拒绝时解冻余额
|
||||
|
||||
---
|
||||
|
||||
## 📊 文档统计
|
||||
|
||||
| 文档 | 大小 | 章节数 | 代码位置数 |
|
||||
|------|------|--------|-----------|
|
||||
| order_status_commission_analysis.md | 14K | 9 | 20+ |
|
||||
| order_status_quick_reference.md | 6.0K | 12 | 15+ |
|
||||
| order_status_search_summary.md | 6.1K | 8 | 10+ |
|
||||
|
||||
**总计**:26.1K,29个章节,45+个代码位置
|
||||
|
||||
---
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 第一次接触系统?
|
||||
1. 阅读 order_status_search_summary.md(5分钟)
|
||||
2. 查看 order_status_commission_analysis.md 的流程图(10分钟)
|
||||
3. 收藏 order_status_quick_reference.md(备用)
|
||||
|
||||
### 需要快速查询?
|
||||
1. 打开 order_status_quick_reference.md
|
||||
2. 使用速查表或常见问题部分
|
||||
3. 参考代码位置索引
|
||||
|
||||
### 需要深入理解?
|
||||
1. 阅读 order_status_commission_analysis.md 的完整内容
|
||||
2. 对照代码位置查看源代码
|
||||
3. 参考业务规则总结
|
||||
|
||||
---
|
||||
|
||||
## 📞 相关资源
|
||||
|
||||
### 相关文档
|
||||
- [套餐与佣金业务模型](../commission-package-model.md)
|
||||
- [分佣逻辑验证指引](../优化说明/分佣逻辑正确与否验证.md)
|
||||
|
||||
### 相关代码
|
||||
- 订单服务:`internal/service/order/`
|
||||
- 佣金服务:`internal/service/commission_calculation/`
|
||||
- 提现服务:`internal/service/commission_withdrawal/`
|
||||
- 钱包服务:`internal/service/shop_commission/`
|
||||
|
||||
---
|
||||
|
||||
**最后更新**:2024年4月11日
|
||||
**文档版本**:1.0
|
||||
**维护者**:开发团队
|
||||
|
||||
244
docs/PACKAGE_DOCS_INDEX.md
Normal file
244
docs/PACKAGE_DOCS_INDEX.md
Normal file
@@ -0,0 +1,244 @@
|
||||
# 套餐接口文档索引
|
||||
|
||||
本目录包含套餐(Package/PackageSeries)相关接口的完整代码结构分析文档。
|
||||
|
||||
## 📚 文档清单
|
||||
|
||||
### 1. 📋 [完整结构分析](./package_structure_summary.md)
|
||||
**用途**:全面了解套餐接口的代码结构
|
||||
|
||||
**包含内容**:
|
||||
- Model 层文件位置和结构
|
||||
- DTO 层请求/响应定义
|
||||
- Handler 层方法列表
|
||||
- Service 层业务逻辑
|
||||
- Store 层数据访问
|
||||
- API 路由注册
|
||||
- 关键字段详解(duration_days)
|
||||
- 业务逻辑关键点
|
||||
- 数据库表结构
|
||||
- 完整请求/响应示例
|
||||
|
||||
**适合场景**:
|
||||
- 需要全面了解套餐接口架构
|
||||
- 查看具体的代码位置和行号
|
||||
- 理解业务逻辑和校验规则
|
||||
|
||||
---
|
||||
|
||||
### 2. 🎯 [快速导航指南](./package_quick_navigation.md)
|
||||
**用途**:快速定位需要的代码
|
||||
|
||||
**包含内容**:
|
||||
- 按功能快速查找表
|
||||
- 按字段快速查找表
|
||||
- 按 API 端点快速查找表
|
||||
- 关键代码片段
|
||||
- 数据流向图
|
||||
- 常见操作指南
|
||||
- 相关模块链接
|
||||
|
||||
**适合场景**:
|
||||
- 需要快速找到某个字段或方法
|
||||
- 想了解数据流向
|
||||
- 需要修改或扩展功能
|
||||
|
||||
---
|
||||
|
||||
### 3. 🏗️ [架构详细图](./package_architecture_diagram.md)
|
||||
**用途**:可视化理解系统架构
|
||||
|
||||
**包含内容**:
|
||||
- 整体架构流程图
|
||||
- 分层详细结构(Handler/Service/Store)
|
||||
- 数据模型关系图
|
||||
- 请求流程示例
|
||||
- 代理用户查询流程
|
||||
- 字段验证流程
|
||||
|
||||
**适合场景**:
|
||||
- 需要理解系统整体架构
|
||||
- 想了解数据流向和处理过程
|
||||
- 需要设计新功能时参考
|
||||
|
||||
---
|
||||
|
||||
## 🔍 快速查找
|
||||
|
||||
### 按需求查找
|
||||
|
||||
| 需求 | 文档 | 位置 |
|
||||
|------|------|------|
|
||||
| 查看套餐数据模型 | 完整结构分析 | 第 1 节 |
|
||||
| 查看 API 请求/响应结构 | 完整结构分析 | 第 2 节 |
|
||||
| 查看 HTTP 处理器 | 完整结构分析 | 第 3 节 |
|
||||
| 查看业务逻辑 | 完整结构分析 | 第 4 节 |
|
||||
| 查看数据访问 | 完整结构分析 | 第 5 节 |
|
||||
| 快速定位代码 | 快速导航指南 | 第 1-2 节 |
|
||||
| 理解数据流向 | 快速导航指南 | 第 3 节 |
|
||||
| 了解系统架构 | 架构详细图 | 第 1-2 节 |
|
||||
| 查看请求流程 | 架构详细图 | 第 4-5 节 |
|
||||
|
||||
### 按文件查找
|
||||
|
||||
| 文件 | 说明 | 文档 |
|
||||
|------|------|------|
|
||||
| `internal/model/package.go` | 数据模型 | 完整结构分析 1.1 |
|
||||
| `internal/model/dto/package_dto.go` | DTO 定义 | 完整结构分析 1.2 |
|
||||
| `internal/handler/admin/package.go` | HTTP 处理 | 完整结构分析 1.3 |
|
||||
| `internal/service/package/service.go` | 业务逻辑 | 完整结构分析 1.4 |
|
||||
| `internal/store/postgres/package_store.go` | 数据访问 | 完整结构分析 1.5 |
|
||||
| `internal/routes/package.go` | 路由注册 | 完整结构分析 2.2 |
|
||||
| `internal/routes/admin.go` | 路由入口 | 完整结构分析 2.1 |
|
||||
|
||||
### 按字段查找
|
||||
|
||||
| 字段 | Model 位置 | DTO 位置 | 文档 |
|
||||
|------|-----------|---------|------|
|
||||
| `duration_days` | package.go:46 | package_dto.go:96 | 完整结构分析 3.1 |
|
||||
| `calendar_type` | package.go:45 | package_dto.go:95 | 完整结构分析 3.1 |
|
||||
| `duration_months` | package.go:37 | package_dto.go:79 | 完整结构分析 3.1 |
|
||||
| `real_data_mb` | package.go:38 | package_dto.go:80 | 完整结构分析 3.1 |
|
||||
| `virtual_data_mb` | package.go:39 | package_dto.go:81 | 完整结构分析 3.1 |
|
||||
|
||||
---
|
||||
|
||||
## 🔑 关键信息速查
|
||||
|
||||
### duration_days 字段
|
||||
|
||||
**定义位置**:
|
||||
- Model: `internal/model/package.go:46`
|
||||
- DTO: `internal/model/dto/package_dto.go:96`
|
||||
|
||||
**用途**:
|
||||
- 当 `calendar_type = "by_day"` 时必填
|
||||
- 用于按天计算套餐有效期
|
||||
|
||||
**验证规则**:
|
||||
- 范围:1-3650 天
|
||||
- 当 `calendar_type = "by_day"` 时必须提供
|
||||
|
||||
**相关校验**:
|
||||
- Service 层校验:`internal/service/package/service.go:65-80`
|
||||
|
||||
---
|
||||
|
||||
### API 端点总览
|
||||
|
||||
| 方法 | 路径 | 处理器 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| GET | `/packages` | `List()` | 套餐列表 |
|
||||
| POST | `/packages` | `Create()` | 创建套餐 |
|
||||
| GET | `/packages/:id` | `Get()` | 获取详情 |
|
||||
| PUT | `/packages/:id` | `Update()` | 更新套餐 |
|
||||
| DELETE | `/packages/:id` | `Delete()` | 删除套餐 |
|
||||
| PATCH | `/packages/:id/status` | `UpdateStatus()` | 更新状态 |
|
||||
| PATCH | `/packages/:id/shelf` | `UpdateShelfStatus()` | 更新上架状态 |
|
||||
| PATCH | `/packages/:id/retail-price` | `UpdateRetailPrice()` | 更新零售价 |
|
||||
|
||||
**路由注册**:
|
||||
- 文件:`internal/routes/package.go:11-78`
|
||||
- 入口:`internal/routes/admin.go:71-78`
|
||||
|
||||
---
|
||||
|
||||
### 关键业务逻辑
|
||||
|
||||
#### 1. 虚流量配置校验
|
||||
**位置**:`internal/service/package/service.go:51-63`
|
||||
|
||||
规则:
|
||||
- 启用虚流量时,虚流量额度必须 > 0
|
||||
- 虚流量额度不能大于真流量额度
|
||||
|
||||
#### 2. 套餐周期类型校验
|
||||
**位置**:`internal/service/package/service.go:65-80`
|
||||
|
||||
规则:
|
||||
- `natural_month`:必须提供 `duration_months`
|
||||
- `by_day`:必须提供 `duration_days`
|
||||
|
||||
#### 3. 代理用户套餐过滤
|
||||
**位置**:`internal/store/postgres/package_store.go:57-66`
|
||||
|
||||
规则:
|
||||
- 代理用户只能看到已分配的套餐
|
||||
- 通过 INNER JOIN `tb_shop_package_allocation` 实现
|
||||
|
||||
---
|
||||
|
||||
## 📊 数据库表
|
||||
|
||||
### 主要表
|
||||
|
||||
| 表名 | 说明 | 文档 |
|
||||
|------|------|------|
|
||||
| `tb_package` | 套餐表 | 完整结构分析 6.1 |
|
||||
| `tb_package_series` | 套餐系列表 | 完整结构分析 6.2 |
|
||||
| `tb_package_usage` | 套餐使用表 | 架构详细图 3 |
|
||||
| `tb_package_usage_daily_record` | 日记录表 | 架构详细图 3 |
|
||||
| `tb_shop_package_allocation` | 套餐分配表 | 架构详细图 3 |
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 常见操作
|
||||
|
||||
### 添加新的套餐字段
|
||||
|
||||
1. **Model 层**:修改 `internal/model/package.go` 的 `Package` 结构体
|
||||
2. **DTO 层**:修改 `internal/model/dto/package_dto.go` 的相关 Request/Response
|
||||
3. **Service 层**:修改 `internal/service/package/service.go` 的业务逻辑
|
||||
4. **Store 层**:修改 `internal/store/postgres/package_store.go` 的查询条件
|
||||
5. **数据库**:添加数据库迁移
|
||||
|
||||
详见:快速导航指南 第 4 节
|
||||
|
||||
### 修改套餐 API 响应
|
||||
|
||||
1. 修改 `PackageResponse` 结构体(`package_dto.go:71-99`)
|
||||
2. 修改 Service 的转换逻辑
|
||||
3. 测试 API 端点
|
||||
|
||||
详见:快速导航指南 第 4 节
|
||||
|
||||
### 添加新的套餐 API 端点
|
||||
|
||||
1. 在 `PackageHandler` 中添加方法
|
||||
2. 在 `PackageService` 中添加业务逻辑
|
||||
3. 在 `registerPackageRoutes()` 中注册路由
|
||||
|
||||
详见:快速导航指南 第 4 节
|
||||
|
||||
---
|
||||
|
||||
## 📝 相关文档
|
||||
|
||||
- **套餐系统升级**:`docs/package-system-upgrade/`
|
||||
- **套餐与佣金业务模型**:`docs/commission-package-model.md`
|
||||
- **API 文档生成规范**:`docs/api-documentation-guide.md`
|
||||
|
||||
---
|
||||
|
||||
## 🔗 相关模块
|
||||
|
||||
| 模块 | 说明 | 文件 |
|
||||
|------|------|------|
|
||||
| **PackageSeries** | 套餐系列管理 | `internal/routes/package_series.go` |
|
||||
| **PackageUsage** | 套餐使用记录 | `internal/routes/package_usage.go` |
|
||||
| **ShopPackageAllocation** | 套餐分配给代理 | `internal/store/postgres/shop_package_allocation_store.go` |
|
||||
| **ShopSeriesAllocation** | 系列分配给代理 | `internal/store/postgres/shop_series_allocation_store.go` |
|
||||
|
||||
---
|
||||
|
||||
## 💡 使用建议
|
||||
|
||||
1. **首次了解**:先读 [架构详细图](./package_architecture_diagram.md),理解整体流程
|
||||
2. **深入学习**:再读 [完整结构分析](./package_structure_summary.md),了解具体实现
|
||||
3. **快速查找**:使用 [快速导航指南](./package_quick_navigation.md),定位具体代码
|
||||
4. **修改代码**:参考 [快速导航指南](./package_quick_navigation.md) 的常见操作部分
|
||||
|
||||
---
|
||||
|
||||
**最后更新**:2025-04-10
|
||||
**文档版本**:1.0
|
||||
@@ -199,8 +199,11 @@ components:
|
||||
description: 店铺名称
|
||||
type: string
|
||||
status:
|
||||
description: 状态 (1:待支付, 2:已完成, 3:已取消)
|
||||
description: 状态 (1:待支付, 2:已支付, 3:已完成, 4:已关闭, 5:已退款)
|
||||
type: integer
|
||||
status_name:
|
||||
description: 状态名称(中文)
|
||||
type: string
|
||||
updated_at:
|
||||
description: 更新时间
|
||||
type: string
|
||||
@@ -8682,11 +8685,11 @@ paths:
|
||||
minimum: 0
|
||||
nullable: true
|
||||
type: integer
|
||||
- description: 按状态过滤 (1:待支付, 2:已完成, 3:已取消)
|
||||
- description: 按状态过滤 (1:待支付, 2:已支付, 3:已完成, 4:已关闭, 5:已退款)
|
||||
in: query
|
||||
name: status
|
||||
schema:
|
||||
description: 按状态过滤 (1:待支付, 2:已完成, 3:已取消)
|
||||
description: 按状态过滤 (1:待支付, 2:已支付, 3:已完成, 4:已关闭, 5:已退款)
|
||||
nullable: true
|
||||
type: integer
|
||||
- description: 创建时间起始日期(YYYY-MM-DD)
|
||||
|
||||
366
docs/architecture_diagram.md
Normal file
366
docs/architecture_diagram.md
Normal file
@@ -0,0 +1,366 @@
|
||||
# 套餐接口架构图
|
||||
|
||||
## 1. 整体架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ HTTP 请求 │
|
||||
│ POST /api/admin/packages │
|
||||
└────────────────────────────┬────────────────────────────────────┘
|
||||
│
|
||||
┌────────▼────────┐
|
||||
│ 认证中间件 │
|
||||
│ (AdminAuth) │
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌────────────────────▼────────────────────┐
|
||||
│ 路由注册 (routes/admin.go) │
|
||||
│ registerPackageRoutes() │
|
||||
│ ↓ │
|
||||
│ internal/routes/package.go │
|
||||
└────────────────────┬────────────────────┘
|
||||
│
|
||||
┌────────────────────▼────────────────────┐
|
||||
│ PackageHandler │
|
||||
│ (internal/handler/admin/package.go) │
|
||||
│ │
|
||||
│ - List() │
|
||||
│ - Create() │
|
||||
│ - Get() │
|
||||
│ - Update() │
|
||||
│ - Delete() │
|
||||
│ - UpdateStatus() │
|
||||
│ - UpdateShelfStatus() │
|
||||
│ - UpdateRetailPrice() │
|
||||
└────────────────────┬────────────────────┘
|
||||
│
|
||||
┌────────────────────▼────────────────────┐
|
||||
│ PackageService │
|
||||
│ (internal/service/package/service.go) │
|
||||
│ │
|
||||
│ 业务逻辑: │
|
||||
│ - 虚流量校验 │
|
||||
│ - 周期类型校验 │
|
||||
│ - 代理权限检查 │
|
||||
│ - DTO 转换 │
|
||||
└────────────────────┬────────────────────┘
|
||||
│
|
||||
┌────────────────────▼────────────────────┐
|
||||
│ PackageStore │
|
||||
│ (internal/store/postgres/...) │
|
||||
│ │
|
||||
│ - PackageStore │
|
||||
│ - PackageSeriesStore │
|
||||
│ - ShopPackageAllocationStore │
|
||||
│ - ShopSeriesAllocationStore │
|
||||
└────────────────────┬────────────────────┘
|
||||
│
|
||||
┌────────────────────▼────────────────────┐
|
||||
│ PostgreSQL 数据库 │
|
||||
│ │
|
||||
│ - tb_package │
|
||||
│ - tb_package_series │
|
||||
│ - tb_shop_package_allocation │
|
||||
│ - tb_shop_series_allocation │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 分层详细结构
|
||||
|
||||
### Handler 层
|
||||
```
|
||||
PackageHandler
|
||||
├── List(c *fiber.Ctx)
|
||||
│ └── 解析查询参数 → 调用 Service.List() → 返回分页结果
|
||||
├── Create(c *fiber.Ctx)
|
||||
│ └── 解析请求体 → 调用 Service.Create() → 返回创建结果
|
||||
├── Get(c *fiber.Ctx)
|
||||
│ └── 解析 ID 参数 → 调用 Service.Get() → 返回详情
|
||||
├── Update(c *fiber.Ctx)
|
||||
│ └── 解析 ID 和请求体 → 调用 Service.Update() → 返回更新结果
|
||||
├── Delete(c *fiber.Ctx)
|
||||
│ └── 解析 ID 参数 → 调用 Service.Delete() → 返回删除结果
|
||||
├── UpdateStatus(c *fiber.Ctx)
|
||||
│ └── 解析 ID 和状态 → 调用 Service.UpdateStatus() → 返回结果
|
||||
├── UpdateShelfStatus(c *fiber.Ctx)
|
||||
│ └── 解析 ID 和上架状态 → 调用 Service.UpdateShelfStatus() → 返回结果
|
||||
└── UpdateRetailPrice(c *fiber.Ctx)
|
||||
└── 解析 ID 和零售价 → 调用 Service.UpdateRetailPrice() → 返回结果
|
||||
```
|
||||
|
||||
### Service 层
|
||||
```
|
||||
PackageService
|
||||
├── Create(ctx, req)
|
||||
│ ├── 校验套餐编码唯一性
|
||||
│ ├── 校验虚流量配置
|
||||
│ │ ├── 启用时虚流量 > 0
|
||||
│ │ └── 虚流量 ≤ 真流量
|
||||
│ ├── 校验周期类型和时长
|
||||
│ │ ├── natural_month: 需要 duration_months
|
||||
│ │ └── by_day: 需要 duration_days
|
||||
│ ├── 查询套餐系列信息
|
||||
│ ├── 创建 Package 模型
|
||||
│ └── 调用 Store.Create()
|
||||
├── List(ctx, req)
|
||||
│ ├── 获取用户类型和店铺 ID
|
||||
│ ├── 代理用户额外过滤
|
||||
│ │ └── INNER JOIN tb_shop_package_allocation
|
||||
│ ├── 应用其他过滤条件
|
||||
│ └── 调用 Store.List()
|
||||
├── Get(ctx, id)
|
||||
│ └── 调用 Store.GetByID()
|
||||
├── Update(ctx, id, req)
|
||||
│ ├── 获取现有套餐
|
||||
│ ├── 更新字段
|
||||
│ └── 调用 Store.Update()
|
||||
├── Delete(ctx, id)
|
||||
│ └── 调用 Store.Delete()
|
||||
├── UpdateStatus(ctx, id, status)
|
||||
│ └── 更新状态字段
|
||||
├── UpdateShelfStatus(ctx, id, shelfStatus)
|
||||
│ └── 更新上架状态字段
|
||||
└── UpdateRetailPrice(ctx, id, price)
|
||||
└── 更新零售价字段
|
||||
```
|
||||
|
||||
### Store 层
|
||||
```
|
||||
PackageStore
|
||||
├── Create(ctx, pkg)
|
||||
│ └── db.Create(pkg)
|
||||
├── GetByID(ctx, id)
|
||||
│ └── db.First(&pkg, id)
|
||||
├── GetByCode(ctx, code)
|
||||
│ └── db.Where("package_code = ?", code).First(&pkg)
|
||||
├── Update(ctx, pkg)
|
||||
│ └── db.Save(pkg)
|
||||
├── Delete(ctx, id)
|
||||
│ └── db.Delete(&Package{}, id)
|
||||
└── List(ctx, opts, filters)
|
||||
├── 构建基础查询
|
||||
├── 代理用户 JOIN 分配表
|
||||
├── 应用过滤条件
|
||||
│ ├── package_name (模糊搜索)
|
||||
│ ├── series_id
|
||||
│ ├── status
|
||||
│ ├── shelf_status
|
||||
│ └── package_type
|
||||
├── 计算总数
|
||||
├── 应用分页
|
||||
└── 执行查询
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 数据模型关系
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ tb_package (套餐表) │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ id (PK) │
|
||||
│ package_code (UNIQUE) │
|
||||
│ package_name │
|
||||
│ series_id (FK → tb_package_series) │
|
||||
│ package_type (formal/addon) │
|
||||
│ duration_months │
|
||||
│ duration_days ← 关键字段 │
|
||||
│ calendar_type (natural_month/by_day) │
|
||||
│ real_data_mb │
|
||||
│ virtual_data_mb │
|
||||
│ enable_virtual_data │
|
||||
│ data_reset_cycle │
|
||||
│ expiry_base │
|
||||
│ cost_price │
|
||||
│ suggested_retail_price │
|
||||
│ shelf_status │
|
||||
│ status │
|
||||
│ created_at, updated_at, deleted_at │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
│ 1:N
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ tb_package_usage (套餐使用表) │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ id (PK) │
|
||||
│ order_id (FK) │
|
||||
│ package_id (FK → tb_package) │
|
||||
│ usage_type (single_card/device) │
|
||||
│ iot_card_id / device_id │
|
||||
│ data_limit_mb │
|
||||
│ data_usage_mb │
|
||||
│ activated_at, expires_at │
|
||||
│ status │
|
||||
│ priority │
|
||||
│ master_usage_id (加油包关联主套餐) │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
│ 1:N
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ tb_package_usage_daily_record (日记录表) │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ id (PK) │
|
||||
│ package_usage_id (FK) │
|
||||
│ date │
|
||||
│ daily_usage_mb │
|
||||
│ cumulative_usage_mb │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ tb_package_series (套餐系列表) │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ id (PK) │
|
||||
│ series_code (UNIQUE) │
|
||||
│ series_name │
|
||||
│ description │
|
||||
│ enable_one_time_commission │
|
||||
│ one_time_commission_config (JSONB) │
|
||||
│ status │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
│ 1:N
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ tb_shop_package_allocation (分配表) │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ id (PK) │
|
||||
│ shop_id (FK → tb_shop) │
|
||||
│ package_id (FK → tb_package) │
|
||||
│ retail_price │
|
||||
│ shelf_status │
|
||||
│ status │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 请求流程示例
|
||||
|
||||
### 创建套餐(按天)
|
||||
|
||||
```
|
||||
1. HTTP 请求
|
||||
POST /api/admin/packages
|
||||
{
|
||||
"package_code": "PKG_DAY_30",
|
||||
"package_name": "30天套餐",
|
||||
"calendar_type": "by_day",
|
||||
"duration_days": 30,
|
||||
...
|
||||
}
|
||||
|
||||
2. Handler.Create()
|
||||
├─ 解析请求体 → CreatePackageRequest
|
||||
└─ 调用 Service.Create(ctx, req)
|
||||
|
||||
3. Service.Create()
|
||||
├─ 校验 package_code 唯一性
|
||||
│ └─ Store.GetByCode(ctx, "PKG_DAY_30") → nil ✓
|
||||
├─ 校验虚流量配置 ✓
|
||||
├─ 校验周期类型
|
||||
│ ├─ calendar_type = "by_day" ✓
|
||||
│ ├─ duration_days = 30 ✓
|
||||
│ └─ 校验通过 ✓
|
||||
├─ 查询套餐系列 (可选)
|
||||
├─ 创建 Package 模型
|
||||
└─ Store.Create(ctx, pkg)
|
||||
|
||||
4. Store.Create()
|
||||
└─ db.Create(pkg)
|
||||
└─ INSERT INTO tb_package (...)
|
||||
|
||||
5. Service 返回 PackageResponse
|
||||
└─ Handler 返回 HTTP 200
|
||||
|
||||
6. HTTP 响应
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"id": 1,
|
||||
"package_code": "PKG_DAY_30",
|
||||
"duration_days": 30,
|
||||
...
|
||||
},
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 代理用户套餐查询流程
|
||||
|
||||
```
|
||||
1. 代理用户请求
|
||||
GET /api/admin/packages?status=1
|
||||
|
||||
2. Handler.List()
|
||||
└─ Service.List(ctx, req)
|
||||
|
||||
3. Service.List()
|
||||
├─ 获取用户类型: UserTypeAgent ✓
|
||||
├─ 获取店铺 ID: 10 ✓
|
||||
└─ Store.List(ctx, opts, filters)
|
||||
|
||||
4. Store.List()
|
||||
├─ 构建基础查询
|
||||
│ └─ SELECT * FROM tb_package
|
||||
├─ 检测代理用户
|
||||
│ └─ isAgent = true ✓
|
||||
├─ 添加 JOIN 条件
|
||||
│ └─ INNER JOIN tb_shop_package_allocation
|
||||
│ ON tb_shop_package_allocation.package_id = tb_package.id
|
||||
│ AND tb_shop_package_allocation.deleted_at IS NULL
|
||||
├─ 添加 WHERE 条件
|
||||
│ └─ WHERE tb_shop_package_allocation.shop_id = 10
|
||||
│ AND tb_shop_package_allocation.status = 1
|
||||
├─ 应用其他过滤
|
||||
│ └─ AND tb_package.status = 1
|
||||
├─ 计算总数
|
||||
├─ 应用分页
|
||||
└─ 执行查询
|
||||
|
||||
5. 返回结果
|
||||
└─ 只返回已分配给店铺 10 的套餐
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 字段验证流程
|
||||
|
||||
```
|
||||
CreatePackageRequest
|
||||
├─ package_code
|
||||
│ └─ validate: required, min=1, max=100
|
||||
│ └─ Service: 检查唯一性
|
||||
├─ package_name
|
||||
│ └─ validate: required, min=1, max=255
|
||||
├─ calendar_type
|
||||
│ └─ validate: oneof=natural_month by_day
|
||||
│ └─ Service: 根据类型校验时长字段
|
||||
├─ duration_months
|
||||
│ └─ validate: required, min=1, max=120
|
||||
│ └─ Service: 当 calendar_type=natural_month 时必填
|
||||
├─ duration_days
|
||||
│ └─ validate: omitempty, min=1, max=3650
|
||||
│ └─ Service: 当 calendar_type=by_day 时必填
|
||||
├─ real_data_mb
|
||||
│ └─ validate: omitempty, min=0
|
||||
├─ virtual_data_mb
|
||||
│ └─ validate: omitempty, min=0
|
||||
│ └─ Service: 启用虚流量时必须 > 0 且 ≤ real_data_mb
|
||||
├─ enable_virtual_data
|
||||
│ └─ Service: 虚流量配置校验
|
||||
├─ cost_price
|
||||
│ └─ validate: required, min=0
|
||||
└─ suggested_retail_price
|
||||
└─ validate: omitempty, min=0
|
||||
```
|
||||
|
||||
319
docs/commission-flow-diagram.md
Normal file
319
docs/commission-flow-diagram.md
Normal file
@@ -0,0 +1,319 @@
|
||||
# 差价佣金计算流程图
|
||||
|
||||
## 一、订单支付到佣金入账的完整流程
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ 客户下单购买套餐 │
|
||||
└────────────────────────────┬────────────────────────────────────────┘
|
||||
│
|
||||
┌────────▼────────┐
|
||||
│ 创建订单 │
|
||||
│ order.id = 123 │
|
||||
│ commission_status = 1 (待计算)
|
||||
│ payment_status = 1 (待支付)
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌────────▼────────┐
|
||||
│ 用户支付 │
|
||||
│ WalletPay() │
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌────────▼────────┐
|
||||
│ 更新订单状态 │
|
||||
│ payment_status = 2 (已支付)
|
||||
│ paid_at = now │
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌────────▼────────────────────────┐
|
||||
│ 激活套餐 │
|
||||
│ activatePackage() │
|
||||
│ (分配套餐到卡/设备) │
|
||||
└────────┬────────────────────────┘
|
||||
│
|
||||
┌────────▼────────────────────────┐
|
||||
│ 入队佣金计算任务 │
|
||||
│ enqueueCommissionCalculation() │
|
||||
│ task_type = "commission:calculate"
|
||||
│ payload = {order_id: 123} │
|
||||
└────────┬────────────────────────┘
|
||||
│
|
||||
│ (异步处理)
|
||||
│
|
||||
┌────────────────────▼────────────────────┐
|
||||
│ Asynq Worker 处理任务 │
|
||||
│ HandleCommissionCalculation() │
|
||||
└────────────────────┬────────────────────┘
|
||||
│
|
||||
┌────────▼────────────────────┐
|
||||
│ CalculateCommission() │
|
||||
│ 开启数据库事务 │
|
||||
└────────┬────────────────────┘
|
||||
│
|
||||
┌────────────────────▼────────────────────┐
|
||||
│ 1. 检查订单佣金状态 │
|
||||
│ if commission_status == 2 → 跳过 │
|
||||
└────────────────────┬────────────────────┘
|
||||
│
|
||||
┌────────────────────▼────────────────────┐
|
||||
│ 2. 计算成本价差佣金 │
|
||||
│ CalculateCostDiffCommission() │
|
||||
└────────────────────┬────────────────────┘
|
||||
│
|
||||
▼
|
||||
```
|
||||
|
||||
## 二、成本价差佣金计算详细流程
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ CalculateCostDiffCommission(order) │
|
||||
└────────────────────┬─────────────────────────────────────────┘
|
||||
│
|
||||
┌────────────▼────────────┐
|
||||
│ 获取销售店铺信息 │
|
||||
│ seller_shop_id = 10 │
|
||||
└────────────┬────────────┘
|
||||
│
|
||||
┌────────────▼────────────────────────────┐
|
||||
│ 计算销售端利润 │
|
||||
│ profit = total_amount - seller_cost_price
|
||||
│ profit = 200 - 130 = 70元 │
|
||||
│ 为销售店铺创建佣金记录 │
|
||||
│ commission_record { │
|
||||
│ shop_id: 10, │
|
||||
│ amount: 70, │
|
||||
│ commission_source: "cost_diff" │
|
||||
│ } │
|
||||
└────────────┬────────────────────────────┘
|
||||
│
|
||||
┌────────────▼────────────────────────────┐
|
||||
│ 链式向上计算 │
|
||||
│ current_shop_id = seller_shop_id │
|
||||
│ child_cost_price = seller_cost_price │
|
||||
└────────────┬────────────────────────────┘
|
||||
│
|
||||
┌────────────▼────────────────────────────┐
|
||||
│ 循环:while current_shop_id != null │
|
||||
└────────────┬────────────────────────────┘
|
||||
│
|
||||
┌────────────▼────────────────────────────┐
|
||||
│ 1. 获取上级店铺 │
|
||||
│ parent_shop_id = current_shop.parent_id
|
||||
│ if parent_shop_id == null → 结束 │
|
||||
└────────────┬────────────────────────────┘
|
||||
│
|
||||
┌────────────▼────────────────────────────┐
|
||||
│ 2. 查询上级的套餐分配配置 │
|
||||
│ allocation = ShopPackageAllocation │
|
||||
│ where shop_id = parent_shop_id │
|
||||
│ and package_id = order.package_id │
|
||||
└────────────┬────────────────────────────┘
|
||||
│
|
||||
├─ 未找到 ──┐
|
||||
│ │
|
||||
│ ┌──────▼──────────────────┐
|
||||
│ │ 链路断裂处理 │
|
||||
│ │ 创建待审佣金记录 │
|
||||
│ │ status = 99 │
|
||||
│ │ amount = 0 │
|
||||
│ │ remark = "套餐系列未分配" │
|
||||
│ │ 结束循环 │
|
||||
│ └─────────────────────────┘
|
||||
│
|
||||
└─ 找到 ──┐
|
||||
│
|
||||
┌─────────────────────▼──────────────────┐
|
||||
│ 3. 计算差价 │
|
||||
│ my_cost_price = allocation.cost_price
|
||||
│ profit = child_cost_price - my_cost_price
|
||||
│ profit = 130 - 120 = 10元 │
|
||||
└─────────────────────┬──────────────────┘
|
||||
│
|
||||
┌─────────────────────▼──────────────────┐
|
||||
│ 4. 如果差价 > 0,创建佣金记录 │
|
||||
│ commission_record { │
|
||||
│ shop_id: parent_shop_id, │
|
||||
│ amount: profit, │
|
||||
│ commission_source: "cost_diff" │
|
||||
│ } │
|
||||
└─────────────────────┬──────────────────┘
|
||||
│
|
||||
┌─────────────────────▼──────────────────┐
|
||||
│ 5. 更新变量,继续向上 │
|
||||
│ child_cost_price = my_cost_price │
|
||||
│ current_shop_id = parent_shop_id │
|
||||
│ 继续循环 │
|
||||
└─────────────────────┬──────────────────┘
|
||||
│
|
||||
┌─────────▴─────────┐
|
||||
│ 返回佣金记录列表 │
|
||||
└───────────────────┘
|
||||
```
|
||||
|
||||
## 三、佣金入账流程
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ 对每条佣金记录调用 creditCommissionInTx() │
|
||||
└────────────────────┬─────────────────────────────────────────┘
|
||||
│
|
||||
┌────────────▼────────────────────────┐
|
||||
│ 1. 获取店铺的分佣钱包 │
|
||||
│ wallet = AgentWallet │
|
||||
│ where shop_id = record.shop_id │
|
||||
│ and wallet_type = 'commission' │
|
||||
└────────────┬────────────────────────┘
|
||||
│
|
||||
┌────────────▼────────────────────────┐
|
||||
│ 2. 增加钱包余额 │
|
||||
│ wallet.balance += record.amount │
|
||||
│ wallet.version += 1 │
|
||||
│ (乐观锁防并发) │
|
||||
└────────────┬────────────────────────┘
|
||||
│
|
||||
┌────────────▼────────────────────────┐
|
||||
│ 3. 更新佣金记录 │
|
||||
│ commission_record.status = 3 │
|
||||
│ (CommissionStatusReleased) │
|
||||
│ commission_record.released_at = now
|
||||
│ commission_record.balance_after │
|
||||
│ = balance_before + amount │
|
||||
└────────────┬────────────────────────┘
|
||||
│
|
||||
┌────────────▼────────────────────────┐
|
||||
│ 4. 创建钱包交易记录 │
|
||||
│ transaction { │
|
||||
│ agent_wallet_id: wallet.id, │
|
||||
│ transaction_type: 'commission',│
|
||||
│ amount: record.amount, │
|
||||
│ balance_before: ..., │
|
||||
│ balance_after: ..., │
|
||||
│ reference_type: 'commission', │
|
||||
│ reference_id: record.id │
|
||||
│ } │
|
||||
└────────────┬────────────────────────┘
|
||||
│
|
||||
┌────────────▼────────────────────────┐
|
||||
│ 5. 事务提交 │
|
||||
│ 佣金入账完成 │
|
||||
└────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 四、订单状态转换图
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ 订单创建 │
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌────────▼────────────────────┐
|
||||
│ commission_status = 1 │
|
||||
│ payment_status = 1 │
|
||||
│ (待计算, 待支付) │
|
||||
└────────┬────────────────────┘
|
||||
│
|
||||
┌────────▼────────────────────┐
|
||||
│ 用户支付 │
|
||||
└────────┬────────────────────┘
|
||||
│
|
||||
┌────────▼────────────────────┐
|
||||
│ payment_status = 2 │
|
||||
│ (已支付) │
|
||||
│ commission_status = 1 │
|
||||
│ (待计算) │
|
||||
└────────┬────────────────────┘
|
||||
│
|
||||
┌────────▼────────────────────┐
|
||||
│ 异步佣金计算 │
|
||||
└────────┬────────────────────┘
|
||||
│
|
||||
┌────────▼────────────────────┐
|
||||
│ commission_status = 2 │
|
||||
│ (已计算) │
|
||||
│ 佣金已入账到代理钱包 │
|
||||
└────────────────────────────┘
|
||||
```
|
||||
|
||||
## 五、佣金记录状态转换图
|
||||
|
||||
```
|
||||
┌──────────────────┐
|
||||
│ 佣金记录创建 │
|
||||
│ status = 3 │
|
||||
│ (已发放) │
|
||||
└────────┬─────────┘
|
||||
│
|
||||
┌────────▼──────────────────┐
|
||||
│ 入账到代理钱包 │
|
||||
│ released_at = now │
|
||||
│ balance_after = ... │
|
||||
└────────┬──────────────────┘
|
||||
│
|
||||
┌────────▼──────────────────┐
|
||||
│ 佣金可用于提现 │
|
||||
│ 或继续分配给下级 │
|
||||
└───────────────────────────┘
|
||||
|
||||
┌──────────────────┐
|
||||
│ 链路断裂情况 │
|
||||
│ status = 99 │
|
||||
│ (待人工修正) │
|
||||
└────────┬─────────┘
|
||||
│
|
||||
┌────────▼──────────────────┐
|
||||
│ 平台人工审核 │
|
||||
│ 修正或失效 │
|
||||
└───────────────────────────┘
|
||||
```
|
||||
|
||||
## 六、代理链佣金分配示例
|
||||
|
||||
```
|
||||
平台 (基础成本价: 100元)
|
||||
│
|
||||
├─ 代理A (成本价: 120元)
|
||||
│ │
|
||||
│ ├─ 代理A1 (成本价: 130元)
|
||||
│ │ │
|
||||
│ │ └─ 代理A1a (成本价: 140元)
|
||||
│ │ │
|
||||
│ │ └─ 客户购买,售价: 200元
|
||||
│ │
|
||||
│ └─ 代理A2 (成本价: 135元)
|
||||
│
|
||||
└─ 代理B (成本价: 125元)
|
||||
|
||||
订单信息:
|
||||
seller_shop_id = A1a (代理A1a)
|
||||
seller_cost_price = 140元
|
||||
total_amount = 200元
|
||||
|
||||
佣金计算结果:
|
||||
┌─────────────────────────────────────────┐
|
||||
│ A1a (销售店铺) │
|
||||
│ 销售利润 = 200 - 140 = 60元 │
|
||||
│ 佣金记录: amount=60, commission_source=cost_diff
|
||||
└─────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────┐
|
||||
│ A1 (A1a的上级) │
|
||||
│ 差价 = 140 - 130 = 10元 │
|
||||
│ 佣金记录: amount=10, commission_source=cost_diff
|
||||
└─────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────┐
|
||||
│ A (A1的上级) │
|
||||
│ 差价 = 130 - 120 = 10元 │
|
||||
│ 佣金记录: amount=10, commission_source=cost_diff
|
||||
└─────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────┐
|
||||
│ 平台 (A的上级) │
|
||||
│ 差价 = 120 - 100 = 20元 │
|
||||
│ (平台收入,不作为佣金) │
|
||||
└─────────────────────────────────────────┘
|
||||
|
||||
总计: 60 + 10 + 10 + 20 = 100元 ✓
|
||||
```
|
||||
|
||||
283
docs/commission-quick-reference.md
Normal file
283
docs/commission-quick-reference.md
Normal file
@@ -0,0 +1,283 @@
|
||||
# 差价佣金快速参考指南
|
||||
|
||||
## 快速查询表
|
||||
|
||||
### 1. 关键文件位置
|
||||
|
||||
| 功能 | 文件 | 行号 |
|
||||
|------|------|------|
|
||||
| 差价佣金计算 | `internal/service/commission_calculation/service.go` | 121-225 |
|
||||
| 佣金入账 | `internal/service/commission_calculation/service.go` | 641-687 |
|
||||
| 佣金计算触发 | `internal/service/order/service.go` | 2132-2150 |
|
||||
| 订单支付完成 | `internal/service/order/service.go` | 1431-1623 |
|
||||
| 异步任务处理 | `internal/task/commission_calculation.go` | 40-62 |
|
||||
| 订单模型 | `internal/model/order.go` | 1-138 |
|
||||
| 佣金记录模型 | `internal/model/commission.go` | 1-46 |
|
||||
| 业务文档 | `docs/commission-package-model.md` | - |
|
||||
|
||||
### 2. 关键常量
|
||||
|
||||
```go
|
||||
// 任务类型
|
||||
constants.TaskTypeCommission = "commission:calculate"
|
||||
|
||||
// 佣金来源
|
||||
model.CommissionSourceCostDiff = "cost_diff" // 成本价差
|
||||
model.CommissionSourceOneTime = "one_time" // 一次性佣金
|
||||
|
||||
// 订单佣金状态
|
||||
model.CommissionStatusPending = 1 // 待计算
|
||||
model.CommissionStatusCalculated = 2 // 已计算
|
||||
|
||||
// 订单支付状态
|
||||
model.PaymentStatusPending = 1 // 待支付
|
||||
model.PaymentStatusPaid = 2 // 已支付
|
||||
model.PaymentStatusCancelled = 3 // 已取消
|
||||
model.PaymentStatusRefunded = 4 // 已退款
|
||||
|
||||
// 佣金记录状态
|
||||
constants.CommissionStatusReleased = 3 // 已发放
|
||||
constants.CommissionStatusInvalid = 4 // 已失效
|
||||
constants.CommissionStatusPendingReview = 99 // 待人工修正
|
||||
```
|
||||
|
||||
### 3. 关键数据库表
|
||||
|
||||
| 表名 | 用途 | 关键字段 |
|
||||
|------|------|---------|
|
||||
| `tb_order` | 订单 | `commission_status`, `payment_status`, `seller_shop_id`, `seller_cost_price` |
|
||||
| `tb_commission_record` | 佣金记录 | `shop_id`, `order_id`, `amount`, `status`, `released_at` |
|
||||
| `tb_agent_wallet` | 代理钱包 | `shop_id`, `balance`, `wallet_type` |
|
||||
| `tb_agent_wallet_transaction` | 钱包交易 | `agent_wallet_id`, `transaction_type`, `amount` |
|
||||
| `tb_shop_package_allocation` | 套餐分配 | `shop_id`, `package_id`, `cost_price` |
|
||||
|
||||
---
|
||||
|
||||
## 常见问题快速查询
|
||||
|
||||
### Q1: 差价佣金什么时候计算?
|
||||
|
||||
**A**: 订单支付成功后立即入队异步任务,由 Worker 异步计算。
|
||||
|
||||
**代码位置**: `internal/service/order/service.go:1621`
|
||||
|
||||
```go
|
||||
s.enqueueCommissionCalculation(ctx, orderID)
|
||||
```
|
||||
|
||||
### Q2: 差价佣金的计算公式是什么?
|
||||
|
||||
**A**: `差价佣金 = 下级成本价 - 自己成本价`
|
||||
|
||||
**代码位置**: `internal/service/commission_calculation/service.go:203`
|
||||
|
||||
```go
|
||||
profit := childCostPrice - myCostPrice
|
||||
```
|
||||
|
||||
### Q3: 如何查询某个订单的佣金记录?
|
||||
|
||||
**A**: 查询 `tb_commission_record` 表,按 `order_id` 过滤。
|
||||
|
||||
```sql
|
||||
SELECT * FROM tb_commission_record
|
||||
WHERE order_id = ?
|
||||
ORDER BY created_at DESC;
|
||||
```
|
||||
|
||||
### Q4: 佣金什么时候入账到代理钱包?
|
||||
|
||||
**A**: 佣金记录创建后立即入账,通过 `creditCommissionInTx()` 方法。
|
||||
|
||||
**代码位置**: `internal/service/commission_calculation/service.go:641-687`
|
||||
|
||||
### Q5: 链路断裂是什么意思?
|
||||
|
||||
**A**: 上级代理未分配该套餐,导致佣金链中断。此时创建待审佣金记录(status=99),需平台人工处理。
|
||||
|
||||
**代码位置**: `internal/service/commission_calculation/service.go:173-200`
|
||||
|
||||
### Q6: 如何追踪一个订单的佣金计算过程?
|
||||
|
||||
**A**:
|
||||
1. 查询订单:`SELECT * FROM tb_order WHERE id = ?`
|
||||
2. 检查 `commission_status` 字段(1=待计算, 2=已计算)
|
||||
3. 查询佣金记录:`SELECT * FROM tb_commission_record WHERE order_id = ?`
|
||||
4. 查询钱包交易:`SELECT * FROM tb_agent_wallet_transaction WHERE reference_id = ? AND reference_type = 'commission'`
|
||||
|
||||
### Q7: 代理钱包余额如何更新?
|
||||
|
||||
**A**: 通过 `AgentWalletStore.AddBalanceWithTx()` 方法,使用乐观锁防并发。
|
||||
|
||||
**代码位置**: `internal/service/commission_calculation/service.go:651`
|
||||
|
||||
```go
|
||||
s.agentWalletStore.AddBalanceWithTx(ctx, tx, wallet.ID, record.Amount)
|
||||
```
|
||||
|
||||
### Q8: 如何处理佣金计算失败?
|
||||
|
||||
**A**: Asynq 会自动重试,最多重试次数由队列配置决定。如果最终失败,任务会进入死信队列。
|
||||
|
||||
**代码位置**: `internal/task/commission_calculation.go:40-62`
|
||||
|
||||
### Q9: 一个订单可能产生多少条佣金记录?
|
||||
|
||||
**A**: 取决于代理链的深度。最多为:销售店铺 + 所有上级店铺。
|
||||
|
||||
**示例**: 4级代理链 → 最多4条佣金记录
|
||||
|
||||
### Q10: 如何验证佣金计算的正确性?
|
||||
|
||||
**A**:
|
||||
1. 验证总金额:所有佣金记录的 `amount` 之和 = 订单总金额 - 平台成本价
|
||||
2. 验证链路:每条佣金记录的 `shop_id` 应该是代理链中的一个
|
||||
3. 验证状态:所有佣金记录的 `status` 应该是 3(已发放)或 99(待审)
|
||||
|
||||
---
|
||||
|
||||
## 调试技巧
|
||||
|
||||
### 1. 查看订单的佣金计算状态
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
id, order_no, commission_status, payment_status,
|
||||
seller_shop_id, seller_cost_price, total_amount
|
||||
FROM tb_order
|
||||
WHERE id = ?;
|
||||
```
|
||||
|
||||
### 2. 查看订单的所有佣金记录
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
id, shop_id, amount, commission_source, status, released_at
|
||||
FROM tb_commission_record
|
||||
WHERE order_id = ?
|
||||
ORDER BY shop_id;
|
||||
```
|
||||
|
||||
### 3. 查看代理的钱包余额变化
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
id, shop_id, balance, wallet_type, version
|
||||
FROM tb_agent_wallet
|
||||
WHERE shop_id = ?;
|
||||
```
|
||||
|
||||
### 4. 查看代理的钱包交易记录
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
id, agent_wallet_id, transaction_type, amount,
|
||||
balance_before, balance_after, reference_type, reference_id
|
||||
FROM tb_agent_wallet_transaction
|
||||
WHERE shop_id = ? AND transaction_type = 'commission'
|
||||
ORDER BY created_at DESC;
|
||||
```
|
||||
|
||||
### 5. 查看待审佣金记录(链路断裂)
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
id, shop_id, order_id, amount, status, remark
|
||||
FROM tb_commission_record
|
||||
WHERE status = 99
|
||||
ORDER BY created_at DESC;
|
||||
```
|
||||
|
||||
### 6. 查看异步任务队列状态
|
||||
|
||||
```bash
|
||||
# 查看待处理任务
|
||||
redis-cli LRANGE asynq:queues:default 0 -1
|
||||
|
||||
# 查看已完成任务
|
||||
redis-cli LRANGE asynq:queues:completed 0 -1
|
||||
|
||||
# 查看失败任务
|
||||
redis-cli LRANGE asynq:queues:failed 0 -1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 性能优化建议
|
||||
|
||||
### 1. 批量查询优化
|
||||
|
||||
避免在循环中逐条查询,使用 `IN` 子句批量查询:
|
||||
|
||||
```go
|
||||
// ❌ 不好:N+1 查询
|
||||
for _, shopID := range shopIDs {
|
||||
allocation, _ := store.GetByShopAndPackage(ctx, shopID, packageID)
|
||||
}
|
||||
|
||||
// ✅ 好:批量查询
|
||||
allocations, _ := store.GetByShopsAndPackage(ctx, shopIDs, packageID)
|
||||
```
|
||||
|
||||
### 2. 事务优化
|
||||
|
||||
确保事务范围最小化,避免长事务:
|
||||
|
||||
```go
|
||||
// ✅ 好:事务只包含必要操作
|
||||
err = s.db.Transaction(func(tx *gorm.DB) error {
|
||||
// 创建佣金记录
|
||||
// 更新钱包余额
|
||||
// 创建交易记录
|
||||
return nil
|
||||
})
|
||||
```
|
||||
|
||||
### 3. 索引优化
|
||||
|
||||
确保以下字段有索引:
|
||||
- `tb_order.id`, `tb_order.commission_status`, `tb_order.payment_status`
|
||||
- `tb_commission_record.order_id`, `tb_commission_record.shop_id`, `tb_commission_record.status`
|
||||
- `tb_agent_wallet.shop_id`, `tb_agent_wallet.wallet_type`
|
||||
|
||||
---
|
||||
|
||||
## 常见错误排查
|
||||
|
||||
### 错误1: 佣金未入账
|
||||
|
||||
**症状**: `commission_status = 2` 但钱包余额未增加
|
||||
|
||||
**排查步骤**:
|
||||
1. 检查 `tb_commission_record` 中是否有记录
|
||||
2. 检查记录的 `status` 是否为 3(已发放)
|
||||
3. 检查 `tb_agent_wallet_transaction` 中是否有对应交易
|
||||
4. 查看应用日志中的错误信息
|
||||
|
||||
### 错误2: 链路断裂
|
||||
|
||||
**症状**: `tb_commission_record.status = 99`,`remark` 包含"套餐系列未分配"
|
||||
|
||||
**排查步骤**:
|
||||
1. 检查上级代理是否分配了该套餐
|
||||
2. 检查 `tb_shop_package_allocation` 表中是否有记录
|
||||
3. 平台管理员需要手动分配套餐或修正佣金记录
|
||||
|
||||
### 错误3: 佣金计算失败
|
||||
|
||||
**症状**: 异步任务失败,订单 `commission_status` 仍为 1
|
||||
|
||||
**排查步骤**:
|
||||
1. 查看应用日志中的错误信息
|
||||
2. 检查订单数据是否完整(`seller_shop_id`, `seller_cost_price` 等)
|
||||
3. 检查数据库连接是否正常
|
||||
4. 手动重试任务或联系技术支持
|
||||
|
||||
---
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [套餐与佣金业务模型](commission-package-model.md) - 完整的业务规则说明
|
||||
- [差价佣金搜索结果](commission-search-result.md) - 详细的代码位置和实现说明
|
||||
- [差价佣金流程图](commission-flow-diagram.md) - 可视化的流程图
|
||||
|
||||
237
docs/commission-search-result.md
Normal file
237
docs/commission-search-result.md
Normal file
@@ -0,0 +1,237 @@
|
||||
# 差价佣金计算和分配逻辑搜索结果
|
||||
|
||||
## 一、差价佣金的计算方式
|
||||
|
||||
### 1.1 计算公式
|
||||
|
||||
差价佣金 = 下级成本价 - 自己成本价
|
||||
|
||||
### 1.2 计算流程
|
||||
|
||||
**文件位置**: `internal/service/commission_calculation/service.go:121-225`
|
||||
|
||||
```go
|
||||
func (s *Service) CalculateCostDiffCommission(ctx context.Context, order *model.Order) ([]*model.CommissionRecord, error)
|
||||
```
|
||||
|
||||
**核心逻辑**:
|
||||
|
||||
1. **销售端利润计算** (第138-153行)
|
||||
- 销售利润 = 订单总金额 - 销售成本价
|
||||
- 如果销售利润 > 0,为销售店铺创建一条佣金记录
|
||||
|
||||
2. **链式向上计算** (第163-222行)
|
||||
- 从销售店铺开始,逐级向上遍历代理链
|
||||
- 对于每一级代理:
|
||||
- 获取该代理的成本价(从 ShopPackageAllocation 表)
|
||||
- 计算差价:childCostPrice - myCostPrice
|
||||
- 如果差价 > 0,创建佣金记录
|
||||
|
||||
3. **链路断裂处理** (第173-200行)
|
||||
- 如果上级代理未分配该套餐,创建待审佣金记录(status=99)
|
||||
- 记录备注:套餐系列未分配给该代理,需人工核查
|
||||
|
||||
### 1.3 示例
|
||||
|
||||
```
|
||||
平台基础成本价: 100元
|
||||
↓
|
||||
分配给代理A,成本价: 120元
|
||||
├─ A的利润: 120 - 100 = 20元
|
||||
↓
|
||||
分配给代理A1,成本价: 130元
|
||||
├─ A1的利润: 130 - 120 = 10元
|
||||
├─ A的差价佣金: 130 - 120 = 10元
|
||||
↓
|
||||
A1销售给客户,售价: 200元
|
||||
├─ A1的销售利润: 200 - 130 = 70元
|
||||
├─ A的差价佣金: 130 - 120 = 10元
|
||||
├─ 平台收入: 120元
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、差价佣金的发放条件
|
||||
|
||||
### 2.1 触发条件
|
||||
|
||||
**文件位置**: `internal/service/order/service.go:1621` 和 `internal/task/commission_calculation.go`
|
||||
|
||||
差价佣金在以下情况触发:
|
||||
|
||||
1. **订单支付成功**
|
||||
- 支付状态从 `PaymentStatusPending` (1) 变为 `PaymentStatusPaid` (2)
|
||||
- 触发点:`WalletPay()` 方法完成后调用 `enqueueCommissionCalculation()`
|
||||
|
||||
2. **异步任务处理**
|
||||
- 任务类型: `commission:calculate`
|
||||
- 处理器: `CommissionCalculationHandler.HandleCommissionCalculation()`
|
||||
- 队列: Asynq 异步任务队列
|
||||
|
||||
### 2.2 发放流程
|
||||
|
||||
**文件位置**: `internal/service/commission_calculation/service.go:74-119`
|
||||
|
||||
```go
|
||||
func (s *Service) CalculateCommission(ctx context.Context, orderID uint) error
|
||||
```
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. **检查订单佣金状态** (第81-84行)
|
||||
- 如果 `commission_status == 2` (已计算),跳过
|
||||
- 防止重复计算
|
||||
|
||||
2. **计算成本价差佣金** (第86-98行)
|
||||
- 调用 `CalculateCostDiffCommission()` 获取佣金记录列表
|
||||
- 逐条创建佣金记录到 `tb_commission_record` 表
|
||||
- 调用 `creditCommissionInTx()` 入账到代理钱包
|
||||
|
||||
3. **触发一次性佣金** (第100-111行)
|
||||
- 仅非代购订单触发
|
||||
- 根据订单类型(单卡/设备)调用对应方法
|
||||
|
||||
4. **更新订单佣金状态** (第113-115行)
|
||||
- 将 `commission_status` 更新为 `2` (已计算)
|
||||
|
||||
### 2.3 佣金入账
|
||||
|
||||
**文件位置**: `internal/service/commission_calculation/service.go:641-687`
|
||||
|
||||
```go
|
||||
func (s *Service) creditCommissionInTx(ctx context.Context, tx *gorm.DB, record *model.CommissionRecord) error
|
||||
```
|
||||
|
||||
**入账步骤**:
|
||||
|
||||
1. 获取店铺的分佣钱包 (wallet_type = 'commission')
|
||||
2. 增加钱包余额
|
||||
3. 更新佣金记录状态为 `CommissionStatusReleased` (3)
|
||||
4. 记录入账时间 `released_at`
|
||||
5. 创建钱包交易记录 (transaction_type = 'commission')
|
||||
|
||||
---
|
||||
|
||||
## 三、差价佣金与订单状态的关系
|
||||
|
||||
### 3.1 订单状态定义
|
||||
|
||||
**文件位置**: `internal/model/order.go:106-110`
|
||||
|
||||
```go
|
||||
// 佣金状态常量
|
||||
const (
|
||||
CommissionStatusPending = 1 // 待计算
|
||||
CommissionStatusCalculated = 2 // 已计算
|
||||
)
|
||||
```
|
||||
|
||||
### 3.2 订单支付状态
|
||||
|
||||
**文件位置**: `internal/model/order.go:98-104`
|
||||
|
||||
```go
|
||||
// 支付状态常量
|
||||
const (
|
||||
PaymentStatusPending = 1 // 待支付
|
||||
PaymentStatusPaid = 2 // 已支付
|
||||
PaymentStatusCancelled = 3 // 已取消
|
||||
PaymentStatusRefunded = 4 // 已退款
|
||||
)
|
||||
```
|
||||
|
||||
### 3.3 状态转换关系
|
||||
|
||||
```
|
||||
订单创建
|
||||
↓
|
||||
commission_status = 1 (待计算)
|
||||
payment_status = 1 (待支付)
|
||||
↓
|
||||
用户支付
|
||||
↓
|
||||
payment_status = 2 (已支付)
|
||||
↓
|
||||
触发佣金计算任务
|
||||
↓
|
||||
异步处理:CalculateCommission()
|
||||
↓
|
||||
commission_status = 2 (已计算)
|
||||
佣金入账到代理钱包
|
||||
```
|
||||
|
||||
### 3.4 佣金记录状态
|
||||
|
||||
**文件位置**: `internal/model/commission.go:40-46` 和 `pkg/constants/iot.go:180-187`
|
||||
|
||||
```go
|
||||
// 佣金记录状态(tb_commission_record.status)
|
||||
const (
|
||||
CommissionStatusReleased = 3 // 已发放
|
||||
CommissionStatusInvalid = 4 // 已失效
|
||||
CommissionStatusPendingReview = 99 // 待人工修正(链路断裂)
|
||||
)
|
||||
|
||||
// 代理钱包交易状态(tb_agent_wallet_transaction.status)
|
||||
const (
|
||||
CommissionStatusFrozen = 1 // 已冻结
|
||||
CommissionStatusUnfreezing = 2 // 解冻中
|
||||
CommissionStatusReleased = 3 // 已发放
|
||||
CommissionStatusInvalid = 4 // 已失效
|
||||
CommissionStatusPendingReview = 99 // 待人工修正
|
||||
)
|
||||
```
|
||||
|
||||
### 3.5 关键字段
|
||||
|
||||
**订单表 (tb_order)**:
|
||||
- `commission_status`: 佣金计算状态
|
||||
- `commission_config_version`: 佣金配置版本快照
|
||||
- `seller_shop_id`: 销售店铺ID(用于成本价差佣金)
|
||||
- `seller_cost_price`: 销售成本价(用于计算利润)
|
||||
- `series_id`: 系列ID(用于查询分配配置)
|
||||
|
||||
**佣金记录表 (tb_commission_record)**:
|
||||
- `shop_id`: 佣金归属店铺
|
||||
- `order_id`: 关联订单
|
||||
- `commission_source`: 佣金来源 (cost_diff/one_time)
|
||||
- `amount`: 佣金金额
|
||||
- `status`: 佣金状态
|
||||
- `released_at`: 入账时间
|
||||
- `balance_after`: 入账后钱包余额
|
||||
|
||||
---
|
||||
|
||||
## 四、关键代码位置总结
|
||||
|
||||
| 功能 | 文件位置 | 方法 |
|
||||
|------|---------|------|
|
||||
| 差价佣金计算 | `internal/service/commission_calculation/service.go` | `CalculateCostDiffCommission()` |
|
||||
| 佣金入账 | `internal/service/commission_calculation/service.go` | `creditCommissionInTx()` |
|
||||
| 佣金计算触发 | `internal/service/order/service.go` | `enqueueCommissionCalculation()` |
|
||||
| 异步任务处理 | `internal/task/commission_calculation.go` | `HandleCommissionCalculation()` |
|
||||
| 订单支付完成 | `internal/service/order/service.go` | `WalletPay()` |
|
||||
| 模型定义 | `internal/model/commission.go` | `CommissionRecord` |
|
||||
| 模型定义 | `internal/model/order.go` | `Order` |
|
||||
| 业务文档 | `docs/commission-package-model.md` | 完整的佣金业务模型说明 |
|
||||
|
||||
---
|
||||
|
||||
## 五、相关常量
|
||||
|
||||
**文件位置**: `pkg/constants/constants.go` 和 `pkg/constants/iot.go`
|
||||
|
||||
```go
|
||||
// 任务类型
|
||||
TaskTypeCommission = "commission:calculate"
|
||||
|
||||
// 佣金来源
|
||||
CommissionSourceCostDiff = "cost_diff" // 成本价差
|
||||
CommissionSourceOneTime = "one_time" // 一次性佣金
|
||||
|
||||
// 佣金状态
|
||||
CommissionStatusReleased = 3 // 已发放
|
||||
CommissionStatusInvalid = 4 // 已失效
|
||||
CommissionStatusPendingReview = 99 // 待人工修正
|
||||
```
|
||||
|
||||
456
docs/order_status_commission_analysis.md
Normal file
456
docs/order_status_commission_analysis.md
Normal file
@@ -0,0 +1,456 @@
|
||||
# 订单状态与佣金系统分析报告
|
||||
|
||||
## 一、订单状态定义
|
||||
|
||||
### 1.1 订单支付状态(Payment Status)
|
||||
**文件**: `internal/model/order.go` (第 98-104 行)
|
||||
|
||||
```go
|
||||
const (
|
||||
PaymentStatusPending = 1 // 待支付
|
||||
PaymentStatusPaid = 2 // 已支付
|
||||
PaymentStatusCancelled = 3 // 已取消
|
||||
PaymentStatusRefunded = 4 // 已退款
|
||||
)
|
||||
```
|
||||
|
||||
**数据库字段**: `tb_order.payment_status` (int)
|
||||
|
||||
**说明**:
|
||||
- 订单创建时默认为 `PaymentStatusPending`(待支付)
|
||||
- 支付成功后更新为 `PaymentStatusPaid`(已支付)
|
||||
- 待支付订单超时 30 分钟自动取消,状态变为 `PaymentStatusCancelled`
|
||||
- 退款后状态变为 `PaymentStatusRefunded`
|
||||
|
||||
### 1.2 订单佣金状态(Commission Status)
|
||||
**文件**: `internal/model/order.go` (第 106-110 行)
|
||||
|
||||
```go
|
||||
const (
|
||||
CommissionStatusPending = 1 // 待计算
|
||||
CommissionStatusCalculated = 2 // 已计算
|
||||
)
|
||||
```
|
||||
|
||||
**数据库字段**: `tb_order.commission_status` (int)
|
||||
|
||||
**说明**:
|
||||
- 订单创建时默认为 `CommissionStatusPending`(待计算)
|
||||
- 佣金计算完成后更新为 `CommissionStatusCalculated`(已计算)
|
||||
- 佣金计算是异步任务,通过 Asynq 队列处理
|
||||
|
||||
---
|
||||
|
||||
## 二、订单状态流转逻辑
|
||||
|
||||
### 2.1 订单创建流程
|
||||
**文件**: `internal/service/order/service.go`
|
||||
|
||||
#### 支付方式决定初始状态:
|
||||
|
||||
**1. 线下支付(offline)- 平台代购**
|
||||
- 创建订单时直接设置 `PaymentStatus = PaymentStatusPaid`(已支付)
|
||||
- 立即激活套餐
|
||||
- 立即入队佣金计算任务
|
||||
- 代码位置: 第 189-209 行、第 464-502 行
|
||||
|
||||
**2. 钱包支付(wallet)- 代理自购或代购**
|
||||
- 创建订单时直接设置 `PaymentStatus = PaymentStatusPaid`(已支付)
|
||||
- 在事务中完成:创建订单 → 扣款 → 激活套餐
|
||||
- 代码位置: 第 210-289 行、第 504-573 行
|
||||
|
||||
**3. 微信/支付宝支付(wechat/alipay)- H5 端**
|
||||
- 创建订单时设置 `PaymentStatus = PaymentStatusPending`(待支付)
|
||||
- 设置 `ExpiresAt` 为 30 分钟后(用于自动取消)
|
||||
- 支付成功后由支付回调更新状态为 `PaymentStatusPaid`
|
||||
- 代码位置: 第 754-927 行
|
||||
|
||||
### 2.2 订单超时自动取消
|
||||
**文件**: `internal/service/order/service.go` (第 1314-1349 行)
|
||||
|
||||
```go
|
||||
func (s *Service) CancelExpiredOrders(ctx context.Context) (int, error) {
|
||||
// 查询超时订单(expires_at < now)
|
||||
orders, err := s.orderStore.FindExpiredOrders(ctx, constants.OrderExpireBatchSize)
|
||||
// 批量取消订单
|
||||
for _, order := range orders {
|
||||
s.cancelOrder(ctx, order)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**取消逻辑** (第 1351-1385 行):
|
||||
- 只有 `PaymentStatusPending` 的订单才能取消
|
||||
- 更新状态为 `PaymentStatusCancelled`
|
||||
- 清除 `ExpiresAt` 字段
|
||||
- 如果是钱包支付,解冻钱包余额
|
||||
|
||||
**触发方式**:
|
||||
- Asynq Scheduler 每分钟执行一次
|
||||
- 常量: `constants.OrderExpireTimeout = 30 * time.Minute`
|
||||
|
||||
---
|
||||
|
||||
## 三、佣金系统与订单状态的关系
|
||||
|
||||
### 3.1 佣金计算触发条件
|
||||
**文件**: `internal/service/commission_calculation/service.go` (第 74-119 行)
|
||||
|
||||
```go
|
||||
func (s *Service) CalculateCommission(ctx context.Context, orderID uint) error {
|
||||
// 1. 检查订单佣金状态
|
||||
if order.CommissionStatus == model.CommissionStatusCalculated {
|
||||
return nil // 已计算,跳过
|
||||
}
|
||||
|
||||
// 2. 计算成本价差佣金
|
||||
costDiffRecords, err := s.CalculateCostDiffCommission(ctx, order)
|
||||
|
||||
// 3. 入账佣金
|
||||
for _, record := range costDiffRecords {
|
||||
s.creditCommissionInTx(ctx, tx, record)
|
||||
}
|
||||
|
||||
// 4. 触发一次性佣金(仅非代购订单)
|
||||
if !order.IsPurchaseOnBehalf {
|
||||
s.triggerOneTimeCommissionForCardInTx(ctx, tx, order, *order.IotCardID)
|
||||
}
|
||||
|
||||
// 5. 更新订单佣金状态为已计算
|
||||
tx.Model(&model.Order{}).Update("commission_status", CommissionStatusCalculated)
|
||||
}
|
||||
```
|
||||
|
||||
**触发时机**:
|
||||
- 线下支付(offline)订单:创建后立即入队
|
||||
- 钱包支付(wallet)订单:创建后立即入队
|
||||
- 微信/支付宝支付:支付成功回调后入队
|
||||
|
||||
### 3.2 佣金类型与订单关系
|
||||
|
||||
#### 成本价差佣金(Cost Diff Commission)
|
||||
**文件**: `internal/service/commission_calculation/service.go` (第 121-230 行)
|
||||
|
||||
```go
|
||||
func (s *Service) CalculateCostDiffCommission(ctx context.Context, order *model.Order) {
|
||||
// 计算销售店铺的利润
|
||||
sellerProfit := order.TotalAmount - order.SellerCostPrice
|
||||
|
||||
// 沿着代理链向上分配佣金
|
||||
// 每一级代理的佣金 = 该级成本价 - 下级成本价
|
||||
}
|
||||
```
|
||||
|
||||
**关键字段**:
|
||||
- `order.SellerShopID`: 销售店铺ID(佣金归属)
|
||||
- `order.SellerCostPrice`: 销售成本价(用于计算利润)
|
||||
- `order.SeriesID`: 系列ID(用于查询分配配置)
|
||||
|
||||
**佣金状态**: `CommissionStatusReleased`(已入账)
|
||||
|
||||
#### 一次性佣金(One-Time Commission)
|
||||
**触发条件**:
|
||||
- 仅非代购订单触发(`!order.IsPurchaseOnBehalf`)
|
||||
- 单卡首次购买或设备首次购买时触发
|
||||
- 代码位置: 第 102-111 行
|
||||
|
||||
---
|
||||
|
||||
## 四、钱包冻结与佣金提现流程
|
||||
|
||||
### 4.1 钱包冻结状态定义
|
||||
**文件**: `pkg/constants/wallet.go` (第 17-22 行)
|
||||
|
||||
```go
|
||||
const (
|
||||
AgentWalletStatusNormal = 1 // 正常
|
||||
AgentWalletStatusFrozen = 2 // 冻结(钱包整体冻结)
|
||||
AgentWalletStatusClosed = 3 // 关闭
|
||||
)
|
||||
```
|
||||
|
||||
**冻结余额字段**:
|
||||
- `tb_agent_wallet.frozen_balance`: 冻结余额(分)
|
||||
- `tb_agent_wallet.balance`: 总余额(分)
|
||||
- 可用余额 = balance - frozen_balance
|
||||
|
||||
### 4.2 佣金提现流程(3 阶段)
|
||||
|
||||
#### 阶段 1: 代理发起提现申请
|
||||
**文件**: `internal/service/shop_commission/service.go` (第 517-649 行)
|
||||
|
||||
```go
|
||||
func (s *Service) CreateWithdrawalRequest(ctx context.Context, shopID uint, req *dto.CreateMyWithdrawalReq) {
|
||||
// 1. 验证可用余额
|
||||
if req.Amount > wallet.GetAvailableBalance() {
|
||||
return "可提现余额不足"
|
||||
}
|
||||
|
||||
// 2. 冻结余额(在事务中)
|
||||
tx.Model(&AgentWallet{}).
|
||||
Where("id = ? AND balance - frozen_balance >= ?", wallet.ID, req.Amount).
|
||||
Updates(map[string]interface{}{
|
||||
"frozen_balance": gorm.Expr("frozen_balance + ?", req.Amount),
|
||||
})
|
||||
|
||||
// 3. 创建提现申请记录
|
||||
withdrawalRequest = &CommissionWithdrawalRequest{
|
||||
Status: 1, // 待审核
|
||||
}
|
||||
|
||||
// 4. 创建钱包流水
|
||||
transaction = &AgentWalletTransaction{
|
||||
TransactionType: "withdrawal",
|
||||
Amount: -req.Amount,
|
||||
Status: TransactionStatusProcessing,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**状态**: `WithdrawalStatusPending`(待审核)
|
||||
|
||||
#### 阶段 2: 平台审核通过
|
||||
**文件**: `internal/service/commission_withdrawal/service.go` (第 145-256 行)
|
||||
|
||||
```go
|
||||
func (s *Service) Approve(ctx context.Context, id uint, req *dto.ApproveWithdrawalReq) {
|
||||
// 1. 检查冻结余额
|
||||
if wallet.FrozenBalance < amount {
|
||||
return "钱包冻结余额不足"
|
||||
}
|
||||
|
||||
// 2. 从冻结余额扣款(在事务中)
|
||||
s.agentWalletStore.DeductFrozenBalanceWithTx(ctx, tx, wallet.ID, amount)
|
||||
|
||||
// 3. 创建提现交易流水
|
||||
transaction = &AgentWalletTransaction{
|
||||
TransactionType: "withdrawal",
|
||||
Amount: -amount,
|
||||
}
|
||||
|
||||
// 4. 更新提现申请状态
|
||||
updates["status"] = WithdrawalStatusApproved
|
||||
}
|
||||
```
|
||||
|
||||
**状态**: `WithdrawalStatusApproved`(已通过)
|
||||
|
||||
#### 阶段 3: 平台审核拒绝
|
||||
**文件**: `internal/service/commission_withdrawal/service.go` (第 258-310 行)
|
||||
|
||||
```go
|
||||
func (s *Service) Reject(ctx context.Context, id uint, req *dto.RejectWithdrawalReq) {
|
||||
// 1. 解冻余额(在事务中)
|
||||
s.agentWalletStore.UnfreezeBalanceWithTx(ctx, tx, wallet.ID, withdrawal.Amount)
|
||||
|
||||
// 2. 创建退款交易流水
|
||||
transaction = &AgentWalletTransaction{
|
||||
TransactionType: "refund",
|
||||
Amount: withdrawal.Amount, // 正数,增加可用余额
|
||||
}
|
||||
|
||||
// 3. 更新提现申请状态
|
||||
updates["status"] = WithdrawalStatusRejected
|
||||
}
|
||||
```
|
||||
|
||||
**状态**: `WithdrawalStatusRejected`(已拒绝)
|
||||
|
||||
### 4.3 钱包余额计算
|
||||
**文件**: `internal/service/shop_commission/service.go` (第 154-198 行)
|
||||
|
||||
```go
|
||||
func (s *Service) buildFundSummaryItem(shop, mainWallet, commissionWallet) {
|
||||
// 佣金钱包
|
||||
balance = commissionWallet.Balance
|
||||
frozenBalance = commissionWallet.FrozenBalance
|
||||
|
||||
// 总佣金 = 可用 + 冻结 + 已提现
|
||||
totalCommission := balance + frozenBalance + withdrawnAmount
|
||||
|
||||
// 未提现佣金 = 总佣金 - 已提现
|
||||
unwithdrawCommission := totalCommission - withdrawnAmount
|
||||
|
||||
// 可提现佣金 = 可用 - 提现中
|
||||
availableCommission := balance - withdrawingAmount
|
||||
|
||||
return ShopFundSummaryItem{
|
||||
TotalCommission: totalCommission,
|
||||
WithdrawnCommission: withdrawnAmount,
|
||||
UnwithdrawCommission: unwithdrawCommission,
|
||||
FrozenCommission: frozenBalance,
|
||||
WithdrawingCommission: withdrawingAmount,
|
||||
AvailableCommission: availableCommission,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、关键数据模型
|
||||
|
||||
### 5.1 订单模型
|
||||
**文件**: `internal/model/order.go`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `payment_status` | int | 支付状态(1-待支付 2-已支付 3-已取消 4-已退款) |
|
||||
| `commission_status` | int | 佣金状态(1-待计算 2-已计算) |
|
||||
| `seller_shop_id` | uint | 销售店铺ID(用于成本价差佣金) |
|
||||
| `seller_cost_price` | int64 | 销售成本价(分) |
|
||||
| `series_id` | uint | 系列ID(用于查询分配配置) |
|
||||
| `is_purchase_on_behalf` | bool | 是否代购订单 |
|
||||
| `expires_at` | time.Time | 订单过期时间(待支付订单) |
|
||||
|
||||
### 5.2 佣金记录模型
|
||||
**文件**: `internal/model/commission.go`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `shop_id` | uint | 店铺ID(佣金归属) |
|
||||
| `order_id` | uint | 订单ID |
|
||||
| `commission_source` | string | 佣金来源(cost_diff-成本价差 one_time-一次性) |
|
||||
| `amount` | int64 | 佣金金额(分) |
|
||||
| `status` | int | 状态(1-已入账 2-已失效) |
|
||||
|
||||
### 5.3 代理钱包模型
|
||||
**文件**: `internal/model/agent_wallet.go`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `balance` | int64 | 总余额(分) |
|
||||
| `frozen_balance` | int64 | 冻结余额(分) |
|
||||
| `status` | int | 钱包状态(1-正常 2-冻结 3-关闭) |
|
||||
| `version` | int | 版本号(乐观锁) |
|
||||
|
||||
### 5.4 提现申请模型
|
||||
**文件**: `internal/model/commission_withdrawal_request.go`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `status` | int | 状态(1-待审核 2-已通过 3-已拒绝) |
|
||||
| `amount` | int64 | 提现金额(分) |
|
||||
| `frozen_balance` | int64 | 冻结余额(分) |
|
||||
|
||||
---
|
||||
|
||||
## 六、关键常量
|
||||
|
||||
### 6.1 订单相关常量
|
||||
**文件**: `pkg/constants/constants.go`
|
||||
|
||||
```go
|
||||
const (
|
||||
OrderExpireTimeout = 30 * time.Minute // 订单超时时间
|
||||
OrderExpireBatchSize = 100 // 批量处理数量
|
||||
)
|
||||
```
|
||||
|
||||
### 6.2 钱包相关常量
|
||||
**文件**: `pkg/constants/wallet.go`
|
||||
|
||||
```go
|
||||
const (
|
||||
AgentWalletStatusNormal = 1 // 正常
|
||||
AgentWalletStatusFrozen = 2 // 冻结
|
||||
AgentWalletStatusClosed = 3 // 关闭
|
||||
|
||||
AgentTransactionTypeWithdrawal = "withdrawal" // 提现
|
||||
TransactionStatusProcessing = 3 // 处理中
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、订单状态与佣金的完整流程图
|
||||
|
||||
```
|
||||
订单创建
|
||||
├─ 线下支付(offline)
|
||||
│ ├─ PaymentStatus = PaymentStatusPaid(已支付)
|
||||
│ ├─ 立即激活套餐
|
||||
│ ├─ 入队佣金计算
|
||||
│ └─ CommissionStatus = CommissionStatusCalculated
|
||||
│
|
||||
├─ 钱包支付(wallet)
|
||||
│ ├─ PaymentStatus = PaymentStatusPaid(已支付)
|
||||
│ ├─ 扣款 → 激活套餐(事务)
|
||||
│ ├─ 入队佣金计算
|
||||
│ └─ CommissionStatus = CommissionStatusCalculated
|
||||
│
|
||||
└─ 微信/支付宝(wechat/alipay)
|
||||
├─ PaymentStatus = PaymentStatusPending(待支付)
|
||||
├─ ExpiresAt = now + 30min
|
||||
├─ 支付成功回调
|
||||
│ ├─ PaymentStatus = PaymentStatusPaid
|
||||
│ ├─ 激活套餐
|
||||
│ ├─ 入队佣金计算
|
||||
│ └─ CommissionStatus = CommissionStatusCalculated
|
||||
└─ 超时自动取消(30min)
|
||||
├─ PaymentStatus = PaymentStatusCancelled
|
||||
├─ 解冻钱包余额
|
||||
└─ CommissionStatus = CommissionStatusPending(不计算)
|
||||
|
||||
佣金计算流程
|
||||
├─ 成本价差佣金
|
||||
│ ├─ 计算销售店铺利润
|
||||
│ ├─ 沿代理链向上分配
|
||||
│ └─ 入账到分佣钱包
|
||||
│
|
||||
└─ 一次性佣金(非代购订单)
|
||||
├─ 单卡/设备首次购买触发
|
||||
└─ 入账到分佣钱包
|
||||
|
||||
提现流程(3 阶段)
|
||||
├─ 阶段 1: 代理发起申请
|
||||
│ ├─ 验证可用余额
|
||||
│ ├─ 冻结余额(frozen_balance += amount)
|
||||
│ ├─ 创建提现申请(Status = 待审核)
|
||||
│ └─ 创建钱包流水(Status = 处理中)
|
||||
│
|
||||
├─ 阶段 2a: 平台审核通过
|
||||
│ ├─ 从冻结余额扣款(frozen_balance -= amount)
|
||||
│ ├─ 创建提现交易流水
|
||||
│ └─ 更新提现申请(Status = 已通过)
|
||||
│
|
||||
└─ 阶段 2b: 平台审核拒绝
|
||||
├─ 解冻余额(frozen_balance -= amount)
|
||||
├─ 创建退款交易流水
|
||||
└─ 更新提现申请(Status = 已拒绝)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、重要业务规则
|
||||
|
||||
### 8.1 订单支付规则
|
||||
1. **待支付订单自动取消**: 30 分钟未支付自动取消,解冻钱包余额
|
||||
2. **幂等性检查**: 防止同一买家对同一资源短时间内重复下单
|
||||
3. **强充要求**: 首次购买需满足最低充值要求
|
||||
|
||||
### 8.2 佣金计算规则
|
||||
1. **成本价差佣金**: 仅当 `seller_profit > 0` 时才计算
|
||||
2. **一次性佣金**: 仅非代购订单触发,且仅首次购买时触发
|
||||
3. **佣金链路**: 沿代理层级向上分配,若中间断裂则标记为待审核
|
||||
|
||||
### 8.3 提现规则
|
||||
1. **冻结机制**: 提现申请时冻结余额,防止重复提现
|
||||
2. **可用余额**: balance - frozen_balance,用于验证提现金额
|
||||
3. **手续费**: 提现时扣除手续费,实际到账 = 申请金额 - 手续费
|
||||
|
||||
---
|
||||
|
||||
## 九、相关文件索引
|
||||
|
||||
| 功能 | 文件 | 关键行数 |
|
||||
|------|------|---------|
|
||||
| 订单模型 | `internal/model/order.go` | 9-138 |
|
||||
| 订单创建 | `internal/service/order/service.go` | 114-354, 359-674, 679-928 |
|
||||
| 订单超时取消 | `internal/service/order/service.go` | 1314-1385 |
|
||||
| 佣金计算 | `internal/service/commission_calculation/service.go` | 74-230 |
|
||||
| 提现申请 | `internal/service/shop_commission/service.go` | 517-649 |
|
||||
| 提现审核 | `internal/service/commission_withdrawal/service.go` | 145-310 |
|
||||
| 钱包模型 | `internal/model/agent_wallet.go` | 9-94 |
|
||||
| 钱包常量 | `pkg/constants/wallet.go` | 1-234 |
|
||||
| 订单常量 | `pkg/constants/constants.go` | 153-166 |
|
||||
|
||||
196
docs/order_status_quick_reference.md
Normal file
196
docs/order_status_quick_reference.md
Normal file
@@ -0,0 +1,196 @@
|
||||
# 订单状态与佣金系统 - 快速参考
|
||||
|
||||
## 订单支付状态速查表
|
||||
|
||||
| 状态值 | 常量名 | 说明 | 触发条件 | 后续操作 |
|
||||
|-------|-------|------|---------|---------|
|
||||
| 1 | `PaymentStatusPending` | 待支付 | 微信/支付宝支付创建订单 | 支付回调或30分钟超时 |
|
||||
| 2 | `PaymentStatusPaid` | 已支付 | 线下/钱包支付或支付成功回调 | 激活套餐、计算佣金 |
|
||||
| 3 | `PaymentStatusCancelled` | 已取消 | 待支付订单超时30分钟 | 解冻钱包余额 |
|
||||
| 4 | `PaymentStatusRefunded` | 已退款 | 退款操作 | 返还金额 |
|
||||
|
||||
## 订单佣金状态速查表
|
||||
|
||||
| 状态值 | 常量名 | 说明 | 触发条件 | 后续操作 |
|
||||
|-------|-------|------|---------|---------|
|
||||
| 1 | `CommissionStatusPending` | 待计算 | 订单创建时默认 | 异步任务计算佣金 |
|
||||
| 2 | `CommissionStatusCalculated` | 已计算 | 佣金计算完成 | 佣金入账到分佣钱包 |
|
||||
|
||||
## 订单创建流程速查
|
||||
|
||||
### 线下支付(offline)
|
||||
```
|
||||
创建订单 → PaymentStatus=2(已支付) → 激活套餐 → 入队佣金计算
|
||||
```
|
||||
|
||||
### 钱包支付(wallet)
|
||||
```
|
||||
创建订单 → PaymentStatus=2(已支付) → 扣款 → 激活套餐 → 入队佣金计算
|
||||
```
|
||||
|
||||
### 微信/支付宝(wechat/alipay)
|
||||
```
|
||||
创建订单 → PaymentStatus=1(待支付) → 支付回调 → PaymentStatus=2 → 激活套餐 → 入队佣金计算
|
||||
↓
|
||||
30分钟超时 → PaymentStatus=3(已取消) → 解冻余额
|
||||
```
|
||||
|
||||
## 钱包冻结状态速查表
|
||||
|
||||
| 字段 | 说明 | 计算方式 |
|
||||
|------|------|---------|
|
||||
| `balance` | 总余额 | 充值 + 佣金入账 - 扣款 |
|
||||
| `frozen_balance` | 冻结余额 | 提现申请时冻结 |
|
||||
| 可用余额 | 可提现金额 | `balance - frozen_balance` |
|
||||
|
||||
## 提现流程速查
|
||||
|
||||
### 阶段 1: 代理发起申请
|
||||
```
|
||||
验证可用余额 ✓
|
||||
↓
|
||||
冻结余额 (frozen_balance += amount)
|
||||
↓
|
||||
创建提现申请 (Status=1 待审核)
|
||||
↓
|
||||
创建钱包流水 (Status=3 处理中)
|
||||
```
|
||||
|
||||
### 阶段 2a: 平台审核通过
|
||||
```
|
||||
检查冻结余额 ✓
|
||||
↓
|
||||
从冻结余额扣款 (frozen_balance -= amount)
|
||||
↓
|
||||
创建提现交易流水
|
||||
↓
|
||||
更新提现申请 (Status=2 已通过)
|
||||
```
|
||||
|
||||
### 阶段 2b: 平台审核拒绝
|
||||
```
|
||||
解冻余额 (frozen_balance -= amount)
|
||||
↓
|
||||
创建退款交易流水
|
||||
↓
|
||||
更新提现申请 (Status=3 已拒绝)
|
||||
```
|
||||
|
||||
## 佣金类型速查
|
||||
|
||||
| 佣金类型 | 常量值 | 触发条件 | 计算方式 |
|
||||
|---------|-------|---------|---------|
|
||||
| 成本价差 | `cost_diff` | 所有订单 | 销售价 - 销售成本价 |
|
||||
| 一次性 | `one_time` | 非代购订单首次购买 | 根据配置 |
|
||||
|
||||
## 关键代码位置速查
|
||||
|
||||
### 订单相关
|
||||
- 订单模型: `internal/model/order.go:98-110`
|
||||
- 订单创建: `internal/service/order/service.go:114-928`
|
||||
- 订单超时: `internal/service/order/service.go:1314-1385`
|
||||
|
||||
### 佣金相关
|
||||
- 佣金计算: `internal/service/commission_calculation/service.go:74-119`
|
||||
- 成本价差: `internal/service/commission_calculation/service.go:121-230`
|
||||
|
||||
### 提现相关
|
||||
- 发起申请: `internal/service/shop_commission/service.go:517-649`
|
||||
- 审核通过: `internal/service/commission_withdrawal/service.go:145-256`
|
||||
- 审核拒绝: `internal/service/commission_withdrawal/service.go:258-310`
|
||||
|
||||
## 常用常量速查
|
||||
|
||||
```go
|
||||
// 订单超时
|
||||
constants.OrderExpireTimeout = 30 * time.Minute
|
||||
|
||||
// 钱包状态
|
||||
constants.AgentWalletStatusNormal = 1 // 正常
|
||||
constants.AgentWalletStatusFrozen = 2 // 冻结
|
||||
constants.AgentWalletStatusClosed = 3 // 关闭
|
||||
|
||||
// 交易状态
|
||||
constants.TransactionStatusSuccess = 1 // 成功
|
||||
constants.TransactionStatusFailed = 2 // 失败
|
||||
constants.TransactionStatusProcessing = 3 // 处理中
|
||||
|
||||
// 提现状态
|
||||
constants.WithdrawalStatusPending = 1 // 待审核
|
||||
constants.WithdrawalStatusApproved = 2 // 已通过
|
||||
constants.WithdrawalStatusRejected = 3 // 已拒绝
|
||||
```
|
||||
|
||||
## 常见问题速查
|
||||
|
||||
### Q: 订单什么时候计算佣金?
|
||||
A: 订单支付成功后立即入队佣金计算任务(异步)。线下/钱包支付创建时已支付,微信/支付宝支付在回调时支付。
|
||||
|
||||
### Q: 提现时冻结的余额什么时候解冻?
|
||||
A:
|
||||
- 审核通过:从冻结余额扣款(不解冻,直接扣除)
|
||||
- 审核拒绝:解冻余额回到可用余额
|
||||
|
||||
### Q: 可用余额如何计算?
|
||||
A: `可用余额 = 总余额 - 冻结余额`
|
||||
|
||||
### Q: 订单超时自动取消后还会计算佣金吗?
|
||||
A: 不会。取消的订单佣金状态保持为 `待计算`,不会入队计算。
|
||||
|
||||
### Q: 代购订单是否计算一次性佣金?
|
||||
A: 不计算。一次性佣金仅对非代购订单触发。
|
||||
|
||||
### Q: 成本价差佣金如何沿代理链分配?
|
||||
A: 从销售店铺开始,沿着 `parent_id` 向上逐级分配。每一级的佣金 = 该级成本价 - 下级成本价。
|
||||
|
||||
## 数据库查询速查
|
||||
|
||||
### 查询待支付订单
|
||||
```sql
|
||||
SELECT * FROM tb_order WHERE payment_status = 1 AND expires_at < NOW();
|
||||
```
|
||||
|
||||
### 查询待计算佣金的订单
|
||||
```sql
|
||||
SELECT * FROM tb_order WHERE commission_status = 1;
|
||||
```
|
||||
|
||||
### 查询店铺可用余额
|
||||
```sql
|
||||
SELECT balance - frozen_balance as available_balance
|
||||
FROM tb_agent_wallet
|
||||
WHERE shop_id = ? AND wallet_type = 'commission';
|
||||
```
|
||||
|
||||
### 查询提现中的金额
|
||||
```sql
|
||||
SELECT SUM(amount) as withdrawing_amount
|
||||
FROM tb_commission_withdrawal_request
|
||||
WHERE shop_id = ? AND status = 1;
|
||||
```
|
||||
|
||||
### 查询已提现的金额
|
||||
```sql
|
||||
SELECT SUM(amount) as withdrawn_amount
|
||||
FROM tb_commission_withdrawal_request
|
||||
WHERE shop_id = ? AND status = 2;
|
||||
```
|
||||
|
||||
## 业务规则速查
|
||||
|
||||
1. **订单支付规则**
|
||||
- 待支付订单 30 分钟未支付自动取消
|
||||
- 取消时解冻钱包余额(如有)
|
||||
- 防止重复下单(幂等性检查)
|
||||
|
||||
2. **佣金计算规则**
|
||||
- 成本价差佣金:仅当 `销售价 > 销售成本价` 时计算
|
||||
- 一次性佣金:仅非代购订单首次购买时触发
|
||||
- 佣金链路断裂时标记为待审核
|
||||
|
||||
3. **提现规则**
|
||||
- 提现申请时冻结余额
|
||||
- 可用余额 = 总余额 - 冻结余额
|
||||
- 手续费 = 提现金额 × 费率 / 10000
|
||||
- 实际到账 = 提现金额 - 手续费
|
||||
|
||||
174
docs/order_status_search_summary.md
Normal file
174
docs/order_status_search_summary.md
Normal file
@@ -0,0 +1,174 @@
|
||||
# 订单状态与佣金系统搜索结果总结
|
||||
|
||||
## 搜索范围
|
||||
- 关键词:订单状态、冻结、差价佣金、order status、frozen、commission
|
||||
- 搜索范围:整个项目代码库
|
||||
- 文件类型:Go 源代码文件
|
||||
|
||||
## 核心发现
|
||||
|
||||
### 1. 订单状态定义(无冻结状态)
|
||||
**重要发现**:订单本身**没有冻结状态**,只有支付状态和佣金状态。
|
||||
|
||||
#### 订单支付状态(4 种)
|
||||
- `PaymentStatusPending = 1`:待支付
|
||||
- `PaymentStatusPaid = 2`:已支付
|
||||
- `PaymentStatusCancelled = 3`:已取消
|
||||
- `PaymentStatusRefunded = 4`:已退款
|
||||
|
||||
#### 订单佣金状态(2 种)
|
||||
- `CommissionStatusPending = 1`:待计算
|
||||
- `CommissionStatusCalculated = 2`:已计算
|
||||
|
||||
**文件位置**:`internal/model/order.go:98-110`
|
||||
|
||||
### 2. 冻结状态存在于钱包,不在订单
|
||||
**重要发现**:冻结状态是钱包的概念,用于提现流程。
|
||||
|
||||
#### 代理钱包状态(3 种)
|
||||
- `AgentWalletStatusNormal = 1`:正常
|
||||
- `AgentWalletStatusFrozen = 2`:冻结(整个钱包冻结)
|
||||
- `AgentWalletStatusClosed = 3`:关闭
|
||||
|
||||
#### 钱包冻结余额字段
|
||||
- `balance`:总余额
|
||||
- `frozen_balance`:冻结余额(用于提现)
|
||||
- 可用余额 = balance - frozen_balance
|
||||
|
||||
**文件位置**:`pkg/constants/wallet.go:17-22`、`internal/model/agent_wallet.go:15-16`
|
||||
|
||||
### 3. 订单状态流转逻辑
|
||||
|
||||
#### 支付方式决定初始状态
|
||||
1. **线下支付(offline)**:创建时直接为 `PaymentStatusPaid`
|
||||
2. **钱包支付(wallet)**:创建时直接为 `PaymentStatusPaid`
|
||||
3. **微信/支付宝(wechat/alipay)**:创建时为 `PaymentStatusPending`,支付回调后变为 `PaymentStatusPaid`
|
||||
|
||||
#### 订单超时自动取消
|
||||
- 待支付订单 30 分钟未支付自动取消
|
||||
- 状态变为 `PaymentStatusCancelled`
|
||||
- 解冻钱包余额(如有)
|
||||
|
||||
**文件位置**:`internal/service/order/service.go:1314-1385`
|
||||
|
||||
### 4. 订单与佣金的关系
|
||||
|
||||
#### 佣金计算触发条件
|
||||
- 订单支付成功后立即入队佣金计算任务
|
||||
- 异步处理,不阻塞订单创建
|
||||
|
||||
#### 佣金类型
|
||||
1. **成本价差佣金**(cost_diff)
|
||||
- 计算方式:销售价 - 销售成本价
|
||||
- 沿代理链向上分配
|
||||
|
||||
2. **一次性佣金**(one_time)
|
||||
- 仅非代购订单触发
|
||||
- 仅首次购买时触发
|
||||
|
||||
**文件位置**:`internal/service/commission_calculation/service.go:74-230`
|
||||
|
||||
### 5. 佣金提现流程(3 阶段)
|
||||
|
||||
#### 阶段 1:代理发起申请
|
||||
- 验证可用余额
|
||||
- **冻结余额**(frozen_balance += amount)
|
||||
- 创建提现申请(Status = 待审核)
|
||||
|
||||
#### 阶段 2a:平台审核通过
|
||||
- 检查冻结余额
|
||||
- **从冻结余额扣款**(frozen_balance -= amount)
|
||||
- 更新提现申请(Status = 已通过)
|
||||
|
||||
#### 阶段 2b:平台审核拒绝
|
||||
- **解冻余额**(frozen_balance -= amount)
|
||||
- 更新提现申请(Status = 已拒绝)
|
||||
|
||||
**文件位置**:
|
||||
- 发起申请:`internal/service/shop_commission/service.go:517-649`
|
||||
- 审核通过:`internal/service/commission_withdrawal/service.go:145-256`
|
||||
- 审核拒绝:`internal/service/commission_withdrawal/service.go:258-310`
|
||||
|
||||
## 关键代码位置
|
||||
|
||||
| 功能 | 文件 | 行数 |
|
||||
|------|------|------|
|
||||
| 订单模型定义 | `internal/model/order.go` | 9-138 |
|
||||
| 订单支付状态常量 | `internal/model/order.go` | 98-104 |
|
||||
| 订单佣金状态常量 | `internal/model/order.go` | 106-110 |
|
||||
| 订单创建逻辑 | `internal/service/order/service.go` | 114-928 |
|
||||
| 订单超时取消 | `internal/service/order/service.go` | 1314-1385 |
|
||||
| 佣金计算 | `internal/service/commission_calculation/service.go` | 74-230 |
|
||||
| 提现申请 | `internal/service/shop_commission/service.go` | 517-649 |
|
||||
| 提现审核 | `internal/service/commission_withdrawal/service.go` | 145-310 |
|
||||
| 钱包模型 | `internal/model/agent_wallet.go` | 9-94 |
|
||||
| 钱包常量 | `pkg/constants/wallet.go` | 1-234 |
|
||||
|
||||
## 重要业务规则
|
||||
|
||||
### 订单支付规则
|
||||
1. 待支付订单 30 分钟未支付自动取消
|
||||
2. 取消时解冻钱包余额(如有)
|
||||
3. 防止重复下单(幂等性检查)
|
||||
|
||||
### 佣金计算规则
|
||||
1. 成本价差佣金:仅当 `销售价 > 销售成本价` 时计算
|
||||
2. 一次性佣金:仅非代购订单首次购买时触发
|
||||
3. 佣金链路断裂时标记为待审核
|
||||
|
||||
### 提现规则
|
||||
1. 提现申请时冻结余额
|
||||
2. 可用余额 = 总余额 - 冻结余额
|
||||
3. 手续费 = 提现金额 × 费率 / 10000
|
||||
|
||||
## 数据流向图
|
||||
|
||||
```
|
||||
订单创建
|
||||
├─ 线下/钱包支付 → PaymentStatus=2(已支付) → 激活套餐 → 入队佣金计算
|
||||
└─ 微信/支付宝 → PaymentStatus=1(待支付) → 支付回调 → PaymentStatus=2 → 激活套餐 → 入队佣金计算
|
||||
↓
|
||||
30分钟超时 → PaymentStatus=3(已取消)
|
||||
|
||||
佣金计算
|
||||
├─ 成本价差佣金:销售价 - 销售成本价 → 沿代理链分配 → 入账分佣钱包
|
||||
└─ 一次性佣金:非代购订单首次购买 → 入账分佣钱包
|
||||
|
||||
提现流程
|
||||
├─ 代理申请 → 冻结余额 → 创建申请(待审核)
|
||||
├─ 平台通过 → 从冻结余额扣款 → 更新申请(已通过)
|
||||
└─ 平台拒绝 → 解冻余额 → 更新申请(已拒绝)
|
||||
```
|
||||
|
||||
## 生成的文档
|
||||
|
||||
已生成两份详细文档:
|
||||
|
||||
1. **order_status_commission_analysis.md**
|
||||
- 完整的系统分析报告
|
||||
- 包含所有状态定义、流程、数据模型
|
||||
- 适合深入理解系统设计
|
||||
|
||||
2. **order_status_quick_reference.md**
|
||||
- 快速参考指南
|
||||
- 包含速查表、常见问题、SQL 查询
|
||||
- 适合日常开发查询
|
||||
|
||||
## 关键发现总结
|
||||
|
||||
### ✅ 已确认
|
||||
- 订单有支付状态和佣金状态,但**没有冻结状态**
|
||||
- 冻结状态存在于**代理钱包**,用于提现流程
|
||||
- 差价佣金通过 `seller_cost_price` 字段计算
|
||||
- 佣金与订单的关系通过 `commission_status` 字段维护
|
||||
|
||||
### ⚠️ 注意事项
|
||||
- 订单超时自动取消不会触发佣金计算
|
||||
- 代购订单不计算一次性佣金
|
||||
- 提现时冻结的余额在审核通过时直接扣款,不是解冻后再扣
|
||||
|
||||
### 📌 最佳实践
|
||||
- 查询订单时同时检查 `payment_status` 和 `commission_status`
|
||||
- 查询可提现金额时使用 `balance - frozen_balance`
|
||||
- 提现流程中使用事务确保数据一致性
|
||||
|
||||
366
docs/package_architecture_diagram.md
Normal file
366
docs/package_architecture_diagram.md
Normal file
@@ -0,0 +1,366 @@
|
||||
# 套餐接口架构图
|
||||
|
||||
## 1. 整体架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ HTTP 请求 │
|
||||
│ POST /api/admin/packages │
|
||||
└────────────────────────────┬────────────────────────────────────┘
|
||||
│
|
||||
┌────────▼────────┐
|
||||
│ 认证中间件 │
|
||||
│ (AdminAuth) │
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌────────────────────▼────────────────────┐
|
||||
│ 路由注册 (routes/admin.go) │
|
||||
│ registerPackageRoutes() │
|
||||
│ ↓ │
|
||||
│ internal/routes/package.go │
|
||||
└────────────────────┬────────────────────┘
|
||||
│
|
||||
┌────────────────────▼────────────────────┐
|
||||
│ PackageHandler │
|
||||
│ (internal/handler/admin/package.go) │
|
||||
│ │
|
||||
│ - List() │
|
||||
│ - Create() │
|
||||
│ - Get() │
|
||||
│ - Update() │
|
||||
│ - Delete() │
|
||||
│ - UpdateStatus() │
|
||||
│ - UpdateShelfStatus() │
|
||||
│ - UpdateRetailPrice() │
|
||||
└────────────────────┬────────────────────┘
|
||||
│
|
||||
┌────────────────────▼────────────────────┐
|
||||
│ PackageService │
|
||||
│ (internal/service/package/service.go) │
|
||||
│ │
|
||||
│ 业务逻辑: │
|
||||
│ - 虚流量校验 │
|
||||
│ - 周期类型校验 │
|
||||
│ - 代理权限检查 │
|
||||
│ - DTO 转换 │
|
||||
└────────────────────┬────────────────────┘
|
||||
│
|
||||
┌────────────────────▼────────────────────┐
|
||||
│ PackageStore │
|
||||
│ (internal/store/postgres/...) │
|
||||
│ │
|
||||
│ - PackageStore │
|
||||
│ - PackageSeriesStore │
|
||||
│ - ShopPackageAllocationStore │
|
||||
│ - ShopSeriesAllocationStore │
|
||||
└────────────────────┬────────────────────┘
|
||||
│
|
||||
┌────────────────────▼────────────────────┐
|
||||
│ PostgreSQL 数据库 │
|
||||
│ │
|
||||
│ - tb_package │
|
||||
│ - tb_package_series │
|
||||
│ - tb_shop_package_allocation │
|
||||
│ - tb_shop_series_allocation │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 分层详细结构
|
||||
|
||||
### Handler 层
|
||||
```
|
||||
PackageHandler
|
||||
├── List(c *fiber.Ctx)
|
||||
│ └── 解析查询参数 → 调用 Service.List() → 返回分页结果
|
||||
├── Create(c *fiber.Ctx)
|
||||
│ └── 解析请求体 → 调用 Service.Create() → 返回创建结果
|
||||
├── Get(c *fiber.Ctx)
|
||||
│ └── 解析 ID 参数 → 调用 Service.Get() → 返回详情
|
||||
├── Update(c *fiber.Ctx)
|
||||
│ └── 解析 ID 和请求体 → 调用 Service.Update() → 返回更新结果
|
||||
├── Delete(c *fiber.Ctx)
|
||||
│ └── 解析 ID 参数 → 调用 Service.Delete() → 返回删除结果
|
||||
├── UpdateStatus(c *fiber.Ctx)
|
||||
│ └── 解析 ID 和状态 → 调用 Service.UpdateStatus() → 返回结果
|
||||
├── UpdateShelfStatus(c *fiber.Ctx)
|
||||
│ └── 解析 ID 和上架状态 → 调用 Service.UpdateShelfStatus() → 返回结果
|
||||
└── UpdateRetailPrice(c *fiber.Ctx)
|
||||
└── 解析 ID 和零售价 → 调用 Service.UpdateRetailPrice() → 返回结果
|
||||
```
|
||||
|
||||
### Service 层
|
||||
```
|
||||
PackageService
|
||||
├── Create(ctx, req)
|
||||
│ ├── 校验套餐编码唯一性
|
||||
│ ├── 校验虚流量配置
|
||||
│ │ ├── 启用时虚流量 > 0
|
||||
│ │ └── 虚流量 ≤ 真流量
|
||||
│ ├── 校验周期类型和时长
|
||||
│ │ ├── natural_month: 需要 duration_months
|
||||
│ │ └── by_day: 需要 duration_days
|
||||
│ ├── 查询套餐系列信息
|
||||
│ ├── 创建 Package 模型
|
||||
│ └── 调用 Store.Create()
|
||||
├── List(ctx, req)
|
||||
│ ├── 获取用户类型和店铺 ID
|
||||
│ ├── 代理用户额外过滤
|
||||
│ │ └── INNER JOIN tb_shop_package_allocation
|
||||
│ ├── 应用其他过滤条件
|
||||
│ └── 调用 Store.List()
|
||||
├── Get(ctx, id)
|
||||
│ └── 调用 Store.GetByID()
|
||||
├── Update(ctx, id, req)
|
||||
│ ├── 获取现有套餐
|
||||
│ ├── 更新字段
|
||||
│ └── 调用 Store.Update()
|
||||
├── Delete(ctx, id)
|
||||
│ └── 调用 Store.Delete()
|
||||
├── UpdateStatus(ctx, id, status)
|
||||
│ └── 更新状态字段
|
||||
├── UpdateShelfStatus(ctx, id, shelfStatus)
|
||||
│ └── 更新上架状态字段
|
||||
└── UpdateRetailPrice(ctx, id, price)
|
||||
└── 更新零售价字段
|
||||
```
|
||||
|
||||
### Store 层
|
||||
```
|
||||
PackageStore
|
||||
├── Create(ctx, pkg)
|
||||
│ └── db.Create(pkg)
|
||||
├── GetByID(ctx, id)
|
||||
│ └── db.First(&pkg, id)
|
||||
├── GetByCode(ctx, code)
|
||||
│ └── db.Where("package_code = ?", code).First(&pkg)
|
||||
├── Update(ctx, pkg)
|
||||
│ └── db.Save(pkg)
|
||||
├── Delete(ctx, id)
|
||||
│ └── db.Delete(&Package{}, id)
|
||||
└── List(ctx, opts, filters)
|
||||
├── 构建基础查询
|
||||
├── 代理用户 JOIN 分配表
|
||||
├── 应用过滤条件
|
||||
│ ├── package_name (模糊搜索)
|
||||
│ ├── series_id
|
||||
│ ├── status
|
||||
│ ├── shelf_status
|
||||
│ └── package_type
|
||||
├── 计算总数
|
||||
├── 应用分页
|
||||
└── 执行查询
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 数据模型关系
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ tb_package (套餐表) │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ id (PK) │
|
||||
│ package_code (UNIQUE) │
|
||||
│ package_name │
|
||||
│ series_id (FK → tb_package_series) │
|
||||
│ package_type (formal/addon) │
|
||||
│ duration_months │
|
||||
│ duration_days ← 关键字段 │
|
||||
│ calendar_type (natural_month/by_day) │
|
||||
│ real_data_mb │
|
||||
│ virtual_data_mb │
|
||||
│ enable_virtual_data │
|
||||
│ data_reset_cycle │
|
||||
│ expiry_base │
|
||||
│ cost_price │
|
||||
│ suggested_retail_price │
|
||||
│ shelf_status │
|
||||
│ status │
|
||||
│ created_at, updated_at, deleted_at │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
│ 1:N
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ tb_package_usage (套餐使用表) │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ id (PK) │
|
||||
│ order_id (FK) │
|
||||
│ package_id (FK → tb_package) │
|
||||
│ usage_type (single_card/device) │
|
||||
│ iot_card_id / device_id │
|
||||
│ data_limit_mb │
|
||||
│ data_usage_mb │
|
||||
│ activated_at, expires_at │
|
||||
│ status │
|
||||
│ priority │
|
||||
│ master_usage_id (加油包关联主套餐) │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
│ 1:N
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ tb_package_usage_daily_record (日记录表) │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ id (PK) │
|
||||
│ package_usage_id (FK) │
|
||||
│ date │
|
||||
│ daily_usage_mb │
|
||||
│ cumulative_usage_mb │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ tb_package_series (套餐系列表) │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ id (PK) │
|
||||
│ series_code (UNIQUE) │
|
||||
│ series_name │
|
||||
│ description │
|
||||
│ enable_one_time_commission │
|
||||
│ one_time_commission_config (JSONB) │
|
||||
│ status │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
│ 1:N
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ tb_shop_package_allocation (分配表) │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ id (PK) │
|
||||
│ shop_id (FK → tb_shop) │
|
||||
│ package_id (FK → tb_package) │
|
||||
│ retail_price │
|
||||
│ shelf_status │
|
||||
│ status │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 请求流程示例
|
||||
|
||||
### 创建套餐(按天)
|
||||
|
||||
```
|
||||
1. HTTP 请求
|
||||
POST /api/admin/packages
|
||||
{
|
||||
"package_code": "PKG_DAY_30",
|
||||
"package_name": "30天套餐",
|
||||
"calendar_type": "by_day",
|
||||
"duration_days": 30,
|
||||
...
|
||||
}
|
||||
|
||||
2. Handler.Create()
|
||||
├─ 解析请求体 → CreatePackageRequest
|
||||
└─ 调用 Service.Create(ctx, req)
|
||||
|
||||
3. Service.Create()
|
||||
├─ 校验 package_code 唯一性
|
||||
│ └─ Store.GetByCode(ctx, "PKG_DAY_30") → nil ✓
|
||||
├─ 校验虚流量配置 ✓
|
||||
├─ 校验周期类型
|
||||
│ ├─ calendar_type = "by_day" ✓
|
||||
│ ├─ duration_days = 30 ✓
|
||||
│ └─ 校验通过 ✓
|
||||
├─ 查询套餐系列 (可选)
|
||||
├─ 创建 Package 模型
|
||||
└─ Store.Create(ctx, pkg)
|
||||
|
||||
4. Store.Create()
|
||||
└─ db.Create(pkg)
|
||||
└─ INSERT INTO tb_package (...)
|
||||
|
||||
5. Service 返回 PackageResponse
|
||||
└─ Handler 返回 HTTP 200
|
||||
|
||||
6. HTTP 响应
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"id": 1,
|
||||
"package_code": "PKG_DAY_30",
|
||||
"duration_days": 30,
|
||||
...
|
||||
},
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 代理用户套餐查询流程
|
||||
|
||||
```
|
||||
1. 代理用户请求
|
||||
GET /api/admin/packages?status=1
|
||||
|
||||
2. Handler.List()
|
||||
└─ Service.List(ctx, req)
|
||||
|
||||
3. Service.List()
|
||||
├─ 获取用户类型: UserTypeAgent ✓
|
||||
├─ 获取店铺 ID: 10 ✓
|
||||
└─ Store.List(ctx, opts, filters)
|
||||
|
||||
4. Store.List()
|
||||
├─ 构建基础查询
|
||||
│ └─ SELECT * FROM tb_package
|
||||
├─ 检测代理用户
|
||||
│ └─ isAgent = true ✓
|
||||
├─ 添加 JOIN 条件
|
||||
│ └─ INNER JOIN tb_shop_package_allocation
|
||||
│ ON tb_shop_package_allocation.package_id = tb_package.id
|
||||
│ AND tb_shop_package_allocation.deleted_at IS NULL
|
||||
├─ 添加 WHERE 条件
|
||||
│ └─ WHERE tb_shop_package_allocation.shop_id = 10
|
||||
│ AND tb_shop_package_allocation.status = 1
|
||||
├─ 应用其他过滤
|
||||
│ └─ AND tb_package.status = 1
|
||||
├─ 计算总数
|
||||
├─ 应用分页
|
||||
└─ 执行查询
|
||||
|
||||
5. 返回结果
|
||||
└─ 只返回已分配给店铺 10 的套餐
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 字段验证流程
|
||||
|
||||
```
|
||||
CreatePackageRequest
|
||||
├─ package_code
|
||||
│ └─ validate: required, min=1, max=100
|
||||
│ └─ Service: 检查唯一性
|
||||
├─ package_name
|
||||
│ └─ validate: required, min=1, max=255
|
||||
├─ calendar_type
|
||||
│ └─ validate: oneof=natural_month by_day
|
||||
│ └─ Service: 根据类型校验时长字段
|
||||
├─ duration_months
|
||||
│ └─ validate: required, min=1, max=120
|
||||
│ └─ Service: 当 calendar_type=natural_month 时必填
|
||||
├─ duration_days
|
||||
│ └─ validate: omitempty, min=1, max=3650
|
||||
│ └─ Service: 当 calendar_type=by_day 时必填
|
||||
├─ real_data_mb
|
||||
│ └─ validate: omitempty, min=0
|
||||
├─ virtual_data_mb
|
||||
│ └─ validate: omitempty, min=0
|
||||
│ └─ Service: 启用虚流量时必须 > 0 且 ≤ real_data_mb
|
||||
├─ enable_virtual_data
|
||||
│ └─ Service: 虚流量配置校验
|
||||
├─ cost_price
|
||||
│ └─ validate: required, min=0
|
||||
└─ suggested_retail_price
|
||||
└─ validate: omitempty, min=0
|
||||
```
|
||||
|
||||
147
docs/package_quick_navigation.md
Normal file
147
docs/package_quick_navigation.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# 套餐接口快速导航
|
||||
|
||||
## 🎯 快速查找
|
||||
|
||||
### 按功能查找
|
||||
|
||||
| 需求 | 文件 | 行号 |
|
||||
|------|------|------|
|
||||
| 查看套餐数据模型 | `internal/model/package.go` | 28-56 |
|
||||
| 查看套餐 API 请求/响应结构 | `internal/model/dto/package_dto.go` | 1-173 |
|
||||
| 查看套餐 HTTP 处理器 | `internal/handler/admin/package.go` | 1-148 |
|
||||
| 查看套餐业务逻辑 | `internal/service/package/service.go` | 1-745 |
|
||||
| 查看套餐数据访问 | `internal/store/postgres/package_store.go` | 1-150 |
|
||||
| 查看套餐 API 路由 | `internal/routes/package.go` | 1-78 |
|
||||
| 查看路由注册入口 | `internal/routes/admin.go` | 71-78 |
|
||||
|
||||
### 按字段查找
|
||||
|
||||
| 字段 | Model 位置 | DTO 位置 | 说明 |
|
||||
|------|-----------|---------|------|
|
||||
| `duration_days` | `package.go:46` | `package_dto.go:96` | 套餐天数(按天时必填) |
|
||||
| `calendar_type` | `package.go:45` | `package_dto.go:95` | 套餐周期类型 |
|
||||
| `duration_months` | `package.go:37` | `package_dto.go:79` | 套餐时长(月数) |
|
||||
| `real_data_mb` | `package.go:38` | `package_dto.go:80` | 真流量额度 |
|
||||
| `virtual_data_mb` | `package.go:39` | `package_dto.go:81` | 虚流量额度 |
|
||||
| `enable_virtual_data` | `package.go:40` | `package_dto.go:82` | 是否启用虚流量 |
|
||||
|
||||
### 按 API 端点查找
|
||||
|
||||
| 端点 | Handler 方法 | 路由文件 | 行号 |
|
||||
|------|-------------|---------|------|
|
||||
| `GET /packages` | `List()` | `package.go` | 15-21 |
|
||||
| `POST /packages` | `Create()` | `package.go` | 23-29 |
|
||||
| `GET /packages/:id` | `Get()` | `package.go` | 31-37 |
|
||||
| `PUT /packages/:id` | `Update()` | `package.go` | 39-45 |
|
||||
| `DELETE /packages/:id` | `Delete()` | `package.go` | 47-53 |
|
||||
| `PATCH /packages/:id/status` | `UpdateStatus()` | `package.go` | 55-61 |
|
||||
| `PATCH /packages/:id/shelf` | `UpdateShelfStatus()` | `package.go` | 63-69 |
|
||||
| `PATCH /packages/:id/retail-price` | `UpdateRetailPrice()` | `package.go` | 71-77 |
|
||||
|
||||
---
|
||||
|
||||
## 🔍 关键代码片段
|
||||
|
||||
### 1. 套餐周期类型校验
|
||||
**文件**:`internal/service/package/service.go:65-80`
|
||||
|
||||
**用途**:创建套餐时验证 `calendar_type` 和 `duration_days` 的配合
|
||||
|
||||
```go
|
||||
if calendarType == constants.PackageCalendarTypeByDay {
|
||||
if req.DurationDays == nil || *req.DurationDays <= 0 {
|
||||
return nil, errors.New(errors.CodeInvalidParam, "按天套餐必须提供有效的duration_days")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 虚流量配置校验
|
||||
**文件**:`internal/service/package/service.go:51-63`
|
||||
|
||||
**用途**:验证虚流量配置的合法性
|
||||
|
||||
```go
|
||||
if req.EnableVirtualData {
|
||||
if *req.VirtualDataMB > realDataMB {
|
||||
return nil, errors.New(errors.CodeInvalidParam, "虚流量额度不能大于真流量额度")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 代理用户套餐过滤
|
||||
**文件**:`internal/store/postgres/package_store.go:57-66`
|
||||
|
||||
**用途**:代理用户只能看到已分配的套餐
|
||||
|
||||
```go
|
||||
if isAgent {
|
||||
query = query.Joins("INNER JOIN tb_shop_package_allocation ...").
|
||||
Where("tb_shop_package_allocation.shop_id = ? AND tb_shop_package_allocation.status = ?",
|
||||
shopID, constants.StatusEnabled)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 数据流向
|
||||
|
||||
```
|
||||
HTTP 请求
|
||||
↓
|
||||
PackageHandler (internal/handler/admin/package.go)
|
||||
↓
|
||||
PackageService (internal/service/package/service.go)
|
||||
├─ 业务逻辑校验
|
||||
├─ 调用 PackageStore
|
||||
└─ 返回 DTO
|
||||
↓
|
||||
PackageStore (internal/store/postgres/package_store.go)
|
||||
├─ 数据库查询
|
||||
└─ 返回 Model
|
||||
↓
|
||||
HTTP 响应 (DTO)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 常见操作
|
||||
|
||||
### 添加新的套餐字段
|
||||
|
||||
1. **Model 层**:`internal/model/package.go` 的 `Package` 结构体
|
||||
2. **DTO 层**:`internal/model/dto/package_dto.go` 的相关 Request/Response
|
||||
3. **Service 层**:`internal/service/package/service.go` 的业务逻辑
|
||||
4. **Store 层**:`internal/store/postgres/package_store.go` 的查询条件
|
||||
5. **数据库**:迁移文件中添加新列
|
||||
|
||||
### 修改套餐 API 响应
|
||||
|
||||
1. 修改 `PackageResponse` 结构体(`package_dto.go:71-99`)
|
||||
2. 修改 Service 的转换逻辑(`service.go` 中的 `toDTO()` 方法)
|
||||
3. 测试 API 端点
|
||||
|
||||
### 添加新的套餐 API 端点
|
||||
|
||||
1. 在 `PackageHandler` 中添加方法(`internal/handler/admin/package.go`)
|
||||
2. 在 `PackageService` 中添加业务逻辑(`internal/service/package/service.go`)
|
||||
3. 在 `registerPackageRoutes()` 中注册路由(`internal/routes/package.go`)
|
||||
|
||||
---
|
||||
|
||||
## 📝 相关文档
|
||||
|
||||
- **套餐系统升级**:`docs/package-system-upgrade/`
|
||||
- **套餐与佣金业务模型**:`docs/commission-package-model.md`
|
||||
- **API 文档生成规范**:`docs/api-documentation-guide.md`
|
||||
|
||||
---
|
||||
|
||||
## 🔗 相关模块
|
||||
|
||||
| 模块 | 说明 | 文件 |
|
||||
|------|------|------|
|
||||
| **PackageSeries** | 套餐系列管理 | `internal/routes/package_series.go` |
|
||||
| **PackageUsage** | 套餐使用记录 | `internal/routes/package_usage.go` |
|
||||
| **ShopPackageAllocation** | 套餐分配给代理 | `internal/store/postgres/shop_package_allocation_store.go` |
|
||||
| **ShopSeriesAllocation** | 系列分配给代理 | `internal/store/postgres/shop_series_allocation_store.go` |
|
||||
|
||||
349
docs/package_structure_summary.md
Normal file
349
docs/package_structure_summary.md
Normal file
@@ -0,0 +1,349 @@
|
||||
# 后台管理套餐相关接口代码结构探索
|
||||
|
||||
## 📋 概览
|
||||
|
||||
套餐(Package/PackageSeries)相关功能采用标准的分层架构:
|
||||
```
|
||||
Handler → Service → Store → Model
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1️⃣ 核心文件位置
|
||||
|
||||
### Model 层(数据模型)
|
||||
| 文件 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| **Package** | `internal/model/package.go:28-56` | 套餐主模型,包含 `duration_days` 字段 |
|
||||
| **PackageSeries** | `internal/model/package.go:10-26` | 套餐系列模型 |
|
||||
| **PackageUsage** | `internal/model/package.go:58-90` | 套餐使用情况模型 |
|
||||
| **PackageUsageDailyRecord** | `internal/model/package.go:92-107` | 套餐流量日记录模型 |
|
||||
|
||||
### DTO 层(数据传输对象)
|
||||
| 文件 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| **PackageResponse** | `internal/model/dto/package_dto.go:71-99` | 套餐响应结构,包含 `duration_days` 字段 |
|
||||
| **CreatePackageRequest** | `internal/model/dto/package_dto.go:3-19` | 创建套餐请求 |
|
||||
| **UpdatePackageRequest** | `internal/model/dto/package_dto.go:21-36` | 更新套餐请求 |
|
||||
| **PackageListRequest** | `internal/model/dto/package_dto.go:38-47` | 套餐列表请求 |
|
||||
| **PackagePageResult** | `internal/model/dto/package_dto.go:125-131` | 套餐分页结果 |
|
||||
|
||||
### Handler 层(HTTP 处理)
|
||||
| 文件 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| **PackageHandler** | `internal/handler/admin/package.go` | 套餐管理处理器 |
|
||||
| 方法 | 行号 | 功能 |
|
||||
| `List()` | 22-34 | 获取套餐列表 |
|
||||
| `Create()` | 36-48 | 创建套餐 |
|
||||
| `Get()` | 50-62 | 获取套餐详情 |
|
||||
| `Update()` | 64-81 | 更新套餐 |
|
||||
| `Delete()` | 83-94 | 删除套餐 |
|
||||
| `UpdateStatus()` | 96-112 | 更新套餐状态 |
|
||||
| `UpdateShelfStatus()` | 114-130 | 更新上架状态 |
|
||||
| `UpdateRetailPrice()` | 132-148 | 更新零售价 |
|
||||
|
||||
### Service 层(业务逻辑)
|
||||
| 文件 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| **Service** | `internal/service/package/service.go:19-38` | 套餐服务主类 |
|
||||
| 依赖 | 说明 | |
|
||||
| `packageStore` | 套餐数据访问 | |
|
||||
| `packageSeriesStore` | 套餐系列数据访问 | |
|
||||
| `packageAllocationStore` | 套餐分配数据访问 | |
|
||||
| `shopSeriesAllocationStore` | 店铺系列分配数据访问 | |
|
||||
|
||||
**关键方法**(`service.go` 第 40-80 行):
|
||||
- `Create()` - 创建套餐,包含虚流量和周期类型校验
|
||||
- `List()` - 列表查询,支持代理用户过滤
|
||||
- `Get()` - 获取详情
|
||||
- `Update()` - 更新套餐
|
||||
- `Delete()` - 删除套餐
|
||||
- `UpdateStatus()` - 更新状态
|
||||
- `UpdateShelfStatus()` - 更新上架状态
|
||||
- `UpdateRetailPrice()` - 更新零售价
|
||||
|
||||
### Store 层(数据访问)
|
||||
| 文件 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| **PackageStore** | `internal/store/postgres/package_store.go:15-21` | 套餐数据访问 |
|
||||
| 方法 | 行号 | 功能 |
|
||||
| `Create()` | 23-25 | 创建套餐 |
|
||||
| `GetByID()` | 27-33 | 按 ID 查询 |
|
||||
| `GetByCode()` | 35-41 | 按编码查询 |
|
||||
| `Update()` | 43-45 | 更新套餐 |
|
||||
| `Delete()` | 47-49 | 删除套餐 |
|
||||
| `List()` | 51-100+ | 列表查询(支持代理用户过滤) |
|
||||
|
||||
**其他相关 Store**:
|
||||
- `PackageSeriesStore` - `internal/store/postgres/package_series_store.go`
|
||||
- `PackageUsageStore` - `internal/store/postgres/package_usage_store.go`
|
||||
- `PackageUsageDailyRecordStore` - `internal/store/postgres/package_usage_daily_record_store.go`
|
||||
- `ShopPackageAllocationStore` - `internal/store/postgres/shop_package_allocation_store.go`
|
||||
|
||||
---
|
||||
|
||||
## 2️⃣ API 路由注册
|
||||
|
||||
### 主路由注册入口
|
||||
**文件**:`internal/routes/admin.go:71-78`
|
||||
|
||||
```go
|
||||
if handlers.PackageSeries != nil {
|
||||
registerPackageSeriesRoutes(authGroup, handlers.PackageSeries, doc, basePath)
|
||||
}
|
||||
if handlers.Package != nil {
|
||||
registerPackageRoutes(authGroup, handlers.Package, doc, basePath)
|
||||
}
|
||||
if handlers.PackageUsage != nil {
|
||||
registerPackageUsageRoutes(authGroup, handlers.PackageUsage, doc, basePath)
|
||||
}
|
||||
```
|
||||
|
||||
### 套餐路由详情
|
||||
**文件**:`internal/routes/package.go:11-78`
|
||||
|
||||
| 方法 | 路径 | 处理器 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| GET | `/packages` | `List()` | 套餐列表 |
|
||||
| POST | `/packages` | `Create()` | 创建套餐 |
|
||||
| GET | `/packages/:id` | `Get()` | 获取详情 |
|
||||
| PUT | `/packages/:id` | `Update()` | 更新套餐 |
|
||||
| DELETE | `/packages/:id` | `Delete()` | 删除套餐 |
|
||||
| PATCH | `/packages/:id/status` | `UpdateStatus()` | 更新状态 |
|
||||
| PATCH | `/packages/:id/shelf` | `UpdateShelfStatus()` | 更新上架状态 |
|
||||
| PATCH | `/packages/:id/retail-price` | `UpdateRetailPrice()` | 更新零售价 |
|
||||
|
||||
### 套餐系列路由
|
||||
**文件**:`internal/routes/package_series.go:11-62`
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | `/package-series` | 列表 |
|
||||
| POST | `/package-series` | 创建 |
|
||||
| GET | `/package-series/:id` | 详情 |
|
||||
| PUT | `/package-series/:id` | 更新 |
|
||||
| DELETE | `/package-series/:id` | 删除 |
|
||||
| PATCH | `/package-series/:id/status` | 更新状态 |
|
||||
|
||||
### 套餐使用记录路由
|
||||
**文件**:`internal/routes/package_usage.go:12-23`
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | `/package-usage/:id/daily-records` | 获取流量详单 |
|
||||
|
||||
---
|
||||
|
||||
## 3️⃣ 关键字段详解
|
||||
|
||||
### Package 模型中的 duration_days 字段
|
||||
**位置**:`internal/model/package.go:46`
|
||||
|
||||
```go
|
||||
DurationDays int `gorm:"column:duration_days;type:int;comment:套餐天数(calendar_type=by_day时必填)" json:"duration_days"`
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- 当 `calendar_type = "by_day"` 时必填
|
||||
- 当 `calendar_type = "natural_month"` 时不需要
|
||||
- 用于按天计算套餐有效期
|
||||
|
||||
### PackageResponse DTO 中的 duration_days
|
||||
**位置**:`internal/model/dto/package_dto.go:96`
|
||||
|
||||
```go
|
||||
DurationDays *int `json:"duration_days,omitempty" description:"套餐天数(calendar_type=by_day时有值)"`
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- 可选字段(指针类型)
|
||||
- 仅当 `calendar_type = "by_day"` 时返回值
|
||||
|
||||
### 创建请求中的 duration_days
|
||||
**位置**:`internal/model/dto/package_dto.go:16`
|
||||
|
||||
```go
|
||||
DurationDays *int `json:"duration_days" validate:"omitempty,min=1,max=3650" minimum:"1" maximum:"3650" description:"套餐天数(calendar_type=by_day时必填)"`
|
||||
```
|
||||
|
||||
**验证规则**:
|
||||
- 可选字段
|
||||
- 当提供时,必须在 1-3650 之间
|
||||
- 当 `calendar_type = "by_day"` 时必须提供
|
||||
|
||||
---
|
||||
|
||||
## 4️⃣ 业务逻辑关键点
|
||||
|
||||
### 套餐周期类型校验(Service 层)
|
||||
**位置**:`internal/service/package/service.go:65-80`
|
||||
|
||||
```go
|
||||
// 校验套餐周期类型和时长配置
|
||||
calendarType := constants.PackageCalendarTypeByDay // 默认按天
|
||||
if req.CalendarType != nil {
|
||||
calendarType = *req.CalendarType
|
||||
}
|
||||
if calendarType == constants.PackageCalendarTypeNaturalMonth {
|
||||
// 自然月套餐:必须提供 duration_months
|
||||
if req.DurationMonths <= 0 {
|
||||
return nil, errors.New(errors.CodeInvalidParam, "自然月套餐必须提供有效的duration_months")
|
||||
}
|
||||
} else if calendarType == constants.PackageCalendarTypeByDay {
|
||||
// 按天套餐:必须提供 duration_days
|
||||
if req.DurationDays == nil || *req.DurationDays <= 0 {
|
||||
return nil, errors.New(errors.CodeInvalidParam, "按天套餐必须提供有效的duration_days")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 虚流量配置校验
|
||||
**位置**:`internal/service/package/service.go:51-63`
|
||||
|
||||
```go
|
||||
if req.EnableVirtualData {
|
||||
if req.VirtualDataMB == nil || *req.VirtualDataMB <= 0 {
|
||||
return nil, errors.New(errors.CodeInvalidParam, "启用虚流量时,虚流量额度必须大于0")
|
||||
}
|
||||
realDataMB := int64(0)
|
||||
if req.RealDataMB != nil {
|
||||
realDataMB = *req.RealDataMB
|
||||
}
|
||||
if *req.VirtualDataMB > realDataMB {
|
||||
return nil, errors.New(errors.CodeInvalidParam, "虚流量额度不能大于真流量额度")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 代理用户套餐过滤
|
||||
**位置**:`internal/store/postgres/package_store.go:57-66`
|
||||
|
||||
```go
|
||||
// 代理用户额外过滤:只能看到已分配(allocation.status=启用)的套餐
|
||||
userType := middleware.GetUserTypeFromContext(ctx)
|
||||
shopID := middleware.GetShopIDFromContext(ctx)
|
||||
isAgent := userType == constants.UserTypeAgent && shopID > 0
|
||||
if isAgent {
|
||||
query = query.Joins("INNER JOIN tb_shop_package_allocation ON tb_shop_package_allocation.package_id = tb_package.id AND tb_shop_package_allocation.deleted_at IS NULL").
|
||||
Where("tb_shop_package_allocation.shop_id = ? AND tb_shop_package_allocation.status = ?",
|
||||
shopID, constants.StatusEnabled)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5️⃣ 相关常量
|
||||
|
||||
**位置**:`pkg/constants/` 目录
|
||||
|
||||
常用常量:
|
||||
- `PackageCalendarTypeNaturalMonth` - 自然月
|
||||
- `PackageCalendarTypeByDay` - 按天
|
||||
- `UserTypeAgent` - 代理用户类型
|
||||
- `StatusEnabled` - 启用状态
|
||||
|
||||
---
|
||||
|
||||
## 6️⃣ 数据库表结构
|
||||
|
||||
### tb_package(套餐表)
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | bigint | 主键 |
|
||||
| package_code | varchar(100) | 套餐编码(唯一) |
|
||||
| package_name | varchar(255) | 套餐名称 |
|
||||
| series_id | bigint | 套餐系列 ID |
|
||||
| package_type | varchar(50) | 套餐类型(formal/addon) |
|
||||
| duration_months | int | 套餐时长(月数) |
|
||||
| real_data_mb | bigint | 真流量额度(MB) |
|
||||
| virtual_data_mb | bigint | 虚流量额度(MB) |
|
||||
| enable_virtual_data | boolean | 是否启用虚流量 |
|
||||
| **duration_days** | **int** | **套餐天数(按天时必填)** |
|
||||
| calendar_type | varchar(20) | 套餐周期类型(natural_month/by_day) |
|
||||
| data_reset_cycle | varchar(20) | 流量重置周期(daily/monthly/yearly/none) |
|
||||
| expiry_base | varchar(30) | 到期时间基准(from_activation/from_purchase) |
|
||||
| cost_price | bigint | 成本价(分) |
|
||||
| suggested_retail_price | bigint | 建议售价(分) |
|
||||
| shelf_status | int | 上架状态(1-上架/2-下架) |
|
||||
| status | int | 状态(1-启用/2-禁用) |
|
||||
| created_at | timestamp | 创建时间 |
|
||||
| updated_at | timestamp | 更新时间 |
|
||||
| deleted_at | timestamp | 删除时间 |
|
||||
|
||||
### tb_package_series(套餐系列表)
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | bigint | 主键 |
|
||||
| series_code | varchar(100) | 系列编码(唯一) |
|
||||
| series_name | varchar(255) | 系列名称 |
|
||||
| description | text | 描述 |
|
||||
| enable_one_time_commission | boolean | 是否启用一次性佣金 |
|
||||
| one_time_commission_config | jsonb | 一次性佣金规则配置 |
|
||||
| status | int | 状态(1-启用/2-禁用) |
|
||||
|
||||
---
|
||||
|
||||
## 7️⃣ 完整请求/响应示例
|
||||
|
||||
### 创建套餐(按天)
|
||||
**请求**:
|
||||
```json
|
||||
POST /api/admin/packages
|
||||
{
|
||||
"package_code": "PKG_DAY_30",
|
||||
"package_name": "30天套餐",
|
||||
"package_type": "formal",
|
||||
"duration_months": 1,
|
||||
"real_data_mb": 10240,
|
||||
"enable_virtual_data": false,
|
||||
"cost_price": 9900,
|
||||
"suggested_retail_price": 19900,
|
||||
"calendar_type": "by_day",
|
||||
"duration_days": 30,
|
||||
"data_reset_cycle": "monthly",
|
||||
"expiry_base": "from_activation"
|
||||
}
|
||||
```
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"id": 1,
|
||||
"package_code": "PKG_DAY_30",
|
||||
"package_name": "30天套餐",
|
||||
"package_type": "formal",
|
||||
"duration_months": 1,
|
||||
"real_data_mb": 10240,
|
||||
"enable_virtual_data": false,
|
||||
"cost_price": 9900,
|
||||
"suggested_retail_price": 19900,
|
||||
"calendar_type": "by_day",
|
||||
"duration_days": 30,
|
||||
"data_reset_cycle": "monthly",
|
||||
"expiry_base": "from_activation",
|
||||
"status": 1,
|
||||
"shelf_status": 2,
|
||||
"created_at": "2025-01-01T00:00:00Z",
|
||||
"updated_at": "2025-01-01T00:00:00Z"
|
||||
},
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📌 总结
|
||||
|
||||
| 层级 | 关键文件 | 核心类/函数 |
|
||||
|------|---------|-----------|
|
||||
| **Model** | `internal/model/package.go` | `Package`、`PackageSeries`、`PackageUsage` |
|
||||
| **DTO** | `internal/model/dto/package_dto.go` | `PackageResponse`、`CreatePackageRequest` |
|
||||
| **Handler** | `internal/handler/admin/package.go` | `PackageHandler` |
|
||||
| **Service** | `internal/service/package/service.go` | `Service` |
|
||||
| **Store** | `internal/store/postgres/package_store.go` | `PackageStore` |
|
||||
| **Routes** | `internal/routes/package.go` | `registerPackageRoutes()` |
|
||||
| **Route Registry** | `internal/routes/admin.go:71-78` | 路由注册入口 |
|
||||
|
||||
**关键字段**:`duration_days` 在 Model、DTO 和数据库中都有定义,用于按天计算套餐有效期。
|
||||
147
docs/quick_navigation.md
Normal file
147
docs/quick_navigation.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# 套餐接口快速导航
|
||||
|
||||
## 🎯 快速查找
|
||||
|
||||
### 按功能查找
|
||||
|
||||
| 需求 | 文件 | 行号 |
|
||||
|------|------|------|
|
||||
| 查看套餐数据模型 | `internal/model/package.go` | 28-56 |
|
||||
| 查看套餐 API 请求/响应结构 | `internal/model/dto/package_dto.go` | 1-173 |
|
||||
| 查看套餐 HTTP 处理器 | `internal/handler/admin/package.go` | 1-148 |
|
||||
| 查看套餐业务逻辑 | `internal/service/package/service.go` | 1-745 |
|
||||
| 查看套餐数据访问 | `internal/store/postgres/package_store.go` | 1-150 |
|
||||
| 查看套餐 API 路由 | `internal/routes/package.go` | 1-78 |
|
||||
| 查看路由注册入口 | `internal/routes/admin.go` | 71-78 |
|
||||
|
||||
### 按字段查找
|
||||
|
||||
| 字段 | Model 位置 | DTO 位置 | 说明 |
|
||||
|------|-----------|---------|------|
|
||||
| `duration_days` | `package.go:46` | `package_dto.go:96` | 套餐天数(按天时必填) |
|
||||
| `calendar_type` | `package.go:45` | `package_dto.go:95` | 套餐周期类型 |
|
||||
| `duration_months` | `package.go:37` | `package_dto.go:79` | 套餐时长(月数) |
|
||||
| `real_data_mb` | `package.go:38` | `package_dto.go:80` | 真流量额度 |
|
||||
| `virtual_data_mb` | `package.go:39` | `package_dto.go:81` | 虚流量额度 |
|
||||
| `enable_virtual_data` | `package.go:40` | `package_dto.go:82` | 是否启用虚流量 |
|
||||
|
||||
### 按 API 端点查找
|
||||
|
||||
| 端点 | Handler 方法 | 路由文件 | 行号 |
|
||||
|------|-------------|---------|------|
|
||||
| `GET /packages` | `List()` | `package.go` | 15-21 |
|
||||
| `POST /packages` | `Create()` | `package.go` | 23-29 |
|
||||
| `GET /packages/:id` | `Get()` | `package.go` | 31-37 |
|
||||
| `PUT /packages/:id` | `Update()` | `package.go` | 39-45 |
|
||||
| `DELETE /packages/:id` | `Delete()` | `package.go` | 47-53 |
|
||||
| `PATCH /packages/:id/status` | `UpdateStatus()` | `package.go` | 55-61 |
|
||||
| `PATCH /packages/:id/shelf` | `UpdateShelfStatus()` | `package.go` | 63-69 |
|
||||
| `PATCH /packages/:id/retail-price` | `UpdateRetailPrice()` | `package.go` | 71-77 |
|
||||
|
||||
---
|
||||
|
||||
## 🔍 关键代码片段
|
||||
|
||||
### 1. 套餐周期类型校验
|
||||
**文件**:`internal/service/package/service.go:65-80`
|
||||
|
||||
**用途**:创建套餐时验证 `calendar_type` 和 `duration_days` 的配合
|
||||
|
||||
```go
|
||||
if calendarType == constants.PackageCalendarTypeByDay {
|
||||
if req.DurationDays == nil || *req.DurationDays <= 0 {
|
||||
return nil, errors.New(errors.CodeInvalidParam, "按天套餐必须提供有效的duration_days")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 虚流量配置校验
|
||||
**文件**:`internal/service/package/service.go:51-63`
|
||||
|
||||
**用途**:验证虚流量配置的合法性
|
||||
|
||||
```go
|
||||
if req.EnableVirtualData {
|
||||
if *req.VirtualDataMB > realDataMB {
|
||||
return nil, errors.New(errors.CodeInvalidParam, "虚流量额度不能大于真流量额度")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 代理用户套餐过滤
|
||||
**文件**:`internal/store/postgres/package_store.go:57-66`
|
||||
|
||||
**用途**:代理用户只能看到已分配的套餐
|
||||
|
||||
```go
|
||||
if isAgent {
|
||||
query = query.Joins("INNER JOIN tb_shop_package_allocation ...").
|
||||
Where("tb_shop_package_allocation.shop_id = ? AND tb_shop_package_allocation.status = ?",
|
||||
shopID, constants.StatusEnabled)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 数据流向
|
||||
|
||||
```
|
||||
HTTP 请求
|
||||
↓
|
||||
PackageHandler (internal/handler/admin/package.go)
|
||||
↓
|
||||
PackageService (internal/service/package/service.go)
|
||||
├─ 业务逻辑校验
|
||||
├─ 调用 PackageStore
|
||||
└─ 返回 DTO
|
||||
↓
|
||||
PackageStore (internal/store/postgres/package_store.go)
|
||||
├─ 数据库查询
|
||||
└─ 返回 Model
|
||||
↓
|
||||
HTTP 响应 (DTO)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 常见操作
|
||||
|
||||
### 添加新的套餐字段
|
||||
|
||||
1. **Model 层**:`internal/model/package.go` 的 `Package` 结构体
|
||||
2. **DTO 层**:`internal/model/dto/package_dto.go` 的相关 Request/Response
|
||||
3. **Service 层**:`internal/service/package/service.go` 的业务逻辑
|
||||
4. **Store 层**:`internal/store/postgres/package_store.go` 的查询条件
|
||||
5. **数据库**:迁移文件中添加新列
|
||||
|
||||
### 修改套餐 API 响应
|
||||
|
||||
1. 修改 `PackageResponse` 结构体(`package_dto.go:71-99`)
|
||||
2. 修改 Service 的转换逻辑(`service.go` 中的 `toDTO()` 方法)
|
||||
3. 测试 API 端点
|
||||
|
||||
### 添加新的套餐 API 端点
|
||||
|
||||
1. 在 `PackageHandler` 中添加方法(`internal/handler/admin/package.go`)
|
||||
2. 在 `PackageService` 中添加业务逻辑(`internal/service/package/service.go`)
|
||||
3. 在 `registerPackageRoutes()` 中注册路由(`internal/routes/package.go`)
|
||||
|
||||
---
|
||||
|
||||
## 📝 相关文档
|
||||
|
||||
- **套餐系统升级**:`docs/package-system-upgrade/`
|
||||
- **套餐与佣金业务模型**:`docs/commission-package-model.md`
|
||||
- **API 文档生成规范**:`docs/api-documentation-guide.md`
|
||||
|
||||
---
|
||||
|
||||
## 🔗 相关模块
|
||||
|
||||
| 模块 | 说明 | 文件 |
|
||||
|------|------|------|
|
||||
| **PackageSeries** | 套餐系列管理 | `internal/routes/package_series.go` |
|
||||
| **PackageUsage** | 套餐使用记录 | `internal/routes/package_usage.go` |
|
||||
| **ShopPackageAllocation** | 套餐分配给代理 | `internal/store/postgres/shop_package_allocation_store.go` |
|
||||
| **ShopSeriesAllocation** | 系列分配给代理 | `internal/store/postgres/shop_series_allocation_store.go` |
|
||||
|
||||
Reference in New Issue
Block a user