feat: complete phase2 baseline cdr quality rbac

This commit is contained in:
hectorzhao
2026-06-24 18:40:21 +08:00
parent 7057fd3c42
commit a86de6545f
63 changed files with 7853 additions and 3678 deletions
@@ -0,0 +1,191 @@
# 业务前缀重构第 8 步迁移与发布前验收 Runbook
## 范围
本文用于业务前缀、客户网关匹配和落地号码改写重构的发布前准备。当前仍以本地 KVM/Tailscale 环境为准,不进入阿里云生产迁移。
本步骤覆盖:
- B 执行新增 Prisma migration 前后的检查。
- 旧客户网关单 IP 和旧 `customer_gateway_policies` 到二期模型的安全迁移。
- Redis 配置发布检查。
- A OpenSIPS/Lua 替换前语法检查和回滚点。
- A/B/T 端到端验收清单。
## 影响、风险与回滚点
影响范围:
- MySQL:新增二期表/列后,`customer_gateways.line_group_id``customer_gateway_ips``customer_gateway_caller_prefixes` 会被迁移脚本写入。
- Redis:迁移脚本成功应用后会写 `customer_gateway_config` outboxconfig-publisher 会发布新版本配置。
- OpenSIPS:后续发布 A 侧 `opensips.cfg``lisglosips_hotpath.lua` 后,INVITE 鉴权、路由、主被叫改写进入新热路径。
- CDR:第 7 步已让 Worker 可写入原始被叫、业务前缀、落地主被叫字段。
主要风险:
- 多条旧策略无法自动合并到“一个客户网关一个线路组”,必须人工拆分或确认保留规则。
-`calleeMode=EQUALS/PREFIX` 不能安全推断为业务前缀,必须先在“业务前缀管理”中建立前缀并重新绑定客户网关。
-`callerMode=EQUALS` 在新模型下没有严格等值语义,不能自动迁移为前缀,否则可能扩大匹配范围。
- A 侧 OpenSIPS 配置替换会影响现有通话,必须维护窗口或确认可接受。
回滚点:
- B 数据库:执行 migration 前必须按 S30 Runbook 备份 MySQL;迁移脚本 apply 前需保存 dry-run 报告。
- B release:保留当前 `/opt/lisglosips/current` 指向,可通过 S30 rollback 脚本切回旧 release。
- Redis:记录 `cfg:active_version``cfg:previous_version`,必要时切回上一版本。
- A OpenSIPS:替换前备份 `/etc/opensips/opensips.cfg``/etc/opensips/lisglosips_hotpath.lua`;语法检查失败不得重启;重启后失败则恢复备份并再次 `opensips -C`
## 迁移脚本
本仓库新增:
```bash
scripts/phase2-gateway-migration.mjs
```
默认 dry-run,不写库:
```bash
node scripts/phase2-gateway-migration.mjs --json
```
显式 apply 才写库:
```bash
node scripts/phase2-gateway-migration.mjs --apply --json
```
脚本只自动迁移满足以下条件的客户网关:
- 每个客户网关恰好一条启用旧策略。
- 旧策略 `calleeMode=ANY`
- 旧策略 `callerMode=ANY`,或 `callerMode=PREFIX``callerValue` 非空。
脚本会拒绝自动迁移以下情况:
- 多条启用旧策略。
- 没有启用旧策略。
- 旧被叫等值或前缀匹配。
- 旧主叫等值匹配。
- 新旧线路组字段冲突。
apply 成功后,脚本会写入一个 `customer_gateway_config` outbox,用于触发配置发布。
## B 侧发布前步骤
1. 记录状态:
```bash
readlink -f /opt/lisglosips/current
mysql -NBe "SELECT migration_name, finished_at FROM _prisma_migrations ORDER BY finished_at DESC LIMIT 5;"
redis-cli --no-auth-warning GET cfg:active_version
```
2. 执行 S30 备份:
```bash
sudo /opt/lisglosips/current/infra/server-b/s30/lisglosips-release-preflight.sh
sudo /usr/local/sbin/lisglosips-mysql-backup
sudo /usr/local/sbin/lisglosips-redis-backup
```
3. 部署新 release 到独立目录,不在线修改 current。
4. 执行数据库 migration
```bash
export DATABASE_URL='mysql://<MIGRATE_USER>@127.0.0.1:3306/lisglosips'
pnpm exec prisma migrate deploy
pnpm exec prisma migrate status
```
5. 迁移脚本 dry-run
```bash
export DATABASE_URL='mysql://<MIGRATE_USER>@127.0.0.1:3306/lisglosips'
node scripts/phase2-gateway-migration.mjs --json > /tmp/phase2-gateway-migration.dry-run.json
```
6.`blockers` 非空,先人工处理,不执行 apply。
7. 确认 dry-run 后执行 apply
```bash
node scripts/phase2-gateway-migration.mjs --apply --json > /tmp/phase2-gateway-migration.apply.json
```
8. 等待或触发 config-publisher,检查新配置发布:
```bash
systemctl status lisglosips@config-publisher --no-pager
redis-cli --no-auth-warning GET cfg:active_version
```
## A 侧 OpenSIPS 发布前步骤
1. 备份:
```bash
sudo install -d -m 0750 /var/backups/lisglosips-phase2
sudo cp -a /etc/opensips/opensips.cfg /var/backups/lisglosips-phase2/opensips.cfg.$(date +%Y%m%d%H%M%S)
sudo cp -a /etc/opensips/lisglosips_hotpath.lua /var/backups/lisglosips-phase2/lisglosips_hotpath.lua.$(date +%Y%m%d%H%M%S)
```
2. 替换候选文件后,加载 Lua 并确认 SHA 与 `opensips.cfg``EVALSHA` 一致。
3. 必须先执行语法检查:
```bash
sudo opensips -C -f /etc/opensips/opensips.cfg
```
4. 语法检查通过后才可重启:
```bash
sudo systemctl restart opensips
sudo systemctl is-active opensips
```
## A/B/T 验收清单
至少验证以下呼叫:
- 命中业务前缀:例如客户送 `67118822190000`,话单 `callee=18822190000``raw_callee=67118822190000``business_prefix=671`
- 任意被叫规则:客户网关 `calleeMatchMode=ANY` 时,旧无业务前缀呼叫仍可路由。
- 主叫前缀命中:配置 `callerPrefixes` 后,命中前缀允许呼叫。
- 主叫前缀不命中:返回 `CALLER_PREFIX_NOT_MATCHED`
- 业务前缀不命中:返回 `BUSINESS_PREFIX_NOT_MATCHED`
- 落地被叫前缀:落地侧实际 Request-URI 被叫包含 `landingCalleePrefix`
- 落地指定主叫:落地侧 From 主叫按权重池选择,话单 `landing_caller` 有值。
- 屏蔽地区跳过:命中落地网关屏蔽地区时尝试同线路组下一网关。
- 当前通话页:呼叫过程中可看到当前通话,强制挂断可用。
- 话单详情:可看到原始被叫、业务前缀、落地主叫、落地被叫、地级市、运营商。
## 回滚
B 应用回滚:
```bash
sudo infra/server-b/s30/lisglosips-release-rollback.sh /opt/lisglosips/releases/<previous-release>
```
Redis 配置回滚:
```bash
# 使用当前系统已验证的 Redis 运维方式切回 cfg:previous_version
```
A OpenSIPS 回滚:
```bash
sudo cp -a /var/backups/lisglosips-phase2/<opensips.cfg.backup> /etc/opensips/opensips.cfg
sudo cp -a /var/backups/lisglosips-phase2/<lisglosips_hotpath.lua.backup> /etc/opensips/lisglosips_hotpath.lua
sudo opensips -C -f /etc/opensips/opensips.cfg
sudo systemctl restart opensips
```
数据库回滚原则:
- 本次 migration 为向前兼容新增表/列,不做破坏性 down migration。
- 若 apply 后发现业务规则错误,优先通过客户网关页面/API 修正 `lineGroupId`、主叫规则和业务前缀绑定,再重新发布 Redis 配置。
- 若必须恢复数据,使用执行前 MySQL 备份在隔离环境验证后再恢复。
+107
View File
@@ -0,0 +1,107 @@
# S42 二期业务前缀基线冻结报告
## 1. 目标
冻结 LisgloSIPS V2 本地 KVM/Tailscale 环境中二期业务前缀、客户网关匹配、号码库、落地号码改写和真实话单接入后的当前可回滚基线。
本冻结不进入阿里云生产迁移,不替代未来生产环境的 VPC、安全组、正式 DNS/TLS、独立数据盘、备份恢复和灰度验收。
## 2. 冻结结论
当前 `s42-phase2-business-prefix-20260624140000` 具备冻结条件,可作为“二期业务前缀小样本联调基线”。
本冻结不包含真实 80 万手机号段导入,不包含生产式号码库压测,不包含浏览器登录态逐页按钮全量人工复测。
## 3. 冻结清单
| 项 | 当前值 |
| --- | --- |
| B current release | `/opt/lisglosips/releases/s42-phase2-business-prefix-20260624140000` |
| Redis active version | `1782286812696` |
| Redis previous version | `1782286145681` |
| A OpenSIPS cfg SHA-256 | `ae1544e9a39d2db114e6981babac042c2ab6d399d59ba597a52b85d597aa5aa5` |
| A Lua SHA-1 | `0b7ee595cdcad2f38a670dd8ff3553387820039e` |
| Prisma migrations | 4 条,B 当前库 `Database schema is up to date` |
| 最新 MySQL 备份 | `/data/backups/mysql/20260624T055810Z`SHA256 校验通过 |
| 最新 Redis 备份 | `/data/backups/redis/20260624T055810Z`SHA256 校验通过 |
| T 测试脚本 | `/opt/lisglosips-s28/lisglosips-s28-sip.py` |
## 4. 验证结果
本地代码验证:
- `corepack pnpm@10.33.0 typecheck` 通过。
- `corepack pnpm@10.33.0 lint` 通过。
- `corepack pnpm@10.33.0 build` 通过。
- `corepack pnpm@10.33.0 exec vitest run --hookTimeout=60000 --maxWorkers=1` 通过:25 个测试文件,87 条测试通过。
B 侧验证:
- `lisglosips@api``lisglosips@cdr-worker``lisglosips@recording-worker``lisglosips@config-publisher``nginx``mysql``redis-server``heplify-server``lisglosips-prometheus``grafana-server` 均为 `active`
- `https://127.0.0.1/api/v2/health/ready` 返回 `ok`
- `infra/server-b/s30/lisglosips-release-preflight.sh` 已补齐执行权限,直接执行通过。
- Redis `stream:cdr_deadletter` 长度为 26,抽样均为 2026-06-23 至 2026-06-24 早期修复前历史死信,包括 `config_version` INT 越界、旧占位外键失败、旧 `NO_POLICY` 事件;冻结验收实呼未新增 deadletter。
A 侧验证:
- `opensips``lisglosips-redis-auth-proxy``rtpengine``rtpengine-recording-daemon` 均为 `active`
- `opensips -C -f /etc/opensips/opensips.cfg` 通过。
- 历史 4 条 dialog 经 `dlg_end_dlg` 无法清理,返回 OpenSIPS MI `Operation failed`;已在语法检查通过后重启 `opensips` 清空。
- 冻结验收实呼后出现 1 条测试 dialog 残留;已再次语法检查并重启 `opensips`,最终 `dlg_list` 为空。
T 侧验证:
- `opensips``active`
- `100.93.185.30:5060``100.93.185.30:50620` 均在 UDP 监听。
端到端小样本呼叫:
- Call-ID`s28-1782288889943-ckv83lce@lisglosips-t`
- SIP`100 Giving it a try``200 OK`、BYE `200 OK`
- B raw CDR`raw_d5732df32b8d496686512870fb087232`
- 话单字段:`caller=s28-ip-1001``callee=13800138000``raw_callee=13800138000``business_prefix=NULL``landing_caller=s28-ip-1001``landing_callee=13800138000``sip_code=200``rating_status=RATED`
- 计费:客户费用 `0.012000`,供应商成本 `0.012000`
- CDR Worker`processed=1``deadlettered=0`
## 5. 已处理冻结前问题
1. 历史 dialog 残留:已通过重启 A OpenSIPS 清空,最终 `dlg_list` 为空。
2. CDR deadletter:未删除历史死信,已确认样本属于早期修复前历史事件;冻结验收呼叫未新增 deadletter。
3. B preflight 执行权限:已将 `/opt/lisglosips/current/infra/server-b/s30/lisglosips-release-preflight.sh` 权限修正为 `0755`,直接执行通过。
4. 全量 Vitest:初次使用默认 hook timeout 时 `app.e2e.spec.ts` 超时;使用 `--hookTimeout=60000` 后全量测试通过。
5. 冻结范围:明确不包含真实 80 万手机号段导入和浏览器登录态逐页按钮全量人工复测。
## 6. 回滚点
B 应用回滚:
```bash
sudo /opt/lisglosips/current/infra/server-b/s30/lisglosips-release-rollback.sh /opt/lisglosips/releases/s33-active-calls-20260623094000
```
Redis 配置回滚:
- 当前版本:`1782286812696`
- 上一版本:`1782286145681`
- 回滚时切回 `cfg:previous_version`,不删除 Stream 或 deadletter。
A OpenSIPS 回滚:
- 当前配置语法已通过,当前 Lua SHA 已记录。
- 若需回滚,使用最近一次 `/var/backups/lisglosips-phase2/` 下的 `opensips.cfg``lisglosips_hotpath.lua` 备份,先执行 `opensips -C -f /etc/opensips/opensips.cfg`,通过后重启 `opensips`
数据库回滚:
- 二期 migration 为向前兼容新增表/列,不建议执行破坏性 down migration。
- 若必须恢复数据,使用 `/data/backups/mysql/20260624T055810Z`,先恢复到隔离库验证后再进入维护窗口处理。
## 7. 遗留风险
- 真实 80 万手机号段尚未导入;号码库相关性能、Redis 快照体量和导入失败明细仍需单独验收。
- 浏览器登录态逐页按钮未做完整人工复测;本次只完成 API、构建、单元/集成测试和端到端小样本呼叫验证。
- OpenSIPS dialog 在测试呼叫结束后仍可能短期残留,当前通过重启清空。后续应单独分析 dialog 生命周期和 `dlg_end_dlg Operation failed` 的根因。
- 当前仍为本地 KVM/Tailscale 环境,开发 CA、Tailscale IP、本地根盘布局不能原样用于阿里云生产。
## 8. 结论
`s42-phase2-business-prefix-20260624140000` 可冻结为二期业务前缀小样本联调基线。后续需求或 Bug 修复应创建新的 release,不再覆盖该冻结点;阿里云迁移前仍需重新执行 S30 Runbook、备份恢复演练、正式网络/TLS 验收和灰度观察。