docs: add research findings
This commit is contained in:
474
.planning/research/ARCHITECTURE.md
Normal file
474
.planning/research/ARCHITECTURE.md
Normal file
@@ -0,0 +1,474 @@
|
||||
# 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
|
||||
富友支付 ← 自研 SDK(pkg/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 → SubordinateShopIDs(Redis 缓存)
|
||||
- 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.go`、`callback_service.go`、`order_state.go`、`wallet_deduction.go`
|
||||
|
||||
**⑥ 轮询系统 asynqClient 依赖缺失(High)**
|
||||
- `internal/task/polling_handler.go` 中实名激活通过 `Redis RPush` 降级实现,而非 Asynq 任务
|
||||
- 架构偏差:同一 Worker 进程内用两套机制投递任务(Asynq + RPush)
|
||||
- **建议:** 在 Worker Bootstrap 中注入 asynqClient,统一走 Asynq 路径
|
||||
|
||||
**⑦ C 端 Handler 直接访问 DB(Medium)**
|
||||
- 方案 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))
|
||||
└── 更新代理钱包余额
|
||||
```
|
||||
|
||||
### 关键流 2:IoT 卡轮询 + 实名激活
|
||||
|
||||
```
|
||||
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 RECURSIVE,Redis 缓存 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 往返,换取金融级安全
|
||||
|
||||
```go
|
||||
// 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` 的条件 UPDATE,RowsAffected 判断
|
||||
**When:** 支付回调、套餐激活、订单状态变更等有明确状态机的场景
|
||||
**Trade-offs:** 需要数据库层有索引支持,效率高但依赖状态设计的正确性
|
||||
|
||||
```go
|
||||
result := tx.Model(&model.Order{}).
|
||||
Where("id = ? AND payment_status = ?", orderID, PaymentStatusPending).
|
||||
Updates(map[string]any{"payment_status": PaymentStatusPaid})
|
||||
if result.RowsAffected == 0 {
|
||||
// 已被处理或并发冲突,根据当前状态决定返回
|
||||
}
|
||||
```
|
||||
|
||||
### 模式 3:Redis Sorted Set 驱动轮询调度
|
||||
|
||||
**What:** 以下次轮询时间为 Score,ICCID 为 Member,定时扫描到期项投递 Asynq 任务
|
||||
**When:** 需要"不同间隔、海量卡片"的轮询调度场景
|
||||
**Trade-offs:** 比 cron 更灵活,内存占用与卡片数量正相关;Redis 重启需重建队列
|
||||
|
||||
```go
|
||||
// 投入轮询队列(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 层或高并发时需物化路径优化
|
||||
|
||||
```go
|
||||
// 当前实现: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 注入 asynqClient(A-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 检测
|
||||
|
||||
### 反模式 5:Worker 内两套任务投递机制
|
||||
|
||||
**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 仍读环境变量(待修复) |
|
||||
| 富友支付 | 自研 SDK(pkg/fuiou/) | SDK 完整,主调接口仍留桩(方案 J-1)|
|
||||
| 联通云 OSS | S3 兼容 SDK | 预签名 URL 上传,异步导入任务 |
|
||||
|
||||
### 内部模块边界
|
||||
|
||||
| 边界 | 通信方式 | 注意事项 |
|
||||
|------|---------|---------|
|
||||
| API → Worker | Asynq 任务队列(Redis) | 任务投递幂等;失败重试需要业务幂等保证 |
|
||||
| Order ↔ Commission | 异步(支付回调完成后投递任务) | 不能同步等待佣金计算完成 |
|
||||
| Package ↔ IoT Card | 同步(Service 层直接调用) | 套餐激活时需持有卡信息,单事务 |
|
||||
| Polling ↔ Gateway | 同步 HTTP(Worker 内) | 限速;超时;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-27):`internal/service/`, `internal/store/postgres/`, `internal/task/`, `internal/routing/`
|
||||
- 多层级佣金系统架构设计(TechNova,2026-02-28):物化路径 vs 邻接表 vs 闭包表对比,CAP 权衡,批流一体
|
||||
- Multi-Tenant SaaS Isolation(AverageDevs,2026-01-25):pooled tables vs schema per tenant,Row Level Security,noisy neighbor 防护
|
||||
- Postgres Multitenancy 2025(DebuggAI):RLS vs Schema vs 独立 DB 性能对比
|
||||
- Go Worker Pool + Asynq 并发控制(BackendBytes,OneUptime,2026):任务队列分区,并发控制
|
||||
|
||||
---
|
||||
|
||||
*Architecture research for: IoT 卡管理平台(君鸿卡管系统)*
|
||||
*Researched: 2026-03-27*
|
||||
263
.planning/research/FEATURES.md
Normal file
263
.planning/research/FEATURES.md
Normal file
@@ -0,0 +1,263 @@
|
||||
# Feature Research
|
||||
|
||||
**Domain:** 物联网卡/号卡/设备全生命周期管理平台(面向代理商 B 端 + C 端个人用户)
|
||||
**Researched:** 2026-03-27
|
||||
**Confidence:** HIGH(主要来源:广梦云/SIMBOSS/Simbest/iotcard 实际竞品研究 + 项目现有功能盘点)
|
||||
|
||||
---
|
||||
|
||||
## 背景:已实现功能(Brownfield 基线)
|
||||
|
||||
以下功能已在代码库中落地,本文档**不重复列出**,仅聚焦**缺口分析**:
|
||||
|
||||
| 已实现模块 | 关键能力 |
|
||||
|-----------|---------|
|
||||
| IoT 卡生命周期 | 开卡/激活/停复机/销户 |
|
||||
| 套餐系统 | 主套餐/加油包/排队/囤货待实名激活/流量扣减 |
|
||||
| 订单系统 | 5 种购买场景,微信/富友/钱包支付 |
|
||||
| 代理商体系 | 7 级层级,差价佣金 + 一次性佣金,提现申请流程 |
|
||||
| RBAC 权限 | 角色/权限/菜单,基于店铺层级数据过滤 |
|
||||
| C 端认证 | 微信 OAuth + 手机号绑定 |
|
||||
| 设备管理 | 绑卡/解绑/批量 Excel 导入 |
|
||||
| 流量轮询 | 实名状态/流量/套餐余额,Asynq + Redis Sorted Set |
|
||||
| 退款流程 | tb_refund_request 模型,7 个退款接口(方案 I,已规划) |
|
||||
|
||||
---
|
||||
|
||||
## Feature Landscape
|
||||
|
||||
### Table Stakes(用户期望有,缺了就感觉产品不完整)
|
||||
|
||||
#### 卡/资产管理
|
||||
|
||||
| Feature | Why Expected | Complexity | 当前状态 | Notes |
|
||||
|---------|--------------|------------|---------|-------|
|
||||
| 卡批量操作(停/复/注销) | 运营场景必须,一次管几百张卡不能单张操作 | MEDIUM | ❌ 缺失 | 已有单卡接口,缺批量接口;广梦云/SIMBOSS 均有 |
|
||||
| 卡信息批量导出(Excel/CSV) | 资产台账、对账、交接必须 | LOW | ❌ 缺失 | 已有导入;竞品均有导出;格式含 ICCID/IMEI/状态/套餐/流量 |
|
||||
| 实名状态手动刷新(单卡/批量) | 运营商实名状态有延迟,客服需要手动触发同步 | LOW | ⚠️ 部分 | 有轮询系统但无手动触发单卡实名刷新接口 |
|
||||
| 卡状态变更历史记录 | 出现纠纷时需要溯源"什么时候谁停了这张卡" | MEDIUM | ❌ 缺失 | 审计日志只覆盖账号操作,不覆盖卡操作 |
|
||||
| 流量使用日粒度详单 | C 端用户最常问"今天用了多少流量" | MEDIUM | ⚠️ 规划中 | 方案 E(流量体系改革)已规划 tb_card_daily_usage |
|
||||
|
||||
#### 套餐/订单
|
||||
|
||||
| Feature | Why Expected | Complexity | 当前状态 | Notes |
|
||||
|---------|--------------|------------|---------|-------|
|
||||
| 订单搜索与筛选(多维度) | 运营/客服必须能按 ICCID/手机号/订单号/状态/时间段查单 | LOW | ⚠️ 部分 | 基础筛选存在,高级组合筛选可能不完整 |
|
||||
| 订单批量导出 | 财务对账、月度报告 | LOW | ❌ 缺失 | 竞品标配;广梦云最近大幅提升导出速度 |
|
||||
| 退款完整流程(申请/审批/退回) | 用户错购、套餐未生效退款场景必须有 | HIGH | ⚠️ 规划中 | 方案 I 已规划 7 个退款接口,尚未实现 |
|
||||
| 订单超时提醒/催付 | 代理采购大额预存时需要提醒 | LOW | ❌ 缺失 | 已有 30 分钟自动取消,无催付通知 |
|
||||
|
||||
#### 代理/分销
|
||||
|
||||
| Feature | Why Expected | Complexity | 当前状态 | Notes |
|
||||
|---------|--------------|------------|---------|-------|
|
||||
| 代理商资金流水(充值/消费/佣金/提现完整账单) | 代理商需要对自己的每一笔资金进来去出有清晰记录 | MEDIUM | ⚠️ 部分 | 有钱包余额,但完整流水账单(按时间倒序,标注来源类型)不确定是否完整 |
|
||||
| 套餐分配记录(谁分配了给谁,什么时候) | 分配纠纷溯源必须 | LOW | ⚠️ 部分 | asset_allocation_record 已实现,但可能不全面 |
|
||||
| 下级代理业绩报表 | 代理商管理下级时必须知道业绩 | MEDIUM | ❌ 缺失 | 有佣金统计 commission_stats,但完整业绩报表(订单数/金额/活跃卡数)缺失 |
|
||||
| 代理商余额充值(线上转账充值) | 代理预存款是核心资金流入,不能只靠线下 | MEDIUM | ⚠️ 部分 | agent_recharge 存在,但富友支付 JSAPI 接口为留桩(方案 J-1)|
|
||||
|
||||
#### C 端体验
|
||||
|
||||
| Feature | Why Expected | Complexity | 当前状态 | Notes |
|
||||
|---------|--------------|------------|---------|-------|
|
||||
| C 端查询套餐列表与详情 | 用户购套餐前必须看清楚买的是什么 | LOW | ⚠️ 部分 | customer_view_service 存在,但套餐展示完整性待确认 |
|
||||
| 套餐到期提醒(微信通知/短信) | 用户断网会直接差评,预告到期是行业标配 | MEDIUM | ❌ 缺失 | 无主动推送机制;竞品用微信模板消息/公众号推送 |
|
||||
| 充值记录/消费历史 | 用户自助查账必须 | LOW | ⚠️ 部分 | 有钱包,但消费历史接口(按时间倒序列表)待确认 |
|
||||
| 卡/设备转手后钱包自然转移展示 | 钱包绑卡而非绑人的设计已实现,需 C 端有清晰提示 | LOW | ✅ 基本实现 | 设计已对 C 端透出 |
|
||||
|
||||
#### 实名认证(中国市场法规强制)
|
||||
|
||||
| Feature | Why Expected | Complexity | 当前状态 | Notes |
|
||||
|---------|--------------|------------|---------|-------|
|
||||
| 二要素身份验证(姓名+身份证) | 工信部实名制要求,物联网卡/号卡必须实名 | MEDIUM | ⚠️ 部分 | 有实名状态轮询,但主动二要素接口验证待确认 |
|
||||
| 三要素身份验证(姓名+身份证+手机号) | 号卡开户严格场景 | MEDIUM | ❌ 可能缺失 | 广梦云 v2.14.8 专门完善了三要素验证 |
|
||||
| 实名认证状态全链路可见(平台→代理→C端) | 合规审计必须 | LOW | ⚠️ 部分 | 实名状态字段存在,但链路完整性待确认 |
|
||||
|
||||
---
|
||||
|
||||
### Differentiators(竞争差异化功能)
|
||||
|
||||
| Feature | Value Proposition | Complexity | 优先级 | Notes |
|
||||
|---------|-------------------|------------|-------|-------|
|
||||
| 流量池(多卡共享流量) | 降低单卡超额风险,SIMBOSS/广梦云核心卖点,运营商原生支持 | HIGH | P2 | 当前每卡独立流量;实现需运营商 API 支持 |
|
||||
| 数据大屏/经营报表(首页工作台) | 让代理一眼看清业绩,销售额/活跃卡数/今日新增 | MEDIUM | P2 | 广梦云 v2.7.13 增加工作台;核心商业决策支持 |
|
||||
| 开放 API 平台(供企业客户对接) | 企业客户有自有系统,需要 API 接入卡信息/套餐/流量 | HIGH | P2 | Simbest/SIMBOSS 均有 SDK;广梦云有开放平台 |
|
||||
| 短信通知(套餐到期/流量告警/提现审批) | 微信通知依赖公众号关注,短信是兜底保障 | MEDIUM | P2 | 广梦云 v0.1.6 就支持;触达率关键 |
|
||||
| 微信公众号模板消息推送 | 在微信生态内操作,公众号消息触达率最高 | MEDIUM | P2 | 广梦云/SIMBOSS 标配;需绑定公众号 APPID |
|
||||
| 流量告警(达量预警/超量告警) | 避免客户意外断网,自动预警降低客服工单 | MEDIUM | P2 | 轮询系统框架存在 polling_alert;但告警通知渠道未接通 |
|
||||
| AI 推广文案生成 | 代理商自动生成商品推广素材,降低运营门槛 | MEDIUM | P3 | 广梦云 v2.11 增加 AI 推广文案;ToB 差异化 |
|
||||
| 电子签约合同(代理商签约) | 合规合同管理,法律保障 | HIGH | P3 | 广梦云 v0.12 增加;降低纠纷风险 |
|
||||
| LBS 基站定位(卡位置查询) | IoT 场景设备追踪 | HIGH | P3 | iotcard/SIMBOSS 支持;需运营商 API |
|
||||
| 卡在线诊断(信号/状态/异常) | 客服自助排查工单,减少人工 | HIGH | P3 | iotcard 有"在线诊断"功能 |
|
||||
| 多支付方式(支付宝 + 微信) | 部分用户无微信支付,支付宝是标配 | MEDIUM | P2 | 当前仅微信 + 富友 + 钱包;缺支付宝 |
|
||||
| 代理商独立品牌域名/店铺定制 | 白标分销,代理商可用自己品牌 | HIGH | P3 | SIMBOSS/广梦云核心卖点;v2 方向 |
|
||||
|
||||
---
|
||||
|
||||
### Anti-Features(看起来好,实际容易踩坑的功能)
|
||||
|
||||
| Feature | Why Requested | Why Problematic | Alternative |
|
||||
|---------|---------------|-----------------|-------------|
|
||||
| 多租户 SaaS 架构(现阶段) | 想一套系统卖给多个卡商 | 当前 MVP 阶段做 SaaS 重构会推迟上线 6-12 个月;数据隔离模型完全不同 | 单租户先上线验证,v2 规划 SaaS;已在 Out of Scope |
|
||||
| 设备定期后台轮询 | 想实时掌握设备在线状态 | 高频轮询对运营商 API 有调用限额,极易触发封禁;成本高 | 按需实时拉取(sync-info),已决策 |
|
||||
| 自动化测试覆盖 | 提升代码质量信心 | 项目明确不做,IoT 卡状态依赖运营商真实回调,Mock 成本极高 | 人工验收 + 生产日志监控;已在项目约束中 |
|
||||
| 实时所有卡流量推送 | 想让用户实时看到流量 | WebSocket 长连接并发维护成本极高;百万卡量级时不可行 | 轮询拉取 + 流量超阈值主动告警即可 |
|
||||
| 全球/国际物联网卡管理 | 扩展市场 | 需要对接 Jasper/SIM Alliance 等国际 CMP,运营商 API 完全不同,开发量是现有的 2-3 倍 | 先做国内三大运营商 + 广电,国际方向单独立项 |
|
||||
| 卡号靓号系统(号卡场景) | 号卡分销平台都有 | 物联网卡无靓号概念;号卡业务涉及选号/归属地/运营商配号,与物联网卡逻辑完全不同 | 物联网卡和号卡维护分离的套餐/渠道体系 |
|
||||
|
||||
---
|
||||
|
||||
## Feature Dependencies
|
||||
|
||||
```
|
||||
退款完整流程(方案I)
|
||||
└──requires──> 订单系统(已完成)
|
||||
└──requires──> 代理佣金计算(已完成)
|
||||
|
||||
批量操作(停/复/导出)
|
||||
└──requires──> 单卡操作接口(已完成)
|
||||
|
||||
套餐到期提醒(微信/短信)
|
||||
└──requires──> 微信公众号配置(wechat_config 已实现)
|
||||
└──requires──> 短信服务商接入(❌ 未接入)
|
||||
|
||||
流量告警通知
|
||||
└──requires──> 轮询系统 alert_service(已实现,告警规则存在)
|
||||
└──requires──> 通知渠道(短信/微信,❌ 未打通)
|
||||
|
||||
数据报表/工作台
|
||||
└──enhances──> 代理佣金统计(commission_stats 已实现)
|
||||
└──enhances──> 订单系统(已完成)
|
||||
|
||||
流量池(共享池)
|
||||
└──requires──> 运营商 API 支持流量池管理(独立对接)
|
||||
└──conflicts──> 当前每卡独立流量计费模型
|
||||
|
||||
开放 API 平台
|
||||
└──requires──> 认证体系(已完成)
|
||||
└──enhances──> 企业客户管理(已完成)
|
||||
|
||||
二/三要素实名验证
|
||||
└──enhances──> 实名认证轮询(已完成)
|
||||
└──requires──> 第三方实名验证服务商接入(国政通/公安部接口)
|
||||
```
|
||||
|
||||
### Dependency Notes
|
||||
|
||||
- **退款 requires 佣金计算**:退款审批通过后需按比例扣减代理佣金钱包,佣金必须已正确落账
|
||||
- **告警 requires 通知渠道**:alert_service 框架已有,但告警结果只写日志,未接短信/微信推送
|
||||
- **流量池 conflicts 当前计费模型**:流量池是设备级/卡群组共享,与当前每套餐独立计费逻辑有冲突,需专项设计
|
||||
- **二要素验证 requires 第三方接口**:国政通/华道数据/阿里实人认证等,需采购接入
|
||||
|
||||
---
|
||||
|
||||
## MVP Definition
|
||||
|
||||
### 当前阶段:修复上线(v1 Production Ready)
|
||||
|
||||
必须完成(对应 PROJECT.md Active 方案 A-J):
|
||||
|
||||
- [x] 实名常量修复(A-1 ~ A-3)— 主链路断裂,P0
|
||||
- [x] 充值回调 + 佣金补全(A-4 ~ A-5)— 资金链路,P0
|
||||
- [x] 退款完整流程(方案 I)— 用户期望有,缺了影响信任
|
||||
- [x] 订单批量导出 — 财务对账必须
|
||||
- [x] 卡批量操作(停/复) — 运营必须
|
||||
- [x] 富友支付 JSAPI(J-1)— 支付链路完整性
|
||||
|
||||
### Add After Validation(v1.x,上线后 1-2 月内)
|
||||
|
||||
- [ ] 套餐到期提醒(微信模板消息) — 核心留存功能,上线第一周就会收到投诉
|
||||
- [ ] 流量告警通知渠道打通(短信/微信) — polling_alert 框架已有,补上通知
|
||||
- [ ] 代理商业绩报表(工作台首页) — 增加代理黏性
|
||||
- [ ] 卡状态变更历史 — 减少客服纠纷
|
||||
|
||||
### Future Consideration(v2+)
|
||||
|
||||
- [ ] 流量池(共享池)— 需运营商 API 合作,独立立项
|
||||
- [ ] 开放 API 平台 — 企业客户集成需求
|
||||
- [ ] 多租户 SaaS — 卖给其他卡商
|
||||
- [ ] LBS 基站定位 — IoT 定位场景
|
||||
- [ ] 支付宝支付 — 扩大支付覆盖,目前微信+富友已覆盖主流
|
||||
|
||||
---
|
||||
|
||||
## Feature Prioritization Matrix
|
||||
|
||||
### 缺口功能优先级
|
||||
|
||||
| Feature | User Value | Implementation Cost | Priority | 对应方案 |
|
||||
|---------|------------|---------------------|----------|---------|
|
||||
| 退款流程(申请/审批/退回) | HIGH | HIGH | P1 | 方案 I |
|
||||
| 富友支付 JSAPI 实现 | HIGH | MEDIUM | P1 | 方案 J-1 |
|
||||
| 卡批量操作(停/复/导出) | HIGH | LOW | P1 | 新增 |
|
||||
| 订单批量导出 | HIGH | LOW | P1 | 新增 |
|
||||
| 套餐到期微信推送 | HIGH | MEDIUM | P1 | 新增 |
|
||||
| 流量告警通知渠道 | HIGH | MEDIUM | P1 | 补全 alert_service |
|
||||
| 代理商资金流水完整账单 | MEDIUM | LOW | P2 | 补全 |
|
||||
| 代理商业绩报表(工作台) | MEDIUM | MEDIUM | P2 | 新增 |
|
||||
| 卡状态变更历史 | MEDIUM | LOW | P2 | 新增 |
|
||||
| 二要素实名验证接口 | MEDIUM | MEDIUM | P2 | 新增 |
|
||||
| 流量池(共享池) | HIGH | HIGH | P3 | v2 |
|
||||
| 开放 API 平台 | MEDIUM | HIGH | P3 | v2 |
|
||||
| LBS 定位 | LOW | HIGH | P3 | v2 |
|
||||
|
||||
**Priority key:**
|
||||
- P1: 必须有,影响上线质量
|
||||
- P2: 上线后优先补充,影响留存
|
||||
- P3: 竞争差异化,长期方向
|
||||
|
||||
---
|
||||
|
||||
## Competitor Feature Analysis
|
||||
|
||||
| Feature | 广梦云(竞品) | SIMBOSS(竞品) | Simbest(竞品) | 君鸿当前状态 |
|
||||
|---------|--------------|---------------|---------------|------------|
|
||||
| 批量停/复/注销 | ✅ 有 | ✅ 有 | ✅ 有 | ❌ 缺 |
|
||||
| 批量数据导出 | ✅ 有(高速) | ✅ 有 | ✅ 有 | ❌ 缺 |
|
||||
| 流量告警 + 通知 | ✅ 微信公众号告警 | ✅ 达量告警 | ✅ 有 | ⚠️ 框架有,通知未打通 |
|
||||
| 套餐到期通知 | ✅ 微信模板消息 | ✅ 公众号推送 | ✅ 有 | ❌ 缺 |
|
||||
| 退款流程 | ✅ 一键退款(卡板/套餐) | ✅ 有 | ✅ 有 | ⚠️ 规划中(方案 I) |
|
||||
| 代理资金流水 | ✅ 完整账单 | ✅ 有 | ✅ 有 | ⚠️ 基础有,完整性待确认 |
|
||||
| 多支付宝支付 | ✅ 支付宝/微信/当面付 | ✅ 有 | ✅ 有 | ⚠️ 微信+富友,缺支付宝 |
|
||||
| 流量池共享 | ✅ 共享池 + 月账单 | ✅ 流量共享 | ✅ 有 | ❌ 缺(v2) |
|
||||
| 开放 API | ✅ 开放平台 + SDK | ✅ 多语言 SDK | ✅ 有 | ❌ 缺(v2) |
|
||||
| 业绩报表/工作台 | ✅ v2.7 增加工作台 | ✅ 数据助力运营 | ✅ 营收成本核算 | ❌ 缺 |
|
||||
| 代理商独立域名 | ✅ 品牌定制 | ✅ 个性化域名 | ✅ 有 | ❌ 缺(v2/SaaS) |
|
||||
| 设备 WiFi 远程控制 | ✅ v2.13 增加 | — | — | ⚠️ 设备 sync-info 规划中 |
|
||||
| 实名二/三要素验证 | ✅ 国政通对接 | ✅ 有 | ✅ 有 | ⚠️ 部分 |
|
||||
|
||||
---
|
||||
|
||||
## 中国市场特殊要求
|
||||
|
||||
### 监管合规
|
||||
- **物联网实名制**(工信部要求):所有物联网卡必须实名,平台需支持实名状态追踪和验证
|
||||
- **号卡实名**(2021 年起更严):号卡开户必须姓名+身份证+手机号三要素,平台需有三证上传
|
||||
- **广电运营商支持**:2022 年起广电(第四运营商)加入物联网卡市场,竞品已快速对接
|
||||
- **运营商 API 依赖**:
|
||||
- 移动:CT-Boss、PBoss
|
||||
- 电信:CMP、DCP
|
||||
- 联通:Jasper、CMP
|
||||
- 广电:广电直连 API
|
||||
|
||||
### 支付合规
|
||||
- 提现需支持**灵活用工平台**(代理商收入合规开票)
|
||||
- 大额打款需支持**对公转账**或**企业支付宝批量打款**
|
||||
|
||||
---
|
||||
|
||||
## Sources
|
||||
|
||||
- **广梦云更新日志**(https://docs.facms.cn)— 2019-2026 完整版本历史,揭示了市场真实功能演进路径
|
||||
- **SIMBOSS 产品页**(https://www.simboss.com)— 核心竞品,国内头部物联网卡平台
|
||||
- **Simbest 平台**(https://www.simbest.cn)— 专注三网对接的分销管理平台
|
||||
- **iotcard IOT CardBOSS**(https://www.iotcard.com/platform)— 运营商系统深度融合的企业级方案
|
||||
- **项目 PROJECT.md + ARCHITECTURE.md** — 已实现功能基线,方案 A-J 规划
|
||||
- **置信度评估**:
|
||||
- Table Stakes 功能:HIGH(多个竞品交叉验证)
|
||||
- Differentiators:MEDIUM(竞品分析 + 市场观察,非用户调研)
|
||||
- 合规要求:HIGH(工信部政策 + 竞品普遍实现)
|
||||
|
||||
---
|
||||
*Feature research for: 物联网卡/号卡/设备全生命周期管理平台(中国市场)*
|
||||
*Researched: 2026-03-27*
|
||||
245
.planning/research/PITFALLS.md
Normal file
245
.planning/research/PITFALLS.md
Normal file
@@ -0,0 +1,245 @@
|
||||
# Pitfalls Research
|
||||
|
||||
**Domain:** IoT 卡管理平台 — SIM 卡分销 + 多级代理佣金 + 异步套餐激活
|
||||
**Researched:** 2026-03-27
|
||||
**Confidence:** HIGH(基于真实代码库分析 + 完整规格书审查)
|
||||
|
||||
> **已知问题不重复**:实名状态常量不一致、Asynq 任务依赖注入不完整、并发乐观锁/悲观锁漏用、流量数据被上游重置覆盖——这四类已在修正方案中覆盖,不再列入本文档。
|
||||
|
||||
---
|
||||
|
||||
## Critical Pitfalls
|
||||
|
||||
### Pitfall 1: 佣金差额基准字段(sellerCostPrice)多处同类 Bug
|
||||
|
||||
**What goes wrong:**
|
||||
`sellerCostPrice = buyerCostPrice` 在 `internal/service/order/service.go` 中出现 **6 次**(第 187、262、472、548、734、809 行),只有场景 5(代理代购下级)被规格书点名,其余 5 处是否也存在同样语义错误尚未被审查。每一处错误都会导致整个上级佣金链计算为 0。
|
||||
|
||||
**Why it happens:**
|
||||
场景 5 是"代理帮下级代理购包",此时 `sellerCostPrice` 表示"卖出方自己的成本价"(用于和再上级计算差额),但开发者误用了"买方(下级)的成本价"。五种购买场景共用的模板代码一起复制了这个错误。
|
||||
|
||||
**How to avoid:**
|
||||
1. 修复 A-5 时,用 `grep` 全量检索 `sellerCostPrice = buyerCostPrice`,对每一行逐场景验证语义
|
||||
2. 添加注释:`sellerCostPrice 含义 = 本订单"卖家"向其上级结算时使用的成本基准(不是买家看到的价格)`
|
||||
3. 为每种购买场景(5 种)单独写一个私有方法,避免在同一个大函数里 if-else 六分支
|
||||
|
||||
**Warning signs:**
|
||||
- 某代理参与了套餐分配(有授权),但佣金记录里上级代理的 `amount` 持续为 0
|
||||
- DBHub 查询 `tb_commission_record WHERE amount = 0 AND commission_source = 'cost_diff'` 有大量记录
|
||||
|
||||
**Phase to address:** 方案 A(A-5)实施时,同步修复全部 6 处,不只修 1 处
|
||||
|
||||
---
|
||||
|
||||
### Pitfall 2: 退款佣金回扣的浮点精度丢失
|
||||
|
||||
**What goes wrong:**
|
||||
方案 I-3 的退款比例计算:
|
||||
```go
|
||||
ratio := float64(refund.ApprovedRefundAmount) / float64(refund.ActualReceivedAmount)
|
||||
deductAmount := int64(float64(commission.Amount) * ratio)
|
||||
```
|
||||
`float64` 乘除后截断到 `int64` 会丢失 1 分精度。多级佣金链叠加后,累计误差可能造成"批量退款后所有上级代理多扣或少扣总计数十元"。
|
||||
|
||||
**Why it happens:**
|
||||
Go 没有内置 Decimal 类型,金融计算的精度问题容易被忽视。项目其他地方(如提现手续费)用的是整数基点计算(`int64`),但退款比例必须用小数,开发者自然选择了 `float64`。
|
||||
|
||||
**How to avoid:**
|
||||
- 退款比例乘法使用整数算术代替:`deductAmount = commission.Amount * refund.ApprovedRefundAmount / refund.ActualReceivedAmount`(全用 `int64`,先乘后除,结果向下取整)
|
||||
- 或引入 `math/big.Rat` 进行精确比例计算
|
||||
- 绝对不要在金额计算链路中出现 `float64`(余额、成本价、佣金均以分为单位存储,应坚持整数运算)
|
||||
|
||||
**Warning signs:**
|
||||
- 退款后批量查各级代理钱包,总扣减金额与退款金额比例不相符
|
||||
- 极端案例:`ApprovedRefundAmount=1, ActualReceivedAmount=3` → `ratio=0.3333...` → `commission*0.3333` 截断
|
||||
|
||||
**Phase to address:** 方案 I(退款功能)实现时在 I-3 处预防
|
||||
|
||||
---
|
||||
|
||||
### Pitfall 3: 多级分佣树形遍历中的循环引用与孤岛
|
||||
|
||||
**What goes wrong:**
|
||||
`CalculateCostDiffCommission` 通过 `shop.ParentID` 向上遍历代理链,直到 `ParentID == nil`。如果数据库中代理层级数据被人工修改,出现:
|
||||
- **循环引用**:A 的父是 B,B 的父是 A → 无限循环直到超时
|
||||
- **孤岛**:某代理的 `parent_id` 指向一个已删除的店铺(软删除残留)→ `GetByID` 返回 Not Found,`break` 掉链路,上游代理丢失佣金
|
||||
|
||||
**Why it happens:**
|
||||
项目禁止外键约束(见 AGENTS.md),代理层级完全依赖代码维护。店铺软删除时没有检查是否有子代理仍在引用它。递归遍历没有深度上限(最多 7 级,但没有强制)。
|
||||
|
||||
**How to avoid:**
|
||||
1. 在遍历循环加深度计数器:`for depth := 0; depth < 8 && currentShopID != nil; depth++`
|
||||
2. 用 `visited map[uint]bool` 检测循环引用,发现后立即 `break` 并打告警日志
|
||||
3. 店铺删除接口增加"有子代理时拒绝删除"的前置检查(参考 H-4 Series 的做法)
|
||||
4. 每日定时任务扫描层级数据一致性(检查 `parent_id` 指向已删除记录)
|
||||
|
||||
**Warning signs:**
|
||||
- 某个订单的佣金任务卡在 Worker 里超时重试
|
||||
- `tb_shop` 中存在 `parent_id` 指向 `deleted_at IS NOT NULL` 的记录
|
||||
|
||||
**Phase to address:** 方案 A(A-5 佣金修复)+ 方案 H 小修阶段
|
||||
|
||||
---
|
||||
|
||||
### Pitfall 4: 异步任务幂等键缺失导致佣金重复计算
|
||||
|
||||
**What goes wrong:**
|
||||
`enqueueCommissionCalculation` 在 service 层多处调用(第 303、618、864、1121、1565、1617 行),但没有业务层面的幂等防护。Asynq 默认重试策略 + 多个触发点并发时,同一个 `order_id` 的佣金计算任务会被入队多次。`CalculateCommission` 开头虽然检查了 `CommissionStatusCalculated`,但在高并发下两个 Worker 可能同时读到 `status != calculated`,然后同时写入两份佣金记录。
|
||||
|
||||
**Why it happens:**
|
||||
Order Service 的支付回调(微信/富友)与自动购包任务(A-4 新增)在成功时都会调用 `enqueueCommissionCalculation`,单个订单存在多条触发路径。`CalculateCommission` 的幂等检查不是原子操作(读 status + 写 records 之间有窗口)。
|
||||
|
||||
**How to avoid:**
|
||||
1. 给 `TaskTypeCommission` 加 Asynq 唯一任务选项:`asynq.TaskID(fmt.Sprintf("commission:%d", orderID))` → Asynq 保证相同 TaskID 只入队一次
|
||||
2. 在 `CalculateCommission` 内部,将"读取 status + 写入 records + 更新 status"放在同一个事务内,利用 `UPDATE tb_order SET commission_status=? WHERE id=? AND commission_status != 'calculated'` 的 RowsAffected 做原子幂等
|
||||
|
||||
**Warning signs:**
|
||||
- `tb_commission_record` 中同一个 `order_id` 出现两条或多条记录
|
||||
- 代理钱包余额远高于预期
|
||||
|
||||
**Phase to address:** 方案 A(A-4 自动购包)实施时同步处理
|
||||
|
||||
---
|
||||
|
||||
### Pitfall 5: 运营商 API 回调重放与支付状态对齐
|
||||
|
||||
**What goes wrong:**
|
||||
微信支付回调(`HandlePaymentCallback`)使用条件更新做幂等:`WHERE payment_status = 'pending'`,这是正确的。但富友支付回调(方案 J-1,当前是留桩)一旦实现,如果直接复制微信回调逻辑而忘记验签,攻击者可以伪造回调通知触发充值到账。另一个场景:运营商 Gateway 的停复机回调(`StopResumeCallback / ResumeCallback`,方案 F-6)目前 bootstrap 中未注入,一旦注入后,回调的幂等保护是否健全尚未验证。
|
||||
|
||||
**Why it happens:**
|
||||
第三方 API 回调通常有以下特点:(1) 会重复发送直到收到成功响应,(2) 需要验证签名,(3) 业务状态变更必须是幂等的。开发者经常在实现新回调时遗漏验签步骤,尤其是"复制微信回调改改"时。
|
||||
|
||||
**How to avoid:**
|
||||
1. 富友支付回调(J-1)实现时:先验签(`pkg/fuiou/` 已有签名工具),再做业务处理
|
||||
2. 所有回调 Handler 必须在最开头验签,验签失败直接返回 `400`,不进入业务逻辑
|
||||
3. 停复机回调(F-6 注入后):检查 `WHERE status = 'stopping'` 条件更新,RowsAffected 为 0 则幂等跳过
|
||||
|
||||
**Warning signs:**
|
||||
- 回调接口没有签名验证中间件或函数调用
|
||||
- 日志中同一笔充值/停复机出现两次"处理成功"
|
||||
|
||||
**Phase to address:** 方案 F(F-6 注入)、方案 J(J-1 富友支付实现)
|
||||
|
||||
---
|
||||
|
||||
## Technical Debt Patterns
|
||||
|
||||
| Shortcut | Immediate Benefit | Long-term Cost | When Acceptable |
|
||||
|----------|-------------------|----------------|-----------------|
|
||||
| Handler 层直接访问 DB(见 F-4) | 少写 Service 方法,快速出货 | 绕过数据权限过滤层,越权漏洞温床;Service 层单测无法覆盖 | Never(项目明确禁止) |
|
||||
| `cards, _ :=` 忽略 error(见 F-3) | 代码简洁 | 上游查询失败时静默返回空数据,下游表现为"数据丢失"而非报错,极难定位 | Never |
|
||||
| 任务断链用 `RPush` 降级(见 A-3 临时方案) | 临时绕过依赖注入问题 | 形成两套并行队列机制,消费者只消费 Asynq,Redis List 堆积无人处理 | Never(应立即修复) |
|
||||
| `sellerCostPrice = buyerCostPrice` 5 处场景(见 Pitfall 1) | 逻辑统一,代码复制快 | 多个购买场景佣金链全部归零,代理分佣核算错误 | Never |
|
||||
| 留桩函数(FuiouPayJSAPI 等)返回业务错误 | 功能模块先占位 | 在线上环境直接暴露"功能未实现"给用户,影响信任度 | MVP 阶段可以,但必须有 TODO 标记和发版前检查清单 |
|
||||
| 激活配置两步 UPDATE 无锁(见 C-3) | 代码简单 | 并发切换配置时可出现两条 `active=true`,支付配置/提现配置混乱 | Never |
|
||||
| `gatewayClient=nil` 时返回成功(见 H-2) | 开发环境无 Gateway 时流程走通 | 本地 DB 标记为停机成功,但运营商侧未操作;数据一致性破坏 | Never(测试环境应 mock,不应用 nil 返回成功) |
|
||||
|
||||
---
|
||||
|
||||
## Integration Gotchas
|
||||
|
||||
| Integration | Common Mistake | Correct Approach |
|
||||
|-------------|----------------|------------------|
|
||||
| 运营商 Gateway(sync-info) | 按 IMEI 调用时长度判断逻辑(>15 位用 ICCID,=15 位用 IMEI,=11 位用 SN)写反或遗漏,导致用错标识调用 API | 严格按文档:`len > 15 → ICCID`,`len == 15 → IMEI`,`len == 11 → SN`;写单独函数 `resolveCardNo(device)` 封装判断,避免散落多处 |
|
||||
| 运营商 Gateway(停复机) | `gatewayClient == nil` 时假装成功(见 H-2),或 Gateway 超时时直接 `return err` 而不记录"本地已标记停机但运营商未执行"的中间态 | 停复机必须是原子的:要么 Gateway + DB 一起成功,要么一起失败;Gateway 失败时 DB 不更新,并告警 |
|
||||
| 微信支付回调 | 回调成功响应后再做业务处理 → 微信认为"回调已处理",但业务处理失败时没有重试机会 | 先做幂等业务处理,成功后再返回 `{"code": "SUCCESS"}`;失败时返回非 SUCCESS,触发微信重发 |
|
||||
| 富友支付(J-1 待实现) | 直接复制微信支付回调,遗漏富友私钥验签步骤 | 富友使用 RSA 私钥签名,`pkg/fuiou/` 已有 `VerifySign()` 方法,回调首行必须调用 |
|
||||
| 运营商流量数据(联通 27 号重置) | 用当前值直接覆盖 `current_month_usage_mb`(E-2 问题),重置日后数据归零 | 改为增量累加;重置检测窗口:`当天 ∈ [reset_day-1, reset_day+1]` 才认为是正常重置,否则告警 |
|
||||
| Asynq 任务注册 | 任务处理器注册(`mux.HandleFunc`)和 Scheduler 调度是两回事,注册后不一定会被调度(见 H-1 polling:protect) | 新增任务类型时检查清单:① 定义常量 → ② 实现 Handler → ③ 在 `RegisterHandlers()` 里注册 → ④ 在 Scheduler 里调度 |
|
||||
|
||||
---
|
||||
|
||||
## Performance Traps
|
||||
|
||||
| Trap | Symptoms | Prevention | When It Breaks |
|
||||
|------|----------|------------|----------------|
|
||||
| 递归查询下级店铺(`GetSubordinateShopIDs`)没有缓存 | 每次 HTTP 请求触发 PostgreSQL WITH RECURSIVE 查询,7 层 × N 个代理时极慢 | 已有 Redis 缓存 30 分钟机制,确保所有数据权限过滤路径都走缓存 | 代理商数量超过 100 时 P99 开始劣化 |
|
||||
| 轮询任务批量处理时 N+1 查询 | Worker CPU 高但 DB 慢,`polling_handler.go` 里对每张卡单独查套餐 | 轮询任务改为批量拉取卡的套餐信息,一次 `WHERE iot_card_id IN (?)` | 轮询卡数量超过 1000 时 |
|
||||
| `tb_card_daily_usage` 缺少分区 | 流量历史查询(跨月范围)全表扫描 | 建立 `idx_card_daily_date` 索引(E-1 已规划);如果日后数据量大,按月分区 | 单卡每日一条,100 万卡 × 365 天 = 3.65 亿行 |
|
||||
| 流量 Redis key 不设 TTL | `traffic:daily:{card_id}:{date}` 无 TTL 永久占用内存 | 设置 48h TTL(E-1 已规划 `Expire(ctx, key, 48*time.Hour)`);落盘任务消费后 `DEL` key | Redis 内存超限时所有业务 key 被 evict |
|
||||
| 套餐列表接口无分页(见 H-3) | C 端用户请求套餐列表时一次返回全量,单卡绑 100+ 套餐时响应体超大 | 强制分页,`pageSize` 默认 50 最大 100 | 套餐历史积累超过 100 条时 |
|
||||
| 1 个设备绑 4 张卡,佣金计算 4 次 | 设备订单的一次性佣金被触发 4 次(每张绑定卡各触发一次) | 设备级一次性佣金必须以设备为单位去重(README 已说明"代理分佣只计算一次");检查 `triggerOneTimeCommissionForDeviceInTx` 是否有设备级幂等 | 销售设备套餐时 |
|
||||
|
||||
---
|
||||
|
||||
## Security Mistakes
|
||||
|
||||
| Mistake | Risk | Prevention |
|
||||
|---------|------|------------|
|
||||
| 企业账号 `Resolve` 接口被错误拦截(见 B-1)→ 修复时移除拦截,但未验证越权范围 | 企业账号能查询到不属于自己的卡(只要知道 ICCID) | `Resolve` 恢复后,必须在 Store 层加企业数据范围过滤:`WHERE enterprise_id = ? OR shop_id IN (?)` |
|
||||
| C 端 Handler 直接 `h.db.SkipPermissionCtx` 访问 DB(见 F-4) | 个人客户能构造请求查看他人订单 | Handler 绑上游数据权限强制走 Service 层;Service 层验证 `buyer_id = 当前用户绑定资产 ID` |
|
||||
| 金额字段以"分"为单位存储,但前端传来 float(如 "0.01" 元)未乘 100 | 金额精度丢失,1 分变 0 分 | 所有金额 API 明确文档"单位:分,整数";DTO 验证标签加 `min=1`;前端统一在传参前乘 100 |
|
||||
| 停复机操作无 RBAC 权限检查,任何登录的代理账号都可操作 | 代理 A 可以通过 ICCID 停掉代理 B 的卡 | 停复机前检查:`卡的 shop_id ∈ 当前代理的下级店铺范围`;企业账号只能操作被分配给自己企业的卡 |
|
||||
| 提现申请中拒绝 remark 前端可传空(见 C-2) | 审批人拒绝时没有填写拒绝原因,代理投诉无凭据 | `Reject` Handler 必须调用 `validator.Validate(&req)` 且 DTO 字段 `validate:"required"` |
|
||||
|
||||
---
|
||||
|
||||
## UX Pitfalls
|
||||
|
||||
| Pitfall | User Impact | Better Approach |
|
||||
|---------|-------------|-----------------|
|
||||
| 套餐激活失败静默(`break` 不创建记录,见 F-1)| 代理看到"下单成功"但实际套餐没有激活,多次联系客服才发现 | 改为创建 `status=99` 待审记录并通知平台,代理端展示"套餐待激活,平台处理中" |
|
||||
| 自动购包任务失败后 `auto_purchase_status` 停留在 `pending` | 客户充值后一直看到"余额已到账,套餐未开通",不知是否需要手动购买 | Worker 失败超出最大重试次数后,将 `auto_purchase_status` 更新为 `failed`,前端展示"自动购包失败,请手动购买"并推送通知 |
|
||||
| 退款状态"已退回(可重提)"语义不清 | 用户不知道"退回"意味着"申请被退回请你修改重提"还是"钱退回给你了" | 状态名改为"已驳回(可重提)"或"需修改重提",避免与"金钱退回"混淆 |
|
||||
| 停复机 Gateway 未配置时返回错误(H-2 修复后)| 已有卡的用户突然发现"停机功能不可用"且错误信息是技术语言 | 错误消息对外返回"停机功能暂时不可用,请联系客服",内部日志详细记录"Gateway 未配置" |
|
||||
| 提现手续费在 `FeeRate` 字段注释中用"万分比"但代码里有地方用"基点(100=1%)"(两种不同描述)| 代理计算实际到账金额与系统计算不符,投诉频发 | 统一文档:全部使用"基点(100=1%)";前端展示时换算为百分比:"手续费率:1%(100基点)" |
|
||||
|
||||
---
|
||||
|
||||
## "Looks Done But Isn't" Checklist
|
||||
|
||||
- [ ] **佣金计算完整性**:任务注册(`mux.HandleFunc`)+ Scheduler 调度 + 入队调用 + 幂等检查——四项缺一不可,逐项确认
|
||||
- [ ] **Asynq 任务处理器注入**:每个新增的 Task Handler struct,其构造函数的所有参数是否都在 `pkg/queue/handler.go` 的对应 `registerXxx()` 里传入(A-3、A-4 教训)
|
||||
- [ ] **富友支付回调**:实现后验签是否在第一行,是否有幂等检查,是否有超时重试机制
|
||||
- [ ] **设备批量导入**:Excel 解析 → DeviceRow → model.Device 的每个字段贯通(IMEI 教训,见 A-6);未来新增 Device 字段时同步更新 Excel 解析规则
|
||||
- [ ] **回调注入到 bootstrap**:新增的回调方法(StopResumeCallback、ResumeCallback)是否在 `internal/bootstrap/services.go` 中真正调用了(F-6 教训)
|
||||
- [ ] **停复机 Gateway nil 检查**:所有调用 `gatewayClient` 方法的地方,`nil` 情况是否明确拒绝而非静默成功(H-2 教训)
|
||||
- [ ] **金额精度**:所有含 `float64` 转 `int64` 的金额运算,是否改为纯整数运算(退款比例 Pitfall 2 教训)
|
||||
- [ ] **佣金链遍历深度上限**:`CalculateCostDiffCommission` 遍历循环是否有深度检测和循环引用检测(Pitfall 3 教训)
|
||||
|
||||
---
|
||||
|
||||
## Recovery Strategies
|
||||
|
||||
| Pitfall | Recovery Cost | Recovery Steps |
|
||||
|---------|---------------|----------------|
|
||||
| 佣金差额多处 `sellerCostPrice` 错误 | HIGH | 逐笔订单重新计算差价佣金;DBHub 查出所有 `amount=0` 的 cost_diff 记录;人工复算并补入差额;通知代理补发佣金 |
|
||||
| 浮点金额精度错误(退款回扣)| MEDIUM | 查出所有已执行的退款佣金扣减记录;计算正确值与实际扣减值之差;通过管理端手动调整代理佣金钱包余额 |
|
||||
| 佣金任务重复入队导致重复发放 | HIGH | 按 `order_id` 分组查 `tb_commission_record`,找出 count > 正常值的订单;手动删除多余记录并扣减对应代理钱包;加 `asynq.TaskID` 幂等后部署 |
|
||||
| 流量数据被运营商重置归零(已发生) | MEDIUM | 从 Gateway 历史接口补拉历史流量数据(如有);未来改为增量累加后运营商重置不再影响(方案 E) |
|
||||
| 激活配置出现两条 `is_active=true`(并发问题) | LOW | DBHub 找出多余记录,手动 UPDATE 置 `is_active=false`;加锁后重部署 |
|
||||
| 停复机 Gateway nil 假装成功(DB 与运营商不一致)| HIGH | DBHub 查出 `status='stopped'` 但 Gateway 实际在运行的卡;逐卡对比并重发停机指令;F-6 注入后历史不一致需人工修正 |
|
||||
|
||||
---
|
||||
|
||||
## Pitfall-to-Phase Mapping
|
||||
|
||||
| Pitfall | Prevention Phase | Verification |
|
||||
|---------|------------------|--------------|
|
||||
| `sellerCostPrice` 多处同类 Bug | 方案 A(A-5 实施)| `grep -n "sellerCostPrice = buyerCostPrice"` 结果为 0 |
|
||||
| 退款浮点精度丢失 | 方案 I(I-3 实现)| Code Review 检查不含 `float64` 金额运算 |
|
||||
| 佣金树形遍历循环引用/孤岛 | 方案 A + 方案 F | 深度计数器 + visited map 存在于 `CalculateCostDiffCommission`;店铺删除有前置检查 |
|
||||
| 佣金任务重复入队 | 方案 A(A-4 实施)| `asynq.TaskID` 选项用于 commission 任务;`task_id` 可在 Asynq 监控台验证唯一性 |
|
||||
| 运营商回调未验签 | 方案 J(J-1 富友支付)、方案 F(F-6)| Code Review 确认每个回调 Handler 首行有签名验证 |
|
||||
| Handler 直接访问 DB | 方案 F(F-4 迁移)| `grep -rn "h\.db\." internal/handler` 结果为 0 |
|
||||
| 任务"注册未调度"模式 | 每次新增 Asynq 任务 | 新增任务时 Code Review 检查清单:4 项全勾选 |
|
||||
| bootstrap 回调未注入 | 方案 F(F-6)| `grep -rn "SetStopResumeCallback\|SetResumeCallback" internal/bootstrap` 有调用 |
|
||||
| Gateway nil 假装成功 | 方案 H(H-2)| `grep -n "return nil" internal/service/iot_card/stop_resume_service.go` 不出现在 `gatewayClient == nil` 分支 |
|
||||
| 金额 `float64` 误用 | 全程 Code Review | `grep -rn "float64.*[Aa]mount\|[Aa]mount.*float64" internal/service` 结果为 0 |
|
||||
|
||||
---
|
||||
|
||||
## Sources
|
||||
|
||||
- `internal/service/commission_calculation/service.go`:佣金树形遍历实现(直接代码审查)
|
||||
- `internal/service/order/service.go`:5 种购买场景 + sellerCostPrice 赋值(grep 分析,第 187、262、472、548、734、809 行)
|
||||
- `internal/task/polling_handler.go`:流量计算逻辑(直接代码审查,第 388-437 行)
|
||||
- `internal/service/iot_card/stop_resume_service.go`:停复机 nil 问题(直接代码审查)
|
||||
- `.sisyphus/plans/修正业务-完整方案.md`:方案 A-J 完整规格书(主要信息来源)
|
||||
- `pkg/queue/handler.go`:Asynq 任务注册 vs 调度模式(直接代码审查)
|
||||
- `internal/model/financial.go`:金额字段均以 `int64` 分为单位(设计确认)
|
||||
- Go 官方 `float64` 精度限制文档(内置知识,HIGH 置信度)
|
||||
|
||||
---
|
||||
*Pitfalls research for: IoT 卡管理平台(SIM 卡分销 + 多级代理佣金 + 异步套餐激活)*
|
||||
*Researched: 2026-03-27*
|
||||
353
.planning/research/STACK.md
Normal file
353
.planning/research/STACK.md
Normal file
@@ -0,0 +1,353 @@
|
||||
# Stack Research
|
||||
|
||||
**Domain:** IoT 卡管理平台 — SIM 卡全生命周期 + 代理分销 + 分佣结算
|
||||
**Researched:** 2026-03-27
|
||||
**Confidence:** MEDIUM-HIGH(核心技术栈 HIGH,外围选项 MEDIUM)
|
||||
|
||||
---
|
||||
|
||||
## 结论先行:当前技术栈评估
|
||||
|
||||
**整体结论:当前技术选型对于该领域是合理的,无需大规模替换。**
|
||||
存在 3 个明确的技术债和 1 个值得关注的升级时机。
|
||||
|
||||
| 组件 | 当前版本 | 最新版本 | 状态 | 建议 |
|
||||
|------|---------|---------|------|------|
|
||||
| Fiber | v2.52.9 | **v3.1.0** | ⚠️ 大版本落后 | 保持 v2,不升级(见分析) |
|
||||
| GORM | v1.31.1 | v1.31.x | ✅ 最新 | 继续使用 |
|
||||
| Asynq | v0.25.1 | **v0.26.0** | ⚠️ 落后一个版本 | 可选升级(安全依赖升级) |
|
||||
| go-redis | v9.7.0 | v9.18.0 | ⚠️ 落后较多 | 建议升级(无 API 破坏性变更) |
|
||||
| sonic | v1.14.2 | v1.14.x | ✅ 近期版本 | 继续使用 |
|
||||
| PowerWeChat | v3.4.38 | v3.4.38 | ✅ 最新 | 继续使用 |
|
||||
| Go runtime | 1.25.x | 1.25.x | ✅ 最新 | 继续使用 |
|
||||
|
||||
---
|
||||
|
||||
## 核心技术栈
|
||||
|
||||
### 一、HTTP 框架 — Fiber v2(**继续坚守**)
|
||||
|
||||
| 项目 | 内容 |
|
||||
|------|------|
|
||||
| 当前版本 | v2.52.9(已是 v2.x 最新) |
|
||||
| 最新大版本 | v3.1.0(2026-02-02 正式发布) |
|
||||
| **不升级的原因** | v3 有重大破坏性变更,迁移成本高,且本项目有大量存量代码 |
|
||||
|
||||
**v3 核心破坏性变更(对本项目影响分析):**
|
||||
|
||||
1. **`c.UserContext()` 被移除** → `Ctx` 本身现在实现 `context.Context`,直接传 `c` 即可。
|
||||
- **影响:**本项目全代码库大量使用 `c.UserContext()` 传递用户 Context 到 Service/Store 层,这是最大的迁移成本。
|
||||
- **估算:**全局搜索 `UserContext()` 调用点,预估 100+ 处改动。
|
||||
|
||||
2. **`c.BodyParser()` 移除** → 改用 `c.Bind().Body()`
|
||||
- **影响:**所有 Handler 层的请求解析代码需要改写。
|
||||
|
||||
3. **路由注册 `Add` 方法签名变更** → 影响 `internal/routes/registry.go` 的统一注册逻辑。
|
||||
|
||||
4. **`app.Mount()` 移除** → 使用 `app.Use()`。
|
||||
|
||||
**置信度:HIGH**(官方文档 https://docs.gofiber.io/whats_new/ 已确认)
|
||||
|
||||
**建议:** 保持 v2.52.x,待 v2 EOL 公告或下一个重大重构机会再考虑升级。v2 安全维护至少到 2026 年底,风险可控。
|
||||
|
||||
---
|
||||
|
||||
### 二、ORM — GORM v2(**继续使用,无更好替代方案**)
|
||||
|
||||
**2026 年 Go ORM 格局:**
|
||||
|
||||
| ORM | 定位 | 适合本项目 |
|
||||
|-----|------|-----------|
|
||||
| **GORM** | 全功能 ORM,code-first,ActiveRecord 风格 | ✅ **最适合** |
|
||||
| sqlc | 编译时类型安全,从 SQL 生成 Go 代码 | ❌ 需要从头重写所有查询 |
|
||||
| sqlx | SQL 轻量封装,接近原生 database/sql | ❌ 需要全部改写,收益有限 |
|
||||
| ent | 图模型 ORM,compile-time type safety | ❌ 学习成本高,编译慢,不适合现有代码库 |
|
||||
|
||||
**为什么 GORM 对本项目是正确选择:**
|
||||
- CRUD 密集型应用(卡管理、套餐管理、订单、佣金),GORM 的约定优于配置大幅减少样板代码
|
||||
- 软删除、钩子(Hooks)、事务等特性本项目大量依赖
|
||||
- 当前 v1.31.x 已是最新,无需升级
|
||||
|
||||
**GORM 的已知性能问题(针对本项目的建议):**
|
||||
- 多级代理佣金树查询(最深 7 层)不应使用 GORM `Preload`,而应使用原生 `WITH RECURSIVE` CTE
|
||||
- 批量导入(ICCID Excel)应使用 `db.CreateInBatches()` 而非逐行 `Create()`
|
||||
|
||||
**置信度:HIGH**(多个 2026 年对比文章,主流观点一致)
|
||||
|
||||
---
|
||||
|
||||
### 三、任务队列 — Asynq(**继续使用,考虑升级到 v0.26.0**)
|
||||
|
||||
**最新版本:** v0.26.0(2026-02-03 发布)
|
||||
|
||||
| 版本 | 关键变更 |
|
||||
|------|---------|
|
||||
| v0.25.1(当前) | 安全依赖升级 + 增加 `RedisUniversalClient` 支持 |
|
||||
| **v0.26.0** | 增加 Task Headers 支持(PR #1070)+ TLS 选项 + 最低 Go 版本升至 1.24 |
|
||||
|
||||
**为什么不替换 Asynq:**
|
||||
在 IoT 卡管理场景中,Asynq 的优势:
|
||||
- 基于 Redis(与现有基础设施共享,无额外运维成本)
|
||||
- 内置定时任务调度(Scheduler)适合轮询任务场景
|
||||
- 任务可视化(Asynqmon Web UI)在生产调试中有价值
|
||||
- 竞品(Machinery、Watermill)依赖更重,对单节点场景没有额外收益
|
||||
|
||||
**建议:** 升级到 v0.26.0(`go get github.com/hibiken/asynq@v0.26.0`),主要收益是上游安全依赖修复,API 完全兼容。
|
||||
|
||||
**置信度:HIGH**(GitHub Releases 直接验证)
|
||||
|
||||
---
|
||||
|
||||
### 四、Redis 客户端 — go-redis v9(**建议升级**)
|
||||
|
||||
**当前版本:** v9.7.0
|
||||
**最新版本:** v9.18.0(2026-02-16)
|
||||
|
||||
v9.7.0 → v9.18.0 跨越 11 个 patch 版本,均为向后兼容。建议升级原因:
|
||||
- 包含若干 bug 修复和性能改进
|
||||
- 升级命令:`go get github.com/redis/go-redis/v9@v9.18.0`
|
||||
|
||||
**置信度:MEDIUM**(pkg.go.dev 直接确认版本)
|
||||
|
||||
---
|
||||
|
||||
### 五、JSON 序列化 — sonic(**继续使用**)
|
||||
|
||||
**sonic(bytedance/sonic)** 在 2025 年基准测试中仍是 Go 生态最快的 JSON 库:
|
||||
|
||||
| 库 | 相对速度 | 适用场景 |
|
||||
|----|---------|---------|
|
||||
| **sonic** | ~3-4x 快于 std | 高并发 API,大 payload(如 Excel 导入响应) |
|
||||
| encoding/json v2(Go 1.25 内置) | ~1.8x 快于 std | 标准库,无需引入依赖 |
|
||||
| encoding/json(std) | 1x 基线 | 无额外依赖场景 |
|
||||
|
||||
**注意:** Go 1.25 引入了 `encoding/json/v2`,性能约为 sonic 的 50%,但消除了历史行为不一致问题。对于本项目,sonic 仍是正确选择:本项目已集成且使用 Fiber 的 sonic 替换,迁移收益不大。
|
||||
|
||||
**置信度:HIGH**(2025-09 Reddit benchmark + Trendyol 生产迁移案例)
|
||||
|
||||
---
|
||||
|
||||
## 领域特有技术栈缺口分析
|
||||
|
||||
### 缺口 1:金融精度问题 ⚠️(**重大技术债**)
|
||||
|
||||
**问题描述:**
|
||||
IoT 卡管理平台涉及两类精度敏感计算:
|
||||
1. **货币金额**:钱包余额、佣金金额(差价佣金、一次性佣金)、套餐价格
|
||||
2. **流量计费**:MB/GB 流量用量计算、按比例退款
|
||||
|
||||
**当前实现风险:**
|
||||
- 余额字段使用 `int64`(存储"分")—— **钱包余额做法正确**
|
||||
- 但佣金链计算(差价 = 上层价格 - 下层成本)中,如果涉及百分比分佣(如"提成 15%"),用 `float64` 计算后再转 `int64` 会有精度损失
|
||||
|
||||
**标准做法(2025 年行业共识):**
|
||||
|
||||
```
|
||||
选项 A:纯整数(分/厘)存储,避免所有浮点运算
|
||||
- 适合固定金额计算
|
||||
- 本项目钱包余额已是此做法 ✅
|
||||
|
||||
选项 B:shopspring/decimal(用于百分比/比例计算)
|
||||
- 适合佣金比例计算、退款按比例扣减
|
||||
- 7000+ GitHub stars,已是 Go 社区财务计算标准库
|
||||
|
||||
选项 C:go-money(Rhymond/go-money)
|
||||
- 适合多币种场景,本项目不需要
|
||||
```
|
||||
|
||||
**建议:**
|
||||
引入 `github.com/shopspring/decimal` v1.4.0,**仅用于**:
|
||||
- 佣金比例计算(如方案 I-3:退款按比例扣减代理佣金)
|
||||
- 流量按比例计费(方案 E 的流量体系改革)
|
||||
|
||||
不需要全局替换 int64 余额字段,仅在需要乘除法的计算环节使用 decimal,最终结果转 int64 存储。
|
||||
|
||||
**置信度:HIGH**(Modern Treasury 官方博客 2025-08 + shopspring/decimal 7286 stars)
|
||||
|
||||
---
|
||||
|
||||
### 缺口 2:多级代理查询性能 ⚠️(**架构风险**)
|
||||
|
||||
**问题描述:**
|
||||
本项目代理层级最多 7 层,`GetSubordinateShopIDs()` 使用 PostgreSQL `WITH RECURSIVE` CTE 查询,结果缓存 30 分钟。这个架构是正确的。
|
||||
|
||||
**潜在风险:**
|
||||
- 佣金链计算时,N 层递归分佣可能退化为 N 次单独 DB 查询(N+1 问题)
|
||||
- 7 层 × 每层查询 = 最坏 7 次 DB 往返,而 DB 查询要求 < 50ms
|
||||
|
||||
**2025 年标准方案:**
|
||||
对于多级分佣树计算,行业标准是一次性将整条链路数据加载到内存,而非逐层查询:
|
||||
|
||||
```sql
|
||||
-- 一次性获取完整佣金链(从叶子节点到根节点)
|
||||
WITH RECURSIVE commission_chain AS (
|
||||
SELECT shop_id, parent_id, level, 0 AS depth
|
||||
FROM tb_shop WHERE id = $1
|
||||
UNION ALL
|
||||
SELECT s.shop_id, s.parent_id, s.level, cc.depth + 1
|
||||
FROM tb_shop s
|
||||
INNER JOIN commission_chain cc ON s.id = cc.parent_id
|
||||
)
|
||||
SELECT * FROM commission_chain ORDER BY depth;
|
||||
```
|
||||
|
||||
当前代码已经有 `WITH RECURSIVE` 基础,但需要确认佣金计算是否利用了这个能力。
|
||||
|
||||
**置信度:MEDIUM**(基于代码库 PROJECT.md 分析 + PostgreSQL 官方文档)
|
||||
|
||||
---
|
||||
|
||||
### 缺口 3:可观测性 ⚠️(**生产上线前需解决**)
|
||||
|
||||
**问题描述:**
|
||||
当前技术栈的可观测性仅有:
|
||||
- 应用日志(Zap)
|
||||
- 访问日志
|
||||
- Asynq 任务状态(via Asynqmon 可选)
|
||||
- 健康检查 `GET /health`
|
||||
|
||||
**缺失:**
|
||||
- 没有指标(Metrics)收集
|
||||
- 没有链路追踪(Tracing)
|
||||
- 没有告警(Alerting)
|
||||
|
||||
**对于当前阶段(MVP 修复上线):** 可以暂时接受,PROJECT.md 的 Out of Scope 已明确 "P2-6 告警通知渠道 — 无关紧要,暂不做"。
|
||||
|
||||
**但如果平台上线后扩展:**
|
||||
- 轻量级方案:Prometheus + Grafana(已是 Go 社区标准)
|
||||
- 零侵入方案:OpenTelemetry Go SDK(标准化,支持后续接入任意后端)
|
||||
|
||||
**置信度:MEDIUM**(基于 Go 社区 2025 年可观测性实践)
|
||||
|
||||
---
|
||||
|
||||
### 缺口 4(IoT 领域特有):运营商网关客户端 — 无通用标准库
|
||||
|
||||
**问题描述:**
|
||||
本项目有 `internal/gateway/client.go`(外部 Gateway 服务 HTTP 客户端)。IoT SIM 卡管理平台通常需要对接:
|
||||
- GSMA M2M 标准(SM-DP+/SM-DS for eSIM)
|
||||
- 运营商私有 API(中国联通、移动、电信)
|
||||
|
||||
**2025 年实际情况:**
|
||||
国内运营商 IoT 平台(移动 OneNET、电信 CTWing、联通 Open)均为私有 REST/SOAP API,**没有标准化的 Go SDK**。
|
||||
|
||||
当前项目自研 `internal/gateway/client.go` 是唯一可行路径,符合行业惯例。
|
||||
|
||||
**置信度:HIGH**(行业调研确认,无通用 Go SDK 存在)
|
||||
|
||||
---
|
||||
|
||||
## 当前栈中已弃用/有问题的依赖
|
||||
|
||||
| 依赖 | 问题 | 建议 |
|
||||
|------|------|------|
|
||||
| `aws/aws-sdk-go` (v1) | AWS SDK Go v1 已于 2025-07-31 进入 Maintenance Mode,不再接收功能更新 | 低优先级,当前对接联通云 OSS,可继续使用;如有大范围重构可迁移到 `aws/aws-sdk-go-v2` |
|
||||
| `excelize/v2` | 无严重问题,版本跟进即可 | — |
|
||||
|
||||
**AWS SDK v1 注意:** 仅 Maintenance Mode,不是 End of Life,安全补丁仍会发布。迁移到 v2 需要较大代码改动,不建议在当前 MVP 阶段操作。
|
||||
|
||||
**置信度:MEDIUM**(AWS 官方公告,2025-07-31)
|
||||
|
||||
---
|
||||
|
||||
## 可选补充库
|
||||
|
||||
以下库不在当前技术栈中,但对特定需求有价值:
|
||||
|
||||
| 库 | 版本 | 用途 | 引入时机 |
|
||||
|----|------|------|---------|
|
||||
| `shopspring/decimal` | v1.4.0 | 佣金比例计算、退款按比例计算 | **方案 I(退款)实现时** |
|
||||
| `golang-migrate/migrate` | v4.18.x | 数据库迁移(已在项目使用) | — 已用 |
|
||||
| `hibiken/asynqmon` | latest | Asynq 任务可视化 Web UI | 生产环境调试时 |
|
||||
|
||||
---
|
||||
|
||||
## 替代方案对比
|
||||
|
||||
| 类别 | 当前选择 | 备选 | 不替换的理由 |
|
||||
|------|---------|------|------------|
|
||||
| HTTP 框架 | Fiber v2 | Gin, Echo, Chi, Fiber v3 | 大量存量代码;v3 破坏性变更成本高 |
|
||||
| ORM | GORM | sqlc, sqlx, ent | 项目已深度集成,CRUD 场景 GORM 是最优解 |
|
||||
| 任务队列 | Asynq | Machinery, Watermill, Temporal | Redis 复用优势;Temporal 对单节点过重 |
|
||||
| 消息队列 | 无(Asynq 兼任) | NATS, Kafka | 当前规模不需要独立 MQ |
|
||||
| 缓存 | Redis | Memcached | Redis 功能更丰富,已是基础设施 |
|
||||
| 数据库 | PostgreSQL | MySQL, CockroachDB | PostgreSQL 的递归 CTE 对层级数据有优势 |
|
||||
| JSON | sonic | encoding/json, json/v2 | sonic 性能优势在高并发场景显著 |
|
||||
|
||||
---
|
||||
|
||||
## 版本兼容性注意事项
|
||||
|
||||
| 依赖组合 | 状态 | 注意 |
|
||||
|---------|------|------|
|
||||
| Fiber v2 + Go 1.25 | ✅ 兼容 | Fiber v2.52.9 已适配 Go 1.25 |
|
||||
| Fiber v3 + Go 1.25 | ⚠️ 要求 Go 1.25+ | v3 以 Go 1.25 为最低版本 |
|
||||
| Asynq v0.26.0 + Go 1.25 | ✅ 兼容 | v0.26.0 要求 Go 1.24+,1.25 满足 |
|
||||
| GORM v1.31.1 + PostgreSQL 14+ | ✅ 兼容 | — |
|
||||
| PowerWeChat v3.4.38 + Go 1.25 | ✅ 兼容 | 官方声明 Go 1.23+ |
|
||||
|
||||
---
|
||||
|
||||
## 关键技术债总结(优先级排序)
|
||||
|
||||
### P0:必须在上线前解决
|
||||
|
||||
**金融精度风险**(佣金计算)
|
||||
- 检查所有涉及百分比/比例的佣金计算代码,确认没有 `float64` 中间步骤
|
||||
- 方案 I(退款按比例扣减佣金)实现时必须引入 `shopspring/decimal`
|
||||
|
||||
### P1:上线后尽快处理
|
||||
|
||||
**go-redis 版本落后**
|
||||
- 从 v9.7.0 升级到 v9.18.0,无 API 破坏性变更,安全隐患修复
|
||||
|
||||
**Asynq 版本落后**
|
||||
- 从 v0.25.1 升级到 v0.26.0,获得安全修复
|
||||
|
||||
### P2:中期规划
|
||||
|
||||
**AWS SDK v1 → v2 迁移**(对象存储)
|
||||
- 不紧急,v1 仍有安全支持,规划下一个重大重构时处理
|
||||
|
||||
**Fiber v2 → v3 迁移评估**
|
||||
- v2 仍在维护,不需要立即升级
|
||||
- 建议在明年底(2027)重新评估是否升级
|
||||
|
||||
---
|
||||
|
||||
## 安装/升级命令参考
|
||||
|
||||
```bash
|
||||
# 升级 Asynq(推荐)
|
||||
go get github.com/hibiken/asynq@v0.26.0
|
||||
|
||||
# 升级 go-redis(推荐)
|
||||
go get github.com/redis/go-redis/v9@v9.18.0
|
||||
|
||||
# 引入 decimal(P0 佣金精度修复时)
|
||||
go get github.com/shopspring/decimal@v1.4.0
|
||||
|
||||
# 升级所有兼容依赖(谨慎,逐一验证)
|
||||
go get -u ./...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Sources
|
||||
|
||||
| 来源 | 内容 | 置信度 |
|
||||
|------|------|--------|
|
||||
| https://docs.gofiber.io/whats_new/ | Fiber v3 破坏性变更完整列表 | HIGH |
|
||||
| https://newreleases.io/project/github/hibiken/asynq/release/v0.26.0 | Asynq v0.26.0 发布说明 | HIGH |
|
||||
| https://pkg.go.dev/github.com/redis/go-redis/v9 | go-redis 最新版本 v9.18.0 | HIGH |
|
||||
| https://reintech.io/blog/sqlc-vs-gorm-vs-sqlx-go-database-libraries-compared-2026 | GORM vs sqlc vs sqlx 2026 深度对比 | MEDIUM |
|
||||
| https://encore.cloud/resources/go-orms | Go ORM 2026 对比(GORM/sqlc/ent)| MEDIUM |
|
||||
| https://pkg.go.dev/github.com/shopspring/decimal@v1.4.0 | shopspring/decimal v1.4.0 | HIGH |
|
||||
| https://www.moderntreasury.com/journal/floats-dont-work-for-storing-cents | 金融精度使用整数的行业实践 | HIGH |
|
||||
| Reddit r/golang 2025-09 JSON benchmark | sonic vs std vs json/v2 基准 | MEDIUM |
|
||||
| https://pkg.go.dev/github.com/ArtisanCloud/PowerWeChat/v3@v3.4.38 | PowerWeChat 最新版本确认 | HIGH |
|
||||
|
||||
---
|
||||
|
||||
*Stack research for: IoT SIM 卡管理平台(Go + Fiber + GORM + Asynq + PostgreSQL + Redis)*
|
||||
*Researched: 2026-03-27*
|
||||
121
.planning/research/SUMMARY.md
Normal file
121
.planning/research/SUMMARY.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# Research Summary
|
||||
|
||||
**Domain:** 君鸿卡管系统 — IoT 卡/号卡/设备全生命周期管理平台
|
||||
**Synthesized:** 2026-03-27
|
||||
**Sources:** STACK.md · FEATURES.md · ARCHITECTURE.md · PITFALLS.md
|
||||
|
||||
---
|
||||
|
||||
## 技术栈评估
|
||||
|
||||
**结论:当前技术选型合理,无需大规模替换。**
|
||||
|
||||
| 组件 | 状态 | 行动建议 |
|
||||
|------|------|---------|
|
||||
| Fiber v2.52.9 | ✅ 继续使用 | 不升级 v3(破坏性变更 100+ 处,迁移成本过高) |
|
||||
| GORM v1.31.x | ✅ 最新 | 继续使用;多级递归查询用原生 `WITH RECURSIVE` |
|
||||
| Asynq v0.24.x | ⚠️ 落后 | 可选升级到 v0.26.0(无破坏性变更) |
|
||||
| go-redis v9.7.0 | ⚠️ 落后 | 建议升级到 v9.18.x(无破坏性变更) |
|
||||
| sonic / PowerWeChat | ✅ 正常 | 继续使用 |
|
||||
|
||||
**技术债**:佣金计算全程使用 `int64` 整数运算是正确的;退款比例计算(方案 I-3)禁止用 `float64`,应使用整数算术 `a * b / c`。
|
||||
|
||||
---
|
||||
|
||||
## 功能完整性分析
|
||||
|
||||
### 已覆盖的 Table Stakes ✅
|
||||
- IoT 卡全生命周期(开卡/激活/停复机/销户)
|
||||
- 套餐系统(主套餐/加油包/排队/囤货待实名/流量扣减)
|
||||
- 五种订单购买场景 + 三种支付方式
|
||||
- 代理商多级分销(7 级层级,差价佣金+一次性佣金)
|
||||
- RBAC 权限系统 + 多租户数据隔离
|
||||
- C 端微信认证 + 钱包充值购包
|
||||
|
||||
### 缺失的 Table Stakes ⚠️(修复阶段应关注)
|
||||
|
||||
| 功能 | 优先级 | 说明 |
|
||||
|------|--------|------|
|
||||
| 卡/资产批量操作(停/复/注销) | HIGH | 运营场景必须,单卡操作无法应对批量场景 |
|
||||
| 卡/订单批量导出(Excel) | HIGH | 财务对账必须,已有导入但缺导出 |
|
||||
| 退款完整流程 | HIGH | 方案 I 已规划,必须实现 |
|
||||
| 卡状态变更历史记录 | MEDIUM | 出现纠纷时需要溯源 |
|
||||
| 套餐到期提醒(微信模板消息) | MEDIUM | 用户断网会差评,竞品标配 |
|
||||
| 代理商完整资金流水账单 | MEDIUM | 代理需要对账 |
|
||||
| 富友支付 JSAPI/小程序接口补全 | HIGH | 方案 J-1,SDK 已实现但接口是留桩 |
|
||||
|
||||
### 竞品差异化(v2+ 方向)
|
||||
- 智能套餐推荐(基于历史用量)
|
||||
- 告警推送(流量耗尽前预警)
|
||||
- 数据大屏(代理商业绩可视化)
|
||||
|
||||
---
|
||||
|
||||
## 架构风险
|
||||
|
||||
### 当前正确的设计决策 ✅
|
||||
- 双进程隔离(API vs Worker)— 独立扩容
|
||||
- 数据权限显式过滤(非 GORM Callback)— 可见性强
|
||||
- Asynq 任务队列解耦同步/异步链路
|
||||
- Bootstrap 集中依赖装配
|
||||
|
||||
### 需要立即处理的架构问题 🔴
|
||||
|
||||
**1. PollingHandler 缺少 asynqClient(方案 A-3)**
|
||||
- 实名激活任务通过 RPush Redis List 降级,绕过 Asynq
|
||||
- 修复后才能保证异步任务链路可靠性
|
||||
|
||||
**2. 多个 Service 实例依赖注入残缺(方案 F-6)**
|
||||
- StopResumeCallback / ResumeCallback 未在 bootstrap 注入
|
||||
- 停复机后套餐联动回调不触发
|
||||
|
||||
**3. Worker 注册遗漏(方案 A-4)**
|
||||
- AutoPurchaseHandler 未在 handler.go 注册
|
||||
- 充值回调后自动购包任务入队但永远不消费
|
||||
|
||||
### SaaS 化时的潜在瓶颈(供参考,v2+ 考量)
|
||||
- 代理层级 7 级限制需松绑为配置化
|
||||
- tb_shop/tb_enterprise 的 owner_shop_id 设计需调整为 tenant_id
|
||||
- 套餐成本价体系(每个代理一套)将成为最大多租户难点
|
||||
|
||||
---
|
||||
|
||||
## 关键陷阱(超出修正方案范围)
|
||||
|
||||
### 🔴 高危
|
||||
|
||||
**1. sellerCostPrice 赋值错误可能不止 1 处(A-5 相关)**
|
||||
- 修正方案只点名场景 5,但 order/service.go 中有 6 处 `sellerCostPrice = buyerCostPrice`
|
||||
- 修复时必须逐一验证每个场景的语义正确性
|
||||
|
||||
**2. 退款佣金回扣禁用 float64(方案 I-3)**
|
||||
- `ratio := float64(x) / float64(y)` 会引入精度误差
|
||||
- 应使用:`deductAmount = commission.Amount * approvedAmount / actualAmount`(纯整数)
|
||||
|
||||
**3. 分佣树形遍历可能循环引用**
|
||||
- `shop.ParentID` 向上遍历没有深度上限和循环检测
|
||||
- 人工修改数据库可能导致 Worker 死循环
|
||||
|
||||
### 🟡 中危
|
||||
|
||||
**4. 店铺软删除没有级联检查**
|
||||
- 删除有子代理的店铺后,子代理 parent_id 指向软删除记录
|
||||
- 佣金计算时找不到父链接,上级代理静默丢失佣金
|
||||
|
||||
**5. 实名状态轮询无限重试设计**
|
||||
- 卡在"未实名"状态的卡每次轮询都入队,没有最大重试次数限制
|
||||
- 大量长期未实名的卡会持续占用轮询资源
|
||||
|
||||
---
|
||||
|
||||
## 构建优先级建议
|
||||
|
||||
```
|
||||
立即(1-2 天):方案 A(7 个 P0 Bug)
|
||||
近期(3-5 天):方案 B/C/G/H + J-4(企业权限/提现/实名重构/轮询小修)
|
||||
中期(1-2 周):方案 D/I/J-1/J-2/J-3/F(设备/退款/富友支付/代码清理)
|
||||
长期(低峰期):方案 E(流量体系改革,破坏性 DB 迁移)
|
||||
```
|
||||
|
||||
---
|
||||
*Research synthesized: 2026-03-27*
|
||||
Reference in New Issue
Block a user