Files
junhong_cmp_fiber/docs/system-audit-report.md

946 lines
36 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CMP 卡管平台 — 系统业务审计报告
> 基于代码实现非规范设计的全链路审计2026-03-20
> 产品 + 技术双视角Mermaid 流程图标注业务断点
---
## 〇、系统角色与代理层级
### 角色一览
| 角色 | 说明 | 登录方式 | 可用接口 | 状态 |
|------|------|---------|----------|------|
| 平台管理员 | 运营全局 | `/api/auth/login` | `/api/admin/*` | ✅ |
| 一级代理 | 平台发展的代理商 | 同上RBAC 区分) | 同上(数据隔离) | ✅ |
| N 级子代理 | 代理发展的下级,最多 7 级 | 同上 | 同上 | ✅ |
| 企业客户 | 被授权管理卡/设备的企业 | 同上(能登录) | **无可用接口** | 🔴 |
| C 端客户 | 终端用户 | 微信/小程序 `/api/c/v1/auth/*` | `/api/c/v1/*` | ✅ |
### 代理层级模型
```mermaid
graph TD
P[平台] --> A1[一级代理 A<br/>Shop level=1, parent_id=NULL]
P --> A2[一级代理 B<br/>Shop level=1]
A1 --> B1[二级代理 C<br/>level=2, parent_id=A]
A1 --> B2[二级代理 D<br/>level=2, parent_id=A]
B1 --> C1[三级代理 E<br/>level=3, parent_id=C]
style P fill:#e1f5fe
style A1 fill:#fff9c4
style A2 fill:#fff9c4
style B1 fill:#ffe0b2
style B2 fill:#ffe0b2
style C1 fill:#ffccbc
```
**技术实现**`model/shop.go``ParentID *uint` + `Level int`1-7创建时自动算层级。
**数据权限**:登录时预算 `SubordinateShopIDs`(自己+所有下级GORM Callback 全局过滤。
---
## 一、平台搭建 → 代理经营 → C 端消费
> 这是系统的**主干流程**平台建好基础数据代理拿到资源去经营C 端客户最终消费。
```mermaid
flowchart TD
subgraph 平台搭建["① 平台管理员搭建基础设施"]
S1["创建运营商<br/>📡 中国移动/联通/电信<br/><code>POST /admin/carriers</code>"]
S2["创建套餐系列<br/>📦 如'移动30G月租系列'<br/><code>POST /admin/package-series</code>"]
S3["创建套餐<br/>💰 30G/月 ¥29.9<br/><code>POST /admin/packages</code>"]
S4["导入 IoT 卡/设备<br/>📥 批量导入 ICCID/设备号<br/><code>POST /admin/iot-cards/import</code>"]
S1 --> S2 --> S3
S1 --> S4
end
subgraph 代理发展["② 发展代理体系"]
D1["创建一级代理<br/>🏪 开设店铺+管理员账号<br/><code>POST /admin/shops</code>"]
D2["代理创建子代理<br/>🏪 parent_id=自己<br/>同一接口RBAC控制"]
D1 --> D2
end
subgraph 代理授权["③ 给代理配货"]
G1["分配资产给代理<br/>📦 IoT卡/设备 → 代理名下<br/><code>POST /admin/iot-cards/allocate</code><br/>status 1→2, 写入 shop_id"]
G2["授权系列给代理<br/>🔑 代理可以卖哪些系列<br/><code>POST /admin/shop-series-grants</code><br/>配置:成本价、佣金金额"]
G3["分配套餐<br/>📋 代理可卖哪些具体套餐<br/><code>POST /admin/shop-package-batch-allocations</code><br/>设置零售价、上架状态"]
G2 --> G3
end
subgraph 代理可向下级重复此过程["④ 代理给子代理配货(同样的流程)"]
R1["代理也可以:<br/>• 分配资产给子代理<br/>• 授权系列给子代理<br/>• 设置子代理的成本价/佣金<br/><br/>⚠️ 子代理佣金 ≤ 自己佣金"]
end
subgraph C端消费["⑤ C 端客户消费"]
C1["扫码/输虚拟号验证资产<br/>📱 识别卡/设备<br/><code>POST /c/v1/auth/verify-asset</code><br/>asset_status 1→2已销售"]
C2["微信授权登录<br/>👤 绑定客户身份<br/><code>POST /c/v1/auth/wechat-login</code>"]
C3["查看可购套餐<br/>📋 根据资产所属代理展示<br/><code>GET /c/v1/asset/packages</code><br/>查 ShopPackageAllocation"]
C4["下单支付<br/>💳 微信支付<br/><code>POST /c/v1/orders/create</code>"]
C5["套餐生效<br/>✅ 立即激活或排队<br/>activateMainPackage()"]
C1 --> C2 --> C3 --> C4 --> C5
end
平台搭建 --> 代理发展 --> 代理授权
代理授权 --> 代理可向下级重复此过程
代理授权 --> C端消费
代理可向下级重复此过程 --> C端消费
classDef ok fill:#c8e6c9,stroke:#2e7d32
classDef warn fill:#fff9c4,stroke:#f9a825
classDef broken fill:#ffcdd2,stroke:#c62828
```
### 这条链路的问题
| 步骤 | 问题 | 严重度 | 详情 |
|------|------|--------|------|
| ③ 创建套餐 | `enable_realname_activation` 默认 `true` | 🟡 | 不显式传 false 的套餐,全部变成"需实名后才激活"。`package/service.go:129` |
| ⑤ 下单 | C 端实名校验值写错 | 🔴 **Bug** | 检查 `RealNameStatus != 1`,但已实名=2已实名的卡反而不能买。`client_order/service.go:115` |
| ④ 佣金链 | 某级缺授权则上级静默丢佣金 | 🟡 | 代理 B 没有系列授权 → A 的佣金直接不算,无告警。`commission_calculation/service.go:505-509` |
---
## 二、套餐激活的三条路径
> 客户买了套餐后,不一定能立刻生效。根据场景走不同路径。
```mermaid
flowchart TD
PAY["客户支付成功<br/>order/service.go"]
PAY --> CHECK_REALNAME{"套餐需要实名激活?<br/>EnableRealnameActivation"}
CHECK_REALNAME -- "false不需要" --> CHECK_ACTIVE{"已有生效中主套餐?"}
CHECK_REALNAME -- "true需要实名" --> PENDING_REALNAME["🟡 进入待实名状态<br/>status=pending<br/>pending_realname_activation=true<br/><br/>等卡实名后自动激活"]
CHECK_ACTIVE -- "没有" --> ACTIVATE["✅ 立即激活<br/>status=active<br/>设置 activated_at / expires_at<br/>如果卡被停机,自动复机"]
CHECK_ACTIVE -- "有" --> QUEUE["🟡 排队等候<br/>status=pending<br/>priority=max+1<br/><br/>前一个套餐到期后自动接力"]
PENDING_REALNAME --> REALNAME_CHECK["轮询检测卡实名状态<br/>polling:realname 任务<br/>Gateway 返回实名=true"]
REALNAME_CHECK --> TRIGGER["触发首次实名激活<br/>triggerFirstRealnameActivation()"]
TRIGGER --> RPUSH["RPush 到 Redis 队列<br/>package:first:activation"]
RPUSH --> BROKEN["🔴 断链!<br/>scheduler 不消费此队列<br/>套餐永远不会被激活"]
QUEUE --> EXPIRE["当前套餐到期/用完"]
EXPIRE --> NEXT["激活排队中的下一个<br/>ActivateQueuedPackage()<br/>package:queue:activation"]
NEXT --> ACTIVATE2["✅ 激活<br/>级联失效旧加油包"]
style BROKEN fill:#ffcdd2,stroke:#c62828,stroke-width:3px
style ACTIVATE fill:#c8e6c9,stroke:#2e7d32
style ACTIVATE2 fill:#c8e6c9,stroke:#2e7d32
style PENDING_REALNAME fill:#fff9c4,stroke:#f9a825
style QUEUE fill:#fff9c4,stroke:#f9a825
```
### 问题
| 路径 | 状态 | 详情 |
|------|------|------|
| 立即激活 | ✅ 正常 | 无活跃主套餐时直接激活 |
| 排队接力 | ✅ 正常 | 前一个到期后自动激活下一个 |
| 待实名激活 | 🔴 **断链** | RPush 到无人消费的队列,**囤货待实名的套餐永远不会自动激活** |
---
## 三、套餐使用生命周期
> 套餐激活后,经历流量消耗、重置、耗尽、到期等阶段。
```mermaid
flowchart TD
ACTIVE["✅ 生效中<br/>status=active"]
ACTIVE --> USE["日常使用消耗流量<br/>轮询 polling:carddata 检测用量<br/>Gateway 同步 current_month_usage_mb"]
USE --> RESET_CHECK{"到重置周期?<br/>data_reset_cycle"}
RESET_CHECK -- "是" --> RESET["流量重置<br/>package:data:reset 任务<br/>used_data_mb → 0"]
RESET --> USE
RESET_CHECK -- "否" --> EXHAUST_CHECK{"流量用完?"}
EXHAUST_CHECK -- "否" --> EXPIRE_CHECK{"到期了?"}
EXHAUST_CHECK -- "是" --> EXHAUSTED["⚡ 流量耗尽<br/>status=exhausted"]
EXPIRE_CHECK -- "否" --> USE
EXPIRE_CHECK -- "是" --> EXPIRED["⏰ 到期<br/>status=expired"]
EXHAUSTED --> STOP["自动停机<br/>Gateway.StopCard()<br/>network_status=0<br/>stop_reason=traffic_exhausted"]
EXHAUSTED --> NEXT_PKG
EXPIRED --> NEXT_PKG["激活排队套餐<br/>ActivateQueuedPackage()"]
EXPIRED --> CASCADE["级联失效该主套餐的加油包"]
NEXT_PKG --> HAS_NEXT{"有排队套餐?"}
HAS_NEXT -- "有" --> ACTIVATE_NEXT["✅ 激活下一个<br/>如被停机则自动复机"]
HAS_NEXT -- "没有" --> NO_PACKAGE["❌ 无可用套餐<br/>保持停机状态<br/>等客户续购"]
NO_PACKAGE --> CUSTOMER_BUY["客户购买新套餐"]
CUSTOMER_BUY --> ACTIVE
style ACTIVE fill:#c8e6c9
style EXHAUSTED fill:#fff9c4
style EXPIRED fill:#fff9c4
style STOP fill:#ffcdd2
style NO_PACKAGE fill:#ffcdd2
style ACTIVATE_NEXT fill:#c8e6c9
```
**技术链路验证**:✅ 这条完整链路都有对应的代码实现。停机→复机→套餐接力全部通了。
---
## 四、充值 → 佣金 → 提现
> 资金是系统的命脉。这里有三条独立的资金流。
```mermaid
flowchart TD
subgraph 充值流["C 端客户充值"]
R1["客户发起充值<br/>💰 选择金额<br/><code>POST /c/v1/wallet/recharge</code>"]
R2["微信支付"]
R3["支付回调<br/><code>POST /callback/wechat</code><br/>recharge/service.go"]
R4["✅ 资产钱包到账<br/>AssetWallet.balance += amount"]
R5["更新累计充值<br/>accumulated_recharge_by_series"]
R6{"满足一次性佣金条件?"}
R7["✅ 佣金直接发放<br/>(同步,不走队列)<br/>→ 店铺 commission 钱包"]
R8["触发自动购包检查<br/>task:auto_purchase_after_recharge"]
R1 --> R2 --> R3 --> R4 --> R5 --> R6
R6 -- "是" --> R7
R6 -- "否" --> R8
R4 --> R8
end
subgraph 订单佣金流["订单支付触发佣金"]
O1["客户购买套餐"]
O2["支付成功"]
O3["异步佣金任务入队<br/>commission:calculate"]
O4["链式佣金计算<br/>沿代理层级往上"]
O5["✅ 各级代理 commission 钱包到账"]
O1 --> O2 --> O3 --> O4 --> O5
end
subgraph 提现流["代理佣金提现"]
W1["代理发起提现<br/><code>POST /admin/my/commission/withdrawals</code>"]
W2["冻结 commission 钱包余额"]
W3["平台审批<br/><code>POST /admin/commission-withdrawals/:id/approve</code>"]
W4["扣除冻结余额<br/>status=已通过"]
W5["🟡 缺失:确认到账<br/>模型有 status=4已到账<br/>但没有代码把它设为 4<br/>实际靠线下打款"]
W1 --> W2 --> W3 --> W4 --> W5
end
subgraph 预充值流["平台给代理预充值"]
P1["平台充值<br/><code>POST /admin/agent-recharges</code>"]
P2["✅ 代理 main 钱包到账<br/>用于钱包支付订单"]
P1 --> P2
end
style W5 fill:#fff9c4,stroke:#f9a825,stroke-width:2px
```
### 问题
| 环节 | 状态 | 说明 |
|------|------|------|
| 充值 → 钱包到账 | ✅ | 链路完整 |
| 充值 → 一次性佣金 | ✅ | 同步发放,但不走异步队列(与订单佣金路径不一致) |
| 订单 → 佣金计算 | ✅ | 异步链路完整 |
| 佣金提现 → 审批 | ✅ | 冻结/审批/拒绝都有 |
| 审批 → 到账确认 | 🟡 **缺失** | 没有"已到账"标记入口,提现审批后就结束了 |
---
## 五、停机 / 复机
```mermaid
flowchart TD
subgraph 自动停机["自动停机(流量耗尽)"]
AS1["轮询检测到无活跃套餐"]
AS2["调用 Gateway 停机<br/>Gateway.StopCard()"]
AS3["更新状态<br/>network_status=0<br/>stopped_at=now<br/>stop_reason=traffic_exhausted"]
AS1 --> AS2 --> AS3
end
subgraph 自动复机["自动复机(新套餐激活)"]
AR1["新套餐激活触发回调"]
AR2{"停机原因是流量耗尽?"}
AR2 -- "是" --> AR3["调用 Gateway 复机<br/>Gateway.StartCard()"]
AR2 -- "否(手动停机)" --> AR4["❌ 不自动复机<br/>手动停的需要手动开"]
AR3 --> AR5["更新状态<br/>network_status=1<br/>resumed_at=now"]
AR1 --> AR2
end
subgraph 手动停复机["后台手动操作"]
M1["管理员/代理手动停机<br/><code>POST /admin/assets/card/:iccid/stop</code>"]
M2["管理员/代理手动复机<br/><code>POST /admin/assets/card/:iccid/start</code>"]
M1 --> M1A["stop_reason=manual"]
M2 --> M2A["stop_reason 清空"]
end
style AS3 fill:#ffcdd2
style AR5 fill:#c8e6c9
style AR4 fill:#fff9c4
```
**状态**:✅ 全部链路完整,包括 Gateway 实际调用、保护期一致性校验。
---
## 六、换货流程
```mermaid
flowchart LR
subgraph 发起["① 后台发起换货"]
E1["代理创建换货单<br/><code>POST /admin/exchanges</code><br/>选择旧资产<br/>状态:待填写信息"]
end
subgraph 客户["② 客户填写信息"]
E2["C 端提交收货地址<br/><code>POST /c/v1/exchange/:id/shipping-info</code><br/>状态:待发货"]
end
subgraph 发货["③ 后台发货"]
E3["绑定新资产+物流<br/><code>POST /admin/exchanges/:id/ship</code><br/>新资产必须 asset_status=1<br/>状态:已发货"]
end
subgraph 完成["④ 完成换货"]
E4["确认完成<br/><code>POST /admin/exchanges/:id/complete</code>"]
E5{"需要迁移数据?"}
E5 -- "是" --> E6["迁移:<br/>• 钱包余额<br/>• 套餐记录<br/>• 累计充值/首充<br/>• 客户绑定<br/>• 资源标签"]
E6 --> E7["旧资产 asset_status → 3已换货"]
E5 -- "否" --> E7B["仅标记完成"]
end
发起 --> 客户 --> 发货 --> 完成
style E7 fill:#fff9c4
```
**状态**:✅ 链路完整。旧资产正确标记,数据迁移覆盖钱包/套餐/标签等。
---
## 七、企业客户流程
> 🔴 **这是系统最大的业务断裂**
```mermaid
flowchart TD
subgraph 平台操作["平台/代理 操作(✅ 已实现)"]
E1["创建企业<br/><code>POST /admin/enterprises</code>"]
E2["创建企业账号<br/>user_type=4"]
E3["授权卡给企业<br/><code>POST /admin/enterprises/:id/allocate-cards</code>"]
E4["授权设备给企业<br/><code>POST /admin/enterprises/:id/allocate-devices</code>"]
E1 --> E2
E1 --> E3
E1 --> E4
end
subgraph 企业使用["企业自己管理(🔴 完全缺失)"]
F1["❌ 企业登录后无可用接口"]
F2["❌ 看不到被授权的卡列表"]
F3["❌ 看不到被授权的设备列表"]
F4["❌ 无法对卡/设备停复机"]
F5["❌ 无法查看套餐/流量"]
F6["❌ 无法为卡/设备续购套餐"]
end
平台操作 --"🔴 断裂"--> 企业使用
subgraph 已有代码未接入["Service 层已写好但没接路由"]
G1["ListDevicesForEnterprise()"]
G2["GetDeviceDetail()"]
G3["SuspendCard() / ResumeCard()"]
end
style 企业使用 fill:#ffcdd2,stroke:#c62828,stroke-width:3px
style 已有代码未接入 fill:#fff9c4
```
**缺失清单**
- ❌ 企业认证中间件(只有 AdminAuth 和 PersonalAuth
-`/api/enterprise/*``/api/h5/*` 路由组
- ❌ 企业端 Handler 层
- ❌ 企业端可用的卡/设备/套餐/流量接口
**规范中的设计**
- `openspec/specs/enterprise-device-authorization/spec.md` — 明确写了企业端 H5 设备列表、详情、停复机
- `openspec/changes/archive/2026-01-29-add-enterprise-device-authorization/proposal.md` — "新增企业端设备管理 APIH5"
---
## 八、资产状态系统
> 当前系统有**三套并行的状态字段**,容易混淆。
```mermaid
flowchart LR
subgraph status字段["status分销链状态"]
S1["1 在库"] -- "AllocateCards()" --> S2["2 已分销"]
S2 -. "❌ 无代码实现" .-> S3["3 已激活"]
S3 -. "❌ 无代码实现" .-> S4["4 已停用"]
end
subgraph asset_status字段["asset_status业务生命周期"]
A1["1 在库"] -- "客户绑定资产" --> A2["2 已销售"]
A2 -- "手动停用" --> A4["4 已停用"]
A1 -- "手动停用" --> A4
A2 -- "换货完成" --> A3["3 已换货"]
end
subgraph network_status字段["network_status网络状态"]
N1["1 开机"] -- "停机" --> N0["0 停机"]
N0 -- "复机" --> N1
end
style S3 fill:#e0e0e0,stroke:#9e9e9e,stroke-dasharray: 5 5
style S4 fill:#e0e0e0,stroke:#9e9e9e,stroke-dasharray: 5 5
```
**问题**`status` 字段的 3已激活和 4已停用从未被任何代码使用。真正的业务状态走 `asset_status`,网络状态走 `network_status`。两个 status 字段语义重叠导致混淆。
---
## 九、实名状态不一致
```mermaid
flowchart TD
subgraph 三处定义["同一个字段,三处定义不一致"]
D1["Model 注释<br/>0=未实名 1=已实名"]
D2["DTO 注释<br/>0=未实名 1=实名中 2=已实名"]
D3["轮询代码实际写入<br/>true→2, false→0"]
end
subgraph 影响["导致的问题"]
I1["C 端下单校验<br/>RealNameStatus != 1<br/>只允许'实名中'通过?"]
I2["已实名的卡 status=2<br/>2 != 1 为 true<br/>🔴 被拒单!"]
end
D1 --> I1
D2 --> I1
D3 --> I2
style I2 fill:#ffcdd2,stroke:#c62828,stroke-width:3px
```
---
## 十、代理佣金链式分配
> 当子代理的客户买了套餐,佣金怎么分?
```mermaid
flowchart BT
CUSTOMER["客户购买套餐<br/>💰 售价 ¥99"]
CUSTOMER --> C_AGENT["三级代理 C<br/>成本价 ¥60<br/>利润¥99 - ¥60 = ¥39<br/>→ C 的 commission 钱包"]
C_AGENT --> B_AGENT["二级代理 B<br/>成本价 ¥40<br/>利润¥60 - ¥40 = ¥20<br/>→ B 的 commission 钱包"]
B_AGENT --> A_AGENT["一级代理 A<br/>成本价 ¥25<br/>利润¥40 - ¥25 = ¥15<br/>→ A 的 commission 钱包"]
A_AGENT --> PLATFORM["平台<br/>成本价 ¥25A的成本价<br/>不生成佣金记录<br/>平台收益在售价中"]
MISSING["⚠️ 如果 B 没有该系列授权<br/>→ B 和 A 都不会拿到佣金<br/>→ 无告警通知"]
style CUSTOMER fill:#e3f2fd
style C_AGENT fill:#c8e6c9
style B_AGENT fill:#c8e6c9
style A_AGENT fill:#c8e6c9
style PLATFORM fill:#e1f5fe
style MISSING fill:#fff9c4,stroke:#f9a825
```
**技术实现**`commission_calculation/service.go``calculateChainOneTimeCommission()` 和差价佣金计算,沿 `Shop.ParentID` 循环向上,每级拿差额。
---
## 十一、异步任务完整性
```mermaid
flowchart LR
subgraph 完整["✅ 链路完整"]
T1["commission:calculate"]
T2["polling:realname / carddata / package"]
T3["polling:protect"]
T4["package:data:reset"]
T5["package:queue:activation"]
T6["order:expire"]
T7["auto_purchase_after_recharge"]
T8["iot_card:import / device:import"]
T9["commission:stats:* (3个)"]
T10["alert:check / data:cleanup"]
end
subgraph 断链["🔴 断链"]
B1["package:first:activation<br/><br/>生产者triggerFirstRealnameActivation()<br/>消费者HandlePackageFirstActivation()<br/><br/>问题RPush 到 Redis list<br/>但 scheduler 不消费这个队列"]
end
subgraph 未确认["⚠️ 待确认"]
U1["sim:status:sync<br/>有消费者,未确认生产者"]
end
style B1 fill:#ffcdd2,stroke:#c62828,stroke-width:2px
style U1 fill:#fff9c4
```
---
## 十二、系统全景互联图
> **这是整个系统的"大地图"**,展示所有业务域之间的数据流和触发关系。
> 红色=断链/缺失,黄色=有风险,绿色=正常。
```mermaid
flowchart TB
%% ===== 平台搭建层 =====
subgraph 平台搭建["🏗️ 平台搭建"]
CARRIER["运营商"]
SERIES["套餐系列"]
PKG["套餐"]
CARRIER --> SERIES --> PKG
end
%% ===== 代理体系层 =====
subgraph 代理体系["🏪 代理体系(多级)"]
SHOP["创建店铺/子代理"]
GRANT["授权系列给代理<br/>配置成本价/佣金"]
ALLOC_PKG["分配套餐<br/>设置零售价/上架"]
SHOP --> GRANT --> ALLOC_PKG
end
%% ===== 资产层 =====
subgraph 资产管理["📦 资产管理"]
IMPORT["导入卡/设备"]
ALLOC_ASSET["分配给代理<br/>status 1→2"]
BIND["C端客户绑定<br/>asset_status 1→2"]
IMPORT --> ALLOC_ASSET --> BIND
end
%% ===== C端层 =====
subgraph C端["📱 C 端客户"]
LOGIN["微信登录"]
SEE_PKG["查看可购套餐"]
ORDER["下单购买"]
PAY["微信支付"]
USE["日常使用"]
RECHARGE["充值"]
LOGIN --> SEE_PKG --> ORDER --> PAY
PAY --> USE
USE --> RECHARGE
end
%% ===== 套餐层 =====
subgraph 套餐系统["📋 套餐生命周期"]
ACTIVATE["激活"]
QUEUE_WAIT["排队等候"]
PENDING_REALNAME["待实名激活"]
DATA_RESET["流量重置"]
EXHAUST["流量耗尽"]
EXPIRE["到期"]
NEXT_ACTIVATE["激活下一个"]
end
%% ===== 支付/财务层 =====
subgraph 财务["💰 资金流"]
WALLET_IN["资产钱包到账"]
COMMISSION_CALC["佣金计算<br/>链式分账"]
COMMISSION_WALLET["代理佣金钱包"]
WITHDRAW["佣金提现"]
AGENT_RECHARGE["平台给代理充值<br/>主钱包"]
end
%% ===== 轮询层 =====
subgraph 轮询["🔄 轮询系统"]
POLL_REALNAME["实名检测"]
POLL_CARDDATA["流量检测"]
POLL_PACKAGE["套餐检测"]
POLL_RESET["数据重置"]
end
%% ===== 网关层 =====
subgraph 网关["📡 Gateway"]
GW_STOP["停机"]
GW_START["复机"]
GW_QUERY["查询实名/流量"]
end
%% ===== 企业层 =====
subgraph 企业["🏢 企业客户"]
ENT_AUTH["授权卡/设备给企业"]
ENT_USE["🔴 企业自己管理<br/>(完全缺失)"]
end
%% ===== 换货层 =====
subgraph 换货["🔄 换货"]
EXCHANGE["发起→发货→完成"]
MIGRATE["迁移钱包/套餐/标签"]
end
%% ===== 自动任务 =====
subgraph 自动任务["⚡ 自动任务"]
AUTO_PURCHASE["🔴 自动购包<br/>(无人触发)"]
ORDER_EXPIRE["订单超时取消"]
ALERT["告警检查"]
end
%% ========== 连线 ==========
%% 平台搭建 → 代理
平台搭建 --> 代理体系
平台搭建 --> 资产管理
%% 代理 → C端可见
ALLOC_PKG --> SEE_PKG
ALLOC_ASSET --> BIND
%% C端购买链路
PAY -->|"支付成功"| ACTIVATE
PAY -->|"有主套餐"| QUEUE_WAIT
PAY -->|"⚠️ 需实名"| PENDING_REALNAME
PAY -->|"异步"| COMMISSION_CALC
%% 充值链路
RECHARGE -->|"回调"| WALLET_IN
WALLET_IN -->|"满足条件"| COMMISSION_CALC
RECHARGE -.->|"🔴 应触发但未接线"| AUTO_PURCHASE
%% 佣金链路
COMMISSION_CALC -->|"各级差额"| COMMISSION_WALLET
COMMISSION_WALLET --> WITHDRAW
AGENT_RECHARGE -->|"主钱包"| ORDER
%% 套餐生命周期
ACTIVATE --> USE
USE --> DATA_RESET
USE --> EXHAUST
USE --> EXPIRE
EXHAUST -->|"停机"| GW_STOP
EXPIRE --> NEXT_ACTIVATE
NEXT_ACTIVATE -->|"复机"| GW_START
QUEUE_WAIT -->|"前一个到期"| NEXT_ACTIVATE
%% 实名激活断链
POLL_REALNAME -->|"首次实名"| PENDING_REALNAME
PENDING_REALNAME -.->|"🔴 断链"| ACTIVATE
%% 轮询 → 网关
POLL_REALNAME --> GW_QUERY
POLL_CARDDATA --> GW_QUERY
POLL_CARDDATA -->|"扣减流量"| EXHAUST
POLL_PACKAGE -->|"超额检测"| GW_STOP
POLL_RESET --> DATA_RESET
%% 企业
ALLOC_ASSET --> ENT_AUTH
ENT_AUTH -.->|"🔴 断裂"| ENT_USE
%% 换货
EXCHANGE --> MIGRATE
MIGRATE -->|"旧资产 asset_status=3"| 资产管理
%% 自动任务
ORDER -->|"超时"| ORDER_EXPIRE
AUTO_PURCHASE -.->|"🔴 应连接"| ORDER
%% 样式
style ENT_USE fill:#ffcdd2,stroke:#c62828,stroke-width:3px
style PENDING_REALNAME fill:#ffcdd2,stroke:#c62828,stroke-width:2px
style AUTO_PURCHASE fill:#ffcdd2,stroke:#c62828,stroke-width:2px
style ACTIVATE fill:#c8e6c9
style NEXT_ACTIVATE fill:#c8e6c9
style COMMISSION_CALC fill:#c8e6c9
style WALLET_IN fill:#c8e6c9
```
### 图中的五条断链 / 缺失
| # | 断链位置 | 问题 | 影响 |
|---|---------|------|------|
| ① | 充值 → 自动购包 | `NewAutoPurchaseTask()` 无人调用 | 强充后不会自动购买套餐 |
| ② | 实名检测 → 待实名套餐激活 | RPush 到无人消费的队列 | 囤货套餐永远不激活 |
| ③ | 企业授权 → 企业管理 | 无企业端接口 | 企业用户无法使用系统 |
| ④ | 自动购包 → 佣金 | 创建已支付订单但不触发佣金计算 | 自动购包的订单不产生佣金 |
| ⑤ | C 端下单 → 实名校验 | `!= 1` 应为 `!= 2` | 已实名的卡被拒单 |
---
## 十三、自动购包完整链路
> 🔴 这条链路**有处理器但无人触发**
```mermaid
flowchart TD
subgraph 预期流程["设计中的完整流程"]
R1["C 端发起强充充值<br/><code>POST /c/v1/wallet/recharge</code><br/>关联套餐 LinkedPackageIDs"]
R2["微信支付成功<br/>充值回调"]
R3["资产钱包到账"]
R4["🔴 应该触发自动购包<br/>Enqueue auto_purchase_after_recharge<br/><br/>但当前代码中<br/>NewAutoPurchaseTask() 无人调用"]
R5["自动购包处理器<br/>auto_purchase.go:ProcessTask()"]
R6["检查钱包余额"]
R7["创建订单(已支付状态)"]
R8["激活套餐"]
R9["🟡 应触发佣金但未触发<br/>没有 enqueueCommissionCalculation"]
R1 --> R2 --> R3 --> R4
R4 -.->|"未接线"| R5
R5 --> R6 --> R7 --> R8
R8 --> R9
end
style R4 fill:#ffcdd2,stroke:#c62828,stroke-width:3px
style R9 fill:#fff9c4,stroke:#f9a825,stroke-width:2px
```
**技术详情**
- 生产者:`task/auto_purchase.go:202-214``NewAutoPurchaseTask()` 创建任务对象,但全项目无调用点
- 消费者:`task/auto_purchase.go:76-199``ProcessTask()` 完整实现了购包逻辑
- 数据前置:`client_order/service.go:370` — 充值记录的 `auto_purchase_status=pending` 已写入
- 佣金缺失:`auto_purchase.go` 内部没有 `enqueueCommissionCalculation` 调用
---
## 十四、轮询系统互联图
```mermaid
flowchart TD
SCHEDULER["轮询调度器<br/>每秒执行一次<br/>scheduler.go"]
SCHEDULER --> REALNAME["实名检测<br/>polling:realname"]
SCHEDULER --> CARDDATA["流量检测<br/>polling:carddata"]
SCHEDULER --> PACKAGE["套餐检测<br/>polling:package"]
SCHEDULER --> RESET["数据重置<br/>每分钟检查"]
SCHEDULER --> PKG_ACTIVATION["套餐过期检查<br/>→ 排队激活"]
SCHEDULER -.->|"🟡 未调度"| PROTECT["保护期一致性<br/>polling:protect"]
REALNAME --> R1["调 Gateway 查实名"]
R1 --> R2["更新 real_name_status"]
R2 --> R3{"首次实名?"}
R3 -- "是" --> R4["🔴 触发待实名激活<br/>断链RPush 到无人消费队列)"]
R3 -- "否" --> R5["重新入队"]
CARDDATA --> C1["调 Gateway 查流量"]
C1 --> C2["更新卡月用量"]
C2 --> C3["扣减套餐流量<br/>加油包优先"]
C3 --> C4{"全部套餐耗尽?"}
C4 -- "是" --> C5["调 Gateway 停机<br/>network_status=0"]
C4 -- "否" --> C6["重新入队"]
PACKAGE --> P1["读套餐配置"]
P1 --> P2{"超额?"}
P2 -- "是" --> P3["调 Gateway 停机"]
P2 -- "否" --> P4["重新入队"]
RESET --> RS1{"日/月/年重置时间到?"}
RS1 -- "是" --> RS2["重置 data_usage_mb=0<br/>status→active<br/>计算 next_reset_at"]
PKG_ACTIVATION --> PA1["检查过期主套餐"]
PA1 --> PA2["级联失效加油包"]
PA2 --> PA3["激活排队主套餐"]
PA3 --> PA4["触发自动复机回调"]
style R4 fill:#ffcdd2,stroke:#c62828,stroke-width:2px
style PROTECT fill:#fff9c4,stroke:#f9a825,stroke-dasharray: 5 5
style C5 fill:#ffcdd2
```
**回调注入情况**
| 回调接口 | 定义位置 | bootstrap 注入 | 状态 |
|---------|---------|---------------|------|
| `PollingCallback`(卡生命周期) | `iot_card/service.go` | `bootstrap/services.go` ✅ | ✅ 已接线 |
| `StopResumeCallback`(停复机) | `package/usage_service.go` | ⚠️ 未确认 | 🟡 可能未注入 |
| `ResumeCallback`(激活后复机) | `package/activation_service.go` | ⚠️ 未确认 | 🟡 可能未注入 |
---
## 十五、订单完整生命周期
```mermaid
flowchart TD
subgraph 创建["创建订单"]
CREATE_ADMIN["后台下单<br/><code>POST /admin/orders</code>"]
CREATE_CLIENT["C端下单<br/><code>POST /c/v1/orders/create</code>"]
CREATE_AUTO["自动购包创建<br/>🔴 未接线"]
end
PENDING["待支付<br/>设置 expires_at<br/>开始超时倒计时"]
subgraph 支付["支付"]
PAY_WECHAT["微信支付"]
PAY_WALLET["钱包支付<br/>立即扣款"]
PAY_CALLBACK["支付回调<br/><code>POST /callback/wechat</code>"]
end
subgraph 支付后副作用["支付成功后的链式反应"]
ACTIVATE_PKG["激活套餐<br/>(立即/排队/待实名)"]
ENQUEUE_COMMISSION["入队佣金计算<br/>commission:calculate"]
CLEAR_EXPIRE["清空 expires_at"]
end
subgraph 超时["超时处理"]
EXPIRE_CHECK["Asynq 每分钟扫描<br/>order_expire 任务"]
CANCEL["取消订单<br/>payment_status=cancelled"]
UNFREEZE["解冻钱包余额<br/>(如果冻结过)"]
end
CREATE_ADMIN --> PENDING
CREATE_CLIENT --> PENDING
CREATE_AUTO -.-> PENDING
PENDING --> PAY_WECHAT --> PAY_CALLBACK
PENDING --> PAY_WALLET
PAY_CALLBACK --> ACTIVATE_PKG
PAY_CALLBACK --> ENQUEUE_COMMISSION
PAY_CALLBACK --> CLEAR_EXPIRE
PAY_WALLET --> ACTIVATE_PKG
PAY_WALLET --> ENQUEUE_COMMISSION
PENDING -->|"超过 expires_at"| EXPIRE_CHECK
EXPIRE_CHECK --> CANCEL --> UNFREEZE
style CREATE_AUTO fill:#ffcdd2,stroke-dasharray: 5 5
```
---
## 十六、C 端客户完整旅程
> 从扫码到日常使用的端到端闭环
```mermaid
flowchart TD
SCAN["📱 扫码/输入虚拟号<br/>verify-asset"]
LOGIN["🔑 微信授权登录<br/>wechat-login / miniapp-login"]
BIND_PHONE["📞 绑定手机号<br/>bind-phone"]
PROFILE["👤 个人资料<br/>get/update profile"]
SEE_ASSET["📊 查看资产信息<br/>asset/info"]
SEE_PKG["📋 查看可购套餐<br/>asset/packages"]
SEE_HISTORY["📜 套餐历史<br/>asset/package-history"]
REFRESH["🔄 刷新资产<br/>asset/refresh"]
BUY["🛒 购买套餐<br/>orders/create → 微信支付"]
ORDER_LIST["📑 我的订单<br/>orders / orders/:id"]
WALLET["💰 钱包详情<br/>wallet/detail"]
RECHARGE["💳 充值<br/>wallet/recharge"]
RECHARGE_LIST["📃 充值记录<br/>wallet/recharges"]
TRANSACTIONS["📃 流水<br/>wallet/transactions"]
REALNAME["🪪 实名认证<br/>realname/link"]
DEVICE_CARDS["📡 设备卡列表<br/>device/cards"]
DEVICE_OPS["⚙️ 设备操作<br/>reboot / factory-reset / wifi / switch-card"]
EXCHANGE_PENDING["🔄 查看换货单<br/>exchange/pending"]
EXCHANGE_SHIP["📦 提交收货信息<br/>exchange/:id/shipping-info"]
SCAN --> LOGIN --> BIND_PHONE --> PROFILE
PROFILE --> SEE_ASSET
SEE_ASSET --> SEE_PKG --> BUY
SEE_ASSET --> SEE_HISTORY
SEE_ASSET --> REFRESH
BUY --> ORDER_LIST
SEE_ASSET --> WALLET --> RECHARGE
WALLET --> RECHARGE_LIST
WALLET --> TRANSACTIONS
SEE_ASSET --> REALNAME
SEE_ASSET --> DEVICE_CARDS --> DEVICE_OPS
SEE_ASSET --> EXCHANGE_PENDING --> EXCHANGE_SHIP
style SCAN fill:#e3f2fd
style BUY fill:#c8e6c9
style RECHARGE fill:#c8e6c9
```
**这条旅程的完整性**:✅ C 端所有接口都已实现(在 `/api/c/v1/*` 下),功能闭环。主要问题在后端处理逻辑(实名校验 bug、激活断链不在接口层。
---
## 十六、TODO 留桩清单
> 代码中明确标记了 TODO 但未实现的功能。
| 位置 | TODO 内容 | 影响 |
|------|----------|------|
| `order/service.go:2356` | 实现富友支付发起逻辑 | 富友支付渠道不可用 |
| `order/service.go:2362` | 实现富友小程序支付发起逻辑 | 同上 |
| `order/service.go:2107,2163` | 从 payment_config_id 动态加载支付配置 | 多商户支付不可用 |
| `recharge/service.go:271` | 按 payment_config_id 加载配置验签 | 充值验签留桩 |
| `customer/service.go:53,66` | 通过 PersonalCustomerPhoneStore 管理手机号 | 客户手机号 CRUD 不完整 |
| `polling/alert_service.go:395,405` | 集成邮件/短信服务 | 告警只写日志,不发通知 |
---
## 📋 完整问题清单
### 🔴 生产 Bug业务跑不通
| # | 问题 | 影响 | 位置 |
|---|------|------|------|
| **P0-1** | C 端实名校验值写错 | 已实名的卡无法购买需实名套餐 | `client_order/service.go:115` |
| **P0-2** | 实名激活任务断链 | 囤货待实名的套餐永远不会自动激活 | `polling_handler.go:1093` |
| **P0-3** | 自动购包任务无生产者 | `NewAutoPurchaseTask()` 全项目无人调用,强充后不会自动购包 | `task/auto_purchase.go:202` |
| **P0-4** | 自动购包不触发佣金 | 创建了已支付订单但没有 `enqueueCommissionCalculation` | `task/auto_purchase.go` |
### 🟠 功能缺失(设计了没实现)
| # | 问题 | 影响 | 出处 |
|---|------|------|------|
| **P1-1** | 企业端接口完全缺失 | 企业用户无法管理自己的资产 | enterprise-device-authorization spec |
| **P1-2** | 企业认证中间件缺失 | 企业无独立登录鉴权 | b-end-auth spec |
| **P1-3** | 提现无"已到账"确认 | 审批后无法标记打款完成 | commission_withdrawal model |
| **P1-4** | 富友支付未实现 | 该支付渠道不可用 | order/service.go:2356 |
| **P1-5** | 多商户支付配置未实现 | 无法按商户动态加载支付 | order/service.go:2107 |
| **P1-6** | `polling:protect` 未被调度 | 保护期一致性检查定义了但 scheduler 不消费 | polling/scheduler.go |
### 🟡 设计缺陷 / 技术债
| # | 问题 | 影响 | 位置 |
|---|------|------|------|
| **P2-1** | EnableRealnameActivation 默认 true | 所有套餐默认需实名 | package/service.go:129 |
| **P2-2** | 实名状态值三处不一致 | 校验逻辑混乱 | model / DTO / polling |
| **P2-3** | status 与 asset_status 冗余 | status 的 3/4 是死状态 | iot_card.go / device.go |
| **P2-4** | 佣金链断裂无告警 | 中间级缺授权则上级静默丢佣金 | commission_calculation |
| **P2-5** | 废弃字段仍在并行写入 | first_commission_paid / accumulated_recharge | recharge/service.go |
| **P2-6** | 告警只写日志不发通知 | 邮件/短信未集成 | alert_service.go |
| **P2-7** | StopResumeCallback / ResumeCallback 可能未注入 | 停复机和激活后复机的回调可能未在 bootstrap 接线 | activation_service / usage_service |
### 🔵 待确认
| # | 问题 | 说明 |
|---|------|------|
| **Q-1** | `sim:status:sync` 任务生产者 | 有消费者无生产者 |
| **Q-2** | `package:queue:activation` 完整性 | 生产→消费链待验证 |
| **Q-3** | 充值验签留桩 | recharge 验签由外层处理,未确认 |
---
## 🔧 修复优先级建议
### 立即修复P0— 业务跑不通
1. **P0-1** `client_order/service.go:115``!= 1` 改为 `!= 2`
2. **P0-2** `polling_handler.go:1093` — RPush 改为 `queueClient.Enqueue()`,或 scheduler 添加消费者
3. **P0-3** 充值回调中补充 `NewAutoPurchaseTask` 的 Enqueue 调用
4. **P0-4** `auto_purchase.go` 处理成功后补充 `enqueueCommissionCalculation`
### 尽快修复P1— 功能缺失
5. 统一实名状态值定义
6. 确认 `EnableRealnameActivation` 默认值是否符合业务预期
7. 补充提现"已到账"标记接口
8. 企业端接口(根据业务优先级排期)
9. `polling:protect` 保护期检查接入调度
### 技术债清理P2
10. 评估统一 `status``asset_status`
11. 停止废弃字段的并行写入
12. 佣金链断裂时添加告警日志/通知
13. 确认 StopResumeCallback / ResumeCallback 注入情况