Initial LisgloSIPS V2 implementation

This commit is contained in:
hectorzhao
2026-06-22 10:56:38 +08:00
commit 5fa1bd35e9
303 changed files with 35644 additions and 0 deletions
+112
View File
@@ -0,0 +1,112 @@
# CDR Minimal Billing Runbook
> 任务:S23 - 最小计费与余额扣减
> 完成时间:2026-06-21 19:55 +08:00
## 1. 目标
S23 在 S22 的 Redis Stream 消费基础上完成最小计费闭环:
- 成功通话 CDR 写入 `raw_cdrs``rated_cdrs`
- 按周期秒数和周期费率计算费用。
- 扣减客户余额,并写入不可变客户余额流水。
- 同一 `event_id` 或同一 `raw_cdr_id` 不重复扣费。
当前系统尚未建立客户侧费率表,因此 S23 使用落地网关的 `billingCycleSec``cycleRate` 同时作为供应商成本和临时客户费用,`grossProfit``0.000000`。正式客户费率和利润计算留给后续计费版本。
## 2. 计费规则
`apps/worker-cdr/src/billing.ts` 提供周期计费函数:
```text
cycles = ceil(duration_sec / billing_cycle_sec)
bill_sec = cycles * billing_cycle_sec
amount = cycles * cycle_rate
```
约束:
- `duration_sec` 必须为非负整数。
- `billing_cycle_sec` 必须为 1 到 60 的整数。
- 金额统一保留 6 位小数。
- `sip_code``2xx``duration_sec > 0` 时才进入计费。
失败 CDR、零时长 CDR、缺少客户或落地网关 ID 的 CDR 只写 `raw_cdrs``ratingStatus` 标记为 `SKIPPED`,不扣余额。
## 3. 写库与幂等
`apps/worker-cdr/src/rating.ts``CdrRatingService` 在单个数据库事务内处理:
1.`raw_cdrs.event_id` 查询是否已处理。
2. 新建 `raw_cdrs`
3. 锁定客户余额行:`SELECT ... FOR UPDATE`
4. 读取落地网关周期费率。
5. 新建 `rated_cdrs`
6. 新建一条负数 `customer_recharges` 作为扣费流水。
7. 更新 `customers.balance`
8.`raw_cdrs.ratingStatus` 标记为 `RATED`
扣费流水约定:
| 字段 | 值 |
| --- | --- |
| `amount` | 负数费用 |
| `idempotencyKey` | `cdr:<event_id>` |
| `remark` | `CDR_CHARGE:<raw_cdr_id>` |
| `createdBy` | `worker-cdr` |
数据库唯一约束共同防止重复扣费:
- `raw_cdrs.event_id`
- `rated_cdrs.raw_cdr_id`
- `customer_recharges.idempotencyKey`
## 4. Worker 行为
`apps/worker-cdr/src/main.ts` 现在连接 Redis 和 MySQL
1.`stream:cdr_payload``billing-workers` Consumer Group 读取 CDR。
2. 解析成功后调用 `CdrRatingService.rate(event)`
3. 处理成功或重复消息后 ACK Redis 消息。
4. 处理异常时沿用 S22 死信机制写入 `stream:cdr_deadletter`
必需环境变量:
```text
REDIS_URL
DATABASE_URL
```
可选环境变量:
```text
CDR_CONSUMER_NAME
CDR_BLOCK_MS
CDR_BATCH_SIZE
CDR_PENDING_IDLE_MS
LISGLOSIPS_LOG_LEVEL
```
## 5. 验证结果
- `corepack pnpm@10.33.0 exec vitest run apps/worker-cdr/src/rating.spec.ts` 通过,3 条测试覆盖周期计费、成功 CDR 扣费幂等、失败 CDR 不扣费。
- `corepack pnpm@10.33.0 --filter @lisglosips/worker-cdr build` 通过。
- `corepack pnpm@10.33.0 typecheck` 通过。
- `corepack pnpm@10.33.0 lint` 通过。
- `corepack pnpm@10.33.0 build` 通过。
本次未连接 B 执行服务部署或真实库写入验证,因为 B sudo 凭据仍未通过校验。S23 仅完成本地代码闭环和构建验收。
## 6. 回滚
代码回滚:
- 恢复 `apps/worker-cdr/src/main.ts` 到 S22 只记录 CDR 的版本。
- 删除或恢复 `apps/worker-cdr/src/billing.ts``apps/worker-cdr/src/rating.ts``apps/worker-cdr/src/rating.spec.ts`
- 恢复 `apps/worker-cdr/package.json``apps/worker-cdr/tsconfig.json`
- 重新执行 `corepack pnpm@10.33.0 build`
数据回滚:
- S23 未执行真实数据库写入验证,无需清理远端数据。
- 若后续环境已运行 worker 并产生扣费数据,回滚前必须先停止 worker,记录待回滚的 `event_id``raw_cdr_id``rated_cdr_id` 和客户流水;不得直接删除余额流水,应通过受控反向流水或备份恢复方案处理。