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

36 KiB
Raw Permalink Blame History

CMP 卡管平台 — 系统业务审计报告

基于代码实现非规范设计的全链路审计2026-03-20 产品 + 技术双视角Mermaid 流程图标注业务断点


〇、系统角色与代理层级

角色一览

角色 说明 登录方式 可用接口 状态
平台管理员 运营全局 /api/auth/login /api/admin/*
一级代理 平台发展的代理商 同上RBAC 区分) 同上(数据隔离)
N 级子代理 代理发展的下级,最多 7 级 同上 同上
企业客户 被授权管理卡/设备的企业 同上(能登录) 无可用接口 🔴
C 端客户 终端用户 微信/小程序 /api/c/v1/auth/* /api/c/v1/*

代理层级模型

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.goParentID *uint + Level int1-7创建时自动算层级。 数据权限:登录时预算 SubordinateShopIDs(自己+所有下级GORM Callback 全局过滤。


一、平台搭建 → 代理经营 → C 端消费

这是系统的主干流程平台建好基础数据代理拿到资源去经营C 端客户最终消费。

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

二、套餐激活的三条路径

客户买了套餐后,不一定能立刻生效。根据场景走不同路径。

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 到无人消费的队列,囤货待实名的套餐永远不会自动激活

三、套餐使用生命周期

套餐激活后,经历流量消耗、重置、耗尽、到期等阶段。

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

技术链路验证 这条完整链路都有对应的代码实现。停机→复机→套餐接力全部通了。


四、充值 → 佣金 → 提现

资金是系统的命脉。这里有三条独立的资金流。

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

问题

环节 状态 说明
充值 → 钱包到账 链路完整
充值 → 一次性佣金 同步发放,但不走异步队列(与订单佣金路径不一致)
订单 → 佣金计算 异步链路完整
佣金提现 → 审批 冻结/审批/拒绝都有
审批 → 到账确认 🟡 缺失 没有"已到账"标记入口,提现审批后就结束了

五、停机 / 复机

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 实际调用、保护期一致性校验。


六、换货流程

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

状态 链路完整。旧资产正确标记,数据迁移覆盖钱包/套餐/标签等。


七、企业客户流程

🔴 这是系统最大的业务断裂

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"

八、资产状态系统

当前系统有三套并行的状态字段,容易混淆。

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 字段语义重叠导致混淆。


九、实名状态不一致

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

十、代理佣金链式分配

当子代理的客户买了套餐,佣金怎么分?

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.gocalculateChainOneTimeCommission() 和差价佣金计算,沿 Shop.ParentID 循环向上,每级拿差额。


十一、异步任务完整性

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

十二、系统全景互联图

这是整个系统的"大地图",展示所有业务域之间的数据流和触发关系。 红色=断链/缺失,黄色=有风险,绿色=正常。

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 已实名的卡被拒单

十三、自动购包完整链路

🔴 这条链路有处理器但无人触发

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-214NewAutoPurchaseTask() 创建任务对象,但全项目无调用点
  • 消费者:task/auto_purchase.go:76-199ProcessTask() 完整实现了购包逻辑
  • 数据前置:client_order/service.go:370 — 充值记录的 auto_purchase_status=pending 已写入
  • 佣金缺失:auto_purchase.go 内部没有 enqueueCommissionCalculation 调用

十四、轮询系统互联图

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 ⚠️ 未确认 🟡 可能未注入

十五、订单完整生命周期

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 端客户完整旅程

从扫码到日常使用的端到端闭环

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— 功能缺失

  1. 统一实名状态值定义
  2. 确认 EnableRealnameActivation 默认值是否符合业务预期
  3. 补充提现"已到账"标记接口
  4. 企业端接口(根据业务优先级排期)
  5. polling:protect 保护期检查接入调度

技术债清理P2

  1. 评估统一 statusasset_status
  2. 停止废弃字段的并行写入
  3. 佣金链断裂时添加告警日志/通知
  4. 确认 StopResumeCallback / ResumeCallback 注入情况