Files
junhong_cmp_fiber/.planning/research/ARCHITECTURE.md
2026-03-27 19:04:17 +08:00

22 KiB
Raw Permalink Blame History

Architecture Research

Domain: IoT 卡管理 + 多级分销 + 分佣结算平台Brownfield 单体) Researched: 2026-03-27 Confidence: HIGH基于当前代码库深度分析 + 该领域已知模式验证)


当前架构概述

系统总览

┌──────────────────────────────────────────────────────────────────────┐
│                          API 进程cmd/api                          │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐             │
│  │ /auth    │  │ /admin   │  │ /c/v1    │  │ /callback│             │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘  └────┬─────┘             │
│       │             │             │              │                   │
│       └─────────────┴──── Handler 层 ────────────┘                   │
│                           │                                          │
│                     Service 层(~55 个服务包)                         │
│                           │                                          │
│                     Store 层54 个 Store 文件)                       │
│                           │                                          │
│                     PostgreSQL + Redis                               │
└──────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────────┐
│                        Worker 进程cmd/worker                      │
│  ┌────────────────┐  ┌──────────────────┐  ┌─────────────────────┐  │
│  │ Asynq Worker   │  │ 轮询调度器        │  │ Asynq Scheduler     │  │
│  │ (任务消费)      │  │ (Redis SortedSet)│  │ (定时任务)           │  │
│  └────────┬───────┘  └────────┬─────────┘  └──────────┬──────────┘  │
│           └──────────────────┴────────────────────────┘             │
│                               │                                      │
│                         Task Handler 层16 个任务)                   │
│                               │                                      │
│                     Service / Store 层(复用 API 侧)                  │
└──────────────────────────────────────────────────────────────────────┘

外部集成:
  Gateway运营商接口 ← HTTP Client → IoT 卡/设备状态同步
  微信支付JSAPI/H5  ← PowerWeChat SDK
  富友支付             ← 自研 SDKpkg/fuiou
  联通云 OSS           ← S3 兼容(批量导入/导出)

组件职责

组件 职责 关键文件
Bootstrap 依赖装配,进程启动唯一入口 internal/bootstrap/*.go
RouteSpec + Register() 路由即文档,单次声明驱动 Fiber + OpenAPI internal/routes/registry.go
Auth Middleware JWT/Redis Token 解析,写入 UserContextInfo pkg/middleware/auth.go
DataScope Filter Store 层显式过滤,多租户数据隔离 pkg/middleware/data_scope.go
Service业务核心 幂等、事务编排、业务规则、任务提交 internal/service/**
Store数据访问 SQL、分页、显式权限过滤 internal/store/postgres/*.go
Task Handler Asynq 任务处理(轮询/佣金/导入/过期) internal/task/*.go
Polling Scheduler Redis SortedSet 驱动卡片轮询队列 internal/polling/scheduler.go
Commission Calculation 多级分佣链计算(差价佣金 + 一次性佣金) internal/service/commission_calculation/

组件边界分析

当前已正确划分的边界

① 双进程隔离API vs Worker

  • API 进程HTTP 快速响应,无阻塞 I/O
  • Worker 进程:轮询、批量导入、佣金计算、定时任务
  • 边界清晰,独立部署、独立扩容

② 数据权限隔离Shop 层级过滤)

  • 用户身份 → UserContextInfo → SubordinateShopIDsRedis 缓存)
  • Store 层显式调用 ApplyShopFilter / ApplyEnterpriseFilter
  • 不依赖 GORM Callback权限过滤显式可见

③ 任务队列解耦Asynq

  • 同步调用路径Handler → Service → 提交 Asynq 任务
  • 异步执行路径Worker → Task Handler → Service/Store
  • 支付回调触发的佣金计算走异步路径,不阻塞回调响应

④ 外部服务封装Gateway / 微信 / 富友)

  • Gateway HTTP Client 封装在 internal/gateway/
  • 微信 SDK 封装在 pkg/wechat/
  • 富友 SDK 封装在 pkg/fuiou/
  • 外部依赖注入,可以独立替换

当前边界薄弱点(需要关注)

⑤ Order Service 超大文件Critical

  • internal/service/order/service.go = 2365 行
  • 支付发起、支付回调、订单状态流转、幂等锁、钱包扣款全部耦合
  • 任何一处改动都有全链路回归风险
  • 建议: 按职责拆分为子文件:payment_service.gocallback_service.goorder_state.gowallet_deduction.go

⑥ 轮询系统 asynqClient 依赖缺失High

  • internal/task/polling_handler.go 中实名激活通过 Redis RPush 降级实现,而非 Asynq 任务
  • 架构偏差:同一 Worker 进程内用两套机制投递任务Asynq + RPush
  • 建议: 在 Worker Bootstrap 中注入 asynqClient统一走 Asynq 路径

⑦ C 端 Handler 直接访问 DBMedium

  • 方案 F-4部分 C 端 Handler 绕过 Service 层直接访问 DB
  • 破坏了分层边界,导致业务逻辑散落在 Handler 层
  • 建议: 迁回 Service 层Handler 只做 HTTP 入参解析和响应封装

数据流方向

关键流 1订单创建 + 支付 + 佣金链

用户发起购买
    │
    ▼
Handler → 解析 DTO → 调用 OrderService.Create()
    │
    ▼
OrderService.Create()
├── Redis 幂等键检查(业务键防重)
├── 分布式锁RedisOrderCreateLockKey
├── 二次检查(加锁后再查 Redis
├── DB 事务:创建订单 + 冻结钱包余额
└── 提交 Asynq 支付超时任务30min
    │
    ▼
支付回调(微信/富友)
    │
    ▼
CallbackHandler → PaymentService.HandleCallback()
├── WHERE status=pending 条件更新(防重)
├── 更新订单状态为已支付
├── 解冻钱包余额 / 扣款
└── 提交 CommissionCalculation Asynq 任务
    │
    ▼
Worker: CommissionCalculationTask
├── 查询代理层级路径WITH RECURSIVE / parent_id 链)
├── 计算差价佣金链(每层代理差价 × 数量)
├── 计算一次性佣金(绑定规则)
├── DB 事务:写入 commission_record幂等unique(order_id, agent_id)
└── 更新代理钱包余额

关键流 2IoT 卡轮询 + 实名激活

Polling Scheduler独立 Goroutine
    │
    ├── 从 Redis Sorted Set 取到期卡片
    │       key: polling:realname:queue
    │
    ▼
投递 Asynq 任务 → Worker: PollingHandler
    │
    ├── 调用 Gateway HTTP 接口拉取实名状态
    ├── 更新 iot_card.realname_status
    │
    ▼
实名状态变为已认证 → 触发套餐激活
    │
    ▼ 当前路径RPush待修复为 Asynq
激活任务 → PackageService.ActivateByRealname()
    ├── 查找 status=3待实名激活套餐
    ├── 第一个主套餐 → status=1激活
    ├── 其余主套餐 → status=4排队
    └── 加油包检查并激活

关键流 3流量计费当前方案 vs 改革方案)

当前(方案 E 改革前):
  Gateway 上报流量 → 直接覆盖写 current_month_usage_mb
  问题:自然月由上游归零,历史数据被破坏

改革后(方案 E
  Gateway 上报流量增量
      │
      ▼
  Redis 增量累加(今日流量缓冲)
      │
  每日凌晨批任务 → 写入 tb_card_daily_usage日粒度记录
      │
  查询层Redis 今日数据 + DB 历史数据 合并返回
  优点:历史数据不可变,查询范围可控

关键流 4多租户数据权限

HTTP 请求 → Auth Middleware
    │
    ├── 解析 JWT/Redis Token
    ├── 查询用户 user_type, shop_id, enterprise_id
    ├── GetSubordinateShopIDs()WITH RECURSIVERedis 缓存 30min
    └── 写入 UserContextInfo 到 context.Context
    │
    ▼
Store 层查询
    ├── Agent 用户 → ApplyShopFilter(shopIDs)
    │       WHERE shop_id IN (当前+下级)
    ├── Enterprise 用户 → ApplyEnterpriseFilter(enterpriseID)
    │       WHERE enterprise_id = X
    └── SuperAdmin/Platform → 不过滤

架构模式

模式 1两阶段幂等防重订单创建

What: Redis 业务键 + 分布式锁双重防护,防止并发创建重复订单 When: 任何创建类写操作(订单、提现、激活任务) Trade-offs: 增加 2~3 次 Redis 往返,换取金融级安全

// Step 1: Redis GET 快速检测
val, err := redis.Get(ctx, idempotencyKey).Result()
if err == nil && val != "" { return existingResult }

// Step 2: SetNX 分布式锁
locked, _ := redis.SetNX(ctx, lockKey, now, lockTTL).Result()
if !locked { return ErrTooManyRequests }
defer redis.Del(ctx, lockKey)

// Step 3: 加锁后二次检测(防止锁等待期间重复创建)
val, err = redis.Get(ctx, idempotencyKey).Result()
if err == nil && val != "" { return existingResult }

// Step 4: 执行业务逻辑...
// Step 5: 标记成功
redis.Set(ctx, idempotencyKey, resultID, ttl)

模式 2条件更新防并发状态流转

What: WHERE status = expected_status 的条件 UPDATERowsAffected 判断 When: 支付回调、套餐激活、订单状态变更等有明确状态机的场景 Trade-offs: 需要数据库层有索引支持,效率高但依赖状态设计的正确性

result := tx.Model(&model.Order{}).
    Where("id = ? AND payment_status = ?", orderID, PaymentStatusPending).
    Updates(map[string]any{"payment_status": PaymentStatusPaid})
if result.RowsAffected == 0 {
    // 已被处理或并发冲突,根据当前状态决定返回
}

模式 3Redis Sorted Set 驱动轮询调度

What: 以下次轮询时间为 ScoreICCID 为 Member定时扫描到期项投递 Asynq 任务 When: 需要"不同间隔、海量卡片"的轮询调度场景 Trade-offs: 比 cron 更灵活内存占用与卡片数量正相关Redis 重启需重建队列

// 投入轮询队列Score = 下次轮询 Unix 时间戳)
redis.ZAdd(ctx, "polling:realname:queue", redis.Z{
    Score: float64(nextPollAt.Unix()),
    Member: iccid,
})

// Scheduler 每秒扫描
members := redis.ZRangeByScore(ctx, key, &redis.ZRangeBy{
    Min: "-inf",
    Max: strconv.FormatInt(time.Now().Unix(), 10),
    Limit: &redis.Limit{Count: batchSize},
})
// 投递 Asynq 任务...

模式 4多级佣金链计算向上遍历

What: 从发生订单的代理出发,沿 parent_id 向上遍历,逐级计算差价或固定佣金 When: 每笔订单支付成功后触发(异步任务) Trade-offs: 当前 parent_id 邻接表实现,层级 7 层时 WITH RECURSIVE 性能尚可;超 10 层或高并发时需物化路径优化

// 当前实现DB 递归查询代理链WITH RECURSIVE 或逐级查询)
// 建议:超 1000 TPS 时改为物化路径 + Redis 缓存代理链
func (s *Service) CalculateCommissionChain(ctx context.Context, orderID uint) error {
    order := fetchOrder(ctx, orderID)
    agentChain := fetchAncestorChain(ctx, order.SellerShopID) // 向上遍历
    for i, agent := range agentChain {
        diff := agent.SellPrice - agent.CostPrice
        writeCommissionRecord(ctx, tx, order.ID, agent.ShopID, diff)
    }
}

构建顺序建议

原则: 优先修复已存在但断裂的主链路,再扩展新功能,最后优化架构

阶段 0 — 主链路修复P0当前 MVP 阶段的首要目标)
│
├── 1. 实名常量统一A-1→ 解锁整个实名链路
├── 2. C 端实名校验A-2依赖 A-1
├── 3. 轮询 Handler 注入 asynqClientA-3→ 修复架构偏差
├── 4. 充值回调购包触发 + 佣金补全A-4→ 核心收入链路
├── 5. 代购订单金额修正A-5→ 佣金归零问题
└── 6. 并发校验修复A-7, C-3→ 提现/激活配置并发

阶段 1 — 功能补全(参考方案 B/C/D/H/J
│
├── 企业端权限完善B-1, B-2
├── 提现/审批流程完善C-1, C-2
├── 设备体系完善D-0 ~ D-3
└── 轮询系统小修H-1 ~ H-4

阶段 2 — 流量计费改革(方案 E独立窗口
│
├── 新建 tb_card_daily_usage迁移
├── Redis 增量缓存层
└── 查询层适配(双段数据合并)

阶段 3 — 退款体系(方案 I
│
├── 退款模型 + 7 个接口
└── 佣金钱包反向扣减

阶段 4 — Order Service 拆分(架构改善)
│
├── payment_service.go支付发起
├── callback_service.go回调处理
├── order_state.go状态流转
└── wallet_deduction.go钱包操作

组件依赖关系:

IoT卡状态 → 实名认证 → 套餐激活 → 流量计费 → 订单计算
     │              │          │
     ▼              ▼          ▼
  Gateway      数据轮询     Commission
  集成层       调度层       计算层

扩展挑战与 SaaS 化瓶颈

SaaS 化时会成为瓶颈的当前决策

当前决策 SaaS 化面临的问题 迁移路径
单一 PostgreSQL 数据库 所有"租户"shop共享同一 DB 添加 tenant_id 列 + RLS或 Schema 隔离
GORM 数据过滤基于 shop_id 过滤逻辑散布于 54 个 Store 文件 统一 Tenant Context 注入层,确保覆盖率
Config 嵌入二进制 多租户需动态配置微信AppID/支付商户号) 已有 tb_wechat_config,需完全消除环境变量硬依赖
单进程 Worker 一个 Worker 处理所有租户的轮询/佣金 按租户分队列;大租户独立 Worker
parent_id 邻接表代理层级 跨租户代理关系查询膨胀 tenant_id 隔离;超 5 万代理考虑物化路径
commission_calculation 单服务 高 TPS 下单文件 653 行线性计算 异步 + 批量写入;超 1000 TPS 考虑 Flink

各规模架构调整

规模 架构方案
当前(< 100 代理,< 1000 卡) 当前单体完全够用,重点是修复断链
成长期1000+ 代理10万+ 卡) Worker 分队列 + 优化轮询 SortedSet 分片
SaaS 初期(多客户,共享平台) 添加 tenant_id 隔离层;动态支付配置;统一 OpenAPI 网关
SaaS 规模(每客户数万卡) Schema 隔离 or DB 隔离;独立 Worker 进程 per 大客户
高并发计费(> 5000 TPS 引入消息队列Kafka缓冲计费事件离线批处理结算

第一个会触发的瓶颈:轮询调度

  • 现状: Redis Sorted Set + Asynq 混合投递Worker 单机并发
  • 触发条件: 超过 5 万张活跃卡,每卡 5 分钟轮询一次 = ~167 TPS 持续 Gateway 请求
  • 修复方向: Gateway 并发限速配置 + 批量 API 合并 + Sorted Set 分片(多个 queue key

第二个会触发的瓶颈:佣金计算的 DB 递归查询

  • 现状: WITH RECURSIVE 或逐级 parent_id 查询,最深 7 层
  • 触发条件: 单日 > 1 万笔订单,并发处理时 DB CPU 飙升
  • 修复方向: 代理链 Redis 缓存shop_id → ancestor_chain写入时物化路径 path 字段

第三个会触发的瓶颈Order Service 耦合带来的发布风险

  • 现状: 2365 行,支付/回调/钱包全耦合
  • 触发条件: 添加新支付方式(富友小程序)或退款逻辑时,改动影响范围不可控
  • 修复方向: 拆分子文件(见构建顺序阶段 4不需要跨进程拆分

反模式警告

反模式 1在支付回调中同步计算佣金

What: 支付回调 Handler 直接在回调请求的生命周期内计算完整的多级佣金链 Why bad: 回调超时 → 第三方重推 → 佣金重复计算;而且 Gateway 通常要求快速响应3s 内) Do this instead: 回调只更新订单状态,通过 Asynq 任务异步计算佣金(当前架构已正确实现)

反模式 2代理层级递归查询不加缓存

What: 每次数据权限过滤都调用 WITH RECURSIVE 查下级 shop_id Why bad: 高并发下递归查询是 DB 杀手7 层 × 每层 N 个子节点 = 指数级扫描 Do this instead: GetSubordinateShopIDs 结果 Redis 缓存 30 分钟(当前已实现),注意缓存失效策略

反模式 3流量数据直接被上游覆盖写

What: Gateway 上报当月累计流量 → 直接 UPDATE current_month_usage_mb Why bad: 上游自然月归零时,历史用量记录丢失;无法回查历史;无法审计计费 Do this instead: 日粒度缓冲(方案 E增量写入 tb_card_daily_usage,查询层合并 Redis + DB

反模式 4把租户隔离硬编码在每个 Store 文件

What: 54 个 Store 文件各自调用 ApplyShopFilter过滤逻辑分散 Why bad: 新增 Store 文件时容易遗漏SaaS 化时难以统一改造 Do this instead: MVP 阶段保持现状(显式>隐式SaaS 化时考虑统一 TenantContext 注入层 + 强制 lint 检测

反模式 5Worker 内两套任务投递机制

What: 轮询实名激活通过 Redis RPush 投递,其他任务走 Asynq Why bad: 两套消费机制Asynq 的可观测性/重试/超时管理对 RPush 任务无效 Do this instead: Worker Bootstrap 注入 asynq.Client,统一走 Asynq方案 A-3


集成点分析

外部服务

服务 集成模式 关键注意点
运营商 Gateway 同步 HTTP 调用(internal/gateway/ Gateway 宕机时需降级;速率限制;超时设置
微信支付JSAPI/H5 PowerWeChat SDK 配置已迁移至 tb_wechat_config,但 OAuth 仍读环境变量(待修复)
富友支付 自研 SDKpkg/fuiou/ SDK 完整,主调接口仍留桩(方案 J-1
联通云 OSS S3 兼容 SDK 预签名 URL 上传,异步导入任务

内部模块边界

边界 通信方式 注意事项
API → Worker Asynq 任务队列Redis 任务投递幂等;失败重试需要业务幂等保证
Order ↔ Commission 异步(支付回调完成后投递任务) 不能同步等待佣金计算完成
Package ↔ IoT Card 同步Service 层直接调用) 套餐激活时需持有卡信息,单事务
Polling ↔ Gateway 同步 HTTPWorker 内) 限速超时Gateway 不可用时不应导致整个轮询系统崩溃
Auth ↔ RBAC 同步 Redis + DB中间件层 SubordinateShopIDs 缓存是性能关键

架构变更的高风险区

基于当前代码分析,以下区域变更风险最高,需要在 roadmap 中单独规划:

区域 风险原因 建议处理方式
internal/service/order/service.go 2365 行,全链路耦合 先修复 Bug再在低风险时拆分
流量计费体系(方案 E 涉及 DB 迁移 + Redis 架构 + 查询兼容 单独发布窗口,充分测试迁移脚本
支付配置(微信/富友切换) 半迁移状态(环境变量 vs DB 配置同时存在) 明确单一配置源后再添加新支付方式
轮询系统Polling Scheduler RPush 降级路径 + asynqClient 缺失 A-3 修复后,逐步迁移到统一 Asynq 路径
提现并发逻辑A-7, C-3 并发锁逻辑不完整,线上可能已有脏数据 修复时需同步修复历史数据

Sources

  • 当前代码库深度分析2026-03-27internal/service/, internal/store/postgres/, internal/task/, internal/routing/
  • 多层级佣金系统架构设计TechNova2026-02-28物化路径 vs 邻接表 vs 闭包表对比CAP 权衡,批流一体
  • Multi-Tenant SaaS IsolationAverageDevs2026-01-25pooled tables vs schema per tenantRow Level Securitynoisy neighbor 防护
  • Postgres Multitenancy 2025DebuggAIRLS vs Schema vs 独立 DB 性能对比
  • Go Worker Pool + Asynq 并发控制BackendBytesOneUptime2026任务队列分区并发控制

Architecture research for: IoT 卡管理平台(君鸿卡管系统) Researched: 2026-03-27