443 lines
15 KiB
Markdown
443 lines
15 KiB
Markdown
# SMANAGE 抽象分佣引擎 — 设计文档
|
||
|
||
## 一、设计背景
|
||
|
||
通过对阿龙电竞平台分佣机制的完整分析,提取出 **6 种核心分佣模式**:
|
||
订单分佣、邀请链分佣、阶梯分佣、提现抽成、罚款分红、跨平台预结算。
|
||
|
||
这些模式都有一个共同的本质:**事件 → 规则 → 分账**。SMANAGE 分佣引擎将这一本质抽象为 SAAS 级通用模块,子服务只管说"发生了什么",SAAS 负责"怎么分"。
|
||
|
||
---
|
||
|
||
## 二、设计原则
|
||
|
||
| 原则 | 说明 |
|
||
|------|------|
|
||
| **事件驱动** | 子服务触发分佣事件(order_complete / member_purchase / recharge / penalty),引擎自动匹配规则计算分账 |
|
||
| **规则外置** | 分账比例、层级、公式全部存储于 `CommissionRule` 表,零硬编码 |
|
||
| **策略模式** | 比例/固定/阶梯/剩余 四种策略独立封装,新增分账逻辑只需添加策略类 |
|
||
| **参与方可配置** | 参与者身份和金额由 JSON 配置,适配任意业务场景 |
|
||
| **邀请链通用化** | 不管叫"管事/组长"还是"上级/上上级",都是链式分佣 |
|
||
| **幂等保护** | `(TenantUUID, EventKey)` 联合唯一约束,防止重复分账 |
|
||
| **快照不可变** | `CommissionRecord` 生成后冻结,规则变更不影响历史记录 |
|
||
| **与支付解耦** | 分佣算出"该分多少",支付模块负责"怎么打到账" |
|
||
|
||
---
|
||
|
||
## 三、架构分层
|
||
|
||
```
|
||
子服务(如阿龙电竞)
|
||
│
|
||
│ 订单完成 / 会员购买 / 用户充值 / 罚款缴纳
|
||
│
|
||
└──→ CommissionEngine.Dispatch(event_type, event_key, total_amount, ...)
|
||
│
|
||
├── 1. 写入 CommissionEvent(幂等检查)
|
||
├── 2. 匹配 CommissionRule(按优先级)
|
||
├── 3. 调用策略引擎计算分账
|
||
│ ├── PercentageStrategy → total × rate
|
||
│ ├── FixedStrategy → 固定金额
|
||
│ ├── TieredStrategy → 按次数/档位查表
|
||
│ └── RemainderStrategy → 剩余归平台
|
||
├── 4. 写入 CommissionRecord(不可变流水)
|
||
└── 5. 返回分账结果
|
||
```
|
||
|
||
---
|
||
|
||
## 四、文件清单(10 个文件)
|
||
|
||
| 层级 | 文件 | 职责 |
|
||
|------|------|------|
|
||
| **模型** | `models.py` | 6 张核心表:Event / Rule / RateTable / RateEntry / Record / PreSettlement |
|
||
| **策略** | `strategies/engine.py` | 5 种策略类 + 策略工厂 `CreateStrategy()` |
|
||
| **编排器** | `manager.py` | `CommissionEngine` — Dispatch / PreSettle / 邀请链 / 费率计算 / 统计查询 |
|
||
| **API** | `views.py` | 10 个 REST 端点 |
|
||
| **序列化** | `serializers.py` | 12 个 Serializer |
|
||
| **路由** | `urls.py` | 10 个 URL 路由 |
|
||
| **配置** | `apps.py` | AppConfig |
|
||
| **管理** | `admin.py` | 6 个 Django Admin 注册 |
|
||
|
||
---
|
||
|
||
## 五、核心数据模型
|
||
|
||
### 5.1 CommissionEvent — 分佣事件
|
||
|
||
子服务触发的分佣事件记录:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| EventUUID | UUID | 主键 |
|
||
| TenantUUID | UUID | 租户 |
|
||
| EventType | CharField(32) | `order_complete` / `member_purchase` / `recharge` / `penalty` / `register` / `custom` |
|
||
| EventKey | CharField(128) | 业务唯一键(幂等保护),如订单ID |
|
||
| TotalAmount | DecimalField | 可分佣总金额 |
|
||
| SourceUserUUID | UUID | 触发者(如付费用户) |
|
||
| EventPayload | JSONField | 业务上下文(由子服务自行解析) |
|
||
| Participants | JSONField | 参与者映射 `{role: user_id}` |
|
||
| EventStatus | SmallIntegerField | 0=待处理 1=处理中 2=已结算 3=失败 |
|
||
|
||
唯一约束:`(TenantUUID, EventKey)` — 同一事件不可重复分账
|
||
|
||
### 5.2 CommissionRule — 分佣规则
|
||
|
||
定义"怎么分":
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| RuleUUID | UUID | 主键 |
|
||
| TenantUUID | UUID | 租户 |
|
||
| EventType | CharField(32) | 绑定的分佣事件类型 |
|
||
| RuleName | CharField(128) | 规则名称 |
|
||
| ParticipantRules | JSONField | 参与者分账规则列表 |
|
||
| RateTable | ForeignKey | 可选关联费率表 |
|
||
| MaxInviteLevel | SmallIntegerField | 邀请链最大层级(默认2级) |
|
||
| RulePriority | IntegerField | 优先级 |
|
||
|
||
#### ParticipantRules JSON 结构示例
|
||
|
||
```json
|
||
[
|
||
{ "role": "worker", "type": "percentage", "value": 0.80 },
|
||
{ "role": "agent", "type": "fixed", "value": 10 },
|
||
{ "role": "leader", "type": "tiered", "tiers": { "1": 5, "2": 8, "3": 10 } },
|
||
{ "role": "platform", "type": "remainder" }
|
||
]
|
||
```
|
||
|
||
### 5.3 RateTable + RateEntry — 费率表
|
||
|
||
可复用的动态费率配置:
|
||
|
||
```
|
||
RateTable: { TableCode: 'dashou_order_split', TableName: '打手订单分成费率' }
|
||
├── RateEntry: { RateKey: '1', RateLabel: '平台订单打手分成', RateValue: 0.8000 }
|
||
├── RateEntry: { RateKey: '3', RateLabel: '商家订单打手分成', RateValue: 0.7000 }
|
||
├── RateEntry: { RateKey: '5', RateLabel: '打手提现抽成', RateValue: 0.0100 }
|
||
└── RateEntry: { RateKey: '6', RateLabel: '管事提现抽成', RateValue: 0.0100 }
|
||
```
|
||
|
||
### 5.4 CommissionRecord — 分佣记录(不可变)
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| RecordUUID | UUID | 主键 |
|
||
| TenantUUID | UUID | 租户 |
|
||
| Event | ForeignKey | 关联分佣事件 |
|
||
| Rule | ForeignKey | 关联分佣规则(可空,规则删除后保留快照) |
|
||
| ParticipantID | CharField(128) | 分账对象ID |
|
||
| ParticipantRole | CharField(64) | 分账角色(worker / agent / platform / ...) |
|
||
| SplitType | CharField(16) | percentage / fixed / tiered / remainder |
|
||
| Amount | DecimalField | 分账金额 |
|
||
| RuleSnapshot | JSONField | 规则快照(规则变更后保留历史) |
|
||
| TierIndex | IntegerField | 阶梯索引(多次分红时标识第几次) |
|
||
| Status | SmallIntegerField | 0=待结算 1=已结算 2=已提现 3=已取消 |
|
||
|
||
### 5.5 PreSettlement — 预结算
|
||
|
||
跨平台订单待确认款项:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| EventKey | CharField(128) | 业务唯一键 |
|
||
| ExternalPlatform | CharField(64) | 外部平台标识 |
|
||
| ExternalOrderID | CharField(128) | 外部订单ID |
|
||
| ParticipantID | CharField(128) | 应分账对象 |
|
||
| Amount | DecimalField | 预分金额 |
|
||
| Status | SmallIntegerField | 0=待确认 1=已确认 2=已拒绝 |
|
||
|
||
---
|
||
|
||
## 六、策略引擎
|
||
|
||
策略模式实现,四种核心策略 + 一种扩展策略:
|
||
|
||
### 6.1 PercentageStrategy — 比例分账
|
||
|
||
```python
|
||
# 配置: { role: 'worker', type: 'percentage', value: 0.80 }
|
||
amount = total_amount × 0.80
|
||
# 支持保底/封顶:
|
||
# { role: 'worker', type: 'percentage', value: 0.80, min_amount: 5, max_amount: 500 }
|
||
```
|
||
|
||
### 6.2 FixedStrategy — 固定金额
|
||
|
||
```python
|
||
# 配置: { role: 'agent', type: 'fixed', value: 10 }
|
||
amount = 10 # 不受 total_amount 影响
|
||
# 也支持从费率表读取:
|
||
# { role: 'agent', type: 'fixed', rate_table_key: 'guanshi_fc' }
|
||
```
|
||
|
||
### 6.3 TieredStrategy — 阶梯分账
|
||
|
||
```python
|
||
# 配置: { role: 'agent', type: 'tiered', tiers: { "1": 5, "2": 8, "3": 10 } }
|
||
# tier_index=1 → amount=5
|
||
# tier_index=2 → amount=8
|
||
# tier_index=3 → amount=10
|
||
# 支持百分比阶梯: { "1": "5%", "2": "8%" }
|
||
```
|
||
|
||
### 6.4 RemainderStrategy — 剩余归平台
|
||
|
||
```python
|
||
# 配置: { role: 'platform', type: 'remainder' }
|
||
amount = total_amount - SUM(其他所有参与者分账)
|
||
# 自动排在最后计算,确保剩余金额正确
|
||
```
|
||
|
||
### 6.5 WithdrawalFeeStrategy — 提现手续费(扩展)
|
||
|
||
```python
|
||
# 配置: { role: 'platform', type: 'withdrawal_fee', value: 0.01 }
|
||
fee = total_amount × 0.01
|
||
```
|
||
|
||
### 新增策略只需两步
|
||
|
||
```python
|
||
# 1. 实现策略类
|
||
class FormulaStrategy(BaseSplitStrategy):
|
||
def Calculate(self, total_amount, tier_index=0, **context):
|
||
return total_amount * Decimal('0.5') + Decimal('10')
|
||
|
||
# 2. 注册到策略映射
|
||
STRATEGY_MAP['formula'] = FormulaStrategy
|
||
```
|
||
|
||
---
|
||
|
||
## 七、CommissionEngine 编排器
|
||
|
||
```python
|
||
class CommissionEngine:
|
||
def __init__(self, tenant_uuid)
|
||
|
||
# 规则管理
|
||
def ConfigureRule(event_type, rule_name, participant_rules, ...) # 配置分佣规则
|
||
def ConfigureRateTable(table_code, table_name, entries, ...) # 配置费率表
|
||
|
||
# 核心分佣
|
||
def Dispatch(event_type, event_key, total_amount, participants, ...) # 触发分佣
|
||
def BuildInviteChainCommission(event_type, event_key, ...) # 邀请链分佣
|
||
|
||
# 预结算
|
||
def PreSettle(event_key, external_platform, external_order_id, ...) # 创建预结算
|
||
def ConfirmPreSettlement(event_key, participant_id, ...) # 确认
|
||
def RejectPreSettlement(event_key, participant_id, ...) # 拒绝
|
||
|
||
# 查询
|
||
def CalculateFee(total_amount, rate_table_code, rate_key) # 计算手续费
|
||
def QueryRecords(participant_id, event_type, status, ...) # 查询分佣记录
|
||
def TotalByParticipant(participant_id, start_date, end_date) # 统计总额
|
||
```
|
||
|
||
---
|
||
|
||
## 八、API 端点
|
||
|
||
```
|
||
# 规则管理
|
||
GET /api/commission/rule/ # 查询分佣规则列表
|
||
POST /api/commission/rule/ # 创建分佣规则
|
||
DELETE /api/commission/rule/ # 禁用分佣规则
|
||
|
||
# 费率表管理
|
||
GET /api/commission/rate/ # 查询费率表列表
|
||
POST /api/commission/rate/ # 创建费率表
|
||
|
||
# 分佣操作
|
||
POST /api/commission/dispatch/ # 触发分佣事件
|
||
POST /api/commission/dispatch/invite_chain/ # 邀请链分佣
|
||
|
||
# 查询统计
|
||
GET /api/commission/query/ # 查询分佣记录
|
||
GET /api/commission/total/<participant_id>/ # 统计某人分佣总额
|
||
POST /api/commission/fee/ # 计算手续费
|
||
|
||
# 预结算
|
||
POST /api/commission/presettle/ # 创建预结算
|
||
POST /api/commission/presettle/confirm/ # 确认预结算
|
||
|
||
# 事件查询
|
||
GET /api/commission/event/ # 查询分佣事件列表
|
||
```
|
||
|
||
---
|
||
|
||
## 九、子服务集成示例
|
||
|
||
### 9.1 订单分佣
|
||
|
||
```python
|
||
from gvsds.apps.commission.manager import CommissionEngine
|
||
|
||
engine = CommissionEngine(tenant_uuid='al_long_001')
|
||
|
||
# 配置分佣规则
|
||
engine.ConfigureRule(
|
||
event_type='order_complete',
|
||
rule_name='平台订单打手8成',
|
||
participant_rules=[
|
||
{'role': 'worker', 'type': 'percentage', 'value': 0.80},
|
||
{'role': 'platform', 'type': 'remainder'},
|
||
],
|
||
)
|
||
|
||
# 订单完成时触发分佣
|
||
records = engine.Dispatch(
|
||
event_type='order_complete',
|
||
event_key='ORDER-20260611-001', # 幂等键
|
||
total_amount=Decimal('100.00'),
|
||
participants={
|
||
'worker': 'dashou_123',
|
||
},
|
||
payload={'order_id': 'xxx', 'game': 'LoL', 'shop_id': 'shop_001'},
|
||
)
|
||
|
||
# records = [
|
||
# {ParticipantID: 'dashou_123', ParticipantRole: 'worker', Amount: 80},
|
||
# {ParticipantID: 'platform_default', ParticipantRole: 'platform', Amount: 20},
|
||
# ]
|
||
```
|
||
|
||
### 9.2 会员购买+邀请链
|
||
|
||
```python
|
||
# 配置会员购买分佣规则
|
||
engine.ConfigureRule(
|
||
event_type='member_purchase',
|
||
rule_name='会员购买管事+组长分红',
|
||
participant_rules=[
|
||
{'role': 'agent', 'type': 'fixed', 'value': 10},
|
||
{'role': 'leader', 'type': 'fixed', 'value': 5},
|
||
{'role': 'platform', 'type': 'remainder'},
|
||
],
|
||
)
|
||
|
||
# 打手购买会员 → 触发邀请链分佣
|
||
records = engine.BuildInviteChainCommission(
|
||
event_type='member_purchase',
|
||
event_key='MEMBER-VIP001-20260611-001',
|
||
total_amount=Decimal('50.00'),
|
||
source_user_uuid='dashou_123',
|
||
invite_chain=[
|
||
{'user_uuid': 'guanshi_456', 'level': 1, 'role': 'agent'},
|
||
{'user_uuid': 'zuzhang_789', 'level': 2, 'role': 'leader'},
|
||
],
|
||
rule_name='会员购买管事+组长分红',
|
||
participant_rules=[
|
||
{'role': 'agent', 'type': 'fixed', 'value': 10},
|
||
{'role': 'leader', 'type': 'fixed', 'value': 5},
|
||
{'role': 'platform', 'type': 'remainder'},
|
||
],
|
||
)
|
||
|
||
# records = [
|
||
# {ParticipantID: 'guanshi_456', ParticipantRole: 'agent', Amount: 10},
|
||
# {ParticipantID: 'zuzhang_789', ParticipantRole: 'leader', Amount: 5},
|
||
# {ParticipantID: 'platform_default', ParticipantRole: 'platform', Amount: 35},
|
||
# ]
|
||
```
|
||
|
||
### 9.3 阶梯分佣(多次购买)
|
||
|
||
```python
|
||
# 第一次购买
|
||
engine.Dispatch(
|
||
event_type='member_purchase',
|
||
event_key='STAGED-VIP001-DASHOU_123-1',
|
||
total_amount=Decimal('50.00'),
|
||
tier_index=1,
|
||
participants={'agent': 'guanshi_456'},
|
||
)
|
||
|
||
# 第二次购买(金额不同)
|
||
engine.Dispatch(
|
||
event_type='member_purchase',
|
||
event_key='STAGED-VIP001-DASHOU_123-2',
|
||
total_amount=Decimal('50.00'),
|
||
tier_index=2,
|
||
participants={'agent': 'guanshi_456'},
|
||
)
|
||
```
|
||
|
||
### 9.4 提现手续费
|
||
|
||
```python
|
||
# 配置提现费率表
|
||
engine.ConfigureRateTable(
|
||
table_code='withdrawal_fee',
|
||
table_name='提现手续费率',
|
||
entries={
|
||
'dashou': '0.0100', # 打手提现 1%
|
||
'guanshi': '0.0100', # 管事提现 1%
|
||
'zuzhang': '0.0100', # 组长提现 1%
|
||
},
|
||
)
|
||
|
||
# 计算手续费
|
||
fee = engine.CalculateFee(
|
||
total_amount=Decimal('1000.00'),
|
||
rate_table_code='withdrawal_fee',
|
||
rate_key='dashou',
|
||
)
|
||
# fee = 10.00 → 实际到账 990.00
|
||
```
|
||
|
||
### 9.5 跨平台预结算
|
||
|
||
```python
|
||
# 创建预结算
|
||
ps = engine.PreSettle(
|
||
event_key='ORDER-CROSS-001',
|
||
external_platform='partner_club_002',
|
||
external_order_id='P-ORDER-888',
|
||
participant_id='dashou_123',
|
||
amount=Decimal('80.00'),
|
||
order_amount=Decimal('100.00'),
|
||
)
|
||
|
||
# 对方确认后
|
||
engine.ConfirmPreSettlement(
|
||
event_key='ORDER-CROSS-001',
|
||
confirmed_by='partner_admin',
|
||
)
|
||
```
|
||
|
||
---
|
||
|
||
## 十、与支付模块的关系
|
||
|
||
```
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ 子服务业务层 │
|
||
│ │
|
||
│ 订单完成 ──→ CommissionEngine.Dispatch() ──→ 算出分账 │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ 用户提现 ──→ PaymentManager.Transfer() ──→ 执行打款 │
|
||
│ │
|
||
└────────────────────────────────────────────────────────────┘
|
||
|
||
分佣引擎 → 算出来"该分多少"
|
||
支付网关 → 执行"怎么打到账"
|
||
|
||
两者通过 CommissionRecord.Amount 桥接:
|
||
· CommissionRecord 中的分账金额作为 PaymentManager.Transfer 的输入
|
||
· 打款完成后,CommissionRecord 状态从 'settled' → 'withdrawn'
|
||
```
|
||
|
||
---
|
||
|
||
## 十一、配置变更记录
|
||
|
||
| 文件 | 变更 |
|
||
|------|------|
|
||
| `settings.py` | 新增 `'gvsds.apps.commission'` 到 `INSTALLED_APPS` |
|
||
| `db_router.py` | 新增 `'commission': 'default'` 到 `app_db_map` |
|
||
| `urls.py` | 新增 `path('api/commission/', include('gvsds.apps.commission.urls'))` | |