# 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
Shop level=1, parent_id=NULL] P --> A2[一级代理 B
Shop level=1] A1 --> B1[二级代理 C
level=2, parent_id=A] A1 --> B2[二级代理 D
level=2, parent_id=A] B1 --> C1[三级代理 E
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["创建运营商
📡 中国移动/联通/电信
POST /admin/carriers"] S2["创建套餐系列
📦 如'移动30G月租系列'
POST /admin/package-series"] S3["创建套餐
💰 30G/月 ¥29.9
POST /admin/packages"] S4["导入 IoT 卡/设备
📥 批量导入 ICCID/设备号
POST /admin/iot-cards/import"] S1 --> S2 --> S3 S1 --> S4 end subgraph 代理发展["② 发展代理体系"] D1["创建一级代理
🏪 开设店铺+管理员账号
POST /admin/shops"] D2["代理创建子代理
🏪 parent_id=自己
同一接口,RBAC控制"] D1 --> D2 end subgraph 代理授权["③ 给代理配货"] G1["分配资产给代理
📦 IoT卡/设备 → 代理名下
POST /admin/iot-cards/allocate
status 1→2, 写入 shop_id"] G2["授权系列给代理
🔑 代理可以卖哪些系列
POST /admin/shop-series-grants
配置:成本价、佣金金额"] G3["分配套餐
📋 代理可卖哪些具体套餐
POST /admin/shop-package-batch-allocations
设置零售价、上架状态"] G2 --> G3 end subgraph 代理可向下级重复此过程["④ 代理给子代理配货(同样的流程)"] R1["代理也可以:
• 分配资产给子代理
• 授权系列给子代理
• 设置子代理的成本价/佣金

⚠️ 子代理佣金 ≤ 自己佣金"] end subgraph C端消费["⑤ C 端客户消费"] C1["扫码/输虚拟号验证资产
📱 识别卡/设备
POST /c/v1/auth/verify-asset
asset_status 1→2(已销售)"] C2["微信授权登录
👤 绑定客户身份
POST /c/v1/auth/wechat-login"] C3["查看可购套餐
📋 根据资产所属代理展示
GET /c/v1/asset/packages
查 ShopPackageAllocation"] C4["下单支付
💳 微信支付
POST /c/v1/orders/create"] C5["套餐生效
✅ 立即激活或排队
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["客户支付成功
order/service.go"] PAY --> CHECK_REALNAME{"套餐需要实名激活?
EnableRealnameActivation"} CHECK_REALNAME -- "false(不需要)" --> CHECK_ACTIVE{"已有生效中主套餐?"} CHECK_REALNAME -- "true(需要实名)" --> PENDING_REALNAME["🟡 进入待实名状态
status=pending
pending_realname_activation=true

等卡实名后自动激活"] CHECK_ACTIVE -- "没有" --> ACTIVATE["✅ 立即激活
status=active
设置 activated_at / expires_at
如果卡被停机,自动复机"] CHECK_ACTIVE -- "有" --> QUEUE["🟡 排队等候
status=pending
priority=max+1

前一个套餐到期后自动接力"] PENDING_REALNAME --> REALNAME_CHECK["轮询检测卡实名状态
polling:realname 任务
Gateway 返回实名=true"] REALNAME_CHECK --> TRIGGER["触发首次实名激活
triggerFirstRealnameActivation()"] TRIGGER --> RPUSH["RPush 到 Redis 队列
package:first:activation"] RPUSH --> BROKEN["🔴 断链!
scheduler 不消费此队列
套餐永远不会被激活"] QUEUE --> EXPIRE["当前套餐到期/用完"] EXPIRE --> NEXT["激活排队中的下一个
ActivateQueuedPackage()
package:queue:activation"] NEXT --> ACTIVATE2["✅ 激活
级联失效旧加油包"] 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["✅ 生效中
status=active"] ACTIVE --> USE["日常使用消耗流量
轮询 polling:carddata 检测用量
Gateway 同步 current_month_usage_mb"] USE --> RESET_CHECK{"到重置周期?
data_reset_cycle"} RESET_CHECK -- "是" --> RESET["流量重置
package:data:reset 任务
used_data_mb → 0"] RESET --> USE RESET_CHECK -- "否" --> EXHAUST_CHECK{"流量用完?"} EXHAUST_CHECK -- "否" --> EXPIRE_CHECK{"到期了?"} EXHAUST_CHECK -- "是" --> EXHAUSTED["⚡ 流量耗尽
status=exhausted"] EXPIRE_CHECK -- "否" --> USE EXPIRE_CHECK -- "是" --> EXPIRED["⏰ 到期
status=expired"] EXHAUSTED --> STOP["自动停机
Gateway.StopCard()
network_status=0
stop_reason=traffic_exhausted"] EXHAUSTED --> NEXT_PKG EXPIRED --> NEXT_PKG["激活排队套餐
ActivateQueuedPackage()"] EXPIRED --> CASCADE["级联失效该主套餐的加油包"] NEXT_PKG --> HAS_NEXT{"有排队套餐?"} HAS_NEXT -- "有" --> ACTIVATE_NEXT["✅ 激活下一个
如被停机则自动复机"] HAS_NEXT -- "没有" --> NO_PACKAGE["❌ 无可用套餐
保持停机状态
等客户续购"] 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["客户发起充值
💰 选择金额
POST /c/v1/wallet/recharge"] R2["微信支付"] R3["支付回调
POST /callback/wechat
recharge/service.go"] R4["✅ 资产钱包到账
AssetWallet.balance += amount"] R5["更新累计充值
accumulated_recharge_by_series"] R6{"满足一次性佣金条件?"} R7["✅ 佣金直接发放
(同步,不走队列)
→ 店铺 commission 钱包"] R8["触发自动购包检查
task:auto_purchase_after_recharge"] R1 --> R2 --> R3 --> R4 --> R5 --> R6 R6 -- "是" --> R7 R6 -- "否" --> R8 R4 --> R8 end subgraph 订单佣金流["订单支付触发佣金"] O1["客户购买套餐"] O2["支付成功"] O3["异步佣金任务入队
commission:calculate"] O4["链式佣金计算
沿代理层级往上"] O5["✅ 各级代理 commission 钱包到账"] O1 --> O2 --> O3 --> O4 --> O5 end subgraph 提现流["代理佣金提现"] W1["代理发起提现
POST /admin/my/commission/withdrawals"] W2["冻结 commission 钱包余额"] W3["平台审批
POST /admin/commission-withdrawals/:id/approve"] W4["扣除冻结余额
status=已通过"] W5["🟡 缺失:确认到账
模型有 status=4(已到账)
但没有代码把它设为 4
实际靠线下打款"] W1 --> W2 --> W3 --> W4 --> W5 end subgraph 预充值流["平台给代理预充值"] P1["平台充值
POST /admin/agent-recharges"] P2["✅ 代理 main 钱包到账
用于钱包支付订单"] P1 --> P2 end style W5 fill:#fff9c4,stroke:#f9a825,stroke-width:2px ``` ### 问题 | 环节 | 状态 | 说明 | |------|------|------| | 充值 → 钱包到账 | ✅ | 链路完整 | | 充值 → 一次性佣金 | ✅ | 同步发放,但不走异步队列(与订单佣金路径不一致) | | 订单 → 佣金计算 | ✅ | 异步链路完整 | | 佣金提现 → 审批 | ✅ | 冻结/审批/拒绝都有 | | 审批 → 到账确认 | 🟡 **缺失** | 没有"已到账"标记入口,提现审批后就结束了 | --- ## 五、停机 / 复机 ```mermaid flowchart TD subgraph 自动停机["自动停机(流量耗尽)"] AS1["轮询检测到无活跃套餐"] AS2["调用 Gateway 停机
Gateway.StopCard()"] AS3["更新状态
network_status=0
stopped_at=now
stop_reason=traffic_exhausted"] AS1 --> AS2 --> AS3 end subgraph 自动复机["自动复机(新套餐激活)"] AR1["新套餐激活触发回调"] AR2{"停机原因是流量耗尽?"} AR2 -- "是" --> AR3["调用 Gateway 复机
Gateway.StartCard()"] AR2 -- "否(手动停机)" --> AR4["❌ 不自动复机
手动停的需要手动开"] AR3 --> AR5["更新状态
network_status=1
resumed_at=now"] AR1 --> AR2 end subgraph 手动停复机["后台手动操作"] M1["管理员/代理手动停机
POST /admin/assets/card/:iccid/stop"] M2["管理员/代理手动复机
POST /admin/assets/card/:iccid/start"] 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["代理创建换货单
POST /admin/exchanges
选择旧资产
状态:待填写信息"] end subgraph 客户["② 客户填写信息"] E2["C 端提交收货地址
POST /c/v1/exchange/:id/shipping-info
状态:待发货"] end subgraph 发货["③ 后台发货"] E3["绑定新资产+物流
POST /admin/exchanges/:id/ship
新资产必须 asset_status=1
状态:已发货"] end subgraph 完成["④ 完成换货"] E4["确认完成
POST /admin/exchanges/:id/complete"] E5{"需要迁移数据?"} E5 -- "是" --> E6["迁移:
• 钱包余额
• 套餐记录
• 累计充值/首充
• 客户绑定
• 资源标签"] E6 --> E7["旧资产 asset_status → 3(已换货)"] E5 -- "否" --> E7B["仅标记完成"] end 发起 --> 客户 --> 发货 --> 完成 style E7 fill:#fff9c4 ``` **状态**:✅ 链路完整。旧资产正确标记,数据迁移覆盖钱包/套餐/标签等。 --- ## 七、企业客户流程 > 🔴 **这是系统最大的业务断裂** ```mermaid flowchart TD subgraph 平台操作["平台/代理 操作(✅ 已实现)"] E1["创建企业
POST /admin/enterprises"] E2["创建企业账号
user_type=4"] E3["授权卡给企业
POST /admin/enterprises/:id/allocate-cards"] E4["授权设备给企业
POST /admin/enterprises/:id/allocate-devices"] 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` — "新增企业端设备管理 API(H5)" --- ## 八、资产状态系统 > 当前系统有**三套并行的状态字段**,容易混淆。 ```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 注释
0=未实名 1=已实名"] D2["DTO 注释
0=未实名 1=实名中 2=已实名"] D3["轮询代码实际写入
true→2, false→0"] end subgraph 影响["导致的问题"] I1["C 端下单校验
RealNameStatus != 1
只允许'实名中'通过?"] I2["已实名的卡 status=2
2 != 1 为 true
🔴 被拒单!"] end D1 --> I1 D2 --> I1 D3 --> I2 style I2 fill:#ffcdd2,stroke:#c62828,stroke-width:3px ``` --- ## 十、代理佣金链式分配 > 当子代理的客户买了套餐,佣金怎么分? ```mermaid flowchart BT CUSTOMER["客户购买套餐
💰 售价 ¥99"] CUSTOMER --> C_AGENT["三级代理 C
成本价 ¥60
利润:¥99 - ¥60 = ¥39
→ C 的 commission 钱包"] C_AGENT --> B_AGENT["二级代理 B
成本价 ¥40
利润:¥60 - ¥40 = ¥20
→ B 的 commission 钱包"] B_AGENT --> A_AGENT["一级代理 A
成本价 ¥25
利润:¥40 - ¥25 = ¥15
→ A 的 commission 钱包"] A_AGENT --> PLATFORM["平台
成本价 ¥25(A的成本价)
不生成佣金记录
平台收益在售价中"] MISSING["⚠️ 如果 B 没有该系列授权
→ B 和 A 都不会拿到佣金
→ 无告警通知"] 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

生产者:triggerFirstRealnameActivation()
消费者:HandlePackageFirstActivation()

问题:RPush 到 Redis list
但 scheduler 不消费这个队列"] end subgraph 未确认["⚠️ 待确认"] U1["sim:status:sync
有消费者,未确认生产者"] 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["授权系列给代理
配置成本价/佣金"] ALLOC_PKG["分配套餐
设置零售价/上架"] SHOP --> GRANT --> ALLOC_PKG end %% ===== 资产层 ===== subgraph 资产管理["📦 资产管理"] IMPORT["导入卡/设备"] ALLOC_ASSET["分配给代理
status 1→2"] BIND["C端客户绑定
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["佣金计算
链式分账"] COMMISSION_WALLET["代理佣金钱包"] WITHDRAW["佣金提现"] AGENT_RECHARGE["平台给代理充值
主钱包"] 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["🔴 企业自己管理
(完全缺失)"] end %% ===== 换货层 ===== subgraph 换货["🔄 换货"] EXCHANGE["发起→发货→完成"] MIGRATE["迁移钱包/套餐/标签"] end %% ===== 自动任务 ===== subgraph 自动任务["⚡ 自动任务"] AUTO_PURCHASE["🔴 自动购包
(无人触发)"] 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 端发起强充充值
POST /c/v1/wallet/recharge
关联套餐 LinkedPackageIDs"] R2["微信支付成功
充值回调"] R3["资产钱包到账"] R4["🔴 应该触发自动购包
Enqueue auto_purchase_after_recharge

但当前代码中
NewAutoPurchaseTask() 无人调用"] R5["自动购包处理器
auto_purchase.go:ProcessTask()"] R6["检查钱包余额"] R7["创建订单(已支付状态)"] R8["激活套餐"] R9["🟡 应触发佣金但未触发
没有 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["轮询调度器
每秒执行一次
scheduler.go"] SCHEDULER --> REALNAME["实名检测
polling:realname"] SCHEDULER --> CARDDATA["流量检测
polling:carddata"] SCHEDULER --> PACKAGE["套餐检测
polling:package"] SCHEDULER --> RESET["数据重置
每分钟检查"] SCHEDULER --> PKG_ACTIVATION["套餐过期检查
→ 排队激活"] SCHEDULER -.->|"🟡 未调度"| PROTECT["保护期一致性
polling:protect"] REALNAME --> R1["调 Gateway 查实名"] R1 --> R2["更新 real_name_status"] R2 --> R3{"首次实名?"} R3 -- "是" --> R4["🔴 触发待实名激活
(断链:RPush 到无人消费队列)"] R3 -- "否" --> R5["重新入队"] CARDDATA --> C1["调 Gateway 查流量"] C1 --> C2["更新卡月用量"] C2 --> C3["扣减套餐流量
加油包优先"] C3 --> C4{"全部套餐耗尽?"} C4 -- "是" --> C5["调 Gateway 停机
network_status=0"] C4 -- "否" --> C6["重新入队"] PACKAGE --> P1["读套餐配置"] P1 --> P2{"超额?"} P2 -- "是" --> P3["调 Gateway 停机"] P2 -- "否" --> P4["重新入队"] RESET --> RS1{"日/月/年重置时间到?"} RS1 -- "是" --> RS2["重置 data_usage_mb=0
status→active
计算 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["后台下单
POST /admin/orders"] CREATE_CLIENT["C端下单
POST /c/v1/orders/create"] CREATE_AUTO["自动购包创建
🔴 未接线"] end PENDING["待支付
设置 expires_at
开始超时倒计时"] subgraph 支付["支付"] PAY_WECHAT["微信支付"] PAY_WALLET["钱包支付
立即扣款"] PAY_CALLBACK["支付回调
POST /callback/wechat"] end subgraph 支付后副作用["支付成功后的链式反应"] ACTIVATE_PKG["激活套餐
(立即/排队/待实名)"] ENQUEUE_COMMISSION["入队佣金计算
commission:calculate"] CLEAR_EXPIRE["清空 expires_at"] end subgraph 超时["超时处理"] EXPIRE_CHECK["Asynq 每分钟扫描
order_expire 任务"] CANCEL["取消订单
payment_status=cancelled"] UNFREEZE["解冻钱包余额
(如果冻结过)"] 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["📱 扫码/输入虚拟号
verify-asset"] LOGIN["🔑 微信授权登录
wechat-login / miniapp-login"] BIND_PHONE["📞 绑定手机号
bind-phone"] PROFILE["👤 个人资料
get/update profile"] SEE_ASSET["📊 查看资产信息
asset/info"] SEE_PKG["📋 查看可购套餐
asset/packages"] SEE_HISTORY["📜 套餐历史
asset/package-history"] REFRESH["🔄 刷新资产
asset/refresh"] BUY["🛒 购买套餐
orders/create → 微信支付"] ORDER_LIST["📑 我的订单
orders / orders/:id"] WALLET["💰 钱包详情
wallet/detail"] RECHARGE["💳 充值
wallet/recharge"] RECHARGE_LIST["📃 充值记录
wallet/recharges"] TRANSACTIONS["📃 流水
wallet/transactions"] REALNAME["🪪 实名认证
realname/link"] DEVICE_CARDS["📡 设备卡列表
device/cards"] DEVICE_OPS["⚙️ 设备操作
reboot / factory-reset / wifi / switch-card"] EXCHANGE_PENDING["🔄 查看换货单
exchange/pending"] EXCHANGE_SHIP["📦 提交收货信息
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 注入情况