Files
lisglosips/IMPLEMENTATION_STATUS.md
T

1474 lines
288 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# LisgloSIPS V2 实施状态
> 本文件用于跨 Codex 会话持续交接。每次新会话开始必须先读取本文件和 `SOFTSWITCH_PLATFORM_DESIGN_V2.md`;每次任务完成、失败或被阻塞后必须更新本文件。
## 当前状态
### 2026-09-01 主叫实时分析专项整改(已完成)
- 用户已授权执行专项核对文档中的全部整改,包括代码、数据库迁移、B机发布和受控真实呼叫。
- 已完成:ACK起算的应答证据、显式CLOSE生命周期、生产/采集计数核对、持久缺口与死信、独立Redis、保留维护、查询快照/稳定分页、供应商筛选、告警范围和降级暂停、页面证据字段。
- 本地验证:ESLint、TypeScript构建、Prisma schema、Web生产构建通过;Vitest 29个文件/118项通过;B机OpenSIPS候选配置检查通过。
- B机已发布提交`9fa1964dec98cbe3c53c901ba764373308054d5e`,发布目录`/opt/lisglosips/releases/caller-analytics-20260901T024406Z`。OpenSIPS、API、CDR Worker、分析Worker、独立Redis及维护timer均为active/enabledAPI ready为ok。
- 真实验收:6通受控SIP得到T=6/C=4/A=2/N=2,两个成功场景完成SDP协商并经过RTPEngine300ms短通话计A;真实API、MySQL状态/分钟桶、重放幂等、RBAC与页面桌面/移动端均通过。Redis重启恢复、毒事件死信、覆盖降级/恢复、维护与轮转检查通过。证据见`docs/CALLER_REALTIME_ANALYTICS_DELIVERY_20260901.md``tests/reports/caller-analytics-real-20260901.json`
- 收口:受控测试入口/出口均已禁用,临时UI会话已清零。没有修改计费、余额扣减、路由选择或旧固定6秒CDR逻辑;工作区独立的代码质量审查文件未纳入本次提交。
- 限制:未取得约定峰值CPS、号码基数和保留期,故不宣称容量上限或统计P95;Git远端HTTP认证失败,B机已部署版本与本地提交存在,但本轮提交未能推送到origin。
### 2026-08-31 主叫实时分析设计核对与整改方案(仅审查文档)
- 产物:[主叫实时分析设计核对与整改方案](docs/CALLER_REALTIME_ANALYTICS_REVIEW_AND_REMEDIATION_20260831.md),初始基线 `116e316eca5b564f33ec3558dbd00201da173ed8`;已补核对并行提交 `d786165` 及当时可见的存储/测试候选改动,已出现修复与仍待验收项分别标注。
- 结论:保留V1.1业务口径;列出13组整改项(10组P1、3组P2),重点为时长证据、未知状态、乱序校正、完整性/留存、告警与真实验收。整改尚未实施,不代表线上已修复。
- 本轮验证:现有专项Vitest 2文件/16项通过;纯计算复现确认缺过程END误判失败、最终零时长校正受事件到达顺序影响等边界。本审查任务没有连接线上服务、执行迁移/部署、真实呼叫或Git提交推送;没有改动原设计和其他任务文件,其他任务的并行变化均保留。
- 后续按方案W0~W4推进;下方“测试发布验证中”为既有实施记录,本轮未重新核实其现场状态。
### 2026-08-31 主叫号码实时分析实施(测试发布验证中)
- 新增独立分析进程、MySQL事件/状态/分钟汇总/权限范围/告警表、真实API和主叫分析页面,计费Worker不变。
- 新采集使用OpenSIPS进展及结束观察钩子、本机rsyslog持久队列/日志、Redis Stream,再由独立进程事务去重及聚合。
- 本地校验:TypeScript、ESLint、Web构建通过;全量Vitest 29文件/114测试通过;Prisma schema通过;B候选OpenSIPS配置校验通过。测试部署/真实呼叫结果见后续发布记录,不能据此认定已上线。
- 现场发现旧成功CDR固定duration_sec=6,不能作新应答统计权威来源。保留旧计费不变,新功能仅信任独立实际时间证据。T节点SSH暂不可达,真实SIP测试改为B本机独立零费率入口/落地,不拨外部号码。
- Git中代码审查报告属于并行任务,不纳入本次提交。
### 2026-08-31 文档增补:主叫号码接通率与应答率实时分析
- 本轮任务:方案落文档并按用户要求修订接通/应答业务口径;文档已完成,功能实现待开始。
- 产物:[专项设计V1.1](docs/CALLER_REALTIME_ANALYTICS_DESIGN.md),主设计文档索引及测试计划CRA-001至CRA-024验收索引。
- 口径:180/183或后续阶段计接通;正实际通话时长计应答;总体应答率=A/T,已接通应答率=A/C。待接通与未知单列说明,不等同最终失败。
- 验证:本地源码及RFC 3261只读核对;文档公式、场景、链接和Git差异检查。24项功能用例仅为验收设计,均未执行。
- 变更边界:仅Markdown文档;无代码、迁移、生产变更、真实外呼或Git提交/推送。下方原有发布状态保留为历史记录,未在本轮重新验证。
- 遗留/下一步:实施前核验生产呼叫过程、实际通话时长来源与精度、重试机制及容量,按专项方案实施并真实联调;历史缺少180/183的数据不得伪造完整接通统计。
- 当前任务:`S56 - 全部当前服务收敛到 Server B`
- 本次变更摘要:确认 B 已同时运行 OpenSIPS、RTPEngine、录音守护进程、Web/API/Worker、MySQL、Redis 与监控;将 Recording Worker 从 B 到 A 的 SSH 拉取改为 B 本机 ready 目录读取,安装本机 finalizer timer,并通过 systemd BindPaths/附加组暴露最小目录权限。当前 release 为 `s56-b-consolidation-20260827T095812Z`,提交为 `309bffc7ffa70a760bce21ae59cb6c457c9c57fe`
- 总体状态:S30 已完成;本地 KVM 开发环境 V2 闭环原冻结 release 为 `s28-v2-20260621220924`B 二期冻结基线为 `s42-phase2-business-prefix-20260624140000`,当前 release 为 `s53-brand-logo-sip-text-20260630105500`;稳定版名称 `20260630release`Git tag `20260630release` 和分支 `stable/20260630release` 均指向稳定代码提交 `9920575bbae7fbef4929843632f94d687ffbe289`。2026-06-30 开机后已恢复 A/B/T 服务:A `opensips``rtpengine-daemon``rtpengine-recording-daemon``lisglosips-redis-auth-proxy``lisglosips-node-exporter``lisglosips-recording-finalize.timer` active`lisglosips-redis-hotpath-load.service` Result=successB `mysql``redis-server``nginx``lisglosips@api``lisglosips@cdr-worker``lisglosips@recording-worker``lisglosips@config-publisher``heplify-server``lisglosips-prometheus``grafana-server` activepreflight okAPI ready ok,封版 MySQL 备份 `/data/backups/mysql/20260630T061723Z`Redis 备份 `/data/backups/redis/20260630T061723Z`T `opensips``rtpengine-daemon``rtpengine-recording-daemon``lisglosips-s28-uas``apache2``mariadb/mysql` active。B preflight、远程 smoke、8.2/8.7/8.8 定向复测均通过;业务前缀、号码库、客户网关编辑与前缀选择 Web-only 修复后,s53 当前首页引用 Web JS `index-CrjpbrU-.js`、CSS `index-CrJGI5_R.css`,并包含 `/favicon.ico``/brand/logo1.png``/brand/logo2.png`;封版远程 smoke `REMOTE_SMOKE_20260630T061636Z.md` 通过;完整按钮级远程浏览器巡检 `REMOTE_WEB_UI_SMOKE_20260629T104040Z.md` 通过,覆盖 15 个核心菜单及安全主操作,客户网关管理编辑报告包含 `business-prefix-checkbox-toggle-ok: false->true->false`;封版报告为 `docs/20260630_RELEASE_BASELINE_REPORT.md`;当前仍未导入真实 80 万手机号段;阿里云迁移前需按 S30 Runbook 重新演练
- 最后更新:2026-08-27 18:16 +08:00
- 当前阻塞:无发布阻塞。尚未发起新的真实收费/外呼来生成生产录音,因此真实 `incoming -> ready -> /data/recordings -> recordings READY -> 鉴权播放` 仍需在下一次受控呼叫时补验;本次未修改 TLS、数据库结构或 A/T 配置。
- 当前环境:A/B/T 为本地 KVM 开发服务器,通过 Tailscale 联调;开发完成后再迁移阿里云
- 私密访问资料:`.codex-private/SERVER_ACCESS.md`,只在实际连接时读取,禁止回显
## 服务器索引
| 代号 | 地址 | 用户 | 用途 |
| --- | --- | --- | --- |
| A | `100.90.90.90` | `hector` | 历史节点/回滚参考;S56 起不参与当前生产服务或录音传输 |
| B | `100.90.90.91` | `hector` | OpenSIPS、RTPEngine、录音、Nginx、API、Worker、Redis、MySQL、HOMER、Prometheus/Grafana |
| T | `100.93.185.30` | `hector` | 已安装 OpenSIPS,模拟客户 SIP 注册与呼叫 |
## 任务状态表
状态只能使用:`待开始``进行中``已完成``已阻塞``已跳过`
| ID | 任务 | 状态 | 主要产物/验收 |
| --- | --- | --- | --- |
| S00 | SSH 安全接入与凭据迁移 | 已完成 | 三机 SSH Key、密码轮换、访问验证、访问 Runbook;后续 SSH 加固只调整管理端口并保留 Key 登录 |
| S01 | 三机只读资产盘点 | 已完成 | `docs/inventory-A.md``inventory-B.md``inventory-T.md`、汇总报告 |
| S02 | 网络与安全组验收 | 已完成 | `docs/infra-check.md`,含本地端口矩阵和未来阿里云安全组清单 |
| S03 | Server B 基础系统初始化 | 已完成 | `docs/SERVER_B_BASELINE_RUNBOOK.md``infra/server-b/s03/`、远端备份 |
| S04 | Server B MySQL 与 Redis | 已完成 | `docs/SERVER_B_DATA_SERVICES_RUNBOOK.md``infra/server-b/s04/`、备份恢复演练 |
| S05 | Server B Node.js、Nginx 与 TLS 基线 | 已完成 | `docs/SERVER_B_WEB_RUNTIME_RUNBOOK.md``infra/server-b/s05/`、HTTPS 与重启验收 |
| S06 | Server B HOMER 与基础监控 | 已完成 | HEP 接收、HOMER API、Prometheus、Grafana、exporters、A Node Exporter |
| S07 | V2 后端 Monorepo 骨架 | 已完成 | NestJS/Fastify/Prisma、日志、配置、健康检查、测试框架;本地和 B 均可构建 |
| S08 | 数据库 Schema 与迁移 | 已完成 | Prisma Schema、初始化迁移、内置角色/权限种子、B 临时空库迁移和备份恢复测试 |
| S09 | 登录认证基础 | 已完成 | 登录、刷新、退出、Argon2id、Session/Token 安全 |
| S10 | 用户、角色、权限与审计 | 已完成 | RBAC、内置角色保护、重置密码、审计拦截器 |
| S11 | 客户管理 API | 已完成 | 客户 CRUD、启停、余额/授信读取、单元与集成测试 |
| S12 | 充值与不可变余额流水 | 已完成 | 客户/供应商充值、幂等、事务、审计、并发测试 |
| S13 | 客户网关 API | 已完成 | IP/SIP 注册认证配置、启停、密码安全存储 |
| S14 | 客户网关策略与 Redis 配置发布 | 已完成 | 主被叫匹配、优先级、Outbox、配置版本和回滚 |
| S15 | 供应商管理 API | 已完成 | 供应商 CRUD、余额/授信、启停 |
| S16 | 落地网关 API | 已完成 | CPS/并发/时段/编码/号码转换/周期费率/启停 |
| S17 | 落地线路组 API | 已完成 | 成员增删、优先级重排、并发汇总、完整性校验 |
| S18 | Server A OpenSIPS 基线 | 已完成 | 15060、认证、防扫描、MI 本地绑定、Redis/HEP 模块 |
| S19 | Server A RTPEngine 与录音 tmpfs | 已完成 | 内核转发、RTP 端口段、3 GiB tmpfs、录音 ready 规则 |
| S20 | Server A Redis 热路径与 HEP/指标 | 已完成 | Lua 原子检查、配置读取、HEP 9060、Node Exporter |
| S21 | Server T 客户模拟配置 | 已完成 | IP/SIP 注册两套测试账号、呼叫脚本、失败场景脚本 |
| S22 | CDR Redis Stream | 已完成 | 标准事件、XADD/XREADGROUP、ACK、Pending、死信、幂等 |
| S23 | 最小计费与余额扣减 | 已完成 | Decimal 计费、客户费用/成本、重复 CDR 不重复扣费 |
| S24 | 录音搬运与播放 | 已完成 | A->B 私网搬运、校验、Range、权限、源文件安全删除 |
| S25 | 质检后端 | 已完成 | 抽检规则、稳定抽样、录音详情、质检保存和审计 |
| S26 | Dashboard 聚合 | 已完成 | 实时占位/聚合指标、趋势、失败码、异常网关、待质检 |
| S27 | React Demo 接入真实 API | 已完成 | API Client、核心运营页真实 API、错误/加载/空状态 |
| S28 | 三机端到端联调 | 已完成 | T 发起呼叫,A 转发录音,B 出 CDR/费用/信令/质检 |
| S29 | 性能、故障与安全测试 | 已完成 | CPS/并发/录音、Redis/MySQL/Worker 故障、越权与重放 |
| S30 | 备份、Runbook、灰度与上线 | 已完成 | 恢复演练、上线/回滚脚本、灰度记录、验收报告 |
## 每次新会话的固定开场指令
可直接向 Codex 发送:
```text
请先读取 SOFTSWITCH_PLATFORM_DESIGN_V2.md、IMPLEMENTATION_STATUS.md
以及本任务关联的现有脚本/代码。只执行状态文件里的当前任务,不提前执行后续任务。
开始前复核上次交接记录;涉及服务器写操作、服务重启、防火墙、密码、数据库迁移或数据删除时先说明影响和回滚点。
完成后运行验收检查,并更新 IMPLEMENTATION_STATUS.md 的任务状态、产物、验证结果、遗留问题和下一任务。
```
## 单任务交接记录模板
每完成一个任务,在本节顶部新增记录:
```markdown
### YYYY-MM-DD HH:mm - Sxx 任务名称
- 状态:已完成 / 已阻塞
- 操作服务器:A / B / T / 本地
- 完成内容:
- 修改文件:
- 安装软件及固定版本:
- 执行的关键命令:只记录无密码版本
- 验证结果:
- 回滚方式:
- 未解决问题:
- 下一任务:Sxx
```
## 交接记录
### 2026-08-27 15:50 - UI 性能整改提交、推送并发布 B s55
- 状态:已完成,存在一项与本次代码无关的环境告警
- 操作服务器:B、本地;未修改 A/T、TLS、数据库 schema、Redis 配置或 SIP 路由
- 完成内容:提交并推送 `ecb567c0dacd749607808fbed09648267757be6f`B 从精确 Git bundle 在 Linux 上构建并生成固定 artifact,发布为 `/opt/lisglosips/releases/s55-ui-performance-20260827153500`,由 s54 原子切换到 s55。Dashboard 使用 overview 聚合接口,话单默认 25 条并行查询,页面统一 2 秒加载弹窗和 10 秒请求超时。
- 发布证据:artifact SHA256 `acc49bb21506448527732e965c345a5e71e83090781be76df750a93cffccaece`19,681 个文件;MySQL/Redis 备份均为 `/data/backups/*/20260827T073857Z` 且 SHA 校验通过;B preflight、API ready、Nginx 配置与外部 smoke 通过,新首页引用 `index-DcpB-OOG.js``index-u-EPfKoT.css`
- 性能回读:真实管理员认证后,`/dashboard/overview` 外部耗时 1,843ms、24 个趋势桶;`/cdrs?take=25` 外部耗时 2,357ms,返回 25/97 条,较整改前约 5.5 秒下降,但仍会按设计在 2 秒后显示加载弹窗。
- 验证结果:本地 ESLint、TypeScript、Prisma validate、95 条 Vitest、Web build 通过;B Linux workspace build、release artifact、服务 preflight、匿名 smoke、真实认证 Dashboard/CDR 只读检查通过。完整 Playwright UI 巡检因本机缺少 Chromium 二进制未执行,未临时安装软件。
- 回滚方式:`sudo /opt/lisglosips/current/infra/server-b/s30/lisglosips-release-rollback.sh /opt/lisglosips/releases/s54-cmpp-ui-style-20260702103305`;发布脚本自身带失败自动回切,本次未触发。
- 环境告警:B 到 A `100.90.90.90` 的 Tailscale ping 和录音专用 SSH 均超时,导致 recording-worker 约每 135 秒由 systemd 自动重启;本次未改 worker 源码,B 其他服务均 active。需 A 恢复在线后复核录音 Worker,不应通过回滚 s55 掩盖 A 不可达问题。
### 2026-06-30 14:20 - `20260630release` 稳定版封版
- 状态:已完成
- 操作服务器:本地、A、B、T;未改业务代码,未发布新 release,未改数据库 schema
- 完成内容:将稳定代码提交 `9920575bbae7fbef4929843632f94d687ffbe289` 打 tag `20260630release`,创建分支 `stable/20260630release`;触发 B `lisglosips-backup.service` 生成封版 MySQL/Redis 备份;采集 A/B/T 服务状态、OpenSIPS 配置校验和关键 SHA;生成封版报告 `docs/20260630_RELEASE_BASELINE_REPORT.md`
- 修改文件:`docs/20260630_RELEASE_BASELINE_REPORT.md``IMPLEMENTATION_STATUS.md`、新增 smoke 报告 `tests/reports/REMOTE_SMOKE_20260630T061636Z.md`
- 验证结果:B current 为 `/opt/lisglosips/releases/s53-brand-logo-sip-text-20260630105500`B backup result `success`MySQL 备份 `/data/backups/mysql/20260630T061723Z`Redis 备份 `/data/backups/redis/20260630T061723Z`B preflight ok;远程 smoke `REMOTE_SMOKE_20260630T061636Z.md` 通过;A OpenSIPS 配置 SHA256 `f163fa37f7aaa492e12ed496cb82f07d9b0f1030a6ed297855a22d2c4bf1c8a5`,热路径 Lua SHA1 `aff9995dbda2a2425e95a3e25b5d7ce194348ce7`,热路径加载 `success`T OpenSIPS 配置 SHA256 `5255dd540d33c3a7bf3db278fe6fe39540b176eb79e7fe12a7a26e5217c263da`A/T `fwupd-refresh.service` failed 为既有无关遗留。
- 回滚方式:代码回滚可执行 `git checkout 20260630release``git switch stable/20260630release`B 应用回滚可将 `/opt/lisglosips/current` 指回 `/opt/lisglosips/releases/s53-brand-logo-sip-text-20260630105500` 并重启 API/Worker、reload Nginx;数据恢复使用封版备份前必须先在隔离环境验证。
- 未解决问题:真实 80 万手机号段尚未导入;8.9/8.10 既有遗留问题未处理;A/T `fwupd-refresh.service` failed 未处理。
### 2026-06-30 11:12 - Web-only s53 菜单展开 Logo 增加 SIP
- 状态:已完成
- 操作服务器:本地、B;未改数据库 schema,未重启 API/Worker,未改 A/T
- 完成内容:菜单展开状态下将品牌位调整为 `logo1 + SIP`,菜单收起状态仍只显示 `logo2`;保持 favicon 与 logo 资源路径不变。
- 修改文件:`apps/web/src/App.jsx``apps/web/src/styles.css``IMPLEMENTATION_STATUS.md`
- 发布情况:本地构建生成 Web 资源 `index-CrjpbrU-.js` / `index-CrJGI5_R.css`;上传 Web tar `s53-brand-logo-sip-text-20260630105500-web.tar.gz`SHA256 `fd2cd44a1828de9ae71bf29f73171091d8690a2b293edee87eb79b78eb3db07a`B 从 s52 复制生成 `/opt/lisglosips/releases/s53-brand-logo-sip-text-20260630105500`,只替换 `public``apps/web/dist` 静态资源,切换 `/opt/lisglosips/current` 到 s53 并 reload Nginx。
- 验证结果:本地 `corepack pnpm@10.33.0 lint` 通过;`corepack pnpm@10.33.0 build` 通过;B `nginx -t` 通过并已 reloadB current 确认为 `/opt/lisglosips/releases/s53-brand-logo-sip-text-20260630105500`;远程 smoke `REMOTE_SMOKE_20260630T031146Z.md` 通过。
- 回滚方式:应用回滚可将 `/opt/lisglosips/current` 指回 `/opt/lisglosips/releases/s52-brand-logo-assets-20260630102000` 并 reload Nginx;本次不涉及数据库迁移或服务状态变更,无数据库回滚。
- 未解决问题:未做完整按钮级浏览器巡检。
### 2026-06-30 10:45 - Web-only s52 接入菜单 Logo 与 favicon
- 状态:已完成
- 操作服务器:本地、B;未改数据库 schema,未重启 API/Worker,未改 A/T
- 完成内容:将根目录 `logo/logo1.png``logo/logo2.png``logo/fav.ico` 复制到 Web public 资源目录;菜单展开时使用 `/brand/logo1.png`,菜单收起时使用 `/brand/logo2.png`,浏览器标签使用 `/favicon.ico`;调整侧边栏品牌区 CSS,使展开/收起 logo 尺寸稳定,移动端保持展开 logo。
- 修改文件:`apps/web/index.html``apps/web/src/App.jsx``apps/web/src/styles.css``apps/web/public/brand/logo1.png``apps/web/public/brand/logo2.png``apps/web/public/favicon.ico``IMPLEMENTATION_STATUS.md`
- 发布情况:本地构建生成 Web 资源 `index-CMgxUenC.js` / `index-DZaQiQoS.css`dist 包含 `favicon.ico``brand/logo1.png``brand/logo2.png`;上传 Web tar `s52-brand-logo-assets-20260630102000-web.tar.gz`SHA256 `cc1fdb2aa29550b6634a4189000507a8b62d5b33bd13bd4d0c715dd3acff3f58`B 从 s51 复制生成 `/opt/lisglosips/releases/s52-brand-logo-assets-20260630102000`,只替换 `public``apps/web/dist` 静态资源,切换 `/opt/lisglosips/current` 到 s52 并 reload Nginx。
- 验证结果:本地 `corepack pnpm@10.33.0 lint` 通过;`corepack pnpm@10.33.0 build` 通过;B `nginx -t` 通过并已 reload;远程 smoke `REMOTE_SMOKE_20260630T024412Z.md` 通过;`https://100.90.90.91/favicon.ico``/brand/logo1.png``/brand/logo2.png` 均返回 200。
- 回滚方式:应用回滚可将 `/opt/lisglosips/current` 指回 `/opt/lisglosips/releases/s51-customer-gateway-list-prefix-ui-20260629184000` 并 reload Nginx;本次不涉及数据库迁移或服务状态变更,无数据库回滚。
- 未解决问题:未做完整按钮级浏览器巡检;未提交 Git。
### 2026-06-30 10:03 - 移除 B SSH/HTTPS 来源 IP 白名单限制
- 状态:已完成
- 操作服务器:B;未改数据库 schema,未发布新 release,未改 SSH Key 登录策略
- 问题分类:B 主机 nftables 来源白名单限制。B `lisglosips_filter` 原规则只允许 `admin_ipv4={100.91.249.119,100.98.167.119}``tailscale0` 访问 TCP 22/443,导致 `100.107.171.69` 无法访问 `https://100.90.90.91/`
- 完成内容:备份 `/etc/nftables.d/lisglosips.nft`,删除 `admin_ipv4` 集合;将 SSH/HTTPS 规则改为 `tcp dport { 22, 443 } counter accept comment "SSH and HTTPS"`,即不再按来源 IP 限制 SSH/HTTPS;保留 A 到 B Redis/HEP 的来源限制规则不变。
- 修改文件:B `/etc/nftables.d/lisglosips.nft``IMPLEMENTATION_STATUS.md`
- 执行的关键命令:B `cp -a /etc/nftables.d/lisglosips.nft /var/backups/lisglosips-firewall/20260630T100129/lisglosips.nft`、修改 nftables 配置、`nft -c -f /etc/nftables.conf``nft -f /etc/nftables.conf``nft list table inet lisglosips_filter`
- 验证结果:B 生效规则已显示 `tcp dport { 22, 443 } counter accept comment "SSH and HTTPS"`,无 `ip saddr @admin_ipv4` 条件;本机到 `100.90.90.91:443``100.90.90.91:22``TcpTestSucceeded=True`B 本机 `curl -k -I https://127.0.0.1/` 返回 HTTP 200`nginx``ssh` 均 active。A/T 未发现同类 SSH/HTTPS 来源白名单限制。
- 回滚方式:B 执行 `sudo cp -a /var/backups/lisglosips-firewall/20260630T100129/lisglosips.nft /etc/nftables.d/lisglosips.nft && sudo nft -c -f /etc/nftables.conf && sudo nft -f /etc/nftables.conf`,即可恢复原来源白名单。
- 未解决问题:尚未从 `100.107.171.69` 客户端侧复测浏览器访问;未提交 Git。
### 2026-06-30 09:44 - A/B/T 写入开机后服务恢复脚本
- 状态:已完成
- 操作服务器:A、B、T;未改数据库 schema,未发布新 release,未改防火墙或 SSH 配置
- 完成内容:在仓库新增三台机器的同源启动脚本,并分别安装到各自服务器 `/usr/local/sbin/lisglosips-start-services.sh`,属主 `root:root`、权限 `0755`。脚本会启动对应机器的 LisgloSIPS 运行服务并执行基础健康检查;可在机器本地手动执行,也可通过 SSH 远程执行。
- 修改文件:`infra/ops/startup/lisglosips-start-a.sh``infra/ops/startup/lisglosips-start-b.sh``infra/ops/startup/lisglosips-start-t.sh``IMPLEMENTATION_STATUS.md`
- 执行的关键命令:本地 `scp` 上传脚本;三机 `sudo install -o root -g root -m 0755 /tmp/lisglosips-start-services.sh /usr/local/sbin/lisglosips-start-services.sh``bash -n /usr/local/sbin/lisglosips-start-services.sh``sudo /usr/local/sbin/lisglosips-start-services.sh`
- 验证结果:A 脚本执行后 `opensips``rtpengine-daemon``rtpengine-recording-daemon``lisglosips-redis-auth-proxy``lisglosips-node-exporter``lisglosips-recording-finalize.timer` active,热路径 `Result=success`failed units 为 0B 脚本执行后 current 为 `/opt/lisglosips/releases/s51-customer-gateway-list-prefix-ui-20260629184000`MySQL/Redis/Nginx/API/CDR Worker/Recording Worker/Config Publisher/HEP/Prometheus/Grafana activeAPI ready okpreflight okfailed units 为 0T 脚本执行后 `opensips``rtpengine-daemon``rtpengine-recording-daemon``lisglosips-s28-uas``apache2``mariadb` activefailed units 为 0。
- 回滚方式:如需撤回脚本,可在对应机器执行 `sudo rm -f /usr/local/sbin/lisglosips-start-services.sh`;运行态停止方式同 09:31 记录。仓库新增脚本可按 Git 回退。
- 未解决问题:脚本是手动恢复脚本,不是开机自启动 unit;未做 A/B/T 实呼端到端验收;未提交 Git。
### 2026-06-30 09:31 - A/B/T 测试服务器开机后服务恢复
- 状态:已完成
- 操作服务器:A、B、T;未改数据库 schema,未发布新 release,未改防火墙或 SSH 配置
- 完成内容:读取三机当前 systemd 状态后恢复运行服务;A 主服务已 active,主动执行 `lisglosips-redis-hotpath-load.service` 确认热路径 Lua 载入成功;B 开机后 `lisglosips@recording-worker``lisglosips@config-publisher` 为 inactive,已启动并确认 activeT 主服务均已 active,执行 start/reset-failed 保持模拟环境可用。
- 修改文件:`IMPLEMENTATION_STATUS.md`
- 执行的关键命令:A `systemctl start lisglosips-redis-hotpath-load.service opensips rtpengine-daemon rtpengine-recording-daemon lisglosips-redis-auth-proxy lisglosips-node-exporter lisglosips-recording-finalize.timer``opensips -C -f /etc/opensips/opensips.cfg`B `systemctl start mysql redis-server nginx heplify-server lisglosips-prometheus grafana-server lisglosips@api lisglosips@cdr-worker lisglosips@recording-worker lisglosips@config-publisher``nginx -t``lisglosips-release-preflight.sh`T `systemctl reset-failed opensips``systemctl start mariadb apache2 rtpengine-daemon rtpengine-recording-daemon opensips lisglosips-s28-uas``opensips -C -f /etc/opensips/opensips.cfg`
- 验证结果:A `opensips``rtpengine-daemon``rtpengine-recording-daemon``lisglosips-redis-auth-proxy``lisglosips-node-exporter``lisglosips-recording-finalize.timer` 均 active`lisglosips-redis-hotpath-load.service` Result=success`systemctl --failed` 为 0B current 为 `/opt/lisglosips/releases/s51-customer-gateway-list-prefix-ui-20260629184000`MySQL/Redis/Nginx/API/CDR Worker/Recording Worker/Config Publisher/HEP/Prometheus/Grafana 均 activeAPI ready 返回 okpreflight ok,最近备份目录 `/data/backups/mysql/20260630T012836Z``/data/backups/redis/20260630T012836Z`,远程 smoke `REMOTE_SMOKE_20260630T012830Z.md` 通过;T `opensips``rtpengine-daemon``rtpengine-recording-daemon``lisglosips-s28-uas``apache2``mariadb/mysql` active`systemctl --failed` 为 0。
- 回滚方式:本次仅恢复运行态服务。若需停止,可分别执行 A `sudo systemctl stop opensips rtpengine-recording-daemon rtpengine-daemon lisglosips-redis-auth-proxy`B `sudo systemctl stop lisglosips@api lisglosips@cdr-worker lisglosips@recording-worker lisglosips@config-publisher`T `sudo systemctl stop opensips lisglosips-s28-uas rtpengine-recording-daemon rtpengine-daemon`;应用 release 未变更,仍可按 S30 Runbook 回滚 B current。
- 未解决问题:未做 A/B/T 实呼端到端验收;真实 80 万手机号段尚未导入;8.9/8.10 既有遗留问题未处理;未提交 Git。
### 2026-06-29 18:43 - Web-only s51 优化客户网关列表与前缀选择交互
- 状态:已完成
- 操作服务器:本地、B;未改数据库,未重启 API/Worker,未改 A/T
- 问题分类:真前端交互 bug 与界面优化。客户网关编辑弹窗中前缀 Checkbox 的 `onChange` 传入原生 event,页面将 event 当作布尔值,导致取消勾选无效;列表展示了用户要求隐藏的认证方式与认证目标列,名称/客户列偏窄。
- 完成内容:客户网关列表隐藏“认证方式”和“IP地址 / 账号名称”;“名称”列宽调整为 240px,“客户”列宽调整为 220px;公共 `Checkbox``Radio``Switch``onChange` 统一传 `(checked, event)`,修复取消勾选无效并保持旧调用方兼容;客户网关业务前缀选择区增加已选计数、全选、清空、空状态和更清晰的网格样式;远程浏览器巡检增加客户网关编辑前缀 checkbox 往返切换断言,并将登录页等待从 `networkidle` 改为更稳定的表单/已登录页面并行等待。
- 修改文件:`apps/web/src/components/ui.jsx``apps/web/src/pages/CustomerGatewaysPage.jsx``apps/web/src/styles.css``tests/web/remote-web-ui-smoke.mjs``IMPLEMENTATION_STATUS.md`
- 发布情况:本地构建生成 Web 资源 `index-C_61kpb7.js` / `index-DWBaYpdZ.css`;上传 Web tar `s51-customer-gateway-list-prefix-ui-20260629184000-web.tar.gz`SHA256 `cb11ad80b84ded7a5605ddedf24058dba854f5fa41de845b972f0643fadfbd0c`B 从 s50 复制生成 `/opt/lisglosips/releases/s51-customer-gateway-list-prefix-ui-20260629184000`,只替换 `public``apps/web/dist` 静态资源,切换 `/opt/lisglosips/current` 到 s51 并 reload Nginx。
- 验证结果:本地 `corepack pnpm@10.33.0 lint` 通过;`corepack pnpm@10.33.0 typecheck` 通过;`corepack pnpm@10.33.0 test` 通过,26 个测试文件 93 条测试通过;`corepack pnpm@10.33.0 build` 通过;`node --check tests/web/remote-web-ui-smoke.mjs` 通过。B `nginx -t` 通过并已 reload;远程 smoke `REMOTE_SMOKE_20260629T103420Z.md` 通过;真实 Chrome 完整按钮级巡检 `REMOTE_WEB_UI_SMOKE_20260629T104040Z.md` 通过,客户网关管理编辑动作验证 `business-prefix-checkbox-toggle-ok: false->true->false`,无 console error/pageerror/空白页/API 数据不可用。
- 回滚方式:应用回滚可将 `/opt/lisglosips/current` 指回 `/opt/lisglosips/releases/s50-web-checkbox-action-smoke-20260629182000` 并 reload Nginx;本次不涉及数据库迁移或服务状态变更,无数据库回滚。
- 未解决问题:充值记录、话单中心在当前测试数据下没有可见启用的安全动作按钮,脚本按规则记录为 skipped;未提交 Git。
### 2026-06-29 18:22 - Web-only s50 修复 Checkbox 子节点透传并完成按钮级浏览器巡检
- 状态:已完成
- 操作服务器:本地、B;未改数据库,未重启 API/Worker,未改 A/T
- 问题分类:真前端 bug。s49 只处理了公共 `Input`,但客户网关编辑弹窗里的业务前缀多选使用 `<Checkbox>前缀 / 名称</Checkbox>`,公共 `Checkbox` 未接收 `children`,导致 `children` 继续透传到原生 `<input type="checkbox">` 并触发 React #137
- 完成内容:修复 `apps/web/src/components/ui.jsx``Checkbox`,支持 `label``children` 作为显示文本且不再把 `children` 传给原生 input;同步防护 `Radio``Switch``Slider`,避免同类组件出现相同问题;巡检脚本增加逐页 `CHECK/ACTION` 输出和 `LISGLOSIPS_WEB_UI_STEP_TIMEOUT_MS` 单步超时,避免完整按钮巡检长时间静默。
- 修改文件:`apps/web/src/components/ui.jsx``tests/web/remote-web-ui-smoke.mjs``IMPLEMENTATION_STATUS.md`
- 发布情况:本地构建生成 Web 资源 `index-CI4aNWXC.js` / `index-B2uYBtS3.css`;上传 Web tar `s50-web-checkbox-action-smoke-20260629182000-web.tar.gz`SHA256 `18492ce4860aca32808b0cf871035e6255a4a3924d0c20f9647ffca1eaf20aa5`B 从 s49 复制生成 `/opt/lisglosips/releases/s50-web-checkbox-action-smoke-20260629182000`,只替换 `public``apps/web/dist` 静态资源,切换 `/opt/lisglosips/current` 到 s50 并 reload Nginx。
- 验证结果:本地 `corepack pnpm@10.33.0 lint` 通过;`corepack pnpm@10.33.0 typecheck` 通过;`corepack pnpm@10.33.0 test` 通过,26 个测试文件 93 条测试通过;`corepack pnpm@10.33.0 build` 通过;`node --check tests/web/remote-web-ui-smoke.mjs` 通过。B `nginx -t` 通过并已 reload;远程 smoke `REMOTE_SMOKE_20260629T101122Z.md` 通过;真实 Chrome 完整按钮级巡检 `REMOTE_WEB_UI_SMOKE_20260629T101911Z.md` 通过,客户网关管理编辑、业务前缀编辑、号码库刷新等动作均无 console error/pageerror/空白页/API 数据不可用。
- 回滚方式:应用回滚可将 `/opt/lisglosips/current` 指回 `/opt/lisglosips/releases/s49-web-input-action-smoke-20260629175000` 并 reload Nginx;本次不涉及数据库迁移或服务状态变更,无数据库回滚。
- 未解决问题:客户管理、充值记录、话单中心在当前测试数据下没有可见启用的安全动作按钮,脚本按规则记录为 skipped;未提交 Git。
### 2026-06-29 17:50 - Web-only s49 修复客户网关编辑 React #137 并扩展按钮级 Web UI 巡检
- 状态:已完成;后续完整按钮级巡检发现修复不完整,根因已在 s50 修复
- 操作服务器:本地、B;未改数据库,未重启 API/Worker,未改 A/T
- 问题分类:真前端 bug。客户网关页面点击编辑时报 React #137,参数为 `input`,属于原生 `<input>` 收到 children 的运行时错误。为避免任意调用方把 `children` 透传到原生 input,已在公共 `Input` 组件中显式丢弃 `children`
- 完成内容:修复 `apps/web/src/components/ui.jsx``Input` 组件,避免 children 透传到 `<input>`;扩展 `tests/web/remote-web-ui-smoke.mjs`,每个核心菜单渲染后继续点击一个安全主操作(如编辑、查看详情、刷新),覆盖客户网关编辑弹窗、业务前缀编辑、号码库刷新等二级交互;更新 `docs/TEST_PLAN_AND_CASES.md` 的 WEB-005,明确按钮级交互也纳入 pageerror/console error 门禁。
- 修改文件:`apps/web/src/components/ui.jsx``tests/web/remote-web-ui-smoke.mjs``docs/TEST_PLAN_AND_CASES.md``IMPLEMENTATION_STATUS.md`
- 发布情况:本地构建生成 Web 资源 `index-D90saQl_.js` / `index-B2uYBtS3.css`;上传 Web tar `s49-web-input-action-smoke-20260629175000-web.tar.gz`SHA256 `9ee7ad3c99d90d0fe0bf09482a47c2b76aedfc298d9aa909397850c5ac317359`B 从 s48 复制生成 `/opt/lisglosips/releases/s49-web-input-action-smoke-20260629175000`,只替换 `public``apps/web/dist` 静态资源,切换 `/opt/lisglosips/current` 到 s49 并 reload Nginx。
- 验证结果:本地 `corepack pnpm@10.33.0 lint` 通过;`corepack pnpm@10.33.0 build` 通过;`node --check tests/web/remote-web-ui-smoke.mjs` 通过。B `nginx -t` 通过,HTTPS 首页已引用 `/assets/index-D90saQl_.js`;远程 smoke `REMOTE_SMOKE_20260629T094744Z.md` 通过。完整 `test:remote-web-ui` 仍需提供 `LISGLOSIPS_AUTH_PASSWORD` 后执行。
- 回滚方式:应用回滚可将 `/opt/lisglosips/current` 指回 `/opt/lisglosips/releases/s48-web-number-library-constants-20260629160000` 并 reload Nginx;本次不涉及数据库迁移或服务状态变更,无数据库回滚。
- 未解决问题:s49 只修复 `Input`,未覆盖 `Checkbox` 子节点透传;完整按钮级巡检在客户网关编辑处仍复现 React #137,已由 s50 修复;未提交 Git。
### 2026-06-29 17:39 - 补齐前端测试门禁:lint 覆盖 Web 源码与 Playwright 菜单巡检
- 状态:已完成本地测试体系修复;未发布服务器
- 操作服务器:本地;未改 B/A/T
- 问题分类:测试覆盖缺口。此前 `eslint.config.mjs` 忽略 `apps/web/src/**``remote-smoke` 只检查 HTML 壳和健康接口,未执行真实 React 路由/菜单渲染,因此 `normalizeBusinessPrefix``emptyNumberLibraryRows``toneForStatus` 这类前端未定义标识不能在发布前被自动发现。
- 完成内容:移除 ESLint 对 `apps/web/src/**` 的忽略,并为前端 JSX 增加 browser globals/JSX parser 配置;新增 `tests/web/remote-web-ui-smoke.mjs`,使用 Playwright 通过 API 验证码登录、注入 refresh cookie,打开真实 Web 页面并逐个点击核心菜单,捕获 `pageerror`、console error、空白页、回到登录页和 `API 数据不可用`;新增 `test:remote-web-ui` 脚本;更新 `tests/README.md``docs/TEST_PLAN_AND_CASES.md` 的 WEB-005/WEB-006,使真实浏览器菜单巡检成为 Web 发布验收项。
- 顺手修复/清理:前端 lint 首次覆盖后发现 `OperationLogsPage.jsx``toneForStatus` 未定义,已补齐;清理若干 Web demo 残留未使用 import/常量/函数,避免前端 lint 被历史死代码阻断;测试脚本依赖新增 `playwright@1.57.0`
- 修改文件:`eslint.config.mjs``package.json``pnpm-lock.yaml``tests/web/remote-web-ui-smoke.mjs``tests/README.md``docs/TEST_PLAN_AND_CASES.md``apps/web/src/pages/ActiveCallsPage.jsx``apps/web/src/pages/CustomerGatewaysPage.jsx``apps/web/src/pages/CustomersPage.jsx``apps/web/src/pages/OperationLogsPage.jsx``apps/web/src/pages/RoutesPage.jsx``apps/web/src/pages/SettingsPage.jsx``apps/web/src/pages/SipOpsPage.jsx``apps/web/src/pages/VendorLineGroupsPage.jsx``apps/web/src/pages/VendorsPage.jsx``apps/web/src/utils/formatters.js``IMPLEMENTATION_STATUS.md`
- 验证结果:`corepack pnpm@10.33.0 lint` 通过,确认前端源码已纳入 lint`corepack pnpm@10.33.0 build` 通过,生成 Web JS `index-gFC83nZO.js``corepack pnpm@10.33.0 typecheck` 通过;`corepack pnpm@10.33.0 test` 通过,26 个测试文件 93 条测试通过;`corepack pnpm@10.33.0 prisma:validate` 通过;`node --check tests/web/remote-web-ui-smoke.mjs` 通过。`corepack pnpm@10.33.0 test:remote-web-ui` 在未设置 `LISGLOSIPS_AUTH_PASSWORD` 时按设计退出并提示必须提供密码,未进行完整远程浏览器登录巡检。
- 回滚方式:回退上述测试配置、脚本、文档和前端 lint 清理文件即可恢复旧测试行为;本次未改数据库、未重启服务、未切换 B release。
- 未解决问题:需要在具备 Web 管理员密码环境变量的验收环境执行 `pnpm test:remote-web-ui` 形成正式远程报告;未提交 Git。
### 2026-06-29 16:10 - Web-only s48 修复号码库页面常量误放导致空白
- 状态:已完成
- 操作服务器:本地、B;未改数据库,未重启 API/Worker,未改 A/T
- 问题分类:真前端 bug。号码库页面 `NumberLibraryPage.jsx` 使用 `numberLibraryTabs``numberLibraryImportExamples``emptyNumberLibraryRows``emptyNumberLibraryTotals``normalizeNumberLibraryList`,但这些常量/函数误放在 `BusinessPrefixesPage.jsx` 文件末尾,导致点击号码库菜单时抛出 `ReferenceError: emptyNumberLibraryRows is not defined` 并出现空白页。这不是后端 API、浏览器登录态或权限问题。
- 完成内容:将号码库页面依赖的 tabs、导入示例、空数据初始值和列表归一化函数移回 `NumberLibraryPage.jsx`;从 `BusinessPrefixesPage.jsx` 删除误放的号码库代码,保留业务前缀自身的 `normalizeBusinessPrefix()`
- 修改文件:`apps/web/src/pages/NumberLibraryPage.jsx``apps/web/src/pages/BusinessPrefixesPage.jsx``IMPLEMENTATION_STATUS.md`
- 发布情况:本地构建生成 Web 资源 `index-wPzMqk8x.js` / `index-B2uYBtS3.css`;上传 Web tar `s48-web-number-library-constants-20260629160000-web.tar.gz`SHA256 `3e2b5e8cc160dc1d40bb5504a4f7960c903f7485c6b295407e0163986161bb1a`B 从 s47 复制生成 `/opt/lisglosips/releases/s48-web-number-library-constants-20260629160000`,只替换 `public``apps/web/dist` 静态资源,切换 `/opt/lisglosips/current` 到 s48 并 reload Nginx。
- 验证结果:本地 `corepack pnpm@10.33.0 lint` 通过;`corepack pnpm@10.33.0 build` 通过。B `nginx -t` 通过,HTTPS 首页已引用 `/assets/index-wPzMqk8x.js`;远程 smoke `REMOTE_SMOKE_20260629T080917Z.md` 通过;B 本机 API 复测 `/number-library/cities?take=1``/phone-segments?take=1``/area-codes?take=1``/carrier-prefix-rules?take=1` 均返回 200。
- 回滚方式:应用回滚可将 `/opt/lisglosips/current` 指回 `/opt/lisglosips/releases/s47-web-business-prefix-normalizer-20260629154500` 并 reload Nginx;本次不涉及数据库迁移或服务状态变更,无数据库回滚。
- 未解决问题:浏览器可能缓存旧 JS,若仍看到同样错误,需强制刷新或清理站点缓存后再测;真实 80 万手机号段尚未导入,号码库列表为空是当前已知数据状态;未提交 Git。
### 2026-06-29 15:55 - Web-only s47 修复业务前缀管理页面归一化函数缺失
- 状态:已完成
- 操作服务器:本地、B;未改数据库,未重启 API/Worker,未改 A/T
- 问题分类:真前端 bug。业务前缀管理页面 `loadPrefixes()` 调用 `normalizeBusinessPrefix`,但 `BusinessPrefixesPage.jsx` 未定义该函数,导致接口请求成功后在前端数据归一化阶段抛出 `ReferenceError`,页面误显示“API 数据不可用”。这不是后端 API、浏览器登录态或权限问题。
- 完成内容:在业务前缀页面补齐 `normalizeBusinessPrefix()`,并引入 `formatDate``zhStatus`,字段映射与现有页面保持一致:`prefix``name``description``priority`、中文状态、使用客户网关数、创建日期。
- 修改文件:`apps/web/src/pages/BusinessPrefixesPage.jsx``IMPLEMENTATION_STATUS.md`
- 发布情况:本地构建生成 Web 资源 `index-MxyRK-fA.js` / `index-B2uYBtS3.css`;上传 Web tar `s47-web-business-prefix-normalizer-20260629154500-web.tar.gz`SHA256 `d23a227e1836f333594d1ea8f8e3bd944b82ee57b23b610d3b03f3dd2670b328`B 从 s46 复制生成 `/opt/lisglosips/releases/s47-web-business-prefix-normalizer-20260629154500`,只替换 `public``apps/web/dist` 静态资源,切换 `/opt/lisglosips/current` 到 s47 并 reload Nginx。
- 验证结果:本地 `corepack pnpm@10.33.0 lint` 通过;`corepack pnpm@10.33.0 build` 通过。B `nginx -t` 通过,HTTPS 首页已引用 `/assets/index-MxyRK-fA.js`;远程 smoke `REMOTE_SMOKE_20260629T075504Z.md` 通过,首页、live/ready health、captcha 均通过。
- 回滚方式:应用回滚可将 `/opt/lisglosips/current` 指回 `/opt/lisglosips/releases/s46-bugfix-recharge-active-numberlib-20260629151000` 并 reload Nginx;本次不涉及数据库迁移或服务状态变更,无数据库回滚。
- 未解决问题:浏览器可能缓存旧 JS,若页面仍显示同样错误,需强制刷新或清理站点缓存后再测;未提交 Git。
### 2026-06-29 15:41 - 发布 s46 修复 8.2/8.7/8.8 到 B 测试环境
- 状态:已完成
- 操作服务器:本地、B;未改 A/T 配置,未执行 A/B/T 三机实呼链路变更
- 测试结果最终分类:8.2 负数客户充值返回 `MONEY_INVALID` 是确定 bug,已修复为客户余额可正向充值也可负数扣款,仍拒绝 0 和负零;8.7 超长 active call dialog id 返回 404 是确定 bug,根因包含服务层校验不足和 Fastify 默认 `maxParamLength=100` 导致 129 位路径参数先被路由层拒绝,已修复为 400 `ACTIVE_CALL_ID_INVALID`;8.8 管理员菜单缺少号码库是既有 B 环境 RBAC 数据缺失 `number_library.view/manage`,不是浏览器缓存或登录态问题,已用迁移补齐内置角色权限;8.9 轻量 API burst p95 超过 10 秒和 BYE 被 `403 Rate Limited` 未纳入本轮代码修复,分别需继续查 B API 性能日志和 A 侧 OpenSIPS 限流策略;8.10 阻塞项属于权限/维护窗口限制,不是本轮业务代码缺陷。
- 完成内容:客户充值接口改为客户侧允许负数扣款、供应商侧继续只允许正数;active call hangup ID 校验收紧为 1-128 位安全字符且必须包含 `@``.``:`,并将 API Fastify `routerOptions.maxParamLength` 调整到 256,让超长 ID 进入应用校验返回 400;新增 active-calls e2e 覆盖 129 位 ID;新增 Prisma 迁移补齐 `number_library.view/manage` 权限及 `ROLE_SUPER_ADMIN``ROLE_OPERATOR``ROLE_TECH_OPS` 的角色权限关联。
- 修改文件:`apps/api/src/main.ts``apps/api/src/modules/recharges/recharges.service.ts``apps/api/src/modules/recharges/recharges.e2e.spec.ts``apps/api/src/modules/active-calls/active-calls.service.ts``apps/api/src/modules/active-calls/active-calls.service.spec.ts``apps/api/src/modules/active-calls/active-calls.e2e.spec.ts``prisma/migrations/20260629150000_number_library_builtin_role_permissions/migration.sql``IMPLEMENTATION_STATUS.md`
- 发布情况:B 已从 `s45-vendor-cps-sipstate-20260629113500` 复制生成并切换到 `/opt/lisglosips/releases/s46-bugfix-recharge-active-numberlib-20260629151000`;本次因大包上传链路多次卡住,采用基于 s45 的补丁 release,替换构建后的 API JS/SourceMap 和新增迁移 SQL,而非完整源码包重建 artifact;B `/opt/lisglosips/current` 已指向 s46。发布前触发 B 备份,MySQL `/data/backups/mysql/20260629T070448Z`、Redis `/data/backups/redis/20260629T070448Z`,两者 SHA256 校验通过。
- 数据库迁移说明:首次 `prisma migrate deploy` 在 s46 未切 current 前因 Prisma engine 权限 `EACCES` 失败,已修正 s46 engine 权限;第二次因迁移 SQL 漏写 `updated_at` 触发 P3018,current 仍未切换,修正迁移后执行 `prisma migrate resolve --rolled-back 20260629150000_number_library_builtin_role_permissions` 并重新 `migrate deploy`,最终迁移成功。迁移只新增/补齐权限主数据和角色权限关联,不改业务表结构。
- 验证结果:本地 `corepack pnpm@10.33.0 lint` 通过;`typecheck` 通过;`prisma:validate` 通过;全量 `test` 通过,26 个测试文件 93 条测试通过;`build` 通过,Web 产物仍为 `index-CBRZZA7t.js` / `index-B2uYBtS3.css`。B preflight 通过,API/MySQL/Redis/Nginx/Worker/监控服务 activeAPI ready 为 `ok`;远程 smoke `REMOTE_SMOKE_20260629T074052Z.md` 通过;B 本机 API 定向复测通过:号码库权限 6 条角色关联存在、`GET /api/v2/number-library/cities?take=1` 返回 200、129 位 active-call hangup 返回 400 `ACTIVE_CALL_ID_INVALID`、临时客户 `cus_aeef878b32534914a9b64ae0196d` 正向充值 10 后负数扣款 `-3.250000`,余额从 `10.000000``6.750000``-0.000000` 返回 400 `MONEY_INVALID`
- 回滚方式:应用回滚可执行 `/opt/lisglosips/current/infra/server-b/s30/lisglosips-release-rollback.sh /opt/lisglosips/releases/s45-vendor-cps-sipstate-20260629113500`,或手动将 `/opt/lisglosips/current` 指回 s45 后重启 `lisglosips@api lisglosips@cdr-worker lisglosips@recording-worker lisglosips@config-publisher` 并 reload Nginx;数据库回滚通常可保留权限主数据,若必须撤回菜单权限,删除 `role_permissions``ROLE_SUPER_ADMIN``ROLE_OPERATOR``ROLE_TECH_OPS``number_library.view/manage` 的 6 条关联,必要时先用上述 MySQL/Redis 备份在隔离环境验证;本轮远程验证留下的 `codex_s46_` 临时客户和对应余额流水可按测试数据保留审计或在维护窗口按客户 ID 清理。
- 未解决问题:未提交 Git8.9 API p95 和 BYE 限流仍待单独定位;8.10 阻塞项仍需权限或维护窗口;本轮是 B API/RBAC 修复,不涉及 A/T SIP 热路径,因此未做新的三机实呼验收,若后续调整 A 侧限流则必须补做 A/B/T 端到端验收。
### 2026-06-29 14:59 - 远程测试报告核对与本地修复:负数扣款、active call ID、号码库权限
- 状态:已完成本地修复,待用户确认是否发布到 B
- 操作服务器:本地;未连接 A/B/T,未读取或回显任何服务器凭据
- 测试结果分类:8.2 负数客户充值返回 `MONEY_INVALID` 是确定 bug8.7 超长/无效 active call dialog id 返回 404 是确定 bug;8.8 管理员菜单缺少号码库不是浏览器缓存问题,前端已有菜单和权限裁剪,判断为 B 既有 RBAC 数据未补齐导致的发布/数据问题;8.9 轻量 API burst p95 超过 10 秒是待定位性能问题,SIP CPS 探针 BYE 被限流是 A 侧限流策略待确认问题;8.10 备份、隔离恢复、真实回滚、OpenSIPS 配置恢复为 sudo 权限或维护窗口阻塞,不是代码缺陷,灰度呼叫和健康回归已通过。
- 完成内容:客户充值接口改为允许 `POST /customers/:id/recharges` 传负数金额作为扣款,继续拒绝 0、负零、超精度和超范围金额;供应商充值接口保持只允许正数。当前通话 hangup ID 校验收紧为 1-128 位安全字符且必须包含 `@``.``:`,避免无意义超长字符串进入 OpenSIPS MI。新增 Prisma 数据迁移补齐 `number_library.view/manage` 权限,并为 `ROLE_SUPER_ADMIN``ROLE_OPERATOR``ROLE_TECH_OPS` 写入号码库权限关联,修复既有环境 seed 未重跑时管理员缺菜单的问题。
- 修改文件:`apps/api/src/modules/recharges/recharges.service.ts``apps/api/src/modules/recharges/recharges.e2e.spec.ts``apps/api/src/modules/active-calls/active-calls.service.ts``apps/api/src/modules/active-calls/active-calls.service.spec.ts``prisma/migrations/20260629150000_number_library_builtin_role_permissions/migration.sql``IMPLEMENTATION_STATUS.md`
- 验证结果:定向 `corepack pnpm@10.33.0 exec vitest run apps/api/src/modules/recharges/recharges.e2e.spec.ts apps/api/src/modules/active-calls/active-calls.service.spec.ts --hookTimeout=60000 --maxWorkers=1` 通过,2 个测试文件 10 条测试通过;`corepack pnpm@10.33.0 lint` 通过;`corepack pnpm@10.33.0 typecheck` 通过;`corepack pnpm@10.33.0 prisma:validate` 通过;`corepack pnpm@10.33.0 test` 通过,25 个测试文件 92 条测试通过;`corepack pnpm@10.33.0 build` 通过,Web 产物仍为 `index-CBRZZA7t.js` / `index-B2uYBtS3.css`
- 发布情况:未提交 Git,未发布 B,未执行数据库迁移;B 当前 release 仍为 `s45-vendor-cps-sipstate-20260629113500`
- 回滚方式:回退上述源码和测试文件即可恢复旧本地行为;若已在 B 执行新增 Prisma 数据迁移,可通过删除 `role_permissions``ROLE_SUPER_ADMIN`/`ROLE_OPERATOR`/`ROLE_TECH_OPS``number_library.view/manage` 的关联来撤回菜单权限补齐,通常无需删除 `permissions` 主数据;应用层按 S30 Runbook 切回上一 release。
- 未解决问题:8.9 API p95 需服务端日志/指标进一步定位;BYE 被 CPS 限流涉及 A OpenSIPS 防护策略,修改前必须备份 A 配置并说明重启影响;8.10 阻塞项需 sudo 或维护窗口;号码库真实 80 万号段尚未导入。
### 2026-06-29 12:12 - 需求修改/二期变更发布到 B:供应商启停移除、客户启停、CPS 与当前通话 SIP 状态
- 状态:已完成
- 操作服务器:B,本地;A/T 只读服务复核
- 完成内容:将本轮供应商启停移除、客户启停按钮、落地网关 CPS 1-10000 数字校验、当前通话 SIP 信令状态展示,以及登录 500 的完整 Argon2id vendor artifact 修复一起发布为 B 新 release `s45-vendor-cps-sipstate-20260629113500`B `/opt/lisglosips/current` 已从 `s44-phase2-gateway-match-20260628225000` 切换到新 release;重启 `lisglosips@api``lisglosips@cdr-worker``lisglosips@recording-worker``lisglosips@config-publisher`Nginx 配置检查通过并 reload。
- 修改文件:`IMPLEMENTATION_STATUS.md`
- Release artifactB 上 Linux 构建并生成 `/tmp/lisglosips-build-s45-vendor-cps-sipstate-20260629113500/dist/releases/s45-vendor-cps-sipstate-20260629113500.tar.gz`SHA256 `87f1372b726a6c0a3a83c68b0ada2559a3f92be35d92451551a24ad1e25d9663`staged files `19673`Web 产物为 `index-CBRZZA7t.js` / `index-B2uYBtS3.css`
- 执行的关键命令:本地 `git commit`;本地 `git archive` 并上传源码 tarB 从 s44 current 复用 Linux `node_modules` 构建,执行 `corepack pnpm@10.33.0 build``corepack pnpm@10.33.0 release:artifact -- --release-id s45-vendor-cps-sipstate-20260629113500 --skip-build``sha256sum``systemctl start lisglosips-backup.service`、解包 release、切换 `/opt/lisglosips/current`、重启 API/Worker、`nginx -t`、reload Nginx、`lisglosips-release-preflight.sh`、API ready 和 HTTPS 首页检查;A/T 只读 `systemctl is-active` 复核。
- 验证结果:B MySQL、Redis、Nginx、API、CDR Worker、Recording Worker、Config Publisher 均 activepreflight okAPI ready 返回 `ok`database/redis/config checks 均 `ok`;HTTPS 首页返回 200 并引用新 Web 资产 `index-CBRZZA7t.js``index-B2uYBtS3.css`;发布前备份目录为 `/data/backups/mysql/20260629T041103Z``/data/backups/redis/20260629T041103Z`A `opensips``rtpengine``rtpengine-recording-daemon``lisglosips-redis-auth-proxy` activeT `opensips``rtpengine``lisglosips-s28-uas` active。
- 回滚方式:B 可执行 `/opt/lisglosips/current/infra/server-b/s30/lisglosips-release-rollback.sh /opt/lisglosips/releases/s44-phase2-gateway-match-20260628225000` 或手动将 `/opt/lisglosips/current` 指回该 release 后重启 `lisglosips@api lisglosips@cdr-worker lisglosips@recording-worker lisglosips@config-publisher` 并 reload Nginx;本次未做数据库 schema 变更,通常不需要数据库回滚,必要时使用本次发布前 MySQL/Redis 备份先在隔离环境验证。
- 未解决问题:未执行新的 A/B/T 实呼验收,因本次只发布 Web/API/Worker 业务管理变更且不改 OpenSIPS 热路径;浏览器逐页按钮、登录态和录音播放仍需用户在网页验收。
### 2026-06-29 11:29 - 需求修改/二期变更:供应商启停移除、客户启停入口、CPS 数字校验、当前通话 SIP 状态
- 状态:已完成
- 操作服务器:本地
- 完成内容:移除供应商管理的启用/禁用 API 和前端状态列,供应商新增/编辑不再接收或返回启停字段;客户管理页面新增启用/禁用按钮、权限裁剪和确认弹窗,调用既有 `POST /customers/:id/enable|disable`;落地网关 `cpsLimit` 改为 1-10000 整数,Web 表单使用数字输入且不再接受 `120 CPS` 这类文本;当前通话 API 新增 `sipState``sipStatusCode``sipStatusText`,Web 列表改显示 SIP 信令状态;同步更新 `docs/TEST_PLAN_AND_CASES.md``SOFTSWITCH_PLATFORM_DESIGN_V2.md`
- 修改文件:`apps/api/src/modules/vendors/``apps/api/src/modules/vendor-gateways/``apps/api/src/modules/active-calls/``apps/web/src/App.jsx``apps/web/src/api.js``apps/web/src/pages/CustomersPage.jsx``apps/web/src/pages/VendorsPage.jsx``apps/web/src/pages/VendorGatewaysPage.jsx``apps/web/src/pages/ActiveCallsPage.jsx``apps/web/src/utils/formatters.js``apps/web/src/fixtures/devFixtures.js``docs/TEST_PLAN_AND_CASES.md``SOFTSWITCH_PLATFORM_DESIGN_V2.md``IMPLEMENTATION_STATUS.md`
- 验证结果:`corepack pnpm@10.33.0 typecheck` 通过;`corepack pnpm@10.33.0 test -- apps/api/src/modules/vendors/vendors.e2e.spec.ts apps/api/src/modules/customers/customers.e2e.spec.ts apps/api/src/modules/vendor-gateways/vendor-gateways.e2e.spec.ts apps/api/src/modules/active-calls/active-calls.service.spec.ts` 实际触发 Vitest 全量选择,25 个测试文件 91 条测试通过;`corepack pnpm@10.33.0 lint` 通过;`corepack pnpm@10.33.0 build` 通过,Web 产物为 `index-CBRZZA7t.js` / `index-B2uYBtS3.css`
- 回滚方式:回退上述本地文件即可恢复旧行为;本次未新增数据库迁移、未连接 A/B/T、未改 Redis/OpenSIPS/RTPEngine/Worker 运行配置、未重启服务。
- 未解决问题:本次仅完成本地代码和测试用例调整,尚未发布到 A/B/T;供应商表的数据库 `status` 字段仍保留用于历史兼容和软删除内部状态,不再作为产品启停能力暴露。
### 2026-06-29 10:52 - 修复 B Web 登录 500:补齐 Argon2id vendor lib
- 状态:已完成
- 操作服务器:B、本地
- 完成内容:根据 B `lisglosips@api` 日志定位 `/api/v2/auth/login` 500 根因:当前 s44 release 缺少 `packages/auth/vendor/argon2id/lib/setup.js`,导致密码校验动态 import 失败;备份当前 s44 Argon2id vendor 目录到 `/var/backups/lisglosips-auth-vendor/20260629T105135`;从已验证的旧 release 复制完整 `packages/auth/vendor/argon2id` 到当前 s44 release,设置属主 `root:lisglosips`、目录 `0750`、文件 `0640`;重启 `lisglosips@api`;修复本地 `scripts/build-release-artifact.mjs`,将 release artifact 条目从仅打包 `packages/auth/vendor/argon2id/dist` 改为打包完整 `packages/auth/vendor/argon2id`
- 修改文件:`scripts/build-release-artifact.mjs``IMPLEMENTATION_STATUS.md`B 当前 release `/opt/lisglosips/releases/s44-phase2-gateway-match-20260628225000/packages/auth/vendor/argon2id`
- 安装软件及固定版本:未安装新软件。
- 执行的关键命令:B `journalctl -u lisglosips@api``cp -a``chown``chmod``systemctl restart lisglosips@api``curl http://127.0.0.1:3000/api/v2/health/ready`、Argon2id hash/verify 冒烟;本地 `node scripts/build-release-artifact.mjs --release-id s-local-auth-vendor-check --check`
- 验证结果:B API activeAPI ready 返回 `ok`;当前 release 内 Argon2id hash/verify 输出 `argon2-smoke-ok`;最近 API 日志未再出现 `ERR_MODULE_NOT_FOUND` 或缺少 `setup.js`;本地 release artifact check 通过,确认 39 个 required entries 均存在。
- 回滚方式:如需撤回本次热修复,可恢复 `/var/backups/lisglosips-auth-vendor/20260629T105135``/opt/lisglosips/releases/s44-phase2-gateway-match-20260628225000/packages/auth/vendor/argon2id` 后重启 `lisglosips@api`,但会恢复登录 500;更完整的应用回滚仍按 S30 Runbook 切回上一 release。
- 未解决问题:尚未由浏览器完成真实账号登录复测;本地脚本已修复,但 s44 已生成 tarball 本身仍是旧 artifact,需要后续重新生成 release artifact 才能让制品与热修复一致。
### 2026-06-29 10:45 - A/B/T 重启后恢复本地联调环境
- 状态:已完成
- 操作服务器:A、B、T、本地
- 完成内容:按 `IMPLEMENTATION_STATUS.md``SOFTSWITCH_PLATFORM_DESIGN_V2.md`、二期迁移 Runbook 和 S30 Runbook 建立边界;先只读检查三机状态;确认 A `opensips``rtpengine``rtpengine-recording-daemon``lisglosips-redis-auth-proxy``lisglosips-node-exporter` active`lisglosips-redis-hotpath-load` 为 oneshot 且上次结果 success;确认 A Lua SHA1 `aff9995dbda2a2425e95a3e25b5d7ce194348ce7``opensips.cfg` EVALSHA 一致,OpenSIPS 配置 SHA-256 为 `f163fa37f7aaa492e12ed496cb82f07d9b0f1030a6ed297855a22d2c4bf1c8a5``opensips -C -f /etc/opensips/opensips.cfg` 通过;确认 B current 为 `/opt/lisglosips/releases/s44-phase2-gateway-match-20260628225000`,启动关机后 inactive 的 `lisglosips@recording-worker``lisglosips@config-publisher`;确认 B MySQL、Redis、Nginx、API、CDR Worker、Recording Worker、Config Publisher 均 active;确认 T `opensips``rtpengine``apache2``mariadb``lisglosips-s28-uas` activeS28/S40 脚本存在可执行。
- 修改文件:`IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件。
- 执行的关键命令:`systemctl is-active``systemctl --failed``readlink -f /opt/lisglosips/current`、B `systemctl start lisglosips@recording-worker lisglosips@config-publisher`、B `lisglosips-release-preflight.sh`、B `curl http://127.0.0.1:3000/api/v2/health/ready`、B `curl -k https://127.0.0.1/`、A/T `opensips -C -f /etc/opensips/opensips.cfg`、Redis `SCRIPT EXISTS`、T S40 单通呼叫、B 按 Call-ID 查询 `raw_cdrs`/`rated_cdrs`
- 验证结果:B API ready 返回 `ok`,HTTPS 首页返回 200 并加载 `assets/index-BBq8iMHt.js``assets/index-B2uYBtS3.css`B preflight ok,最新备份目录显示 `/data/backups/mysql/20260629T013942Z``/data/backups/redis/20260629T013942Z`Redis `cfg:active_version=s28-v1``SCRIPT EXISTS aff9995dbda2a2425e95a3e25b5d7ce194348ce7` 返回 `1`A/B/T 目标服务最终均 activeS40 最小呼叫 `s40-01-1782701049649-q3u8wrqi@lisglosips-t` 收到 `200 OK``200 BYE`B 新增 `raw_cdrs` 记录 `raw_4281ea422d1c4465964c6bf054a90115``rating_status=RATED`,生成 `rated_cdrs` 记录 `rated_b3e4b572d8f94fd6a0d6779a86a0294e``bill_sec=6`、客户费用 `0.012000`、供应商成本 `0.012000`
- 回滚方式:本次未切换 release、未改数据库 schema、未删除数据。若需撤回本次运行态启动,可在 B 执行 `sudo systemctl stop lisglosips@recording-worker lisglosips@config-publisher`B current 仍指向 `/opt/lisglosips/releases/s44-phase2-gateway-match-20260628225000`。若后续需应用回滚,仍按 S30 Runbook 使用 `/opt/lisglosips/current/infra/server-b/s30/lisglosips-release-rollback.sh`
- 未解决问题:S40 脚本内置 UAS 端口 `50620` 仍提示已占用,但现有 UAS 正常接起;A/T 各有一个无关 `fwupd-refresh.service` failed unit,未在本次任务中处理;真实 80 万手机号段尚未导入;浏览器逐页权限按钮和真实录音播放仍需用户人工复测。
### 2026-06-28 22:34 - 需求修改/二期变更:提交并部署客户网关匹配规则
- 状态:已完成
- 操作服务器:A、B、T、本地
- 完成内容:提交客户网关匹配规则、测试用例和 release artifact 脚本;在 B 生成 Linux release artifact `s44-phase2-gateway-match-20260628225000.tar.gz`SHA256 `f7c1264c5aaac62e35890d783641139aacb54821045521d6c669c41737e9d5e2`B current 已切换到 `/opt/lisglosips/releases/s44-phase2-gateway-match-20260628225000`A 已替换 `/etc/opensips/lisglosips_hotpath.lua`,加载 Redis Lua SHA1 `aff9995dbda2a2425e95a3e25b5d7ce194348ce7` 并更新 `opensips.cfg`;T 服务保持 active 并完成最小呼叫。
- 修改文件:`IMPLEMENTATION_STATUS.md``SOFTSWITCH_PLATFORM_DESIGN_V2.md``docs/PHASE2_BUSINESS_PREFIX_GATEWAY_MIGRATION_RUNBOOK.md``docs/TEST_PLAN_AND_CASES.md``docs/TEST_RESULTS_STEP1_BASELINE_20260628.md``apps/api/src/modules/customer-gateways/customer-gateways.service.ts``apps/api/src/modules/customer-gateways/customer-gateways.e2e.spec.ts``apps/web/src/pages/CustomerGatewaysPage.jsx``apps/web/src/pages/BusinessPrefixesPage.jsx``apps/web/src/pages/UsersPage.jsx``apps/web/src/pages/RolesPage.jsx``infra/server-a/s28/lisglosips_hotpath.lua``scripts/build-release-artifact.mjs``package.json``vitest.config.ts`、Argon2id wasm 发布文件。
- Git 提交:`175166a fix phase2 gateway matching and release artifact``ef7874f fix release artifact packaging on linux``08c755f clean release artifact manifest status`
- 执行的关键命令:本地 `eslint``tsc -b`、全量 `vitest run`、Web `vite build`、release artifact `--check`B `pnpm build``pnpm release:artifact -- --release-id s44-phase2-gateway-match-20260628225000 --skip-build``sha256sum -c`、release 解包、服务重启、preflightA 备份 Lua/cfg、计算 SHA1、`lisglosips-redis-hotpath-load.service``opensips -C -f /etc/opensips/opensips.cfg`、重启 OpenSIPST S40 单通呼叫。
- 验证结果:本地 lint/typecheck/全量测试通过,Vitest 25 文件 90 用例通过;B preflight okAPI ready okNginx 首页引用 `index-BBq8iMHt.js`A OpenSIPS `opensips -C` 通过且 activeT 呼叫 `s40-01-1782657231921-z1w146wc@lisglosips-t` 返回 200 OK/200 BYEB `raw_cdrs` 新增 `raw_9b630cf678c24390bb27f3c3527f8051`CDR worker 生成 `rated_29711c328edc44ecba8f4471bb6c8714`,客户费用和供应商成本均为 `0.012000`
- 回滚方式:B 执行 `sudo /opt/lisglosips/current/infra/server-b/s30/lisglosips-release-rollback.sh /opt/lisglosips/releases/s43-bugfix-cdr-quality-rbac-20260624184854`A 恢复 `/var/backups/lisglosips-phase2/s44-20260628T225000/opensips.cfg``lisglosips_hotpath.lua`,执行 `opensips -C -f /etc/opensips/opensips.cfg` 后重启 OpenSIPSMySQL/Redis 备份点为 `/data/backups/mysql/20260628T143121Z``/data/backups/redis/20260628T143122Z`
- 未解决问题:未导入真实 80 万手机号段;S40 脚本自带 UAS 端口提示已占用但现有 UAS 正常接起;浏览器逐页权限按钮和真实录音播放仍需用户人工复测。
### 2026-06-28 22:09 - 需求修改/二期变更:客户网关匹配口径固化与 release artifact 脚本
- 状态:已完成本地代码与文档修改,未发布服务器
- 操作服务器:本地
- 完成内容:将产品口径固化为“业务前缀优先于主叫前缀,精确规则优先于空业务前缀兜底”;明确业务前缀只作为客户网关匹配字典,不参与计费、报表或权限隔离;明确客户网关决定客户侧计费、落地网关决定供应商侧成本;后端客户网关保存/启用时校验同一 IP + 同一业务前缀唯一、同一 IP 主叫前缀不互相覆盖、同一网关内主叫前缀不互相覆盖;A 侧热路径仓库 Lua 改为业务前缀优先、空业务前缀下再按最长主叫前缀、最后默认兜底;客户网关编辑页新增匹配规则提示;新增固定 release artifact 脚本与 npm script。
- 修改文件:`SOFTSWITCH_PLATFORM_DESIGN_V2.md``docs/PHASE2_BUSINESS_PREFIX_GATEWAY_MIGRATION_RUNBOOK.md``apps/api/src/modules/customer-gateways/customer-gateways.service.ts``apps/api/src/modules/customer-gateways/customer-gateways.e2e.spec.ts``apps/web/src/pages/CustomerGatewaysPage.jsx``infra/server-a/s28/lisglosips_hotpath.lua``scripts/build-release-artifact.mjs``package.json``IMPLEMENTATION_STATUS.md`
- 验证结果:`node_modules/.bin/tsc.cmd -b tsconfig.build.json --pretty false` 通过;`node_modules/.bin/eslint.cmd` 针对后端/脚本文件通过,Web 页面文件因仓库 ignore 规则被跳过且无错误;客户网关 e2e 7/7 通过,覆盖重复业务前缀和主叫前缀覆盖冲突;`node scripts/build-release-artifact.mjs --release-id s-local-check --check` 通过;`apps/web/node_modules/.bin/vite.CMD build` 通过,产物 `index-BBq8iMHt.js`
- 回滚方式:本地回滚上述文件即可;若未来已发布服务器,B 应回滚到上一 release artifactA 应恢复发布前备份的 `/etc/opensips/lisglosips_hotpath.lua` 并执行 `opensips -C -f /etc/opensips/opensips.cfg` 后重启 OpenSIPS。
- 未解决问题:未连接服务器、未生成正式 Linux release artifact、未部署 A/B、未做 A/B/T 实呼验收;本地无 Lua/luac,Lua 解释器级语法检查需在 A 或装有 Lua 的环境补跑。
### 2026-06-28 16:45 - 修复用户管理/角色权限页面空白
- 状态:已完成
- 操作服务器:B
- 完成内容:
- 修复 `apps/web/src/pages/UsersPage.jsx``emptyUserForm` 未定义导致用户管理页面空白的问题。
- 修复 `apps/web/src/pages/RolesPage.jsx``emptyRoleForm` 未定义导致角色与权限页面空白的问题。
- 增加一次轻量源码检查,确认 `apps/web/src/pages``empty*Form` 引用均有同文件定义。
- 重新构建前端并发布到 B 当前 release `/opt/lisglosips/releases/s43-bugfix-cdr-quality-rbac-20260624184854``apps/web/dist``public` 目录。
- 修改文件:`apps/web/src/pages/UsersPage.jsx``apps/web/src/pages/RolesPage.jsx``IMPLEMENTATION_STATUS.md`;B 当前 release 静态资源目录。
- 安装软件及固定版本:未安装新软件。
- 执行的关键命令:`corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 build`、页面 `empty*Form` 引用检查、上传并解包前端 dist、`curl -k https://127.0.0.1/`
- 验证结果:
- lint 通过。
- typecheck 通过。
- build 通过。
- 页面 `empty*Form` 引用检查无输出,未发现同类缺失定义。
- B HTTPS 首页已引用 `/assets/index-BK00zN_M.js``/assets/index-B2uYBtS3.css`
- 回滚方式:恢复上述两个页面文件并重新构建发布;或将 B `public`/`apps/web/dist` 恢复为上一版前端静态资源。
- 未解决问题:未使用真实浏览器逐页点击复测;请用户强刷后重新进入“用户管理”“角色与权限”确认。
- 下一任务:等待用户复测反馈。
### 2026-06-28 16:39 - 修复业务前缀页面空白
- 状态:已完成
- 操作服务器:B
- 完成内容:
- 修复 `apps/web/src/pages/BusinessPrefixesPage.jsx``emptyBusinessPrefixForm` 未定义导致业务前缀菜单页面空白的问题。
- 重新执行本地 lint、typecheck、build,生成新前端资产 `assets/index-CVzJDPkO.js`
-`apps/web/dist` 发布到 B 当前 release `/opt/lisglosips/releases/s43-bugfix-cdr-quality-rbac-20260624184854``apps/web/dist``public` 目录。
- 修改文件:`apps/web/src/pages/BusinessPrefixesPage.jsx``IMPLEMENTATION_STATUS.md`;B 当前 release 静态资源目录。
- 安装软件及固定版本:未安装新软件。
- 执行的关键命令:`corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 build`、上传并解包前端 dist、`curl -k https://127.0.0.1/`
- 验证结果:
- lint 通过。
- typecheck 通过。
- build 通过。
- B HTTPS 首页已引用 `/assets/index-CVzJDPkO.js``/assets/index-B2uYBtS3.css`,不再引用旧 `index-BudcefOn.js`
- 回滚方式:恢复 `BusinessPrefixesPage.jsx` 本次修改并重新构建发布;或将 B `public`/`apps/web/dist` 恢复为上一版前端静态资源。
- 未解决问题:未使用真实浏览器逐页点击复测;请用户刷新页面后重新进入“业务前缀管理”确认。
- 下一任务:等待用户复测反馈。
### 2026-06-28 16:31 - 修复 B 登录 500:补齐 Argon2id wasm 发布文件
- 状态:已完成
- 操作服务器:B
- 完成内容:
- 根据 B `lisglosips@api` 日志定位 `/api/v2/auth/login` 500 根因:当前 `s43` release 缺少 `packages/auth/vendor/argon2id/dist/no-simd.wasm`,导致密码校验加载 wasm 失败。
- 从上一可用 release `/opt/lisglosips/releases/s42-phase2-business-prefix-20260624140000` 复制 `no-simd.wasm``simd.wasm` 到当前 release `/opt/lisglosips/releases/s43-bugfix-cdr-quality-rbac-20260624184854`
- 调整文件属主与权限为 `root:lisglosips``0640`,重启 `lisglosips@api`
- 在 B 当前 release 内执行 Argon2id hash/verify 冒烟,返回 `argon2-smoke-ok`
- 本地将 `packages/auth/vendor/argon2id/dist/no-simd.wasm``packages/auth/vendor/argon2id/dist/simd.wasm` 使用 `git add -f` 纳入 Git 跟踪,避免后续发布包再遗漏。
- 修改文件:B 当前 release 新增/补齐 `packages/auth/vendor/argon2id/dist/no-simd.wasm``packages/auth/vendor/argon2id/dist/simd.wasm`;本地暂存上述两个 wasm 文件,并更新 `IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件。
- 执行的关键命令:`journalctl -u lisglosips@api``cp -a``chown``chmod``systemctl restart lisglosips@api``curl http://127.0.0.1:3000/api/v2/health/ready`、B Argon2id hash/verify 冒烟、`git add -f packages/auth/vendor/argon2id/dist/*.wasm`
- 验证结果:B `lisglosips@api` activeAPI ready 返回 okArgon2id wasm 冒烟通过。用户可重新尝试 Web 登录;未使用或回显用户密码。
- 回滚方式:如需回滚本次运行修复,可删除 B 当前 release 的两个 wasm 文件并重启 API,但会恢复登录 500;推荐回滚方式仍是将 B `current` 指回 `/opt/lisglosips/releases/s42-phase2-business-prefix-20260624140000` 后重启 API/Worker。
- 未解决问题:`refresh 401` 在无有效登录态时仍属预期;需用户重新登录确认前端流程。
- 下一任务:等待用户复测反馈。
### 2026-06-28 16:25 - 启动 A/B/T 本地联调环境供功能检查
- 状态:已完成
- 操作服务器:A、B、T
- 完成内容:
- A:确认 `opensips``lisglosips-redis-auth-proxy``rtpengine``rtpengine-recording-daemon``lisglosips-node-exporter` 均为 active;未修改 A 配置。
- B:确认当前 release 为 `/opt/lisglosips/releases/s43-bugfix-cdr-quality-rbac-20260624184854`;启动此前 inactive 的 `lisglosips@recording-worker``lisglosips@config-publisher`;确认 MySQL、Redis、Nginx、API、CDR Worker、Recording Worker、Config Publisher、HEP、Prometheus、Grafana 均为 active。
- T:发现 `opensips` 为 failed;执行 `systemctl reset-failed opensips``systemctl restart opensips` 后恢复 active;确认 `rtpengine``apache2``mariadb``lisglosips-s28-uas` 均为 active。
- 修改文件:本地更新 `IMPLEMENTATION_STATUS.md`;服务器未修改配置文件,未执行数据库 migration,未改防火墙。
- 安装软件及固定版本:未安装新软件。
- 执行的关键命令:`systemctl is-active`、B `systemctl start lisglosips@recording-worker lisglosips@config-publisher`、T `systemctl reset-failed opensips && systemctl restart opensips`、B `lisglosips-release-preflight.sh``curl http://127.0.0.1:3000/api/v2/health/ready``curl -k https://127.0.0.1/``ss -lunpt`
- 验证结果:
- A OpenSIPS 配置 SHA-256 仍为 `ae1544e9a39d2db114e6981babac042c2ab6d399d59ba597a52b85d597aa5aa5`Lua SHA-1 仍为 `0b7ee595cdcad2f38a670dd8ff3553387820039e`A 监听 `100.90.90.90:15060/udp`
- B preflight 通过;API ready 返回 okHTTPS 首页返回 200,加载 `/assets/index-BudcefOn.js``/assets/index-B2uYBtS3.css`
- T `opensips` active,监听 `100.93.185.30:5060/udp`UAS 监听 `100.93.185.30:50620/udp`
- 回滚方式:如需停止本次启动项,可停止 B 的 `lisglosips@recording-worker``lisglosips@config-publisher`,以及按需停止 T `opensips`B 应用回滚仍可将 `/opt/lisglosips/current` 指回 `/opt/lisglosips/releases/s42-phase2-business-prefix-20260624140000` 后重启服务。
- 未解决问题:未执行真实呼叫;T OpenSIPS 曾处于 failed,已恢复但未进一步分析失败前原因。
- 下一任务:等待用户功能检查反馈。
### 2026-06-24 18:58 - 需求修改/二期变更:Bug 修复发布到 B
- 状态:已完成
- 操作服务器:B;A 只读核对;T 未操作
- 完成内容:
- 已将本地 Git 提交 `a86de65 feat: complete phase2 baseline cdr quality rbac` 构建并发布到 B 新 release:`/opt/lisglosips/releases/s43-bugfix-cdr-quality-rbac-20260624184854`
- 发布内容包含:CDR 后端/API 前端接线、质检中心真实 API、前端页面拆分、权限驱动导航和按钮裁剪。
- 切换前 B 当前 release 为 `/opt/lisglosips/releases/s42-phase2-business-prefix-20260624140000`,已作为直接回滚点保留。
- 切换前执行 B preflight 通过,并生成 MySQL/Redis 备份:`/data/backups/mysql/20260624T104812Z``/data/backups/redis/20260624T104812Z`SHA256 校验通过。
- 首次切换 `s43` 时发现 release 组装缺少 workspace 级 `apps/*/node_modules` 和部分 `packages/*/node_modules` 链接,服务报 `ERR_MODULE_NOT_FOUND`;已立即回滚到 `s42`,确认 API ready 200;随后补齐链接、完成模块解析冒烟后再次切换到 `s43` 成功。
- A 侧只读核对:运行中 OpenSIPS 配置 SHA-256 仍为 `ae1544e9a39d2db114e6981babac042c2ab6d399d59ba597a52b85d597aa5aa5`Lua SHA-1 仍为 `0b7ee595cdcad2f38a670dd8ff3553387820039e`;本次 Bug 修复不需要发布 A,且仓库 `infra/server-a/s28/opensips.cfg` 含部署模板占位符 `@@LISGLOSIPS_REDIS_URL@@`,不能直接覆盖运行中 A 配置。
- 修改文件:本地更新 `IMPLEMENTATION_STATUS.md`;服务器新增 B release 目录 `/opt/lisglosips/releases/s43-bugfix-cdr-quality-rbac-20260624184854`,未修改数据库 schema,未执行 Prisma migration。
- 安装软件及固定版本:未安装新软件;复用 B 既有 Node.js `v22.22.2`、pnpm 依赖和 systemd 模板。
- 执行的关键命令:本地 `corepack pnpm@10.33.0 build`B `lisglosips-release-preflight.sh``systemctl start lisglosips-backup.service``sha256sum -c`、创建 release、切换 `/opt/lisglosips/current`、重启 `lisglosips@api``lisglosips@cdr-worker``lisglosips@recording-worker``lisglosips@config-publisher``nginx -t``systemctl reload nginx``curl http://127.0.0.1:3000/api/v2/health/ready``curl -k https://127.0.0.1/`
- 验证结果:
- 本地 build 通过。
- B 当前 release 为 `/opt/lisglosips/releases/s43-bugfix-cdr-quality-rbac-20260624184854`
- B `mysql``redis-server``nginx``lisglosips@api``lisglosips@cdr-worker``lisglosips@recording-worker``lisglosips@config-publisher` 均为 active。
- B API ready 返回 `{"status":"ok","service":"api",...}`
- B preflight 通过;HTTPS 首页返回 200,并加载新前端资产 `/assets/index-BudcefOn.js``/assets/index-B2uYBtS3.css`
- A OpenSIPS/Lua 哈希与冻结报告一致,未执行 A 侧写操作或重启。
- 回滚方式:在 B 执行 `sudo ln -sfn /opt/lisglosips/releases/s42-phase2-business-prefix-20260624140000 /opt/lisglosips/current`,然后重启 `lisglosips@api lisglosips@cdr-worker lisglosips@recording-worker lisglosips@config-publisher` 并执行 preflight;本次未做数据库迁移,通常不需要数据库回滚。
- 未解决问题:未执行 A/B/T 实呼验收;用户需在 B Web 登录态逐页复测权限按钮、CDR、质检、录音播放和信令入口;真实录音文件播放与 Nginx X-Accel 链路仍需用实际文件复核。
- 下一任务:等待用户测试反馈或指定下一项。
### 2026-06-24 17:50 - 需求修改/二期变更:权限驱动导航和按钮
- 状态:已完成
- 操作服务器:本地
- 完成内容:
- 后端登录/刷新响应新增 `user.permissions`,从用户启用角色的 `role_permissions` 汇总去重并排序后返回;保留原 `roles` 字段。
- 前端新增 `apps/web/src/permissions.js`,提供页面权限表、`permissionSet``can``canAll``canAny`
- `App.jsx` 根据权限裁剪菜单;启动和手动刷新时只请求当前用户有权限的全局 API,避免无权限接口产生批量 403;无权限页面显示明确“无权限访问”提示。
- 核心页面按钮按后端真实权限 key 裁剪:当前通话挂断、客户/供应商管理、充值、客户网关、业务前缀、落地网关、落地线路组、号码库导入、话单录音播放、质检录音播放/评分/抽检规则、用户管理、角色管理。
- 修正角色权限页 dev fixture 的权限 key,使其与后端 `RequirePermissions` 使用的真实 key 保持一致。
- 修改文件:`apps/api/src/modules/auth/auth.types.ts``apps/api/src/modules/auth/auth.repository.ts``apps/api/src/modules/auth/auth.service.ts``apps/api/src/modules/auth/auth.service.spec.ts``apps/api/src/modules/auth/auth.e2e.spec.ts``apps/web/src/permissions.js``apps/web/src/App.jsx``apps/web/src/fixtures/devFixtures.js``apps/web/src/pages/*.jsx``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件。
- 执行的关键命令:`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 apps/api/src/modules/auth/auth.service.spec.ts apps/api/src/modules/auth/auth.e2e.spec.ts --hookTimeout=60000 --maxWorkers=1``corepack pnpm@10.33.0 exec vitest run --hookTimeout=60000 --maxWorkers=1``corepack pnpm@10.33.0 --filter @lisglosips/web dev -- --host 127.0.0.1`
- 验证结果:
- typecheck/lint/build 均通过。
- Auth 定向测试 2 files / 6 tests 通过,确认 login/refresh 均返回 permissions。
- 全量 Vitest 25 files / 88 tests 通过。
- 使用本地临时 mock API 与 in-app Browser 验证只读账号仅含 `dashboard.view/customers.view`:侧边栏只显示“概览 Dashboard”“客户管理”;“供应商管理”“质检中心”等菜单不可见;客户页可查看客户列表,但无“新增客户”“编辑”“充值”“删除”按钮;无 Vite overlay,无应用 console error/warn。
- 回滚方式:回滚上述后端 auth 文件、前端权限/页面文件和本状态文件即可;本次未修改 Prisma schema、未执行数据库迁移、未连服务器、未重启服务。
- 未解决问题:当前按钮级裁剪覆盖真实 API 核心页面;费率与计费、SIP 运维、监控告警、系统设置仍属于待设计/演示页面,后续接真实 API 时需继续补权限 key 和按钮裁剪。真实 B 登录态逐页按钮仍需后续人工复测。
- 下一任务:等待用户指定。
### 2026-06-24 17:30 - 需求修改/二期变更:前端拆分与信息架构整理
- 状态:已完成
- 操作服务器:本地
- 完成内容:
-`App.jsx` 中的页面组件拆到 `apps/web/src/pages/``CdrPage.jsx``QualityPage.jsx``ActiveCallsPage.jsx``CustomersPage.jsx``CustomerGatewaysPage.jsx``VendorsPage.jsx``VendorGatewaysPage.jsx``VendorLineGroupsPage.jsx``BusinessPrefixesPage.jsx``NumberLibraryPage.jsx``RechargeRecordsPage.jsx``RoutesPage.jsx``BillingPage.jsx``SipOpsPage.jsx``MonitoringPage.jsx``SettingsPage.jsx``UsersPage.jsx``RolesPage.jsx``OperationLogsPage.jsx`
- 将页面共享布局组件拆到 `apps/web/src/components/layout.jsx`,包含 `Icon``PageTitle``Panel``ApiNotice``Drawer``Modal``SimpleTable``KeyValue` 等。
- 将格式化和 API 归一化函数拆到 `apps/web/src/utils/formatters.js`,供 App 数据刷新和页面组件复用。
- 将残留静态演示/mock 数据集中到 `apps/web/src/fixtures/devFixtures.js`,避免继续散落在 `App.jsx`;未把这些旧 fixture 接入生产 API 流程。
- `App.jsx` 保留应用入口、登录、导航、全局 API 刷新与页面 props 分发,体积从约 237 KB 降到约 20 KB。
- 修改文件:`apps/web/src/App.jsx``apps/web/src/components/layout.jsx``apps/web/src/utils/formatters.js``apps/web/src/fixtures/devFixtures.js``apps/web/src/pages/*.jsx``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件。
- 执行的关键命令:`corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 build``corepack pnpm@10.33.0 exec vitest run --hookTimeout=60000 --maxWorkers=1``corepack pnpm@10.33.0 --filter @lisglosips/web dev -- --host 127.0.0.1`
- 验证结果:
- lint/typecheck/build 均通过。
- 全量 Vitest 25 files / 88 tests 通过。
- 使用本地临时 mock API 与 in-app Browser 验证 `http://127.0.0.1:5173/`:登录后可切换“当前通话”“话单中心”“质检中心”“客户管理”,对应页面 `h1` 正确、root 正常挂载、无 Vite overlay、无应用 console error/warn。
- 拆分过程中发现并修复两个运行时拆分问题:`App.jsx` 主壳补回 `Badge` 导入;`emptySamplingRuleForm``CdrPage.jsx` 挪回 `QualityPage.jsx`
- 回滚方式:回滚上述前端拆分文件和本状态文件即可;本次未修改 Prisma schema、未执行数据库迁移、未连服务器、未重启服务。
- 未解决问题:`fixtures/devFixtures.js` 仍承载费率、路由、监控、系统设置等未接真实 API 的旧演示数据;后续应按页面逐步接真实 API 或加 dev-only 开关彻底隔离。真实 B 服务器浏览器登录态逐页按钮仍需后续人工复测。
- 下一任务:等待用户指定。
### 2026-06-24 17:10 - 需求修改/二期变更:质检中心接真实 API
- 状态:已完成
- 操作服务器:本地
- 完成内容:
- 录音列表从静态数据切换为 `api.recordings()`,接入 `GET /recordings`,支持待质检/已质检/全部筛选、条数筛选、加载中、错误提示与空状态。
- 录音详情接入 `GET /recordings/:id`,详情抽屉展示客户、被叫、业务、录音时长、抽样状态、命中规则与既有质检结果。
- 录音播放接入 `GET /recordings/:id/play`,以 blob URL 驱动页面内 `<audio controls>`,支持播放/暂停、进度、播放失败提示和自动播放下一条。
- 质检评分保存接入 `PUT /recordings/:id/review`,提交结果、评分、问题标签和备注;保存后刷新录音列表与当前详情,避免“看似保存但未刷新”。
- 抽检规则接入 `/quality/rules`,支持新增、编辑、启用、禁用、删除;规则表单使用真实客户与落地线路组选项,并支持全部客户/全部线路。
- 修改文件:`apps/web/src/App.jsx``apps/web/src/api.js``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件。
- 执行的关键命令:`corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 build``corepack pnpm@10.33.0 exec vitest run --hookTimeout=60000 --maxWorkers=1``corepack pnpm@10.33.0 --filter @lisglosips/web dev -- --host 127.0.0.1`
- 验证结果:
- lint/typecheck/build 均通过。
- 全量 Vitest 25 files / 88 tests 通过。
- 使用本地临时 mock API 与 in-app Browser 验证质检中心主流程:进入质检中心、打开录音详情、读取 `/recordings/:id/play` blob、填写标签/备注/评分并保存,页面出现保存反馈且刷新详情与列表;无应用 console error/warn。
- 定向验证录音播放:详情抽屉内 `<audio>` 存在,`src` 为 blob URL,抽样状态与质检控件可见。
- 定向验证抽检规则 CRUD:新增 `QA 保留验证规则` 成功,禁用后状态刷新为禁用,删除后列表移除;无应用 console error/warn。
- 回滚方式:回滚上述 2 个前端文件和本状态文件即可;本次未修改 Prisma schema、未执行数据库迁移、未连服务器、未重启服务。
- 未解决问题:真实 B 服务器浏览器登录态逐页按钮仍需后续人工复测;真实录音文件的播放、Range/Nginx X-Accel 链路仍需在 B 上用实际文件复测;话单信令入口仍是 CDR payload/Call-ID 索引展示,未接独立 HEP/SIP trace 检索接口;CSV 导出仍未实现。
- 下一任务:等待用户指定。
### 2026-06-24 16:55 - 需求修改/二期变更:话单中心前端接真实 API
- 状态:已完成
- 操作服务器:本地
- 完成内容:
- 替换话单中心静态 `cdrs/ratedCdrs` 数据,页面改为调用 `api.cdrs()` 查询真实列表,并调用 `api.cdrDetail()` 按需读取详情。
- 新增话单查询条件:主叫、被叫、客户网关、落地网关、地级市代码、运营商、开始时间、结束时间、每页条数;查询时重置到第一页。
- 新增分页状态:展示当前范围、总数、上一页、下一页,并使用后端 `meta.total/take/skip/hasMore`
- 详情抽屉改为真实契约展示,包含链路信息、关联对象、计费结果、时间轴、录音与信令;信令入口展示 Call-ID、eventId、网关、配置版本和 payload 索引。
- 新增录音播放接线:`api.recordingPlayback()` 拉取 `/recordings/:id/play` blob,详情抽屉内使用 `<audio controls>` 播放;无录音或读取失败时给出禁用/错误状态。
- 保留导出 CSV 入口但禁用,避免误导为已实现导出。
- 修改文件:`apps/web/src/App.jsx``apps/web/src/api.js``apps/web/src/styles.css``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件。
- 执行的关键命令:`corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build``corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 exec vitest run --hookTimeout=60000 --maxWorkers=1``corepack pnpm@10.33.0 --filter @lisglosips/web dev -- --host 127.0.0.1`
- 验证结果:
- lint/typecheck/build 均通过。
- 全量 Vitest 25 files / 88 tests 通过。
- in-app Browser 验证 `http://127.0.0.1:5173/` 应用壳可加载,标题为 `LisgloSIPS - 聆界SIP管理平台`,无 Vite overlay,无 console error/warn。
- 使用本地临时 mock API 验证登录后话单中心:查询主叫 `1001` 后列表过滤为 1 条;详情抽屉打开成功;计费结果、信令索引、录音 `<audio>` 控件可见;查询不存在主叫 `999999` 后出现空状态且总数为 0console error/warn 为 0。
- 回滚方式:回滚上述 3 个前端文件和本状态文件即可;本次未修改 Prisma schema、未执行数据库迁移、未连服务器、未重启服务。
- 未解决问题:真实 B 服务器浏览器登录态逐页按钮仍需后续人工复测;信令入口当前展示 CDR payload/Call-ID 索引,未接独立 HEP/SIP trace 检索接口;CSV 导出仍未实现。
- 下一任务:等待用户指定。
### 2026-06-24 16:28 - 需求修改/二期变更:CDR/话单后端 API 补强
- 状态:已完成
- 操作服务器:本地
- 完成内容:
- 在现有 `CdrsModule` 上补强话单后端能力,保持路由 `GET /cdrs``GET /cdrs/:id` 不变。
- 列表筛选新增 `startedFrom/startedTo`,并兼容 `from/to``startTime/endTime`;保留主叫、被叫、客户网关、落地网关、归属地城市、运营商、`take/skip` 分页。
- 列表返回新增 `meta: { total, take, skip, hasMore }`,并补充业务前缀、原始被叫、落地主被叫、录音摘要字段。
- 详情返回整理为前端可直接展示的稳定契约,关联 raw CDR、rated CDR、recording、customer、vendor、customer gateway、vendor gateway、line group、business prefix、customer gateway policy,并保留 payload、receivedAt、createdAt。
- 修改文件:`apps/api/src/modules/cdrs/cdrs.repository.ts``apps/api/src/modules/cdrs/cdrs.service.ts``apps/api/src/modules/cdrs/cdrs.service.spec.ts``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件。
- 执行的关键命令:`corepack pnpm@10.33.0 exec vitest run apps/api/src/modules/cdrs/cdrs.service.spec.ts --hookTimeout=60000``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`
- 验证结果:CDR service spec 3 tests 通过;typecheck/lint/build 均通过;全量 Vitest 25 files / 88 tests 通过。
- 回滚方式:回滚上述 3 个 CDR 模块文件和本状态文件即可;本次未修改 Prisma schema、未执行数据库迁移、未连服务器、未重启服务。
- 未解决问题:前端话单列表/详情页尚未接入新契约;如未来要按客户、供应商、落地线路组、SIP 状态码等更多条件筛选,可在当前 `CdrQuery` 上继续扩展。
- 下一任务:等待用户指定。
### 2026-06-24 16:16 - 需求修改/二期变更:S42 二期业务前缀基线冻结
- 状态:已完成
- 操作服务器:A、B、T、本地
- 完成内容:基于当前文档、代码和服务器状态对 `s42-phase2-business-prefix-20260624140000` 执行冻结前收口。补齐 B 当前 release 中 `infra/server-b/s30/lisglosips-release-preflight.sh` 执行权限为 `0755`,直接执行 preflight 通过;确认 B current、API/CDR/Recording/Config Publisher/Nginx/MySQL/Redis/HEP/Prometheus/Grafana activeAPI ready 正常,Prisma 4 条 migration up to dateRedis active/previous 配置版本为 `1782286812696`/`1782286145681`,最新 MySQL/Redis 备份 `/data/backups/*/20260624T055810Z` SHA256 校验通过。抽样确认 Redis `stream:cdr_deadletter` 26 条为早期修复前历史死信,冻结验收呼叫未新增 deadletter。A 侧历史 4 条 dialog 使用 `dlg_end_dlg` 仍返回 `Operation failed` 且未消失,已先执行 `opensips -C` 再重启 OpenSIPS 清空;冻结验收实呼后新增 1 条测试 dialog 残留,再次语法检查并重启 OpenSIPS,最终 `dlg_list` 为空。新增正式冻结报告 `docs/S42_PHASE2_FREEZE_REPORT.md`
- 修改文件:`docs/S42_PHASE2_FREEZE_REPORT.md``IMPLEMENTATION_STATUS.md`;B 当前 release 脚本权限已修正为 `0755`
- 安装软件及固定版本:未安装新软件。
- 执行的关键命令:本地 `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`B `lisglosips-release-preflight.sh`、Prisma migrate status、Redis `MGET cfg:active_version cfg:previous_version`、备份 `sha256sum -c SHA256SUMS`、deadletter 抽样;A `opensips -C -f /etc/opensips/opensips.cfg``systemctl restart opensips``lisglosips-call-control dlg_list`T `python3 /opt/lisglosips-s28/lisglosips-s28-sip.py invite --host 100.90.90.90 --port 15060 --bind-host 100.93.185.30 --contact-host 100.93.185.30 --caller s28-ip-1001 --callee 13800138000 --hold 3 --timeout 20`
- 验证结果:本地 typecheck/lint/build 均通过;全量 Vitest 在 `--hookTimeout=60000` 下 25 个测试文件、87 条测试通过;B preflight 直接执行通过,API ready 正常,Prisma up to date,最新 MySQL/Redis 备份哈希通过;T 冻结验收实呼 Call-ID `s28-1782288889943-ckv83lce@lisglosips-t` 收到 `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`CDR Worker 日志 `processed=1 deadlettered=0`A 最终 `dlg_list` 为空。
- 回滚方式:B 应用可执行 `/opt/lisglosips/current/infra/server-b/s30/lisglosips-release-rollback.sh /opt/lisglosips/releases/s33-active-calls-20260623094000`Redis 可从当前 `1782286812696` 切回上一版本 `1782286145681`,不删除 Stream/deadletterA 可从最近 `/var/backups/lisglosips-phase2/` 备份恢复 `opensips.cfg``lisglosips_hotpath.lua`,先 `opensips -C` 再重启;数据库如必须恢复,使用 `/data/backups/mysql/20260624T055810Z` 先在隔离库验证后进入维护窗口处理。
- 未解决问题:真实 80 万手机号段尚未导入;浏览器登录态逐页按钮未做完整人工复测;OpenSIPS dialog 在测试呼叫结束后可能残留,冻结前已通过重启清空,后续应单独分析 dialog 生命周期和 `dlg_end_dlg Operation failed` 根因;当前仍为本地 KVM/Tailscale 冻结,不进入阿里云生产迁移。
### 2026-06-24 15:42 - 需求修改/二期变更:业务前缀重构第 10 步 A/B/T 端到端验收
- 状态:已完成
- 操作服务器:A、B、T、本地
- 完成内容:复核 B 当前 release `/opt/lisglosips/releases/s42-phase2-business-prefix-20260624140000`A OpenSIPS 与 Redis AUTH 代理 active,热路径 Lua SHA `0b7ee595cdcad2f38a670dd8ff3553387820039e`T UAS `100.93.185.30:50620` 监听正常。临时写入 Step 10 验收数据:业务前缀 `671`、S28 客户网关业务前缀匹配、北京/移动号码段、S28 第一落地网关屏蔽北京、同线路组第二落地网关、落地被叫前缀 `88`、指定主叫 `02160010001`。发布临时配置版本 `1782286145681` 后从 T 发起组合实呼 `s28-1782286191063-ig215mpx@lisglosips-t`A 热路径选择第二落地网关 `vgw_91379ef9dc254ab29039d4b77197`B CDR 入库 `raw_callee=67113800138000``callee=13800138000``business_prefix=671``callee_city_code=110100``callee_operator=MOBILE``landing_caller=02160010001``landing_callee=8813800138000``rating_status=RATED`。随后通过 `/api/v2/active-calls` 验证当前通话列表包含 `callerIp``landingIp`,并通过 `/api/v2/active-calls/:id/hangup``bae.39c14471` 发起强制挂断,返回 `HANGUP_IN_PROGRESS` 且目标进入 state `5`
- 修改文件:`IMPLEMENTATION_STATUS.md`
- 验证结果:B `lisglosips@api``lisglosips@cdr-worker``lisglosips@config-publisher` activeA `opensips``lisglosips-redis-auth-proxy` activeT UAS 监听正常;组合实呼 SIP `100/200/BYE 200`A OpenSIPS 日志记录热路径 allow 与 CDR XADDB CDR Worker `processed=1 deadlettered=0`;当前通话 API 返回 6 条 dialog 并包含呼叫方 IP/落地 IP;强制挂断 API 返回 `HANGUP_IN_PROGRESS`
- 回滚方式:已执行回滚 SQL:S28 客户网关 `callee_match_mode` 恢复 `ANY`,删除临时客户网关业务前缀绑定、临时线路组成员、屏蔽地区、指定主叫池、号码段和运营商规则;第二落地网关 `landing_callee_prefix` 恢复 `NULL`;临时业务前缀 `bp_p2e2e_671` 软删除以保留 CDR 引用;回滚配置已发布为 `1782286812696`manifest 回到 `customerGatewayBusinessPrefixCount=0``callerRewriteCount=0``phoneSegmentCount=0``carrierPrefixRuleCount=0``blockedRegionCount=0`
- 未解决问题:A `dlg_list` 仍有历史测试 dialog 残留,包括旧 S28 呼叫;强制挂断 OpenSIPS MI 对部分 dialog 仍可能返回 `Operation failed` 但会推进终止态;本次验证使用 API 级当前通话/挂断,未在浏览器登录态逐页点击 Web 按钮;真实 80 万手机号段尚未导入。
### 2026-06-24 15:00 - 需求修改/二期变更:业务前缀重构第 9 步 A/B/T 验收续跑与 CDR 修复
- 状态:已完成第 9 步主链路验收;仍有历史 dialog 清理和 Web 登录态页面按钮复测遗留
- 操作服务器:A、B、T、本地
- 完成内容:按用户要求先 ping B,`100.90.90.91` 4 次 ping 0% 丢包,SSH 可达,B current 为 `s42-phase2-business-prefix-20260624140000`API/CDR/Recording/Config Publisher 均 active。重新上传 A/B 候选 Lua/配置,发现直接 `redis-cli SCRIPT LOAD "$(cat file)"` 会丢失末尾换行导致 SHA 为 `e1a853...`,已改用 `redis-cli -x SCRIPT LOAD < file`Redis 中确认 `SCRIPT EXISTS 0b7ee595cdcad2f38a670dd8ff3553387820039e` 返回 1。A 侧首次重启时误将 B 本机 `REDIS_URL` 写入 OpenSIPS,导致 cachedb 连接 `127.0.0.1:6379` 失败;根据 S20 Runbook 修正为 OpenSIPS cachedb URL `redis:s20://127.0.0.1:6380/`,通过 A 本地 `lisglosips-redis-auth-proxy` 连接 B Redis,重新 `opensips -C` 并重启 OpenSIPS,日志显示连接 `127.0.0.1:6380`,无新增 Redis connection refused。T 发起 S28 实呼 `s28-1782283559224-yo6iqlqg@lisglosips-t` 成功,但 B CDR Worker 将事件投 deadletter;定位为二期热路径写 `customer_gateway_policy_id=single_gateway` 占位值,而 `raw_cdrs.customer_gateway_policy_id` 仍有外键。修复 `apps/worker-cdr/src/rating.ts` 的 nullable ID 归一化,将 `single_gateway``no_policy_match` 一并转 `null`,补充单元测试,构建并仅重启 B `lisglosips@cdr-worker`。再次发起实呼 `s28-1782283796126-nx2488rc@lisglosips-t`SIP 100/200/BYE 200 成功,A 日志显示 hotpath allow 和 CDR XADDB CDR Worker `processed=1 deadlettered=0``raw_cdrs` 入库 `customer_gateway_policy_id=NULL``business_prefix_id=NULL``landing_caller=s28-ip-1001``landing_callee=13800138000``rating_status=RATED`。随后发起长呼 `s28-1782283960806-f6ct79b4@lisglosips-t` 验证当前通话/强制挂断控制面,A `dlg_list` 可见目标 dialog `03a.af0b33d6`,包含呼叫方 SDP/IP 与落地侧 SDP/IP;执行 `dlg_end_dlg 03a.af0b33d6` 返回 MI `Operation failed`,但目标 dialog 状态进入 `5``timeout=0`,二次检查目标 Call-ID/ID 已不可见,T 测试脚本收到平台侧 BYE 形式结束。该行为与 API 现有兼容逻辑一致:Operation failed 后若 dialog 消失则视为 `HANGUP_CONFIRMED`,若 state=5 则视为 `HANGUP_IN_PROGRESS`
- 修改文件:`apps/worker-cdr/src/rating.ts``apps/worker-cdr/src/rating.spec.ts``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件。
- 执行的关键命令:`ping 100.90.90.91 -n 4`、B SSH 服务状态检查、B Redis `SCRIPT LOAD`/`SCRIPT EXISTS`、A `lisglosips-redis-load-hotpath`、A `opensips -C -f /etc/opensips/opensips.cfg`、A `systemctl restart opensips`、T `python3 /opt/lisglosips-s28/lisglosips-s28-sip.py invite`、A `/usr/local/sbin/lisglosips-call-control dlg_list`、A `/usr/local/sbin/lisglosips-call-control dlg_end_dlg 03a.af0b33d6`、B `raw_cdrs` 查询、`node_modules\.bin\vitest.cmd run apps/worker-cdr/src/rating.spec.ts``node_modules\.bin\tsc.cmd -p apps/worker-cdr/tsconfig.json`
- 验证结果:B ping 0% 丢包,SSH 可达;A OpenSIPS 与 Redis proxy activeA OpenSIPS 新配置语法检查通过并重启成功,日志显示 cachedb 连接 `127.0.0.1:6380`B API ready 正常,config-publisher 仍为 schemaVersion 2 配置版本 `1782282403866`;Worker 定向测试 4 条通过;CDR Worker 修复后实呼入库并计费,`deadlettered=0`;强制挂断控制面对目标 dialog 生效,目标二次检查不可见。
- 回滚方式:A 可恢复 `/var/backups/lisglosips-phase2/20260624144208` 中的 `opensips.cfg``lisglosips_hotpath.lua``opensips -C` 并重启。B 可切回 `/opt/lisglosips/releases/s33-active-calls-20260623094000` 并重启 API/CDR/Recording/Config Publisher;数据库如需回滚,使用 `/data/backups/mysql/20260624T055810Z` 先在隔离环境验证。CDR Worker 单点修复可恢复上一 release 的 `apps/worker-cdr/dist` 并重启 `lisglosips@cdr-worker`
- 未解决问题:A `dlg_list` 仍显示几条历史 S28 测试 dialog 残留,需要单独清理或确认 dialog timeout;Web 页面级“当前通话”列表和按钮因缺少登录态未通过浏览器/API 直接复测,本次以底层 MI 和实呼验证为准;真实 80 万手机号段仍未导入,号码归属地为 UNKNOWN。
- 下一任务:清理历史 dialog 后,在网页登录态下复测“当前通话”页面展示呼叫方 IP/落地 IP 与强制挂断按钮;随后按需导入真实号码库数据并做业务前缀命中场景测试。
### 2026-06-24 14:40 - 需求修改/二期变更:业务前缀重构第 9 步 B 侧发布与 A 侧发布阻塞
- 状态:部分完成;B 侧数据库 migration、release 切换、API/Worker 重启、Redis 配置发布和 preflight 已完成;A 侧 OpenSIPS 未重启到新热路径,A/B/T 实呼验收未完成;当前因 B 再次不可达阻塞
- 操作服务器:B、A、本地;T 未执行实呼
- 完成内容:B 开机恢复后完成 S30 备份,MySQL 备份 `/data/backups/mysql/20260624T055810Z` 与 Redis 备份 `/data/backups/redis/20260624T055810Z` 哈希校验通过。创建 B 新 release `/opt/lisglosips/releases/s42-phase2-business-prefix-20260624140000`,从旧 current 复制基底并覆盖本地构建产物、Prisma schema/migration、业务前缀模块、迁移脚本与 Web dist。执行 Prisma generate 后,发现不能用应用账号跑 DDL,改用 `/etc/lisglosips/secrets/mysql-migrate.env` 迁移账号。号码库 migration `20260624095000_number_library` 先因应用账号无 CREATE 权限失败,随后又暴露 `vendor_gateway_blocked_regions_vendor_gateway_id_region_scope_province_code_city_code_key` 索引名过长;已修正本地 migration 为短名 `vgb_regions_gateway_scope_region_key`,并在 B 上补齐部分落地后的剩余 DDL,再 `prisma migrate resolve --applied`。业务前缀 migration `20260624121000_business_prefix_gateway_rewrite` 暴露 `customer_gateway_business_prefixes_gateway_id_business_prefix_id_key` 索引名过长;已修正为 `cgbp_gateway_business_prefix_key`,在 B 上幂等补齐客户网关新字段、多 IP 表、业务前缀绑定表、主叫前缀表、落地主叫池、CDR 新字段及索引/外键,再标记 applied。Prisma status 显示 4 条 migration 全部 up to date。旧客户网关迁移脚本 dry-run 显示 2 个 safe、0 blockersapply 成功,更新 2 个客户网关,新增 1 条主叫前缀,写入 1 条 outbox。B current 已切换到 `s42-phase2-business-prefix-20260624140000``lisglosips@api``lisglosips@cdr-worker``lisglosips@recording-worker``lisglosips@config-publisher` 重启后均 active。API live/ready 和 HTTPS ready 通过,config-publisher 发布 schemaVersion 2 配置版本 `1782282403866`B preflight 通过。A 侧候选 `opensips.cfg``lisglosips_hotpath.lua` 已上传到 `/tmp` 并安装到 `/etc/opensips``opensips -C` 通过,备份目录为 `/var/backups/lisglosips-phase2/20260624142935`;但发现本地模板 `cachedb_url` 占位符仍未替换,且 B 随后再次不可达,无法安全读取 Redis URL、加载 Redis Lua 或重启 A OpenSIPS。为避免 A 半发布,已将 A `/etc/opensips/opensips.cfg``/etc/opensips/lisglosips_hotpath.lua` 恢复到该备份,恢复后 `opensips -C` 通过且 `opensips` 服务 active。
- 修改文件:`prisma/migrations/20260624095000_number_library/migration.sql``prisma/migrations/20260624121000_business_prefix_gateway_rewrite/migration.sql``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件。
- 执行的关键命令:B `systemctl start lisglosips-backup.service`、备份目录内 `sha256sum -c SHA256SUMS`、创建 release 并覆盖构建产物、`prisma generate``prisma migrate resolve --rolled-back 20260624095000_number_library`、迁移账号执行补齐 DDL、`prisma migrate resolve --applied 20260624095000_number_library``prisma migrate resolve --applied 20260624121000_business_prefix_gateway_rewrite``prisma migrate status``node scripts/phase2-gateway-migration.mjs --json``node scripts/phase2-gateway-migration.mjs --apply --json``systemctl restart lisglosips@api lisglosips@cdr-worker lisglosips@recording-worker lisglosips@config-publisher`、B `lisglosips-release-preflight.sh`A `opensips -C -f /etc/opensips/opensips.cfg`、恢复 A phase2 备份。
- 验证结果:本地 `prisma validate` 使用占位 `DATABASE_URL` 通过;B MySQL/Redis 备份哈希通过;B Prisma status 为 `Database schema is up to date!`B API live/ready 与 HTTPS ready 返回 `ok`config-publisher 日志显示 schemaVersion 2 快照发布成功,manifest 包含 `businessPrefixCount``customerGatewayIpCount``customerGatewayCallerPrefixCount`、号码库与屏蔽地区统计;B preflight ok。A 候选配置语法检查曾通过;A 恢复备份后语法检查通过,OpenSIPS active。B 在最后阶段再次 ping/SSH 超时,A/B/T 实呼未执行。
- 回滚方式:B 应用可执行 `infra/server-b/s30/lisglosips-release-rollback.sh /opt/lisglosips/releases/s33-active-calls-20260623094000` 或手动将 `/opt/lisglosips/current` 指回该 release 并重启 API/CDR/Recording/Config Publisher。数据库已执行向前兼容新增表/列,优先用新页面/API 修正配置;如必须恢复,使用 `/data/backups/mysql/20260624T055810Z` 在隔离环境验证后恢复。Redis 可切回上一配置版本。A 侧已恢复到 `/var/backups/lisglosips-phase2/20260624142935`,未重启进入新热路径。
- 未解决问题:B Tailscale/主机网络不稳定,发布后再次不可达;A Redis URL 占位符问题需在后续发布脚本中修正,不能直接部署模板;尚未将 Lua SHA 加载到 RedisA OpenSIPS 未重启到新热路径;A/B/T 端到端呼叫、强制挂断、话单详情字段验证未执行;真实 80 万手机号段仍未导入。
- 下一任务:先恢复并稳定 B 连通性;确认 B current、服务、Redis 配置版本仍正常;读取受控 Redis URL 并安全替换 A cfg 占位符,加载 Lua SCRIPT 到 Redis`opensips -C` 通过后重启 A OpenSIPS;再执行 T 实呼和当前通话/话单验收。
### 2026-06-24 13:40 - 需求修改/二期变更:业务前缀重构第 9 步三机发布前验收
- 状态:已阻塞;未执行数据库迁移,未部署 release,未发布 Redis,未替换 A OpenSIPS,未重启服务
- 操作服务器:本地、A、T 只读探测;B 未连通
- 完成内容:按第 9 步 Runbook 先复核影响、风险与回滚点;确认本步骤会涉及 B MySQL/Redis 备份、Prisma migration、旧客户网关迁移 dry-run/apply、Redis 配置发布、A OpenSIPS/Lua 语法检查和 A/B/T 实呼验收。执行三机连通性检查时,A `100.90.90.90` 与 T `100.93.185.30` SSH 可达且 OpenSIPS activeB `100.90.90.91` 从开发机 SSH 超时,开发机 `Test-NetConnection` 显示 Ping 与 TCP 22 失败;从 A、T 到 B 的 ping 也 100% 丢包,`nc -vz 100.90.90.91 22` 超时。由于 B 是数据库、Redis、API/Worker 与 release 目标机,未满足安全发布前提,停止执行。
- 修改文件:`IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件。
- 执行的关键命令:`ssh -F .codex-private/ssh/config lisglosips-a "hostname; whoami; systemctl is-active opensips || true"``ssh -F .codex-private/ssh/config lisglosips-t "hostname; whoami; systemctl is-active opensips || true"``Test-NetConnection 100.90.90.91 -Port 22``ping 100.90.90.91 -n 4`、从 A/T 执行 `ping -c 2 -W 2 100.90.90.91``nc -vz -w 3 100.90.90.91 22`
- 验证结果:A/T SSH 与 OpenSIPS 状态正常;B 当前不可达,无法记录 B current release、无法执行 S30 备份、无法执行 Prisma migration dry-run、无法做 Redis 配置发布与 A/B/T 端到端验收。
- 回滚方式:本次未对服务器做写操作,无需远端回滚。恢复 B 连通后,从第 9 步开头重新执行:先记录 B release/migration/Redis 版本,再执行 S30 MySQL/Redis 备份和恢复校验,然后部署新 release 到独立目录并 dry-run 迁移。
- 未解决问题:需要恢复 B 的 Tailscale/主机网络/SSH 可达性;恢复后需确认 B 上 `lisglosips@api``lisglosips@cdr-worker``lisglosips@recording-worker``lisglosips@config-publisher`、MySQL、Redis 状态,再继续第 9 步。
- 下一任务:恢复 B 可达性后重新执行第 9 步发布前验收;如需现场处理 B,优先通过 KVM/控制台检查电源、Tailscale、nftables、sshd 与系统状态。
### 2026-06-24 13:25 - 需求修改/二期变更:业务前缀重构第 8 步旧数据迁移与发布前验收准备
- 状态:已完成本地代码与 Runbook;未执行数据库迁移,未连接服务器,未发布 A/B,未重启服务
- 操作服务器:本地
- 完成内容:新增 `scripts/phase2-gateway-migration.mjs`,用于审计和安全回填旧客户网关数据。脚本默认 dry-run,不写库;只有显式 `--apply` 才写入。自动迁移仅允许“每个客户网关恰好 1 条启用旧策略、被叫任意、主叫任意或主叫前缀”的安全场景;多旧策略、无启用策略、旧被叫等值/前缀、旧主叫等值、新旧线路组冲突均列为人工 blockers。apply 时会写入 `customer_gateways.line_group_id/caller_match_mode/callee_match_mode`、补齐 `customer_gateway_ips``customer_gateway_caller_prefixes`,并写 `customer_gateway_config` outbox 触发配置发布。新增 `docs/PHASE2_BUSINESS_PREFIX_GATEWAY_MIGRATION_RUNBOOK.md`,覆盖 B migration、dry-run/apply、Redis 发布、A OpenSIPS 备份/语法检查/重启、A/B/T 实呼验收和回滚。
- 修改文件:`scripts/phase2-gateway-migration.mjs``docs/PHASE2_BUSINESS_PREFIX_GATEWAY_MIGRATION_RUNBOOK.md``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件。
- 执行的关键命令:`node --check scripts/phase2-gateway-migration.mjs``node_modules\.bin\tsc.cmd -b tsconfig.build.json --pretty``node_modules\.bin\eslint.cmd .``node_modules\.bin\vitest.cmd run`、逐包 `node_modules\.bin\tsc.cmd -p ...``apps\web\node_modules\.bin\vite.CMD build`
- 验证结果:迁移脚本语法检查通过;typecheck 通过;lint 通过;全量 test 通过,25 个测试文件、86 条测试通过;所有 TypeScript package 构建通过;Web 使用 `apps/web` 自身 Vite 5 构建通过。
- 回滚方式:本地回滚可删除上述脚本/Runbook 并恢复状态文件。若未来在 B 执行 `--apply` 后需回滚,优先通过客户网关 API/页面修正新字段并重新发布 Redis;若必须恢复数据库,使用执行前 MySQL 备份在隔离环境验证后恢复。Redis 可切回上一 `cfg:previous_version`A OpenSIPS 可恢复发布前 cfg/Lua 备份并重新 `opensips -C` 后重启。
- 未解决问题:尚未在 B 执行 Prisma migration 和 dry-run;真实 blockers 数量未知;A 侧 `opensips -C``uac_replace_from` 实呼行为尚未验证;客户侧计费仍未切换到客户网关费率;业务前缀绑定仍需按真实客户号码规则人工配置。
- 下一任务:第 9 步本地/三机发布前总验收:先在 B 执行 dry-run 审计并处理 blockers,再按 Runbook 执行 migration、配置发布、A OpenSIPS 语法检查和 A/B/T 实呼验收;执行前必须再次确认备份和维护窗口。
### 2026-06-24 13:15 - 需求修改/二期变更:业务前缀重构第 7 步 CDR Stream 与入库链路
- 状态:已完成本地代码;未执行数据库迁移,未发布服务器,未重启服务
- 操作服务器:本地
- 完成内容:CDR Redis Stream 事件契约新增 `raw_callee``business_prefix_id``business_prefix``landing_caller``landing_callee``normalized_callee`,发布端可显式写入这些字段,解析端兼容旧消息缺字段。CDR Worker 创建 `raw_cdrs` 时写入 `rawCallee``businessPrefixId``businessPrefix``landingCaller``landingCallee`,并将 `normalized_callee` 保留在 payload 中。话单 API DTO 透出原始被叫、业务前缀、落地主叫、落地被叫;Web 话单详情弹窗的链路信息区展示这些字段,列表列宽不增加。
- 修改文件:`packages/redis/src/cdr-stream.ts``packages/redis/src/cdr-stream.spec.ts``apps/worker-cdr/src/rating.ts``apps/worker-cdr/src/rating.spec.ts``apps/api/src/modules/cdrs/cdrs.repository.ts``apps/api/src/modules/cdrs/cdrs.service.spec.ts``apps/web/src/App.jsx``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件。
- 执行的关键命令:`node_modules\.bin\vitest.cmd run packages/redis/src/cdr-stream.spec.ts apps/worker-cdr/src/rating.spec.ts apps/api/src/modules/cdrs/cdrs.service.spec.ts``node_modules\.bin\tsc.cmd -b tsconfig.build.json --pretty``node_modules\.bin\eslint.cmd .``node_modules\.bin\vitest.cmd run`、逐包 `node_modules\.bin\tsc.cmd -p ...``apps\web\node_modules\.bin\vite.CMD build`
- 验证结果:定向测试通过,3 个测试文件、13 条测试通过;typecheck 通过;lint 通过;全量 test 通过,25 个测试文件、86 条测试通过;所有 TypeScript package 构建通过;Web 使用 `apps/web` 自身 Vite 5 构建通过。
- 回滚方式:恢复上述 Stream/Worker/CDR API/Web/状态文件即可;若未来已发布并产生新字段 CDR,回滚旧 Worker 前需确认旧版本能忽略 Stream 中额外字段,或先停止 A 侧新 Lua XADD;数据库已含兼容 nullable 字段,本次无额外 migration。
- 未解决问题:真实 B 环境仍需执行第 1 步 migration 后才能写入新 `raw_cdrs` 字段;A/B/T 尚未发布验收;客户侧计费仍未切换到客户网关费率;旧客户网关策略迁移脚本仍待实现;OpenSIPS `uac_replace_from` 行为仍需实呼验证。
- 下一任务:第 8 步旧数据迁移与发布前验收准备:迁移现有客户网关单 IP/旧策略到新表和单线路组字段,准备 B migration、配置发布、A OpenSIPS 语法检查与 A/B/T 实呼验收脚本。
### 2026-06-24 13:05 - 需求修改/二期变更:业务前缀重构第 6 步 OpenSIPS/Lua 热路径本地改造
- 状态:已完成本地代码;未连接服务器,未发布 A/B,未重启 OpenSIPS,未执行 `opensips -C`
- 操作服务器:本地
- 完成内容:重写 S28 热路径 Lua,使其优先读取 `auth:ip:{source}:gateways` 候选客户网关列表,兼容旧 `auth:ip:{source}` 单网关;按客户网关状态、客户状态、主叫前缀规则、被叫业务前缀规则匹配客户网关;被叫命中业务前缀时剥离前缀并输出真实被叫,同时保留原始被叫、业务前缀 ID/值、号码库归一化号码。线路组选择继续按同组落地网关优先级和地区屏蔽跳过逻辑执行;落地网关命中后按 `landingCalleePrefix` 生成落地被叫,按 `callerRewritePool` 权重和 Call-ID 确定性哈希选择落地指定主叫。保留旧 `customer_gateway_policies` 兼容分支,供未迁移客户网关继续路由。OpenSIPS S28 模板加载 `uac.so`,更新 Lua SHA,接收 24 个热路径返回字段,转发前改写 `$ru` 为落地被叫,并在需要时通过 `uac_replace_from` 改写 From;成功/失败 CDR XADD 追加 `raw_callee``business_prefix_id``business_prefix``landing_caller``landing_callee``normalized_callee`
- 修改文件:`infra/server-a/s28/lisglosips_hotpath.lua``infra/server-a/s28/opensips.cfg``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件。
- 执行的关键命令:`node_modules\.bin\tsc.cmd -b tsconfig.build.json --pretty``node_modules\.bin\eslint.cmd .``node_modules\.bin\vitest.cmd run`、逐包 `node_modules\.bin\tsc.cmd -p ...``apps\web\node_modules\.bin\vite.CMD build`、Node 静态校验 Lua SHA 与 OpenSIPS AVP 索引。
- 验证结果:typecheck 通过;lint 通过;全量 test 通过,25 个测试文件、86 条测试通过;所有 TypeScript package 构建通过;Web 使用 `apps/web` 自身 Vite 5 构建通过;静态校验确认 `opensips.cfg` 中 EVALSHA 与当前 Lua SHA `0b7ee595cdcad2f38a670dd8ff3553387820039e` 一致,`s28_hotpath` AVP 映射覆盖 0-23。当前本地无 OpenSIPS/Lua 运行时,尚未执行 `opensips -C -f /etc/opensips/opensips.cfg`,发布到 A 前必须补做。
- 回滚方式:本地回滚可恢复 `infra/server-a/s28/lisglosips_hotpath.lua``infra/server-a/s28/opensips.cfg` 与状态文件。若未来已发布到 A,回滚前先停止变更窗口,恢复发布前备份的 `/etc/opensips/opensips.cfg``/etc/opensips/lisglosips_hotpath.lua`,重新加载旧 Lua SHA,执行 `opensips -C -f /etc/opensips/opensips.cfg` 通过后重启 OpenSIPS;B 侧 Redis 可切回上一配置版本。
- 未解决问题:真实 A 机语法检查和重启验收尚未执行;`uac_replace_from` 的最终行为需在 A/B/T 呼叫中确认;CDR Worker 目前仍未解析并入库 `raw_callee``business_prefix_id``business_prefix``landing_caller``landing_callee``normalized_callee` 新字段;客户侧计费仍未切换到客户网关费率;旧客户网关策略迁移脚本仍待实现。
- 下一任务:第 7 步改造 CDR Stream/Worker 入库链路,解析并写入业务前缀与落地主被叫字段,同时准备 A/B/T 端到端验收脚本。
### 2026-06-24 12:55 - 需求修改/二期变更:业务前缀重构第 5 步 Redis 配置发布结构
- 状态:已完成本地代码;未执行数据库迁移,未发布服务器,未重启服务,未写真实 Redis
- 操作服务器:本地
- 完成内容:`worker-config-publisher` 快照清单升级为 `schemaVersion: 2`,新增业务前缀数量、客户网关 IP 数、客户网关业务前缀数、客户网关主叫前缀数、落地指定主叫池数量等统计;配置快照新增 `businessPrefixes`,客户网关快照新增多 IP、单线路组、客户侧计费、主叫前缀规则、被叫业务前缀规则,落地网关快照新增 `landingCalleePrefix``callerRewritePool`。Redis 写入新增 `business_prefix:*``business_prefix_value:*``business_prefixes``auth:ip:*:gateways``customer_gateway:*:caller_prefixes``customer_gateway:*:business_prefixes``customer_gateway:*:line_group``vendor_gateway:*:caller_rewrite_pool` 等热路径 key,同时保留旧 `auth:ip:*`、旧客户策略列表和旧落地前缀规则兼容输出。业务前缀 CRUD 增删改启停时新增 `business_prefix_config` outbox,发布器纳入该 aggregate type。
- 修改文件:`apps/worker-config-publisher/src/publisher.ts``apps/worker-config-publisher/src/publisher.spec.ts``apps/api/src/modules/business-prefixes/business-prefixes.repository.ts``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件;执行 `prisma generate` 重新生成本地 Prisma Client。
- 执行的关键命令:`node_modules\.bin\prisma.cmd format``node_modules\.bin\prisma.cmd generate``node_modules\.bin\vitest.cmd run apps/worker-config-publisher/src/publisher.spec.ts``node_modules\.bin\tsc.cmd -b tsconfig.build.json --pretty``node_modules\.bin\eslint.cmd .``node_modules\.bin\vitest.cmd run`、逐包 `node_modules\.bin\tsc.cmd -p ...``apps\web\node_modules\.bin\vite.CMD build`
- 验证结果:Prisma format/generate 通过;发布器定向测试通过,1 条测试通过;typecheck 通过;lint 通过;全量 test 通过,25 个测试文件、86 条测试通过;所有 TypeScript package 构建通过;Web 使用 `apps/web` 自身 Vite 5 构建通过。根级 `node scripts/pnpm-run.mjs typecheck/build` 因当前 shell 找不到 `pnpm``spawnSync pnpm ENOENT`,已用本地 `.cmd` 可执行文件完成等效验证。
- 回滚方式:恢复上述发布器、业务前缀仓储和状态文件即可;若未来已经发布到 B 并产生 `business_prefix_config` outbox,可停止 config-publisher 后将相关 outbox 标为已处理或恢复发布前数据库备份,再切回旧 release。Redis 活跃配置可通过 `CONFIG_PREVIOUS_VERSION_KEY` 回滚到上一版本;本次未对服务器 Redis 进行写入。
- 未解决问题:真实 B 环境仍需先执行第 1 步 migration 后才能运行新发布器;OpenSIPS/Lua 尚未读取新 Redis key,尚未完成业务前缀剥离、客户网关候选匹配、客户侧计费、落地被叫加前缀和指定主叫权重选择;CDR 写入 raw callee/business prefix/landing caller/landing callee 与 A/B/T 验收仍待后续步骤。
- 下一任务:第 6 步改造 OpenSIPS/Lua 热路径:按 IP 候选客户网关、主叫前缀和被叫业务前缀匹配客户网关,并输出剥离后的真实被叫号码。
### 2026-06-24 12:50 - 需求修改/二期变更:业务前缀重构第 4 步落地网关新改写字段
- 状态:已完成本地代码;未执行数据库迁移,未发布服务器,未重启服务
- 操作服务器:本地
- 完成内容:落地网关 API 增加 `landingCalleePrefix``callerRewritePool`;创建/更新时写入 `vendor_gateways.landing_callee_prefix``vendor_gateway_caller_rewrite_pool`,并继续保留旧 `vendor_gateway_prefix_rules` 表作为兼容字段。落地被叫前缀支持空值或英文/数字;指定主叫号码池支持多个号码、权重和启停状态,并校验重复号码。前端落地网关编辑页将旧“被叫前缀转换/主叫前缀转换”替换为“落地要求被叫前缀”和“落地要求指定主叫”,列表新增落地被叫前缀与指定主叫数展示;保存时调用真实 `PATCH /api/v2/vendor-gateways/:id`。顺带修正落地网关子表 ID 生成长度,避免 `vgfp_...``vgcr_...` 超过 32 字符字段长度。
- 修改文件:`apps/api/src/modules/vendor-gateways/vendor-gateways.repository.ts``apps/api/src/modules/vendor-gateways/vendor-gateways.service.ts``apps/api/src/modules/vendor-gateways/vendor-gateways.e2e.spec.ts``apps/web/src/api.js``apps/web/src/App.jsx``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件;执行 `prisma generate` 重新生成本地 Prisma Client。
- 执行的关键命令:`node_modules\.bin\prisma.cmd generate``node_modules\.bin\tsc.cmd -b tsconfig.build.json --pretty false``node_modules\.bin\vitest.cmd run apps/api/src/modules/vendor-gateways/vendor-gateways.e2e.spec.ts``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build``corepack pnpm@10.33.0 test`
- 验证结果:Prisma generate 通过;typecheck 通过;落地网关 e2e 通过,4 条测试通过;lint 通过;workspace build 通过;全量 test 通过,25 个测试文件、86 条测试通过。
- 回滚方式:恢复上述落地网关 API/Web 文件和 `IMPLEMENTATION_STATUS.md` 即可;若只回滚第 4 步,可保留第 1-3 步。若未来已执行 migration 且已录入新落地改写数据,回滚前需导出 `vendor_gateways.landing_callee_prefix``vendor_gateway_caller_rewrite_pool`,确认没有生产配置依赖后再删列/删表或恢复备份。
- 未解决问题:当前前端仍只完善落地网关编辑路径,新增按钮仍是占位;真实 B 环境必须先执行第 1 步 migration 后才能使用本页面新字段;Redis 发布结构、OpenSIPS/Lua 热路径落地被叫加前缀/主叫按权重改写、CDR 写入 landing caller/callee 和 A/B/T 验收仍待后续步骤。
- 下一任务:第 5 步改造 Redis 配置发布与 Worker/热路径配置结构,先在本地生成新配置快照契约,再安排 OpenSIPS/Lua 处理顺序。
### 2026-06-24 12:35 - 需求修改/二期变更:业务前缀重构第 3 步客户网关新模型
- 状态:已完成本地代码;未执行数据库迁移,未发布服务器,未重启服务
- 操作服务器:本地
- 完成内容:客户网关 API 扩展为多 IP、单落地线路组、客户侧计费周期/周期费率、主叫匹配模式与前缀列表、被叫匹配模式与业务前缀多选;创建/更新时写入 `customer_gateway_ips``customer_gateway_caller_prefixes``customer_gateway_business_prefixes`,同时保留旧 `source_ip` 为首个 IP 兼容字段,旧 `customer_gateway_policies` 表和模块未删除。前端客户网关页面新增客户选择、多个 IP、SIP 域、落地线路组、客户费率、主叫规则和业务前缀多选;列表展示落地线路组、客户费率、主叫规则和被叫规则;旧“策略配置”入口已从客户网关列表隐藏。客户网关新增/编辑已接入真实 API。
- 修改文件:`apps/api/src/modules/customer-gateways/customer-gateways.repository.ts``apps/api/src/modules/customer-gateways/customer-gateways.service.ts``apps/api/src/modules/customer-gateways/customer-gateways.e2e.spec.ts``apps/web/src/api.js``apps/web/src/App.jsx``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件;执行 `prisma generate` 重新生成本地 Prisma Client。
- 执行的关键命令:`node_modules\.bin\prisma.cmd generate``node_modules\.bin\tsc.cmd -b tsconfig.build.json --pretty false``node_modules\.bin\vitest.cmd run apps/api/src/modules/customer-gateways/customer-gateways.e2e.spec.ts``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build``corepack pnpm@10.33.0 test`
- 验证结果:Prisma generate 通过;typecheck 通过;客户网关 e2e 通过,5 条测试通过;lint 通过;workspace build 通过;全量 test 通过,25 个测试文件、86 条测试通过。
- 回滚方式:恢复上述客户网关 API/Web 文件和 `IMPLEMENTATION_STATUS.md` 即可;若只回滚第 3 步,可保留第 1 步 Schema 与第 2 步业务前缀管理。本次未连接服务器、未执行 MySQL migration、未写 Redis、未重启 OpenSIPS/RTPEngine/Worker/Web。
- 未解决问题:真实 B 环境必须先执行第 1 步 migration 后才能使用本页面新字段;旧客户网关策略数据尚未迁移到 `customer_gateways.line_group_id` 与新匹配表;落地网关“落地要求被叫前缀/指定主叫池”、Redis 发布结构、OpenSIPS/Lua 热路径号码剥离与落地改写、CDR 写入链路和 A/B/T 验收仍待后续步骤。
- 下一任务:第 4 步改造落地网关 API/Web:用 `landing_callee_prefix``vendor_gateway_caller_rewrite_pool` 替代旧主被叫前缀转换规则。
### 2026-06-24 12:20 - 需求修改/二期变更:业务前缀重构第 2 步 API 与 Web 菜单
- 状态:已完成本地代码;未执行数据库迁移,未发布服务器,未重启服务
- 操作服务器:本地
- 完成内容:新增 `BusinessPrefixesModule`,提供 `GET/POST/PATCH/DELETE /api/v2/business-prefixes` 以及启用/禁用接口;权限复用 `customer_gateways.view/manage`,审计模块记录 business-prefixes 创建、修改、启停、删除。前端新增“业务前缀管理”菜单和页面,支持关键字/状态筛选、新增、编辑、启用/禁用确认、删除确认,删除按钮使用红色警示色;前端 API Client 增加业务前缀相关方法和错误文案。业务前缀删除时若已被客户网关引用会返回 `BUSINESS_PREFIX_IN_USE`
- 修改文件:`apps/api/src/modules/business-prefixes/``apps/api/src/modules/app.module.ts``apps/web/src/api.js``apps/web/src/App.jsx``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件;执行 `prisma generate` 重新生成本地 Prisma Client。
- 执行的关键命令:`node_modules\.bin\prisma.cmd generate``node_modules\.bin\tsc.cmd -b tsconfig.build.json --pretty false``corepack pnpm@10.33.0 lint``node_modules\.bin\vitest.cmd run packages/database/src/schema-contract.spec.ts``corepack pnpm@10.33.0 build``corepack pnpm@10.33.0 test`
- 验证结果:Prisma generate 通过;typecheck 通过;lint 通过;database contract 定向测试通过;workspace build 通过;全量 test 通过,25 个测试文件、86 条测试通过。
- 回滚方式:恢复 `apps/api/src/modules/business-prefixes/``apps/api/src/modules/app.module.ts``apps/web/src/api.js``apps/web/src/App.jsx``IMPLEMENTATION_STATUS.md`,并保留/恢复第 1 步 Schema 取决于是否继续二期;本次未连接服务器、未执行 MySQL migration、未写 Redis、未重启 OpenSIPS/RTPEngine/Worker/Web。
- 未解决问题:当前业务前缀页面依赖第 1 步 migration 实际执行后才可在真实 B API 使用;客户网关多 IP、单线路组、客户侧费率、业务前缀多选、主叫前缀规则、落地网关新改写字段、Redis 发布结构、OpenSIPS/Lua 热路径和 A/B/T 验收仍待后续步骤。
- 下一任务:第 3 步改造客户网关 API/Web:支持多 IP、单落地线路组、客户侧费率、业务前缀多选和主叫前缀规则,同时设计旧策略兼容/迁移方式。
### 2026-06-24 12:05 - 需求修改/二期变更:业务前缀重构第 1 步本地数据模型
- 状态:已完成本地代码;未执行数据库迁移,未发布服务器,未重启服务
- 操作服务器:本地
- 完成内容:按兼容迁移方式新增业务前缀、客户网关多 IP、客户网关单落地线路组、客户侧计费周期/费率、客户网关主叫/被叫匹配模式、客户网关业务前缀关联、客户网关主叫前缀列表、落地网关“落地要求被叫前缀”、落地网关指定主叫号码池和权重、话单 raw callee/business prefix/landing caller/landing callee 等二期字段。保留旧 `customer_gateway_policies``vendor_gateway_prefix_rules`,用于后续 API/Web/OpenSIPS 分阶段切换。
- 修改文件:`prisma/schema.prisma``prisma/migrations/20260624121000_business_prefix_gateway_rewrite/migration.sql``packages/database/src/schema-contract.spec.ts``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件;继续使用本地仓库依赖中的 Prisma `6.19.0`、Vitest `4.0.14`
- 执行的关键命令:`node_modules\.bin\prisma.cmd format``$env:DATABASE_URL='mysql://lisglosips_app@127.0.0.1:3306/lisglosips'; node_modules\.bin\prisma.cmd validate``node_modules\.bin\vitest.cmd run packages/database/src/schema-contract.spec.ts`
- 验证结果:Prisma format 通过;Prisma validate 通过;数据库 contract 定向测试通过,1 个测试文件、6 条测试通过。全量 lint/typecheck/test/build 尚未运行,因为本步只改 Schema、迁移与 contract,下一步 API/Web 改动后再跑全量验证。
- 回滚方式:恢复 `prisma/schema.prisma`、删除 `prisma/migrations/20260624121000_business_prefix_gateway_rewrite/` 并恢复 `packages/database/src/schema-contract.spec.ts``IMPLEMENTATION_STATUS.md` 即可;本次未连接服务器、未执行 MySQL migration、未写 Redis、未重启 OpenSIPS/RTPEngine/Worker/Web。
- 未解决问题:业务前缀管理 API/Web、客户网关新表单与新 API、旧策略迁移脚本、落地网关新字段 API/Web、Redis 发布结构、OpenSIPS/Lua 热路径号码剥离与落地改写、CDR 写入链路、A/B/T 端到端验收仍待后续步骤实施。
- 下一任务:第 2 步实现“业务前缀管理” API 与 Web 菜单,继续保持本地开发,不直接执行 B MySQL migration。
### 2026-06-24 11:58 - 需求修改/二期变更:业务前缀、客户网关匹配和落地号码改写重构需求评估
- 状态:已完成需求文档更新;未改代码,未执行数据库迁移,未发布服务器
- 操作服务器:本地
- 完成内容:将新增“业务前缀管理”菜单、客户网关多 IP、客户网关单线路组、客户网关客户侧费率、主叫前缀/被叫业务前缀匹配规则、真实被叫号码语义、落地要求被叫前缀、落地要求指定主叫号码池和权重选择等需求写入 `SOFTSWITCH_PLATFORM_DESIGN_V2.md`。明确该需求会替代旧客户网关多路由策略模型和旧落地网关主被叫前缀转换规则,必须分阶段迁移,不能两套热路径并存。补充数据模型建议、Redis 热路径结构、OpenSIPS/Lua 处理顺序、CDR 字段、失败原因、风险约束和 10 步实施拆分。
- 修改文件:`SOFTSWITCH_PLATFORM_DESIGN_V2.md``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件。
- 执行的关键命令:`rg` 检查现有客户网关策略、落地前缀转换、号码库、OpenSIPS 热路径和文档位置;文档更新后用关键字复核“业务前缀管理”“customer_gateway_ips”“landing_callee_prefix”“vendor_gateway_caller_rewrite_pool”“BUSINESS_PREFIX_NOT_MATCHED”等条目。
- 验证结果:本次为 Markdown 需求文档更新,未运行 typecheck/lint/test/build;未连接服务器,未执行 Prisma migration,未写 Redis,未重启 OpenSIPS。
- 回滚方式:恢复 `SOFTSWITCH_PLATFORM_DESIGN_V2.md``IMPLEMENTATION_STATUS.md` 本次文档改动即可;不涉及 B release、MySQL、Redis、OpenSIPS、RTPEngine、Worker 或 Web 回滚。
- 未解决问题:实施前仍需确认业务前缀允许字符和长度、是否全局唯一、同一 IP 下多个客户网关规则冲突策略、客户侧费率是否立即替代 S23 最小计费逻辑、旧客户网关多策略如何迁移、落地指定主叫权重选择是否要求严格随机或按 Call-ID 可复现。
- 下一任务:建议先做第 1 步“需求冻结与冲突口径确认”,随后单独开数据库建模/migration,不要直接改 OpenSIPS 热路径。
### 2026-06-24 11:41 - 需求修改/二期变更:号码库第 6、7 步本地实现
- 状态:已完成本地代码;准备提交 Git;未发布到 B/A,未执行数据库迁移,未重启服务
- 操作服务器:本地
- 完成内容:CDR Stream 契约新增可选号码识别快照字段,并保持旧 Stream/pending 消息兼容;CDR Worker 入库 `raw_cdrs` 时写入 `calleeCityCode``calleeCityName``calleeProvinceName``calleeOperator``calleeNumberType`。新增 `CdrsModule`,提供 `GET /api/v2/cdrs``GET /api/v2/cdrs/:id`,复用 `cdr.view` 权限,支持主叫、被叫、客户网关、落地网关、地级市代码、运营商分页筛选,并返回客户/网关/线路组名称与 rated CDR 费用。Web 话单中心改为读取真实 CDR API,列表和详情展示地级市、运营商、号码类型、SIP 状态码、客户费用、成本和毛利。
- 修改文件:`packages/redis/src/cdr-stream.ts``packages/redis/src/cdr-stream.spec.ts``apps/worker-cdr/src/rating.ts``apps/worker-cdr/src/rating.spec.ts``apps/api/src/modules/app.module.ts``apps/api/src/modules/cdrs/cdrs.module.ts``apps/api/src/modules/cdrs/cdrs.controller.ts``apps/api/src/modules/cdrs/cdrs.service.ts``apps/api/src/modules/cdrs/cdrs.repository.ts``apps/api/src/modules/cdrs/cdrs.service.spec.ts``apps/web/src/api.js``apps/web/src/App.jsx``SOFTSWITCH_PLATFORM_DESIGN_V2.md``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件;继续使用 pnpm `10.33.0`、Prisma `6.19.0`
- 执行的关键命令:`corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 vitest run packages/redis/src/cdr-stream.spec.ts apps/worker-cdr/src/rating.spec.ts apps/api/src/modules/cdrs/cdrs.service.spec.ts``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 test``corepack pnpm@10.33.0 build`
- 验证结果:typecheck 通过;目标测试 3 个文件、13 条测试通过;lint 通过;workspace build 通过;第一次将全量 test 与 build 并行执行时 3 个依赖本地服务的 e2e `beforeAll` 因资源争用超时,随后单独重跑 `corepack pnpm@10.33.0 test` 通过,25 个测试文件、85 条测试全部通过。
- 回滚方式:恢复上述文件即可;本次未连接服务器、未发布 B/A、未写 Redis、未执行 MySQL migration、未重启 OpenSIPS,因此无远端回滚项。后续若发布,B 发布前需备份 MySQL 和当前 releaseA 发布仍需先备份 OpenSIPS 配置/Lua 并执行 `opensips -C -f`
- 未解决问题:真实 80 万号段文件导入、Redis 发布容量评估、落地网关屏蔽地区 API/Web 管理入口、A/B/T 地区屏蔽端到端验收仍未实施;当前 CDR API 已能显示新字段,但历史话单在迁移前/旧消息场景下会显示 `UNKNOWN``-`
- 下一任务:提交 Git 后,建议优先补落地网关屏蔽地区管理入口和大批量号码库导入 Runbook,再安排 B/A 发布与 A/B/T 验收。
### 2026-06-24 11:31 - 需求修改/二期变更:号码库第 4、5 步本地实现
- 状态:已完成本地代码;未发布到 B/A,未执行数据库迁移,未重启服务
- 操作服务器:本地
- 完成内容:Config Publisher 快照新增地级市、手机 7 位号段、固话区号、运营商前缀和落地网关屏蔽地区;Redis 写入版本化 `geo_city``phone_segment``area_code``carrier_prefix` key,以及 `vendor_gateway:{id}:blocked_city_codes`/`blocked_province_codes` set。号码库导入新增 `number_library_config` outbox,导入后可触发发布器生成新快照。S28 OpenSIPS Lua 热路径新增被叫号码规范化、手机 7 位号段解析、固话区号解析、运营商前缀解析、落地网关城市/省份屏蔽跳过逻辑;全部可用网关都被地区屏蔽时返回 `NO_VENDOR_ROUTE_REGION_BLOCKED`。S28 `opensips.cfg` 更新 Lua SHA1、读取追加返回字段,并在成功/失败 CDR Stream 中携带 `callee_city_code``callee_city_name``callee_province_name``callee_operator``callee_number_type`
- 修改文件:`apps/worker-config-publisher/src/publisher.ts``apps/worker-config-publisher/src/publisher.spec.ts``apps/api/src/modules/number-library/number-library.repository.ts``infra/server-a/s28/lisglosips_hotpath.lua``infra/server-a/s28/opensips.cfg``SOFTSWITCH_PLATFORM_DESIGN_V2.md``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件;继续使用 pnpm `10.33.0`、Prisma `6.19.0`
- 执行的关键命令:`corepack pnpm@10.33.0 vitest run apps/worker-config-publisher/src/publisher.spec.ts apps/api/src/modules/number-library/number-library.e2e.spec.ts``corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 test``corepack pnpm@10.33.0 build``Get-FileHash -Algorithm SHA1 infra/server-a/s28/lisglosips_hotpath.lua`
- 验证结果:目标测试 2 个文件、4 条测试通过;typecheck 通过;lint 通过;全量 Vitest 24 个测试文件、82 条测试通过;workspace build 通过;本机无 `lua`、无 `opensips` 命令,因此未在本地执行 Lua 解释器语法检查和 `opensips -C`
- 回滚方式:恢复上述本地文件即可;本次未连接服务器、未发布 B/A、未写 Redis、未执行 MySQL migration、未重启 OpenSIPS,因此无远端回滚项。后续若发布:B 需先备份 MySQL/Redis 和当前 release,回滚 Redis 可切回 `cfg:previous_version`A 需先备份 `/etc/opensips/opensips.cfg``/etc/opensips/lisglosips_hotpath.lua`,回滚时恢复备份、执行 `opensips -C -f /etc/opensips/opensips.cfg` 通过后重启 OpenSIPS。
- 未解决问题:第 6 步 CDR Worker 尚未把 `callee_city_code``callee_city_name``callee_province_name``callee_operator``callee_number_type` 入库到 `raw_cdrs` 新字段;落地网关屏蔽地区仍缺少 Web/API 管理入口;真实 80 万号段文件导入、Redis 发布容量评估、A/B/T 端到端地区屏蔽验收仍未执行。
- 下一任务:建议第 6 步改造 CDR Stream/Worker 入库号码识别字段,并补落地网关屏蔽地区的 API/Web 管理入口后,再安排 B/A 发布和 A/B/T 验收。
### 2026-06-24 11:15 - 需求修改/二期变更:号码库第 3 步 Web 本地实现
- 状态:已完成本地代码;未发布到 B,未执行数据库迁移
- 操作服务器:本地
- 完成内容:新增 Web “号码库”运营菜单,页面包含地级市字典、手机号码库、城市区号、运营商号码段规则四个 Tab。每个 Tab 接入 `/number-library/*` API,支持服务端查询入口、筛选、刷新、总数显示和批量 JSON 导入弹窗;导入成功后刷新当前 Tab。同步更新 V2 设计文档,将号码库菜单从三个 Tab 调整为四个 Tab,并补充第 3 步本地实施状态。
- 修改文件:`apps/web/src/api.js``apps/web/src/App.jsx``SOFTSWITCH_PLATFORM_DESIGN_V2.md``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件;继续使用 pnpm `10.33.0`
- 执行的关键命令:`corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build``corepack pnpm@10.33.0 test`
- 验证结果:typecheck 通过;lint 通过;workspace build 通过;第一次将 test 与 build 并行执行时 5 个依赖本地服务的 e2e `beforeAll` 因资源争用超时,随后单独重跑 `corepack pnpm@10.33.0 test` 通过,23 个测试文件、81 条测试全部通过。
- 回滚方式:恢复 `apps/web/src/api.js``apps/web/src/App.jsx``SOFTSWITCH_PLATFORM_DESIGN_V2.md``IMPLEMENTATION_STATUS.md` 本次改动即可;本次未连接服务器、未发布 B、未执行 MySQL migration、未写入 Redis、未重启服务,因此无远端回滚项。
- 未解决问题:号码库前端当前使用批量 JSON 导入,只适合小批验证和接口联调;真实 80 万号段仍需文件导入/后台任务/进度与失败明细。号码解析服务、Config Publisher Redis 快照、OpenSIPS/Lua 屏蔽地区跳转、CDR Worker 新字段入库、话单中心展示与 A/B/T 端到端验收仍未实施。
- 下一任务:建议第 4 步实现 Config Publisher/Redis 热路径快照前,先补大批量导入任务 Runbook 和文件导入接口,避免把 80 万号段通过普通 JSON 表单导入。
### 2026-06-24 11:07 - 需求修改/二期变更:号码库第 1、2 步本地实现
- 状态:已完成本地代码;未发布到 B,未执行数据库迁移
- 操作服务器:本地
- 完成内容:完成号码库第 1、2 步本地基线。Prisma Schema 和迁移新增 `geo_cities``phone_number_segments``phone_area_codes``carrier_prefix_rules``vendor_gateway_blocked_regions`,并在 `raw_cdrs` 预留被叫地级市、省份、运营商和号码类型快照字段。新增 `NumberLibraryModule`,提供城市、手机 7 位号段、固话区号、运营商前缀规则的分页查询与批量 upsert 导入 API,单批限制 1000 条,接入 RBAC 和审计。新增权限 `number_library.view``number_library.manage` 并写入 seed;更新 schema contract 和号码库 e2e 测试。
- 修改文件:`prisma/schema.prisma``prisma/migrations/20260624095000_number_library/migration.sql``apps/api/src/modules/app.module.ts``apps/api/src/modules/number-library/number-library.module.ts``apps/api/src/modules/number-library/number-library.controller.ts``apps/api/src/modules/number-library/number-library.service.ts``apps/api/src/modules/number-library/number-library.repository.ts``apps/api/src/modules/number-library/number-library.e2e.spec.ts``packages/auth/src/index.ts``packages/database/src/schema-contract.spec.ts``prisma/seed.ts``SOFTSWITCH_PLATFORM_DESIGN_V2.md``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件;继续使用 Prisma `6.19.0`、pnpm `10.33.0`
- 执行的关键命令:`corepack pnpm@10.33.0 prisma format``corepack pnpm@10.33.0 prisma:validate``corepack pnpm@10.33.0 prisma:generate``corepack pnpm@10.33.0 vitest run apps/api/src/modules/number-library/number-library.e2e.spec.ts packages/database/src/schema-contract.spec.ts``corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 test``corepack pnpm@10.33.0 build`
- 验证结果:Prisma format/validate/generate 通过;号码库 e2e 与 schema contract 共 8 条测试通过;typecheck 通过;lint 通过;全量 Vitest 23 个测试文件、81 条测试通过;workspace build 通过。
- 回滚方式:恢复上述本地文件改动即可;因为未连接服务器、未发布 B、未执行 MySQL migration、未导入真实数据,所以无远端回滚项。若后续发布到 B,执行迁移前必须先备份 B MySQL;回滚需恢复迁移前备份或执行经验证的反向迁移,并清理新增权限/seed 变更。
- 未解决问题:真实 80 万号段文件导入、导入任务进度/失败明细、号码解析服务、Config Publisher Redis 发布、OpenSIPS/Lua 屏蔽地区跳转、CDR Worker 入库新字段、话单中心展示与筛选、A/B/T 端到端验收仍未实施。
- 下一任务:建议第 3 步 Web “号码库”菜单和三个 Tab;或先实现第 4 步 Config Publisher/Redis 快照结构前的导入任务 Runbook。
### 2026-06-24 09:48 - 需求修改/二期变更:号码库与屏蔽地区路由需求评估
- 状态:已完成
- 操作服务器:本地
- 完成内容:评估新增“号码库”菜单及手机号码库、城市区号、运营商号码段规则三个 Tab 的影响范围;明确需要新增地级市字典 `geo_cities`,用于手机 7 位号段、固话区号、落地网关屏蔽地区和 CDR 快照统一引用;明确每通话单需要保存被叫地级市、省份、运营商和号码类型快照;明确落地网关屏蔽地区应在 OpenSIPS/Lua 热路径中跳过当前网关并尝试同一线路组下一落地网关。已将需求、数据模型建议、Redis 热路径结构、CDR 字段、路由规则、风险和 8 步实施拆分写入 `SOFTSWITCH_PLATFORM_DESIGN_V2.md`,文档版本升级为 V2.2。
- 修改文件:`SOFTSWITCH_PLATFORM_DESIGN_V2.md``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件。
- 执行的关键命令:`rg` 检查现有 Schema、CDR Stream、OpenSIPS Lua 和 Web 中的地区/运营商/屏蔽地区相关实现;文档更新后用 `rg` 校验 V2.2、号码库、`geo_cities``phone_number_segments``carrier_prefix_rules``vendor_gateway_blocked_regions``NO_VENDOR_ROUTE_REGION_BLOCKED` 等关键条目。
- 验证结果:本次为需求评估和 Markdown 文档更新,未运行 typecheck/lint/test/build;未连接服务器,未做数据库迁移、服务重启或数据导入。
- 回滚方式:恢复 `SOFTSWITCH_PLATFORM_DESIGN_V2.md``IMPLEMENTATION_STATUS.md` 本次文档改动即可;不涉及 B release、MySQL、Redis、OpenSIPS、RTPEngine、Worker 或 Web 发布回滚。
- 未解决问题:真实实施前仍需确认号码库数据来源、更新频率、导入格式、行政区划编码口径、运营商枚举口径、省级屏蔽是否需要、以及 80 万号段导入对 MySQL/Redis 内存和发布耗时的影响。
- 下一任务:如确认实施,建议从“数据建模与迁移 + 导入 Runbook”单独开工,不要与 OpenSIPS 热路径改造混在同一次发布。
### 2026-06-24 09:31 - 需求修改/二期变更:用户与角色删除
- 状态:已完成
- 操作服务器:本地、B
- 完成内容:用户管理新增红色“删除”按钮和自研确认弹窗,确认后调用 `DELETE /api/v2/users/:id`;角色与权限新增红色“删除”按钮和自研确认弹窗,内置角色前端禁用删除,后端同时保护内置角色并在角色仍有关联用户时拒绝删除。后端用户删除为软删除:禁用账号、写入 `deletedAt`、清理用户角色关联以释放角色用户数;角色删除为软删除:禁用角色并写入 `deletedAt`。两个删除接口均接入 RBAC `users.manage`/`roles.manage` 和审计日志。未改 Prisma Schema,未执行数据库迁移。
- 修改文件:`apps/api/src/modules/users/users.controller.ts``apps/api/src/modules/users/users.service.ts``apps/api/src/modules/users/users.repository.ts``apps/api/src/modules/roles/roles.controller.ts``apps/api/src/modules/roles/roles.service.ts``apps/api/src/modules/roles/roles.repository.ts``apps/api/src/modules/security/security-rbac-audit.e2e.spec.ts``apps/api/src/modules/users-roles-delete.e2e.spec.ts``apps/web/src/App.jsx``apps/web/src/api.js``IMPLEMENTATION_STATUS.md`B 当前 release `/opt/lisglosips/releases/s33-active-calls-20260623094000` 中已覆盖 API/Web dist。
- 安装软件及固定版本:未安装新软件。
- 执行的关键命令:本地 `corepack pnpm@10.33.0 vitest run apps/api/src/modules/users-roles-delete.e2e.spec.ts``corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 test``corepack pnpm@10.33.0 build`B `tar --no-same-owner -xzf` 覆盖当前 release API/Web dist、`systemctl restart lisglosips@api``nginx -t``systemctl reload nginx``curl http://127.0.0.1:3000/api/v2/health/ready`
- 验证结果:新增用户/角色删除 E2E 3 条通过;typecheck 通过;lint 通过;全量 Vitest 22 个测试文件、77 条测试通过;workspace build 通过。B `lisglosips@api``nginx` activeAPI ready 返回 `config/database/redis` 均 ok;服务日志确认已映射 `DELETE /api/v2/users/:id``DELETE /api/v2/roles/:id`Web 首页 `https://100.90.90.91/` 返回 200 并引用新构建资源。
- 回滚方式:B 已备份覆盖前 dist 到 `/var/backups/lisglosips-s41-user-role-delete/20260624T092950/api-dist``/var/backups/lisglosips-s41-user-role-delete/20260624T092950/web-dist`;如需回滚,将两目录恢复到 `/opt/lisglosips/releases/s33-active-calls-20260623094000/apps/api/dist``/opt/lisglosips/releases/s33-active-calls-20260623094000/apps/web/dist`,重启 `lisglosips@api` 并执行 `nginx -t && systemctl reload nginx`。本次无数据库 schema migration;若已通过新功能误删用户/角色,删除为软删除,需要在维护窗口受控恢复对应 `deleted_at/status`,用户还需按审计和业务确认恢复 `user_roles` 关联。
- 未解决问题:用户管理、角色与权限的新增/编辑/启停仍沿用旧的前端本地态实现,未纳入本次需求重构;未做 A/B/T SIP 端到端,因为本次只涉及 B API/Web 管理面,不影响 Redis 热路径、OpenSIPS、RTPEngine、Worker 或三机呼叫链路。
- 下一任务:等待下一个二期需求。
### 2026-06-23 22:07 - 需求修改/二期变更:S30 后需求写入 V2 设计文档
- 状态:已完成
- 操作服务器:本地
- 完成内容:将 S30 以后用户新增需求整理进 `SOFTSWITCH_PLATFORM_DESIGN_V2.md`,文档版本从 V2.0 调整为 V2.1,新增“25.5 S30 后二期需求变更”章节;同步更新 V2 范围表,纳入当前通话、受控删除、网关/线路组计数、人工充值记录语义、待设计菜单隐藏等内容;补充登录验证码、当前通话和强制挂断、删除/启停确认、充值与计费流水语义、动态路由和 CDR 补齐、前端交互、10 路虚拟呼叫测试工具、二期回归要求;修正 S27 描述中“待设计菜单继续保留标识”的旧表述。
- 修改文件:`SOFTSWITCH_PLATFORM_DESIGN_V2.md``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件。
- 执行关键命令:`rg` 检查 V2 文档关键章节和条目;`git diff -- SOFTSWITCH_PLATFORM_DESIGN_V2.md --stat` 复核文档差异。
- 验证结果:文档已包含 V2.1 状态、当前通话实施范围、待设计菜单隐藏说明、“25.5 S30 后二期需求变更”及 25.5.1-25.5.8 子章节;本次为纯 Markdown 文档更新,未运行 typecheck/lint/build。
- 回滚方式:恢复 `SOFTSWITCH_PLATFORM_DESIGN_V2.md``IMPLEMENTATION_STATUS.md` 本次文档改动即可;不涉及服务器、数据库、Redis、OpenSIPS、RTPEngine 或 Worker 回滚。
- 未解决问题:S40 10 路虚拟呼叫完整场景此前仍在 T 后台运行,本文档更新未等待其全程结束;若需确认最终结果,应继续查看 `/tmp/lisglosips-s40-virtual-calls-20260623T215430.log`
### 2026-06-23 21:51 - 需求修改/二期变更:10 路虚拟呼叫测试工具与当前通话自动刷新
- 状态:已完成工具部署,完整 10 路场景已启动并运行中
- 操作服务器:A、B、T、本地
- 完成内容:当前通话页面新增默认开启的 5 秒自动刷新开关,保留手动刷新;T 新增 `/opt/lisglosips-s40/lisglosips-s40-virtual-calls.py`,可按 15 秒间隔发起 10 路虚拟呼叫,前 5 路先回 `180 Ringing`、45 秒后 `200 OK`、保持 600 秒后 BYE,后 5 路先回 `180 Ringing`、70 秒后 `480 Temporarily Unavailable`。脚本默认使用当前已打通策略的主叫 `s36-1001`、被叫 `13800136036`,并按 Call-ID 序号区分接通/不接通场景。首次完整场景暴露 A `fr_inv_timeout=30` 会在 45 秒接通前返回 408,已将 A OpenSIPS `fr_inv_timeout` 临时调为 95 并重启后重新启动场景。
- 修改文件:`apps/web/src/App.jsx``infra/server-t/s40/lisglosips-s40-virtual-calls.py``infra/server-a/s28/opensips.cfg``IMPLEMENTATION_STATUS.md`B 当前 release `/opt/lisglosips/releases/s33-active-calls-20260623094000` 中已覆盖 `apps/web/dist`T 已部署 `/opt/lisglosips-s40`A 已就地修改 `/etc/opensips/opensips.cfg`
- 安装软件及固定版本:未安装新软件;继续使用 Node.js `v22.22.2`、pnpm `10.33.0`、Vite `5.4.21`、T 既有 Python 3.12。
- 执行关键命令:本地 `corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build`B `tar -xzf` 覆盖 web dist、`nginx -t``systemctl reload nginx``curl -sk https://127.0.0.1/`T `python3 -m py_compile /opt/lisglosips-s40/lisglosips-s40-virtual-calls.py`、短烟测 `--total 1 --answer-ring 2 --hold 3`、后台启动完整场景;A `opensips -C -f /etc/opensips/opensips.cfg``systemctl restart opensips`
- 验证结果:本地 typecheck/lint/build 均通过;B nginx 配置测试通过且 active,首页 HTML 返回新资源 `/assets/index-DwIXfg91.js``/assets/index-TA3aiKvG.css`;T 脚本 Python 编译通过。短烟测先发现 `50620` 被旧 S28 UAS 占用且新号码无策略,已调整为临时停旧 UAS、固定主叫/被叫并按 Call-ID 分流;最终 1 路短烟测通过:收到 `100 Giving it a try``180 Ringing`、延迟后 `200 OK`、3 秒后 BYE 且 UAS 返回 `200 OK`。首次完整场景因 A `fr_inv_timeout=30` 导致 45 秒接通前被 A 返回 408,已停止并恢复旧 UAS;A 超时调为 95 后重新启动完整场景,PID `32120`,日志 `/tmp/lisglosips-s40-virtual-calls-20260623T215430.log`,已验证第 1、2 路均在 45 秒后收到 `200 OK` 并进入 `connected hold=600.0s`
- 回滚方式:B 前端回滚可从 `/var/backups/lisglosips-s40-active-calls-autorefresh/20260623T214444/web-dist` 恢复当前 release 的 `apps/web/dist`,再执行 `nginx -t && systemctl reload nginx`。A 超时回滚可从 `/var/backups/lisglosips-s40-fr-inv-timeout/20260623T215330/opensips.cfg` 恢复 `/etc/opensips/opensips.cfg`,再执行 `opensips -C -f /etc/opensips/opensips.cfg && systemctl restart opensips`。T 脚本回滚可删除 `/opt/lisglosips-s40`,或从 `/var/backups/lisglosips-s40-virtual-calls/20260623T214907/lisglosips-s40` 恢复上一版。若需提前停止完整场景:在 T 执行 `sudo pkill -f /opt/lisglosips-s40/lisglosips-s40-virtual-calls.py`,然后确认 `100.93.185.30:50620` 有旧 S28 UAS;若未恢复,执行 `sudo nohup runuser -u nobody -- /usr/bin/python3 /opt/lisglosips-s28/lisglosips-s28-sip.py uas --host 100.93.185.30 --port 50620 >/tmp/lisglosips-s28-uas.log 2>&1 &`
- 未解决问题:完整 10 路场景仍在运行中,尚未等待约 12 分钟全程结束;测试会产生真实本地开发环境呼叫事件和少量计费/失败 CDR,使用的是现有 S36 测试客户网关/路由。当前通话页面观察地址为 `https://100.90.90.91/`
### 2026-06-23 18:38 - 需求修改/二期变更:话单详情弹窗布局重排
- 状态:已完成
- 操作服务器:B、本地
- 完成内容:话单中心“查看详情”抽屉内部从单列 KeyValue 堆叠调整为主被叫概览、通话摘要、链路信息、时间轴和录音/信令操作分区;新增专用 `cdr-detail` 系列样式,移动端自动切为单列;补充本地 `Icon``arrow` 图标用于主叫到被叫方向展示。
- 修改文件:`apps/web/src/App.jsx``apps/web/src/styles.css``IMPLEMENTATION_STATUS.md`B 当前 release `/opt/lisglosips/releases/s33-active-calls-20260623094000` 中已覆盖 `apps/web/dist`
- 安装软件及固定版本:未安装新软件;继续使用 Node.js `v22.22.2`、pnpm `10.33.0`、Vite `5.4.21`
- 执行关键命令:`corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build`、B `tar -xzf` 覆盖 web dist、`nginx -t``systemctl reload nginx``curl -sk https://127.0.0.1/`
- 验证结果:本地 typecheck/lint/build 均通过;B nginx 配置测试通过且 active;首页 HTML 返回新资源 `/assets/index-CYrIkAAk.js``/assets/index-TA3aiKvG.css`
- 回滚方式:从 B `/var/backups/lisglosips-s39-cdr-detail-layout/20260623T183751/web-dist` 恢复当前 release 的 `apps/web/dist`,再执行 `nginx -t && systemctl reload nginx`;本地恢复 `apps/web/src/App.jsx``apps/web/src/styles.css` 的本次改动。
- 未解决问题:未做浏览器截图交互验证;本次为纯前端抽屉布局调整,不涉及 API、数据库、Redis 热路径、OpenSIPS、RTPEngine 或 Worker。
### 2026-06-23 18:29 - 需求修改/二期变更:列表列宽优化与隐藏待设计菜单
- 状态:已完成
- 操作服务器:B、本地
- 完成内容:增强 `SimpleTable` 支持可选列宽和单元格 class;客户管理、客户网关管理、充值记录、供应商管理、落地网关管理页面的 ID/名称类列已适当收窄,并对长文本使用省略号避免撑宽列表;左侧菜单渲染时过滤 `pending` 项,隐藏“费率与计费”“SIP 运维”“监控告警”“系统设置”等待设计菜单。
- 修改文件:`apps/web/src/App.jsx``apps/web/src/styles.css``IMPLEMENTATION_STATUS.md`B 当前 release `/opt/lisglosips/releases/s33-active-calls-20260623094000` 中已覆盖 `apps/web/dist`
- 安装软件及固定版本:未安装新软件;继续使用 Node.js `v22.22.2`、pnpm `10.33.0`、Vite `5.4.21`
- 执行关键命令:`corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build`、B `tar -xzf` 覆盖 web dist、`nginx -t``systemctl reload nginx``curl -sk https://127.0.0.1/`
- 验证结果:本地 typecheck/lint/build 均通过;B nginx 配置测试通过且 active;首页 HTML 返回新资源 `/assets/index-N60ySjGq.js``/assets/index-uhdBdVZH.css`
- 回滚方式:从 B `/var/backups/lisglosips-s38-web-ui-layout/20260623T182343/web-dist` 恢复当前 release 的 `apps/web/dist`,再执行 `nginx -t && systemctl reload nginx`;本地恢复 `apps/web/src/App.jsx``apps/web/src/styles.css` 的本次改动。
- 未解决问题:未做浏览器截图交互验证;本次为纯前端布局/菜单渲染调整,不涉及 API、数据库、Redis 热路径、OpenSIPS、RTPEngine 或 Worker。
### 2026-06-23 17:47 - 需求修改/二期变更:自研确认弹窗与删除按钮警示色
- 状态:已完成
- 操作服务器:B、本地
- 完成内容:新增通用 `ConfirmDialog`,按现有 Modal/UI 风格替换原生 `window.confirm` 和旧确认弹窗;当前通话强制挂断、客户/供应商/客户网关/落地网关/落地线路组删除、客户网关/落地网关启停、策略删除、抽检规则删除、重置密码确认均使用自研弹窗;所有删除按钮改为 `danger` 红色警示态,包括表格删除、编辑抽屉中的删除行按钮以及线路组移除网关按钮。
- 修改文件:`apps/web/src/App.jsx``apps/web/src/styles.css``IMPLEMENTATION_STATUS.md`B 当前 release `/opt/lisglosips/releases/s33-active-calls-20260623094000` 中已覆盖 `apps/web/dist`
- 安装软件及固定版本:未安装新软件;继续使用 Node.js `v22.22.2`、pnpm `10.33.0`、Vite `5.4.21`
- 执行关键命令:`rg 'window\.confirm' apps/web/src/App.jsx``corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build`、B `tar -xzf` 覆盖 web dist、`nginx -t``systemctl reload nginx``curl -sk https://127.0.0.1/`
- 验证结果:本地 typecheck/lint/build 均通过;`rg 'window\.confirm' apps/web/src/App.jsx` 无匹配;B nginx 配置测试通过且 active;首页 HTML 返回新资源 `/assets/index-CWnw3J5O.js``/assets/index-BuNsq8w9.css`
- 回滚方式:从 B `/var/backups/lisglosips-s37-confirm-dialog-ui/20260623T094434/web-dist` 恢复当前 release 的 `apps/web/dist`,再执行 `nginx -t && systemctl reload nginx`;本地恢复 `apps/web/src/App.jsx``apps/web/src/styles.css` 的本次改动。
- 未解决问题:未做浏览器交互截图验证;功能行为已通过构建和页面资源烟测。
### 2026-06-23 16:20 - 需求修改/二期变更:充值记录过滤、充值性能与新建链路通话测试
- 状态:已完成
- 操作服务器:A、B、T、本地
- 完成内容:客户话单消费不再写入 `customer_recharges`CDR worker 仅写 raw/rated CDR 并扣客户余额;充值记录 API 过滤历史 `worker-cdr`/`cdr:`/`CDR_CHARGE:` 客户消费流水;Web 客户/供应商充值成功后改为局部更新余额与充值列表,不再触发全量 `refreshApi()`,避免供应商充值后长时间停留在“拉取API”。完整流程测试时发现 A 热路径仍硬编码成功 CDR 和落地网关,已新增 S28 Lua 动态选择线路组首个启用落地网关,并让 OpenSIPS 成功 CDR 从 dialog 变量写入动态 customer/gateway/policy/vendorGateway/lineGroup;补齐 B `/etc/lisglosips/config-publisher.env` 并启动 `lisglosips@config-publisher`;修复 CDR worker 对超大 `config_version` 写 MySQL INT 越界的问题。
- 修改文件:`apps/worker-cdr/src/rating.ts``apps/worker-cdr/src/rating.spec.ts``apps/api/src/modules/recharges/recharges.repository.ts``apps/web/src/App.jsx``infra/server-a/s28/opensips.cfg``infra/server-a/s28/lisglosips_hotpath.lua``IMPLEMENTATION_STATUS.md`B 当前 release 的 `apps/api/dist``apps/web/dist``apps/worker-cdr/dist` 已覆盖部署;A `/etc/opensips/opensips.cfg``/usr/local/lib/lisglosips/lisglosips_hotpath.lua` 已更新;B 新增 `/etc/lisglosips/config-publisher.env`
- 验证结果:本地 `corepack pnpm@10.33.0 typecheck` 通过;`corepack pnpm@10.33.0 exec vitest run apps/worker-cdr/src/rating.spec.ts apps/api/src/modules/recharges/recharges.e2e.spec.ts --maxWorkers=1` 通过;全量 `corepack pnpm@10.33.0 exec vitest run --maxWorkers=1` 21 个测试文件 74 条测试通过;最终 `lint` 与全 workspace `build` 通过。B API `/api/v2/health/ready` 正常;客户充值记录按测试客户查询仅 1 条人工充值,未出现 CDR 消费充值;供应商充值接口实测约 818ms,客户充值约 1063ms。A `opensips -C` 通过并 activeRedis active config 版本 `1782201896870``auth:ip:100.93.185.30` 命中新建 `cgw_zz_s36_20260623160046`。完整流程测试创建供应商、落地网关、落地线路组、客户、客户网关、策略、客户充值、供应商充值后,由 T 发起 `s36-1001 -> 13800136036`,当前通话 API 显示 `callerIp=100.93.185.30``landingIp=100.93.185.30`,强制挂断返回 `HANGUP_IN_PROGRESS`,随后当前通话列表清空;最终 CDR `raw_66b73108dc1e485ab17c6ac52a9464c1``RATED`rated CDR `customer_fee=0.012000``vendor_cost=0.012000`,测试客户余额从 `10.000000` 扣为 `9.988000`
- 回滚方式:B 应用回滚可从 `/var/backups/lisglosips-s36-recharge-flow/20260623T074955` 恢复 `api-dist``web-dist``worker-cdr-dist` 并重启 `lisglosips@api``lisglosips@cdr-worker`worker 后续补丁可从 `/var/backups/lisglosips-s36-worker-cdr-config-version/20260623T081403` 恢复。A OpenSIPS 热路径回滚点包括 `/var/backups/lisglosips-s36-hotpath/20260623T080010``/var/backups/lisglosips-s36-dialog-cdr/20260623T080956`,恢复 `/etc/opensips/opensips.cfg` 后执行 `opensips -C -f /etc/opensips/opensips.cfg` 并重启 `opensips`B config-publisher env 回滚点为 `/var/backups/lisglosips-s36-config-publisher-env/20260623T080456`。测试业务数据使用前缀 `S36 Flow 20260623160046`,如需清理,应按 API 软删除客户网关/策略、线路组、落地网关、客户和供应商,不直接删除充值/话单审计记录。
- 遗留问题:测试实体当前保留启用,T 源 IP 会命中新建测试客户网关;若后续要恢复 S28 原始测试路由,应软删除或停用该测试客户网关并等待 config-publisher 发布。两次早期测试呼叫因 A 成功 CDR 动态变量和 worker `config_version` 越界进入 `stream:cdr_deadletter`,未进入 raw/rated CDR,已作为本次排障证据保留。T 在测试中曾出现一次 SSH banner 超时和一次 ping 丢包,重试后恢复。
### 2026-06-23 15:36 - 需求修改/二期变更:列表计数、删除功能和启停确认
- 状态:已完成
- 操作服务器:B、本地
- 完成内容:供应商列表显示落地网关数;客户管理、供应商管理前端增加删除按钮和确认弹窗,后端沿用已有软删除并分别在客户网关数/落地网关数大于 0 时拒绝删除。客户网关、落地网关后端新增 `DELETE` 软删除接口并纳入审计;客户网关删除会同步软删该网关下的路由策略并发布配置 outbox;落地网关删除在仍被未删除落地线路组引用时拒绝,避免热配置断链。落地线路组 API 新增 `customerGatewayCount`,按正在使用该线路组的去重客户网关数统计;删除落地线路组时若该数大于 0 则拒绝。Web 客户网关、落地网关启用/禁用前增加确认弹窗;客户网关、落地网关、落地线路组页面增加删除按钮和确认弹窗,并接入真实 API 列表/操作。
- 修改文件:`IMPLEMENTATION_STATUS.md``apps/api/src/modules/customer-gateways/customer-gateways.controller.ts``apps/api/src/modules/customer-gateways/customer-gateways.service.ts``apps/api/src/modules/customer-gateways/customer-gateways.repository.ts``apps/api/src/modules/customer-gateways/customer-gateways.e2e.spec.ts``apps/api/src/modules/vendor-gateways/vendor-gateways.controller.ts``apps/api/src/modules/vendor-gateways/vendor-gateways.service.ts``apps/api/src/modules/vendor-gateways/vendor-gateways.repository.ts``apps/api/src/modules/vendor-gateways/vendor-gateways.e2e.spec.ts``apps/api/src/modules/landing-line-groups/landing-line-groups.repository.ts``apps/api/src/modules/landing-line-groups/landing-line-groups.e2e.spec.ts``apps/web/src/api.js``apps/web/src/App.jsx`B 当前 release `/opt/lisglosips/releases/s33-active-calls-20260623094000` 中覆盖 API/Web dist。
- 安装软件及固定版本:未安装新软件;继续使用 B 既有 Node.js `v22.22.2`、pnpm `10.33.0`、NestJS、Vite、Prisma、Nginx。
- 执行的关键命令:本地 `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 apps/api/src/modules/customer-gateways/customer-gateways.e2e.spec.ts apps/api/src/modules/vendor-gateways/vendor-gateways.e2e.spec.ts apps/api/src/modules/landing-line-groups/landing-line-groups.e2e.spec.ts apps/api/src/modules/customers/customers.e2e.spec.ts --maxWorkers=1``corepack pnpm@10.33.0 exec vitest run apps/api/src/modules/vendors/vendors.e2e.spec.ts --maxWorkers=1`B `tar -xzf` 覆盖当前 release API/Web dist、`systemctl restart lisglosips@api``nginx -t``systemctl reload nginx``curl http://127.0.0.1:3000/api/v2/health/ready`
- 验证结果:typecheck、lint、build 均通过。相关 e2e 验证通过:客户、供应商、客户网关、落地网关、落地线路组;一次组合运行中 `vendors.e2e` 因 Nest 多模块启动超过 10 秒 hookTimeout 被跳过/失败,随后单独运行 `vendors.e2e` 3 条测试全部通过。最终全量 Vitest 单 worker 模式 21 个测试文件、74 条测试通过。B `lisglosips@api``nginx` activeAPI ready 返回 `config/database/redis` 均 ok。B 只读烟测通过:`/vendors` 返回 `gatewayCount``/landing-line-groups` 返回 `customerGatewayCount`;当前本地数据示例 `ven_s28_t.gatewayCount=1``llg_s28_t.customerGatewayCount=1`
- 回滚方式:B 已备份覆盖前 dist 到 `/var/backups/lisglosips-s35-delete-count-confirm/20260623T153500/api-dist``/var/backups/lisglosips-s35-delete-count-confirm/20260623T153500/web-dist`;如需回滚,将两目录恢复到 `/opt/lisglosips/releases/s33-active-calls-20260623094000/apps/api/dist``/opt/lisglosips/releases/s33-active-calls-20260623094000/apps/web/dist`,重启 `lisglosips@api``nginx -t && systemctl reload nginx`。本次无数据库 schema migration;如已通过新功能执行业务删除,删除为软删除,需要按具体对象在维护窗口受控恢复 `deleted_at/status` 与相关 outbox。
- 未解决问题:落地网关删除目前在被落地线路组引用时会拒绝,比本次需求更保守,目的是避免线路组热配置引用已删除网关;如果后续希望“删除落地网关时自动从线路组移除”,需单独设计优先级重排和配置发布语义。
- 下一任务:继续等待下一项二期需求;阿里云迁移仍不要直接开始。
### 2026-06-23 15:12 - 需求修改/二期变更:当前通话增加呼叫方 IP 与落地 IP
- 状态:已完成
- 操作服务器:B、T、本地
- 完成内容:在当前通话 API 归一化结果中新增 `callerIp``landingIp` 字段;解析优先级为 SIP Contact、SDP `c=IN IP4/IP6`、SIP URI、bind addr 兜底。Web “当前通话 / 实时呼叫列表”新增“呼叫方 IP”和“落地 IP”两列。该变更不涉及数据库 schema、Redis 热路径、OpenSIPS 配置、RTPEngine 或 Worker。
- 修改文件:`IMPLEMENTATION_STATUS.md``apps/api/src/modules/active-calls/active-calls.service.ts``apps/api/src/modules/active-calls/active-calls.service.spec.ts``apps/web/src/App.jsx`B 当前 release `/opt/lisglosips/releases/s33-active-calls-20260623094000` 中覆盖 API/Web dist。
- 安装软件及固定版本:未安装新软件;继续使用 B 既有 Node.js `v22.22.2`、pnpm `10.33.0`、NestJS、Vite、Nginx。
- 执行的关键命令:本地 `corepack pnpm@10.33.0 exec vitest run apps/api/src/modules/active-calls/active-calls.service.spec.ts --maxWorkers=1``corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build`B `tar -xzf` 覆盖当前 release API/Web dist、`systemctl restart lisglosips@api``nginx -t``systemctl reload nginx``curl http://127.0.0.1:3000/api/v2/health/ready`T `/opt/lisglosips-s28/lisglosips-s28-sip.py invite --timeout 20 --hold 60`
- 验证结果:当前通话单测 5 条通过;typecheck、lint、build 均通过。B `lisglosips@api``nginx` activeAPI ready 返回 `config/database/redis` 均 ok。A/B/T 端到端验证:T 发起保持呼叫后,B API 查询到当前通话并返回 `callerIp=100.93.185.30``landingIp=100.93.185.30`;当前本地 T 同时模拟主叫与落地 UAS,因此两者相同符合测试拓扑。API 挂断返回 `HANGUP_IN_PROGRESS`,等待 OpenSIPS 删除延迟后当前通话列表清空。
- 回滚方式:B 已备份覆盖前 dist 到 `/var/backups/lisglosips-s34-active-call-ips/20260623T150800/api-dist``/var/backups/lisglosips-s34-active-call-ips/20260623T150800/web-dist`;如需回滚,将两目录恢复到 `/opt/lisglosips/releases/s33-active-calls-20260623094000/apps/api/dist``/opt/lisglosips/releases/s33-active-calls-20260623094000/apps/web/dist`,重启 `lisglosips@api``nginx -t && systemctl reload nginx`。无数据库、A/OpenSIPS、Redis、RTPEngine 回滚项。
- 未解决问题:真实生产落地网关若不返回 SIP Contact 或 SDP 连接地址,`landingIp` 会退回到可解析的 URI/bind 地址,可能显示为 OpenSIPS 本地地址;后续可在路由侧显式写入 dialog 变量以获得更精确的落地网关 IP。
- 下一任务:继续等待下一项二期需求;阿里云迁移仍不要直接开始。
### 2026-06-23 10:15 - 需求修改/二期变更:新增“当前通话”菜单与强制挂断
- 状态:已完成
- 操作服务器:A、B、T、本地
- 完成内容:新增 Web “业务 / 当前通话”菜单,可刷新查看 OpenSIPS 当前 Dialog 列表,展示主叫、被叫、Call-ID、状态、时长、联系方式,并提供确认后的“挂断”按钮。B API 新增 `GET /api/v2/active-calls``POST /api/v2/active-calls/:id/hangup`,使用新增权限 `active_calls.view``active_calls.manage` 和审计拦截器;超级管理员具备全部权限,技术运维具备查看/挂断,运营管理员仅查看。A 启用 OpenSIPS `dialog.so` 并在 INVITE 路径执行 `create_dialog()`;新增受限 SSH forced-command `/usr/local/sbin/lisglosips-call-control`,仅允许 `dlg_list``dlg_end_dlg`MI HTTP 仍绑定本机 `127.0.0.1:8888`。B 新增专用 `lisglosips` 服务用户出站 SSH key 与 `/etc/lisglosips/call-control/ssh_config`,API 通过该受限通道访问 A;修复 `AUTH_COOKIE_SECURE``ACTIVE_CALLS_SSH_HOST` 空值导致的 B API 启动/调用兼容问题。
- 修改文件:`IMPLEMENTATION_STATUS.md``apps/api/src/modules/active-calls/active-calls.controller.ts``apps/api/src/modules/active-calls/active-calls.module.ts``apps/api/src/modules/active-calls/active-calls.service.ts``apps/api/src/modules/active-calls/active-calls.service.spec.ts``apps/api/src/modules/active-calls/opensips-mi.client.ts``apps/api/src/modules/app.module.ts``apps/api/src/shared/config.ts``apps/web/src/App.jsx``apps/web/src/api.js``packages/auth/src/index.ts``prisma/seed.ts``infra/server-a/s28/opensips.cfg``infra/server-a/s32/lisglosips-call-control`A `/etc/opensips/opensips.cfg`、A `/usr/local/sbin/lisglosips-call-control`、B `/etc/lisglosips/api.env`、B `/etc/lisglosips/call-control/*`、B release `/opt/lisglosips/releases/s33-active-calls-20260623094000`
- 安装软件及固定版本:未安装新软件;使用 A 既有 OpenSIPS `3.4.11` dialog 模块、B 既有 Node.js `v22.22.2`、pnpm `10.33.0`、NestJS、Vite、Prisma、MySQL、Redis、Nginx。
- 执行的关键命令:本地 `corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 exec vitest run --maxWorkers=1``corepack pnpm@10.33.0 build`A `opensips -C -f``systemctl restart opensips``systemctl is-active opensips rtpengine`B `systemctl start lisglosips-backup.service``corepack pnpm@10.33.0 db:seed``systemctl restart lisglosips@api``nginx -t``systemctl reload nginx``curl http://127.0.0.1:3000/api/v2/health/ready`T `/opt/lisglosips-s28/lisglosips-s28-sip.py invite --timeout 20 --hold 60`
- 验证结果:本地 lint 通过;typecheck 通过;全量 Vitest 单 worker 模式 21 个测试文件、73 条测试通过;workspace build 通过。A OpenSIPS 配置语法检查通过并已重启,`opensips``rtpengine` activeB `lisglosips@api``nginx``mysql``redis-server` activeAPI ready 返回 `config/database/redis` 均 ok。未登录访问 `/api/v2/active-calls` 返回 401;登录态访问空闲列表返回 `source=opensips-mi,total=0`。A/B/T 端到端验收:T 发起保持呼叫后,B API 查询到 1 条当前通话;调用 B API 挂断返回 `HANGUP_IN_PROGRESS`OpenSIPS Dialog 状态进入 `5`,等待删除延迟后 B API 当前通话列表清空。
- 回滚方式:B 应用回滚可将 `/opt/lisglosips/current` 切回上一 release `/opt/lisglosips/releases/s32-captcha-admin-20260622124659`,重启 `lisglosips@api` 并 reload Nginx;如需还原 B 环境变量,使用 `/var/backups/lisglosips-s33-active-calls-b/20260623T014402Z/api.env`。A 回滚可从 `/var/backups/lisglosips-s33-active-calls/20260623T014315Z` 恢复 `/etc/opensips/opensips.cfg` 后重启 `opensips`,并移除 `/usr/local/sbin/lisglosips-call-control``lisglo-callctl` 用户/authorized_keys 以及 B `/etc/lisglosips/call-control` 专用 key。数据库无 schema migration;新增权限/角色授权来自 seed,可保留,若需清理需维护窗口执行受控 SQL。B 发布前 MySQL/Redis 备份为 `/data/backups/mysql/20260623T014114Z``/data/backups/redis/20260623T014114Z`
- 未解决问题:OpenSIPS HTTP MI 的 `dlg_end_dlg` 在实际终止 Dialog 时会返回 `500 Operation failed`,但 Dialog 会进入 state `5` 并随后清空;API 已按“500 后复查消失或 state 5 则视为已受理”处理。当前通话列表依赖 OpenSIPS dialog 模块,只覆盖本地 KVM A 单节点;未来多 OpenSIPS 节点需按节点聚合。强制挂断会影响被选中的实时呼叫,操作已纳入 RBAC 与审计,生产迁移前需再次演练。
- 下一任务:继续等待下一项二期需求;阿里云迁移仍不要直接开始。
### 2026-06-22 14:20 - 需求修改/二期变更:修复登录验证码刷新后不显示
- 状态:已完成,等待用户浏览器复验
- 操作服务器:B、本地
- 完成内容:定位验证码接口返回的是 `data:image/svg+xml;base64,...` SVG 图片,而 B Nginx 安全头 `Content-Security-Policy` 未显式允许 `img-src data:`,浏览器会拦截验证码图片渲染,表现为“验证码刷新不出来”。已将仓库和 B 当前 Nginx 安全头更新为 `default-src 'self'; img-src 'self' data:; base-uri 'self'; frame-ancestors 'none'; form-action 'self'; object-src 'none'`,保留原有同源、禁止嵌入和禁止 object 的安全基线。
- 修改文件:`IMPLEMENTATION_STATUS.md``infra/server-b/s05/nginx/lisglosips-security-headers.conf`B `/etc/nginx/snippets/lisglosips-security-headers.conf`
- 安装软件及固定版本:未安装新软件;继续使用 B 既有 Nginx `1.24.0-2ubuntu7.12` 和当前 release `/opt/lisglosips/releases/s32-captcha-admin-20260622124659`
- 执行的关键命令:本地 `corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 prisma:validate``corepack pnpm@10.33.0 exec vitest run --maxWorkers=1``corepack pnpm@10.33.0 build`B `cp -a` 备份 Nginx 安全头、`nginx -t``systemctl reload nginx``curl -k -sI https://127.0.0.1/``curl http://127.0.0.1:3000/api/v2/auth/captcha`
- 验证结果:本地 lint、typecheck、Prisma validate 均通过;全量 Vitest 单 worker 模式 20 个测试文件、69 条测试通过;workspace build 通过。B 当前 release 确认为 `/opt/lisglosips/releases/s32-captcha-admin-20260622124659``nginx -t` 通过并已 reload;HTTPS 首页响应头已返回 `Content-Security-Policy: default-src 'self'; img-src 'self' data:; base-uri 'self'; frame-ancestors 'none'; form-action 'self'; object-src 'none'``GET http://127.0.0.1:3000/api/v2/auth/captcha` 返回新的 `captchaId``data:image/svg+xml;base64,...` 图片和过期时间;B `nginx``lisglosips@api``lisglosips@cdr-worker``lisglosips@recording-worker` 均 active。本次未做 A/B/T SIP 呼叫链路,因为变更只涉及 B Web 安全头,不影响 Redis 热路径、OpenSIPS、RTPEngine、CDR 或录音链路。
- 回滚方式:B 已备份原安全头到 `/var/backups/lisglosips-s32-captcha-csp/20260622T061944Z/lisglosips-security-headers.conf`;如需回滚,恢复该文件到 `/etc/nginx/snippets/lisglosips-security-headers.conf`,执行 `nginx -t` 通过后 `systemctl reload nginx`。仓库侧回滚 `infra/server-b/s05/nginx/lisglosips-security-headers.conf` 的本次一行 CSP 修改即可。无数据库、Redis、服务 release、OpenSIPS 或 RTPEngine 回滚项。
- 未解决问题:尚未由用户在真实浏览器中点击验证码刷新并确认图片可见;验证码仍为 API 单进程内存存储,后续多 API 实例/生产高可用建议迁移到 Redis 或共享缓存。
- 下一任务:用户浏览器复验 B 登录页验证码刷新;继续等待下一项二期需求。
### 2026-06-22 12:54 - 需求修改/二期变更发布到 B:登录图形验证码与 admin 初始账号
- 状态:已完成,等待用户验收
- 操作服务器:B、本地
- 完成内容:在登录接口新增一次性图形验证码,`GET /api/v2/auth/captcha` 生成 5 位 SVG 验证码并只返回 `captchaId`、图片 data URL 和过期时间;`POST /api/v2/auth/login` 必须携带 `captchaId``captchaCode`,验证码错误返回 `401 AUTH_CAPTCHA_INVALID`,缺少登录字段继续返回统一认证失败以降低账号枚举风险。Web 登录页新增验证码图片、输入框和刷新按钮,登录失败后自动刷新验证码。B 数据库已新增/更新用户名 `admin`,启用状态,授予超级管理员角色,并按用户提供的初始口令写入 Argon2id 哈希;未在文档或命令输出中记录明文口令。发布为 B 新 release `/opt/lisglosips/releases/s32-captcha-admin-20260622124659``/opt/lisglosips/current` 已切换到该 release。
- 修改文件:`IMPLEMENTATION_STATUS.md``apps/api/src/modules/auth/auth.controller.ts``apps/api/src/modules/auth/auth.module.ts``apps/api/src/modules/auth/captcha.service.ts``apps/api/src/modules/auth/auth.e2e.spec.ts``apps/web/src/App.jsx``apps/web/src/api.js``apps/web/src/styles.css`B 新 release 中覆盖 API/Web dist 与对应源码。延续上一轮登录防爆破相关文件:`apps/api/src/modules/auth/auth.service.ts``apps/api/src/modules/auth/auth.service.spec.ts``apps/api/src/shared/config.ts`
- 安装软件及固定版本:未安装新软件;继续使用 B 既有 Node.js `v22.22.2`、pnpm `10.33.0`、NestJS、Vite、Prisma、Nginx、Grafana `13.0.2`、Prometheus `2.45.3`
- 执行的关键命令:本地 `corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 prisma:validate``corepack pnpm@10.33.0 exec vitest run --maxWorkers=1``corepack pnpm@10.33.0 build``tar -czf` 打包、`scp` 上传;B `systemctl start lisglosips-backup.service``cp -a` 创建新 release、`tar --no-same-owner -xzf` 覆盖产物、`ln -sfn` 切换 current、`systemctl restart lisglosips@api``nginx -t``systemctl reload nginx``/tmp/lisglosips-release-preflight.sh`;B 通过临时脚本按用户提供口令 upsert `admin` 用户并撤销旧会话。
- 验证结果:本地 lint、typecheck、Prisma validate、build 均通过;全量 Vitest 单 worker 模式 20 个测试文件、69 条测试通过,新增认证 e2e 覆盖 captcha 接口不泄露答案、无验证码登录被拒绝、携带正确验证码后可登录。B 发布前备份目录为 MySQL `/data/backups/mysql/20260622T044834Z`、Redis `/data/backups/redis/20260622T044834Z`,两者 `SHA256SUMS` 校验通过。B preflight 通过:MySQL、Redis、Nginx、API、CDR Worker、Recording Worker、Heplify、Prometheus、Grafana 均 activeAPI ready 返回 okNginx 配置测试成功。`GET http://127.0.0.1:3000/api/v2/auth/captcha` 返回 captcha JSON 与 SVG data URL 且不含答案;使用错误验证码调用登录返回 `401 AUTH_CAPTCHA_INVALID``https://127.0.0.1/` 返回新前端构建资产 `index-B6ms7F5U.js``index-C60rDiFV.css`
- 回滚方式:如需回滚应用,按 `docs/S30_RELEASE_BACKUP_RUNBOOK.md` 将 B `/opt/lisglosips/current` 切回上一稳定 release `/opt/lisglosips/releases/s31-auth-login-20260622115851` 或原冻结 release `/opt/lisglosips/releases/s28-v2-20260621220924`,重启 `lisglosips@api` 并 reload Nginx。数据库只新增/更新 `admin` 用户密码哈希和会话撤销记录;如需回滚账号变更,可在维护窗口按 B 发布前 MySQL 备份 `/data/backups/mysql/20260622T044834Z` 恢复,或单独执行受控账号禁用/密码重置脚本,执行前需再次说明影响和回滚点。
- 未解决问题:尚未由用户在浏览器中完成真实登录验收;验证码当前为单 API 进程内存存储,适配当前 B 单实例开发环境,后续多 API 实例/生产高可用应迁移到 Redis 或共享缓存。登录防爆破仍为进程内存级节流,生产多实例同样建议升级为 Redis/网关级共享限流。本次未做 A/B/T SIP 呼叫链路,因为变更只涉及 Web/API 登录鉴权和 B 用户数据,不影响 Redis 热路径、OpenSIPS、RTPEngine、CDR 或录音链路。
- 下一任务:用户验收 B 登录页;继续等待下一项二期需求。
### 2026-06-22 12:04 - 需求修改/二期变更发布到 B:登录页与登录防爆破
- 状态:已完成,等待用户验收
- 操作服务器:B、本地
- 完成内容:将本地登录页与登录防爆破变更发布为 B 新 release `/opt/lisglosips/releases/s31-auth-login-20260622115851``/opt/lisglosips/current` 已切换到该 release。发布方式为复制上一 release `/opt/lisglosips/releases/s28-v2-20260621220924` 后覆盖本次 API/Web 构建产物和相关源码;未执行数据库迁移,未改 Redis 配置,未触碰 A/T、OpenSIPS 或 RTPEngine。发布前触发 B MySQL/Redis 备份并校验哈希。
- 修改文件:本地 `IMPLEMENTATION_STATUS.md`B 新 release 中的 `apps/api/dist/``apps/api/src/modules/auth/auth.service.ts``apps/api/src/modules/auth/auth.service.spec.ts``apps/api/src/shared/config.ts``apps/web/dist/``apps/web/src/App.jsx``apps/web/src/api.js``apps/web/src/styles.css`
- 安装软件及固定版本:未安装新软件;继续使用 B 既有 Node.js `v22.22.2`、pnpm `10.33.0`、NestJS、Vite、Prisma、Nginx、Grafana `13.0.2`、Prometheus `2.45.3`
- 执行的关键命令:本地 `corepack pnpm@10.33.0 build``tar -czf` 打包发布覆盖包、`scp` 上传;B `systemctl start lisglosips-backup.service``cp -a` 创建新 release、`tar --no-same-owner -xzf` 覆盖产物、`ln -sfn` 切换 current、`systemctl restart lisglosips@api``nginx -t``systemctl reload nginx``/tmp/lisglosips-release-preflight.sh`
- 验证结果:发布前备份目录为 MySQL `/data/backups/mysql/20260622T040339Z`、Redis `/data/backups/redis/20260622T040339Z`,两者 `SHA256SUMS` 校验通过。B preflight 通过:MySQL、Redis、Nginx、API、CDR Worker、Recording Worker、Heplify、Prometheus、Grafana 均 activeAPI ready 返回 okNginx 配置测试成功。`GET http://127.0.0.1:3000/api/v2/health/ready` 返回 ok;未登录访问 `/api/v2/dashboard/summary` 返回 `401 AUTH_REQUIRED``https://127.0.0.1/` 返回新前端构建资产 `index-7KkZS1Fz.js``index-ChBVfysf.css`。Grafana health `http://127.0.0.1:3001/api/health` 返回 database ok/version `13.0.2`Prometheus `/-/ready` 返回 ready。
- 回滚方式:B 应用可执行 `sudo /tmp/lisglosips-release-preflight.sh` 复核状态;如需回滚,按 `docs/S30_RELEASE_BACKUP_RUNBOOK.md``/opt/lisglosips/current` 切回 `/opt/lisglosips/releases/s28-v2-20260621220924`,重启 `lisglosips@api` 并 reload Nginx;数据库和 Redis 未变更,备份目录仅作为发布前恢复点保留。
- 未解决问题:尚未由用户在浏览器中完成真实登录验收;本次未做 A/B/T SIP 呼叫链路,因为变更只涉及 Web/API 登录鉴权,不影响 Redis 热路径、OpenSIPS、RTPEngine、CDR 或录音链路。后续多 API 实例/生产环境仍建议把登录节流升级为 Redis 或网关级共享限流。
- 下一任务:用户验收登录页面;继续等待下一项二期需求。
### 2026-06-22 11:53 - 需求修改/二期变更:登录页与登录防爆破
- 状态:已完成
- 操作服务器:本地
- 完成内容:完善 Web 登录闭环,打开应用时先尝试 `/api/v2/auth/refresh` 恢复会话,未认证时只显示登录页面;输入用户名和密码调用 `/api/v2/auth/login`,登录成功后保存 access token 并加载业务数据;顶部显示当前登录用户并支持退出,退出时调用 `/api/v2/auth/logout`、清理本地 token 和页面数据。后端在既有 Argon2id、用户失败次数和账号锁定基础上,新增内存级登录节流,按用户名与来源 IP 统计失败次数;即使用户名不存在,连续失败也会触发 `429 AUTH_LOGIN_THROTTLED`,避免撞库/爆破时泄露账号存在性。新增运行时配置 `AUTH_LOGIN_THROTTLE_MAX_FAILURES``AUTH_LOGIN_THROTTLE_WINDOW_SECONDS`
- 修改文件:`IMPLEMENTATION_STATUS.md``apps/web/src/App.jsx``apps/web/src/api.js``apps/web/src/styles.css``apps/api/src/shared/config.ts``apps/api/src/modules/auth/auth.service.ts``apps/api/src/modules/auth/auth.service.spec.ts`
- 安装软件及固定版本:未安装新软件;继续使用 Node.js/pnpm、NestJS、Vite、Vitest、Prisma 既有版本。
- 执行的关键命令:本地 `corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 exec vitest run apps/api/src/modules/auth/auth.service.spec.ts apps/api/src/modules/auth/auth.e2e.spec.ts``corepack pnpm@10.33.0 exec vitest run --maxWorkers=1``corepack pnpm@10.33.0 prisma:validate``corepack pnpm@10.33.0 build`
- 验证结果:lint 通过;typecheck 通过;认证单测/e2e 6 条通过;全量 Vitest 使用 `--maxWorkers=1` 后 20 个测试文件、69 条测试全部通过;Prisma schema validate 通过;workspace build 通过。首次全量并行测试曾出现多个 Nest e2e `beforeAll` 超时,改为单 worker 后全部通过,判断为本地并行资源/初始化竞争导致的假失败。
- 回滚方式:恢复上述 7 个文件到本次修改前版本即可;无数据库 schema 变更、无服务器写操作、无 Redis/OpenSIPS/RTPEngine 配置变更。若已发布到 B,可按 `docs/S30_RELEASE_BACKUP_RUNBOOK.md` 使用上一 release 切回并重启 API/Web 相关服务。
- 未解决问题:本次只完成本地代码修改,尚未发布到 B 当前冻结 release,也未做浏览器视觉截图或 A/B/T 端到端验收;登录节流为单进程内存级,足以覆盖当前单 API 进程开发环境,后续多 API 实例或生产环境建议迁移到 Redis/网关级限流以跨进程共享计数。
- 下一任务:等待下一个二期需求;如需上线本变更,应按 S30 发布流程创建新 B release 并做 preflight/登录冒烟。
### 2026-06-22 10:35 - S30 备份、Runbook、灰度与上线完成
- 状态:已完成
- 操作服务器:A、B、T、本地
- 完成内容:完成 S30 本地 KVM 开发环境上线前冻结与演练。新增 S30 总控 Runbook、最终验收报告、B release preflight/rollback 脚本和 A OpenSIPS 配置恢复脚本;触发 B MySQL/Redis 新备份并完成隔离恢复演练;使用 T 发起灰度呼叫并核对 B raw CDR、rated CDR 和 READY 录音;实际运行 B preflightA OpenSIPS 当前配置语法检查通过;三机关键服务最终复核 active。
- 修改文件:`IMPLEMENTATION_STATUS.md``docs/S30_RELEASE_BACKUP_RUNBOOK.md``docs/S30_FINAL_ACCEPTANCE_REPORT.md``infra/server-b/s30/lisglosips-release-preflight.sh``infra/server-b/s30/lisglosips-release-rollback.sh``infra/server-a/s30/lisglosips-opensips-config-restore.sh`
- 安装软件及固定版本:未安装新系统软件;继续使用 OpenSIPS `3.6.x`、RTPEngine `11.5.1.18-1ubuntu1.2`、Node.js `v22.22.2`、pnpm `10.33.0`、Prisma `6.19.0`、MySQL `8.0.46`、Redis `7.0.15`
- 执行的关键命令:B `systemctl start lisglosips-backup.service`、MySQL 备份恢复到临时库 `lisglosips_s30_restore`、Redis RDB 恢复到隔离 Unix socket 临时实例、`bash -n` 检查 S30 脚本、`/tmp/lisglosips-release-preflight.sh` preflightA `opensips -C -f /etc/opensips/opensips.cfg`T `/opt/lisglosips-s28/lisglosips-s28-sip.py invite --timeout 12 --hold 3`。所有 sudo/数据库/API secret 均从 `.codex-private/` 或远端 env 读取并只在进程内使用,未写入文档。
- 验证结果:MySQL 备份 `/data/backups/mysql/20260622T022811Z``all-databases.sql.gz``lisglosips.sql.gz``metadata.tsv` 校验通过,临时库恢复计数 `raw_cdrs=33``recordings=27``customer_recharges=34``users=2` 后已删除;Redis 备份 `/data/backups/redis/20260622T022812Z``dump.rdb``metadata.txt` 校验通过,隔离实例核对 `DBSIZE=51``stream:cdr_payload=54``cfg:active_version=s28-v1` 后已关闭并清理。灰度 Call-ID `s28-1782095548702-luppqjd4@lisglosips-t` 收到 SIP `100/200/BYE 200`B 生成 raw `raw_7a6356cc76974652aa30a9d7a350041b`、rated `rated_e0eb8cbe1b90454cb3cf2d9a3bbc9290``billSec=6`、客户费用/供应商成本均 `0.012000`、录音 `rec_5fdf599d679f493a83fc7a2d205a128a``READY` 且 55758 bytes。B preflight 通过,A/B/T 关键服务最终 active,S30 文档/脚本未检出待补充或明文 secret。
- 回滚方式:B 应用按 `docs/S30_RELEASE_BACKUP_RUNBOOK.md` 使用 `infra/server-b/s30/lisglosips-release-rollback.sh /opt/lisglosips/releases/<previous-release>` 切回上一 releaseA OpenSIPS 配置按 `infra/server-a/s30/lisglosips-opensips-config-restore.sh <backup-opensips.cfg>` 先语法检查再恢复;Redis 配置切回上一 `cfg:active_version`,不删除 Stream;数据库优先向前修复迁移,只有完整备份和维护窗口下才恢复数据。S30 产生的测试 CDR、录音和备份作为验收证据保留。
- 未解决问题:本地开发环境不是阿里云生产环境;正式迁移前必须重新完成 VPC/安全组、正式 DNS/TLS、独立数据盘、备份恢复、故障注入和端到端呼叫验收。S29 遗留 Redis 恢复后 OpenSIPS 热路径可能需要重启 `opensips` 的根因级修复仍需后续单独处理;真实多 A、多 B、高可用和完整生产路由不属于本地 S30 闭环。
- 下一任务:无;S00-S30 本地 V2 闭环完成,后续应按 S30 Runbook 进入阿里云迁移/生产化专项
### 2026-06-22 10:25 - S29 性能、故障与安全测试完成
- 状态:已完成
- 操作服务器:A、B、T、本地
- 完成内容:完成本地 KVM 开发环境内的 S29 小规模性能、故障与安全测试。5 路并发和 12 路短突发呼叫均完成 SIP 100/200/BYE 200、CDR、计费、录音 READY 入库;Recording Worker 停止期间的录音恢复、CDR Worker 停止期间的 Redis pending 恢复均通过;Redis 停止期间 A 正确返回 `503 Redis Unavailable`,恢复后发现 OpenSIPS 热路径连接不自动恢复,已通过重启 A `opensips` 做运行时恢复并复测成功;MySQL 停止期间发现 CDR 被 deadletter 的可恢复性缺陷,已修复并部署到 B 当前 release,复测 MySQL 恢复后 pending CDR 自动入库且无新增 deadletter;完成 API 未认证、RBAC 越权、录音播放保护、充值幂等重放和 SIP 非法来源探针。S29 报告已写入 `docs/S29_PERFORMANCE_FAULT_SECURITY_REPORT.md`
- 修改文件:`IMPLEMENTATION_STATUS.md``docs/S29_PERFORMANCE_FAULT_SECURITY_REPORT.md``infra/server-t/s28/lisglosips-s28-sip.py``packages/redis/src/cdr-stream.ts``packages/redis/src/cdr-stream.spec.ts``apps/worker-cdr/src/main.ts``apps/worker-cdr/src/rating.ts`T 的 `/opt/lisglosips-s28/lisglosips-s28-sip.py`B 当前 release `/opt/lisglosips/releases/s28-v2-20260621220924/apps/worker-cdr/``/opt/lisglosips/releases/s28-v2-20260621220924/packages/redis/`
- 安装软件及固定版本:未安装新系统软件;继续使用 OpenSIPS `3.6.x`、RTPEngine `11.5.1.18-1ubuntu1.2`、Node.js `v22.22.2`、pnpm `10.33.0`、Prisma `6.19.0`
- 执行的关键命令:本地 `corepack pnpm@10.33.0 exec vitest run packages/redis/src/cdr-stream.spec.ts apps/worker-cdr/src/rating.spec.ts``corepack pnpm@10.33.0 --filter @lisglosips/redis build``corepack pnpm@10.33.0 --filter @lisglosips/worker-cdr build`T 多路 `/opt/lisglosips-s28/lisglosips-s28-sip.py invite --timeout 10 --hold 1/3`B/A 使用 `systemctl stop/start/restart` 做受控故障注入和恢复;B 通过 API 与 Prisma 只读/小额写入探针核对 RBAC、录音播放保护和充值幂等。所有 sudo/数据库/API secret 均从 `.codex-private/` 或远端 env 读取并只在进程内使用,未写入文档。
- 验证结果:5 路并发生成 5 条 raw CDR、5 条 rated CDR、5 条 READY 录音;12 路短突发生成 `raws=12``rated=12``recordingsReady=12``totalDuration=72`、录音大小 `55758-56078` bytesRecording Worker 恢复后补搬运录音;CDR Worker 恢复后 Redis `pending=0``lag=0` 并入库;MySQL 故障修复后复测 Call-ID `s28-1782093477852-a8bqqohb@lisglosips-t` 自动入库 raw/rated CDR,无新增 deadletterAPI `/api/v2/users` 未认证返回 `401 AUTH_REQUIRED`,质检角色访问用户管理返回 `403 RBAC_FORBIDDEN`,录音播放未认证返回 `401 AUTH_REQUIRED`,伪造录音 ID 播放返回 `404 RECORDING_NOT_READY`;充值重放两次返回同一充值 ID `rch_da0bba1c7b1d44428ab92d77a55e503d`,数据库 `customer_recharges.rows=1``idempotency_keys.rows=1`,余额只增加 `0.000001` 一次;B 实际 Prometheus 服务 `lisglosips-prometheus.service` active 且 readyA/B/T 关键服务复核 active。
- 回滚方式:T 可恢复 S29 前 `/opt/lisglosips-s28/lisglosips-s28-sip.py` 或继续使用修复后的动态端口脚本;B 可用本次部署前保留在 `/tmp/s29-worker-cdr-backup-*``/tmp/s29-redis-backup-*` 的备份恢复 worker-cdr 与 packages/redis 后重启 `lisglosips@cdr-worker`;A Redis 故障恢复只做过服务重启,无配置文件改动。S29 产生的测试 CDR、录音、充值、幂等键和测试用户作为审计证据保留,不直接删除;如需清理应单独说明影响与回滚点。
- 未解决问题:Redis 恢复后 A/OpenSIPS 热路径不会自动从 `Redis Unavailable` 恢复,当前运行时修复是重启 A `opensips`,根因级修复需继续排查 OpenSIPS Redis 连接池/重连策略;SIP 非法来源探针表现为无响应且未进入 OpenSIPS 日志,后续建议增加明确拒绝日志/指标;S29 期间 Tailscale 管理链路出现短时高延迟/丢包,记录为开发环境风险;真实多 A、多 B 和完整生产路由仍不在本任务范围。
- 下一任务:`S30 - 备份、Runbook、灰度与上线`,尚未开始
### 2026-06-22 09:32 - S28 三机端到端联调完成
- 状态:已完成
- 操作服务器:A、B、T、本地
- 完成内容:补齐 S28 剩余录音与质检闭环。T 呼叫脚本新增双向 PCMU RTP 发送,UAS 在 200 OK 后向 A/RTPEngine 回送 RTP,主叫侧在 ACK 后向应答 SDP 媒体地址发送 RTPA 的 S28 OpenSIPS 路由启用 `record-call=on`,保持 INVITE/ACK/BYE 经原 A,仍绑定本机 RTPEngine;修正 A `/dev/shm/voip_rec` 历史 root-owned 日期目录权限;修复 A recording finalizer 过早搬运问题,跳过 0 字节、仍被打开或未稳定的录音文件;B Recording Worker 新增按 RTPEngine 文件名反解 Call-ID 的 fallback 关联逻辑,使录音可自动关联 `raw_cdrs`,从而让质检抽样按客户/线路组命中。新增 S28 质检测试用户 `usr_s28_quality`、抽检规则 `qs_s28_e2e_20260622`,并通过 API 保存最新录音质检记录。
- 修改文件:`infra/server-t/s28/lisglosips-s28-sip.py``infra/server-a/s28/opensips.cfg``infra/server-a/s19/scripts/lisglosips-recording-finalize``apps/worker-recording/src/transfer.ts``apps/worker-recording/src/transfer.spec.ts``IMPLEMENTATION_STATUS.md`A 的 `/etc/opensips/opensips.cfg``/usr/local/sbin/lisglosips-recording-finalize``/dev/shm/voip_rec` 目录属主/权限;T 的 `/opt/lisglosips-s28/lisglosips-s28-sip.py`B 当前 release `/opt/lisglosips/releases/s28-v2-20260621220924/apps/worker-recording/` 的源码与 dist 产物
- 安装软件及固定版本:未安装新系统软件;继续使用 OpenSIPS `3.6.x`、RTPEngine `11.5.1.18-1ubuntu1.2`、Node.js `v22.22.2`、pnpm `10.33.0`、Prisma `6.19.0`
- 执行的关键命令:本地 `corepack pnpm@10.33.0 exec vitest run apps/worker-recording/src/transfer.spec.ts``corepack pnpm@10.33.0 --filter @lisglosips/worker-recording build`A `opensips -C -f /tmp/opensips-s28.rendered.cfg` 后重启 `opensips`,更新 finalizer 后保持 timer activeT `/opt/lisglosips-s28/lisglosips-s28-sip.py invite --timeout 10 --hold 3`B 替换 worker-recording 产物并重启 `lisglosips@recording-worker`,通过 MySQL 和 API 只读/写入测试核对。所有 sudo/数据库/API secret 均从 `.codex-private/` 或远端 env 读取并只在进程内使用,未输出或写入明文密码。
- 验证结果:最终验收 Call-ID `s28-1782091725991-ewqrpe7w@lisglosips-t`T 收到 `100 Giving it a try``200 OK`、BYE `200 OK`B `raw_cdrs` 生成 `raw_aa6cbaf7758f41ea895a2889f558559d``duration_sec=6``rating_status=RATED``rated_cdrs` 对应客户费用和供应商成本均为 `0.012000``bill_sec=6`B `recordings` 生成 `rec_24b56d60dfd14937ae2e4c09802a91be`,自动关联该 raw CDR`status=READY``bytes=55758`、SHA-256 前缀 `e2cfaf8c7bbc6023`API `/api/v2/health/ready` 返回 ok`GET /api/v2/recordings/rec_24b56d60dfd14937ae2e4c09802a91be` 返回 customer/lineGroup/callId/CDR 摘要,抽样规则 `qs_s28_e2e_20260622` 命中 `selected=true``PUT /api/v2/recordings/:id/review` 成功保存 `qrev_9672ee20119049a1a56082c35dc42e0f``GET /play` 返回 `200``content-type: audio/wav` 和内部 `x-accel-redirect`。A `opensips``rtpengine-daemon``rtpengine-recording-daemon``lisglosips-recording-finalize.timer` activeB `lisglosips@api``lisglosips@cdr-worker``lisglosips@recording-worker``mysql``redis-server``heplify-server` activeT `lisglosips-s28-uas``opensips` active。
- 回滚方式:T 回滚恢复或删除 `/opt/lisglosips-s28/lisglosips-s28-sip.py`,停止 `lisglosips-s28-uas.service`A 回滚可用 `/var/backups/lisglosips-s28/20260621T145920Z/opensips.cfg` 恢复 `/etc/opensips/opensips.cfg` 并语法检查后重启 `opensips`finalizer 可恢复 S24 备份 `/var/backups/lisglosips-s24/20260621T121130Z/lisglosips-recording-finalize`,但旧脚本会重现提前搬运风险;B 回滚可将 worker-recording 产物恢复到本次替换前的 release 版本或把 `/opt/lisglosips/current` 指回前一 release 后重启相关服务。测试 CDR、余额流水、录音和质检记录作为验收证据保留,不直接删除;如必须清理,应另开维护步骤并先说明影响与回滚点。
- 未解决问题:A 当前仍是 S28 单一测试落地端最小路由,不是完整多落地网关生产路由;录音 `duration_sec` 仍为 0,后续可在 Recording Worker 中读取 WAV 时长或由媒体侧补充;S28 调试期间存在一条 0 字节早期录音 `rec_f9b1457861444d4ebbddc11adb208d13` 作为问题证据保留;多 A 真实部署和多 B 高可用仍留给后续阶段。
- 下一任务:S29 - 性能、故障与安全测试
### 2026-06-21 23:06 - S28 三机端到端联调推进
- 状态:进行中
- 操作服务器:A、B、T、本地
- 完成内容:将 B 从 S05 placeholder 发布为完整 V2 release `/opt/lisglosips/releases/s28-v2-20260621220924``/opt/lisglosips/current` 已切换到该 release;执行业务库迁移,补齐 `20260621093000_auth_sessions` additive 迁移记录,权限/角色种子已落库;`lisglosips@api``lisglosips@cdr-worker` activeAPI `/api/v2/health/live``/api/v2/health/ready` 返回 ok;补齐 B 录音拉取 `/etc/lisglosips/recording/ssh_config``lisglosips@recording-worker` 已恢复 active。新增 S28 测试业务数据:客户 `cus_s28_t`、客户网关 `cgw_s28_t_ip`、供应商 `ven_s28_t`、落地网关 `vgw_s28_t_uas`、线路组 `llg_s28_t` 和策略 `cgp_s28_t_default`Redis 热路径配置版本为 `s28-v1`。在 T 部署专用落地模拟服务 `lisglosips-s28-uas.service`,监听 `100.93.185.30:50620`,只终结呼叫不回送 A,避免循环。A 部署 S28 最小真实路由:Lua 热路径 allow 后执行 RTPEngine offer/answer、转发到 T 专用落地端、写成功 CDR 到 Redis Stream,并为 T 源 IP 的 ACK/BYE 添加 fallback,确保回到原 A 后转给 T 落地端。
- 修改文件:`infra/server-t/s28/lisglosips-s28-sip.py``infra/server-a/s28/opensips.cfg``pnpm-lock.yaml``IMPLEMENTATION_STATUS.md`B 的 `/opt/lisglosips/releases/s28-v2-20260621220924``/etc/lisglosips/api.env``/etc/lisglosips/cdr-worker.env``/etc/lisglosips/recording-worker.env``/etc/lisglosips/recording/ssh_config``/etc/systemd/system/lisglosips@recording-worker.service.d/10-s28-recording.conf`T 的 `/opt/lisglosips-s28/lisglosips-s28-sip.py``/etc/systemd/system/lisglosips-s28-uas.service`A 的 `/etc/opensips/opensips.cfg`
- 安装软件及固定版本:未安装新系统软件;B 继续使用 Node.js `v22.22.2`、Corepack `0.34.6`、pnpm `10.33.0`、Prisma `6.19.0`
- 执行的关键命令:本地 `corepack pnpm@10.33.0 install --lockfile-only``corepack pnpm@10.33.0 build``corepack pnpm@10.33.0 prisma:validate``corepack pnpm@10.33.0 fetch`B 端离线 `pnpm install --frozen-lockfile --offline --ignore-scripts`、MySQL 迁移/种子、systemd restart/enableA `opensips -C -f /tmp/opensips-s28.rendered.cfg` 后重启 `opensips`T 启动 `lisglosips-s28-uas.service`。凭据均从 `.codex-private/` 读取并通过 stdin/base64 参数传递,未写入文档或输出明文密码。
- 验证结果:最终候选呼叫 Call-ID `s28-1782054233152-f4eiz799@lisglosips-t`T 收到 `100 Giving it a try``200 OK`,随后 BYE 返回 `200 OK`A 日志显示 hotpath allow、成功 CDR XADD `1782054232944-0`、ACK/BYE 均从 T 回到 ARTPEngine session 最终回到 0B MySQL `raw_cdrs` 生成 `raw_2903404fa10b47a6a4b7968e3662cdd5``sip_code=200``hangup_reason=NORMAL_CLEARING``customer_id=cus_s28_t``vendor_gateway_id=vgw_s28_t_uas``duration_sec=6``rated_cdrs` 生成客户费用/供应商成本 `0.012000`、计费秒数 `6`;客户 `cus_s28_t` 余额扣减到 `99.928000`(包含本次和前面 S28 调试成功 CDR);HOMER `hep_proto_1_call` 可查到该 Call-ID 的 `INVITE``ACK``BYE`B API ready ok`lisglosips@api``lisglosips@cdr-worker``lisglosips@recording-worker` 均 activeT S28 UAS active。
- 回滚方式:B 回滚先停止 `lisglosips@cdr-worker``lisglosips@recording-worker`,将 `/opt/lisglosips/current` 指回备份记录中的 S05 placeholder release,并重启 `lisglosips@api`B 迁移前备份位于 `/var/backups/lisglosips-s28/20260621T140940Z`,如需撤回数据库写入应走备份恢复或受控反向账务处理,不直接删除 CDR/余额流水。A 回滚使用 `/var/backups/lisglosips-s28/20260621T145920Z/opensips.cfg` 恢复 `/etc/opensips/opensips.cfg`,语法检查后重启 `opensips`。T 回滚停止并删除 `lisglosips-s28-uas.service``/opt/lisglosips-s28`。Redis 配置可将 `cfg:active_version` 切回 `cfg:previous_version` 或清理 `s28-v1` 测试 keys,清理前需确认没有后续测试依赖。
- 未解决问题:本次 T 脚本只做 SIP/SDP,不发送 RTP 音频包,因此 RTPEngine 未产生真实录音文件;Recording Worker 虽已 active,但尚未完成真实通话 `.ready` 文件搬运、`recordings` 入库和质检抽样/保存核对。S28 仍需补“RTP 音频生成 -> A 录音 ready -> B Recording Worker 入库 -> 质检接口核对”后才能标记已完成。当前 A 配置中的 S28 路由是单一测试落地端 fallback,不是完整多落地网关生产路由。
- 下一任务:继续 `S28 - 三机端到端联调`,补齐录音和质检闭环;不得进入 S29。
### 2026-06-21 21:47 - S28 三机端到端联调
- 状态:已阻塞
- 操作服务器:A、B、T;仅执行一次 S28 测试 INVITE 和只读核对,未修改服务器配置,未重启服务,未执行数据库迁移、数据删除或防火墙变更
- 完成内容:按 S28 目标先做三机链路探针。T 使用 S21 脚本向 A `100.90.90.90:15060` 发起 IP 认证 INVITECall-ID 为 `s21-invite-1782049439554-088rul75@lisglosips-t`A 返回 `SIP/2.0 503 Config Missing`;A OpenSIPS 日志确认热路径拒绝原因为 `CONFIG_MISSING`,并成功向 B Redis Stream 写入失败 CDRB Redis Stream `stream:cdr_payload` 可按 Call-ID 查到该事件,字段包含 `node_id=a1``opensips_instance=opensips-a1``ingress_a_ip=100.90.90.90``rtpengine_node=a1``sip_code=503``hangup_reason=CONFIG_MISSING`B HOMER `hep_proto_1_call` 最近记录的 `sid``data_header.callid` 均为该 Call-ID`data_header.method=INVITE`,说明 HEP 信令镜像入库正常。
- 修改文件:`IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件
- 执行的关键命令:T `/opt/lisglosips-s21/s21-call-a-ip`A `journalctl -u opensips --since '10 minutes ago'` 按 Call-ID 查询;B `redis-cli XREVRANGE stream:cdr_payload` 按 Call-ID 查询;B PostgreSQL `homer_data.hep_proto_1_call` 只读查询;B 本机 API/数据库只读探针。所有凭据均从 `.codex-private/` 读取并通过 stdin 使用,未输出明文密码。
- 验证结果:T->A SIP 输入、A->B Redis Stream 失败 CDR、A->B HOMER HEP 信令三段可按 Call-ID 串联;但未进入真实路由、媒体、录音、计费或质检闭环。B 当前 `/api/v2/health/live` 返回 HTTP 404`lisglosips` 业务库表数为 `0`,说明 current release 仍不是完整 V2 API/WorkerA 当前 `/etc/opensips/opensips.cfg` 仍为 S20/S22 占位逻辑,配置缺失时返回 `Config Missing`,即便配置 allow 也会返回 `Routing Not Ready`,没有真实落地网关选择、RTPEngine offer/answer、t_relay 和对话内 ACK/BYE/re-INVITE 路由。
- 回滚方式:本次未修改服务器配置和数据库结构,无需服务回滚;产生的一条测试 INVITE、HOMER 记录和 Redis Stream 失败 CDR 作为联调证据保留,不做直接删除。若后续需要清理测试数据,应先单独说明影响和回滚点。
- 未解决问题:S28 阻塞在两个前置缺口:一是 B 需要受控发布完整 V2 API、Web 和 CDR/Recording Worker,并在空业务库上执行迁移/种子和健康检查;二是 A 需要新增真实呼叫路由配置,至少包含从 Redis 配置读取线路组/落地网关、供应商目标地址、RTPEngine offer/answer、t_relay、onreply/failure/dialog 内路由和成功/失败 CDR 事件。当前不能进入 S29。
- 下一任务:继续 `S28 - 三机端到端联调`,先解除上述阻塞;不得提前执行 S29。
### 2026-06-21 21:32 - S27 React Demo 接入真实 API
- 状态:已完成
- 操作服务器:本地;未连接 A/B/T,未执行服务器写操作、服务重启、数据库迁移或数据删除
- 完成内容:新增 Web API Client,默认使用 `/api/v2`,开发环境通过 Vite proxy 转发到本地 API `127.0.0.1:3000`,可选从浏览器 `localStorage` 读取 `lisglosips.accessToken` 作为 Bearer Token,不写入或输出任何明文凭据;Dashboard 接入 `dashboard/summary``dashboard/trends`;客户、供应商、充值记录、用户、角色、操作日志接入真实列表 API;客户/供应商新增、编辑、充值调用真实 API 并在成功后重新拉取数据;页面新增统一加载、错误和空状态,未登录或无权限时显示鉴权提示;顶部状态从“纯前端 Mock”改为真实 API 状态。CDR、计费、SIP 运维、监控告警、系统设置仍保留为未完成业务页面/静态运维原型,等待对应后端查询接口或后续延期页面设计,不在本任务伪造真实数据。
- 修改文件:`apps/web/src/api.js``apps/web/src/App.jsx``apps/web/src/styles.css``apps/web/vite.config.js``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件;继续使用 React `18.2.0`、Vite `5.4.21`、pnpm `10.33.0`
- 执行的关键命令:`corepack pnpm@10.33.0 --filter @lisglosips/web build``corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build`、本地启动 `corepack pnpm@10.33.0 --filter @lisglosips/web dev`
- 验证结果:Web 单包 build 通过;全 workspace `typecheck``lint``build` 均通过;本地 Vite 页面 `http://127.0.0.1:5173/` 返回 HTTP 200。API 服务若未启动或未登录,前端会展示错误态而不是回退到静态运营数据。
- 回滚方式:删除 `apps/web/src/api.js`,恢复 `apps/web/src/App.jsx` 中本次 API 数据加载、真实接口操作、空状态和顶部 API 状态改动,恢复 `apps/web/src/styles.css` 的空状态样式和 `apps/web/vite.config.js` proxy 配置,重新执行 `corepack pnpm@10.33.0 --filter @lisglosips/web build`;S27 未修改远端服务或数据库,无远端数据需回滚。
- 未解决问题:B 当前 `/opt/lisglosips/current` 仍是 S05 placeholder release,本次未部署到 B 当前 Web/API 服务;前端尚无登录页,真实接口需要已有 Bearer Token 或同源 Cookie 才能返回业务数据;CDR 查询、计费页面、自研 SIP 运维、监控告警、系统设置仍缺少对应产品化后端页面接口或属于延期页面,S27 未提前实现。
- 下一任务:`S28 - 三机端到端联调`
### 2026-06-21 21:20 - S26 Dashboard 聚合
- 状态:已完成
- 操作服务器:本地;未连接 A/B/T,未执行服务器写操作、服务重启、数据库迁移或数据删除
- 完成内容:新增 `DashboardModule`,实现 `GET /api/v2/dashboard/summary``GET /api/v2/dashboard/trends`;接口统一要求 `dashboard.view` 权限;summary 按 `Asia/Shanghai` 今日窗口聚合 raw CDR、rated CDR、启用客户/网关、待质检数量、失败响应码和失败落地网关 Top 10;trends 支持 `hours=1..168``bucketMinutes=5|15|60`,按固定桶返回通话、接通率、时长、客户费用、供应商成本和毛利;实时在线通话和注册数当前返回 `0` 并标记 `source: "not_configured"`,等待后续接入 Redis/OpenSIPS 指标。未新增预聚合表,未做 `worker-metrics`,未进入 S27 前端接入。
- 修改文件:`apps/api/src/modules/app.module.ts``apps/api/src/modules/dashboard/``docs/DASHBOARD_AGGREGATION_RUNBOOK.md``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件;继续使用既有 NestJS/Fastify、Prisma `6.19.0`、Vitest `4.0.14`、TypeScript `5.9.3`
- 执行的关键命令:`corepack pnpm@10.33.0 exec vitest run apps/api/src/modules/dashboard/dashboard.service.spec.ts``corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build`
- 验证结果:S26 聚焦测试 1 个文件 4 条通过,覆盖上海自然日边界、接通率、趋势桶和参数校验;全 workspace `typecheck``lint``build` 均通过
- 回滚方式:从 `AppModule` 移除 `DashboardModule`,删除 `apps/api/src/modules/dashboard/``docs/DASHBOARD_AGGREGATION_RUNBOOK.md`,重新执行 `corepack pnpm@10.33.0 build`;S26 未对远端数据库写入,无远端数据需清理
- 未解决问题:B 当前 `/opt/lisglosips/current` 仍是 S05 placeholder release,本次未部署到 B 当前 API 服务;Dashboard 设计要求的真正实时在线通话/注册数仍需后续接入 Redis/OpenSIPS 指标;24 小时趋势最终应由 `worker-metrics` 或 CDR Worker 增量写入预聚合表,本次只做时间窗口受限查询,避免扫描全量 CDR。
- 下一任务:`S27 - React Demo 接入真实 API`
### 2026-06-21 21:05 - S25 质检后端
- 状态:已完成
- 操作服务器:本地;未连接 A/B/T,未执行服务器写操作、服务重启、数据库迁移或数据删除
- 完成内容:新增 `QualityModule`,实现 `GET/POST/PATCH/DELETE /api/v2/quality/rules` 及规则启停,支持按客户、线路组、比例、生效时间和过期时间配置抽检规则;新增稳定抽样算法 `sha256(rule_id:recording_id)`,同一规则与录音重复查询结果稳定;扩展 `RecordingsModule`,实现 `GET /api/v2/recordings``GET /api/v2/recordings/:id``PUT /api/v2/recordings/:id/review`,返回录音 CDR 摘要、最新质检、历史质检、上下条导航和抽样命中信息;保存质检结果写入 `quality_reviews`,不覆盖历史;规则变更和质检保存均接入 `AuditAction` 元数据;权限使用 `quality.view``quality.manage``recordings.play`。未做 Dashboard 聚合,未扩展 S26 内容。
- 修改文件:`apps/api/src/modules/app.module.ts``apps/api/src/modules/quality/``apps/api/src/modules/recordings/recordings.controller.ts``apps/api/src/modules/recordings/recordings.repository.ts``apps/api/src/modules/recordings/recordings.service.ts``apps/api/src/modules/recordings/recordings.module.ts``apps/api/src/modules/recordings/recordings.service.spec.ts``docs/QUALITY_BACKEND_RUNBOOK.md``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新软件;继续使用既有 NestJS/Fastify、Prisma `6.19.0`、Vitest `4.0.14`、TypeScript `5.9.3`
- 执行的关键命令:`corepack pnpm@10.33.0 exec vitest run apps/api/src/modules/quality/sampling.spec.ts apps/api/src/modules/recordings/recordings.service.spec.ts``corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build`
- 验证结果:S25 聚焦测试 2 个文件 6 条通过,覆盖稳定抽样可重复、比例边界、录音列表抽样状态、保存质检输入校验和 S24 内部播放路径安全;全 workspace `typecheck``lint``build` 均通过
- 回滚方式:删除或恢复 `apps/api/src/modules/quality/`,将 `apps/api/src/modules/recordings/` 恢复到 S24 版本,从 `AppModule` 移除 `QualityModule`,重新执行 `corepack pnpm@10.33.0 build`;S25 未对远端数据库写入,无远端数据需清理;若后续已运行 API 并产生 `quality_reviews`,不直接删除历史记录,应通过审计可见的更正记录或受控维护窗口处理
- 未解决问题:B 当前 `/opt/lisglosips/current` 仍是 S05 placeholder release,本次未部署到 B 当前 API 服务;真实录音和 raw CDR 关联仍依赖 S28 端到端闭环产生;Dashboard 的待质检数量、趋势和聚合指标留给 S26。
- 下一任务:`S26 - Dashboard 聚合`
### 2026-06-21 20:20 - S24 录音搬运与播放
- 状态:已完成
- 操作服务器:A、B、本地;T 未操作
- 完成内容:新增 Recording Worker 搬运服务,支持扫描 A `/dev/shm/voip_rec/ready/**/*.ready`、SSH 私网拉取、文件大小校验、SHA-256、B 端 `.part-*` 临时文件原子改名、写入 `recordings` 表、校验成功后才删除 A 源 `.ready`;新增 `GET /api/v2/recordings/:id/play`,要求 `recordings.play` 权限,状态必须为 `READY`,通过 `X-Accel-Redirect` 转发到 Nginx 内部 `/_recordings/`,不暴露真实 `/data/recordings` 路径;A 端 finalizer ready 目录改为 `0770`,使专用拉取用户可在校验后删除源文件;B 建立专用拉取 key 和 known_hostsA 建立专用低权限用户 `lisglo-rec-pull` 并加入 `rtpengine` 组。未调整 SSH 管理端口,未禁止 SSH key 登录,未重启 OpenSIPS、RTPEngine、Nginx 或 API。
- 修改文件:`apps/worker-recording/src/main.ts``apps/worker-recording/src/transfer.ts``apps/worker-recording/src/transfer.spec.ts``apps/worker-recording/package.json``apps/worker-recording/tsconfig.json``apps/api/src/modules/app.module.ts``apps/api/src/modules/recordings/``pnpm-lock.yaml``infra/server-a/s19/scripts/lisglosips-recording-finalize``docs/RECORDING_TRANSFER_PLAYBACK_RUNBOOK.md``IMPLEMENTATION_STATUS.md`A 的 `/usr/local/sbin/lisglosips-recording-finalize``/var/lib/lisglo-rec-pull/.ssh/authorized_keys``/dev/shm/voip_rec/ready` 权限;B 的 `/etc/lisglosips/recording/``/data/recordings/s24-smoke/s24-smoke.wav`
- 安装软件及固定版本:未安装新软件;继续使用既有 OpenSSH、Node.js `v22.22.2`、pnpm `10.33.0`、Nginx `1.24.0-2ubuntu7.12`。新增系统用户 `lisglo-rec-pull`,未新增系统包。
- 执行的关键命令:本地 `corepack pnpm@10.33.0 exec vitest run apps/worker-recording/src/transfer.spec.ts apps/api/src/modules/recordings/recordings.service.spec.ts``corepack pnpm@10.33.0 --filter @lisglosips/worker-recording build``corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build`A `bash -n /usr/local/sbin/lisglosips-recording-finalize`、创建 S24 测试 incoming 文件并运行 finalizerB 使用 `lisglo-recorder` 通过专用 key 从 A 拉取、校验、落盘、删除测试源文件。sudo 凭据通过 `.codex-private/` 读取并经 stdin 使用,未回显。
- 验证结果:本地 2 个测试文件 5 条测试通过,覆盖路径安全、`.ready` 转 storage key、复制/哈希/落盘/源删除、大小不匹配不删除源文件、API 内部路径穿越防护;`worker-recording` 单包 build、全 workspace `typecheck``lint``build` 均通过;A 测试文件从 `incoming` 进入 `ready`B 私网拉取到 `/data/recordings/s24-smoke/s24-smoke.wav`,大小 `31`,属主 `lisglo-recorder:lisglosips`SHA-256 为 `c72dd3909a317ad55817435b7ed5a0db6947455ffc66e09a2dde787fe4a8c348`,校验后 A 源 `.ready` 已删除;外部直接访问 `https://127.0.0.1/_recordings/s24-smoke/s24-smoke.wav` 返回 `404`Nginx 内部录音 location 未暴露。
- 回滚方式:A 备份目录 `/var/backups/lisglosips-s24/20260621T121130Z`,可恢复 `lisglosips-recording-finalize`、删除 `lisglo-rec-pull` 用户并将 `/dev/shm/voip_rec/ready` 恢复 `0750`B 备份目录 `/var/backups/lisglosips-s24/20260621T121100Z`,可删除 `/etc/lisglosips/recording``/data/recordings/s24-smoke`;代码回滚恢复本次新增/修改文件后重新 build。详见 `docs/RECORDING_TRANSFER_PLAYBACK_RUNBOOK.md`
- 未解决问题:B 当前 `/opt/lisglosips/current` 仍是 S05 placeholder release,尚未切换完整 monorepo API/Worker release,因此本次未启用正式 `lisglosips@recording-worker.service`,API 播放入口完成代码与构建但未在 B 当前 release 对外提供;真实通话录音字段仍依赖后续 S28 端到端路由/媒体闭环产生。
- 下一任务:`S25 - 质检后端`
### 2026-06-21 20:02 - B sudo 凭据复验
- 状态:已完成
- 操作服务器:B、本地;仅执行只读 SSH 与 sudo 校验,未修改服务器配置、未重启服务、未执行数据库写入或数据删除
- 完成内容:读取 `.codex-private/` 中 DPAPI 保存的 B 凭据且未回显密码;复验 B SSH Key 登录正常;确认 `hector` 属于 `sudo` 组;使用保存的 sudo 凭据执行 `sudo -v` 成功;使用 sudo 执行只读权限检查成功
- 修改文件:`IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:无
- 执行的关键命令:`ssh -F .codex-private\ssh\config lisglosips-b ...`PowerShell `Import-Clixml .codex-private\SERVER_CREDENTIALS.clixml` 后通过 stdin 向远端 `sudo -S` 提供凭据,命令与输出均未包含明文密码
- 验证结果:B 返回主机名 `YanZi`,用户 `hector``sudo` 组;`sudo -v` 退出码为 0;只读 sudo 检查退出码为 0
- 回滚方式:无服务器变更无需回滚;如需撤销本地状态记录,恢复本文件本节和顶部阻塞说明
- 未解决问题:S13-S17 历史任务仍未补做 B 临时库冒烟;若后续补验会涉及数据库写入,应在执行前单独说明影响与回滚点
- 下一任务:`S24 - 录音搬运与播放`
### 2026-06-21 19:55 - S23 最小计费与余额扣减
- 状态:已完成
- 操作服务器:本地;未连接 A/B/T,未执行服务器写操作、服务重启、数据库迁移或数据删除
- 完成内容:在 `worker-cdr` 中接入最小计费服务,Redis Stream CDR 消费后写入 `raw_cdrs`、成功通话写入 `rated_cdrs`、按落地网关周期秒数和周期费率计算费用、扣减客户余额,并用负数 `customer_recharges` 记录不可变扣费流水;同一 `event_id` 或同一 `raw_cdr_id` 不重复扣费;失败 CDR、零时长 CDR、缺少客户或落地网关 ID 的 CDR 标记 `SKIPPED` 且不扣费。当前尚无客户侧费率表,S23 采用落地网关费率临时镜像为客户费用,供应商成本相同,毛利为 0,后续真实计费版本再扩展客户费率和利润计算。
- 修改文件:`apps/worker-cdr/src/main.ts``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``apps/worker-cdr/dist/``docs/CDR_MINIMAL_BILLING_RUNBOOK.md``IMPLEMENTATION_STATUS.md`;本地因 `pnpm install` 受 registry DNS/metadata 影响,手工补齐 `apps/worker-cdr/node_modules/@lisglosips/database``packages/database` 的开发 junction
- 安装软件及固定版本:未安装新软件;继续使用既有 Node.js/pnpm、Prisma `6.19.0`、Vitest `4.0.14`、TypeScript `5.9.3`
- 执行的关键命令:`corepack pnpm@10.33.0 exec vitest run apps/worker-cdr/src/rating.spec.ts``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`;尝试 `corepack pnpm@10.33.0 install``install --offline` 时被 npm registry DNS/本地 metadata 问题阻塞,未作为验收依据
- 验证结果:`apps/worker-cdr/src/rating.spec.ts` 3 条测试通过,覆盖周期计费、成功 CDR 扣费幂等、失败/零时长 CDR 不扣费;`worker-cdr` 单包 build 通过;全 workspace `typecheck``lint``build` 均通过
- 回滚方式:恢复 `apps/worker-cdr/src/main.ts` 到 S22 只记录 CDR 的版本,删除或恢复 S23 新增的 `billing.ts``rating.ts``rating.spec.ts`,恢复 `apps/worker-cdr/package.json``tsconfig.json` 后重新执行 `corepack pnpm@10.33.0 build`;S23 未执行真实数据库写入验证,无远端数据需要清理;若后续已运行 worker 并产生扣费,必须先停止 worker,再通过受控反向流水或备份恢复处理,不直接删除余额流水
- 未解决问题:B sudo 凭据仍不可用,本次未部署 B systemd worker、未对真实 B MySQL 执行写入式冒烟;当前最小计费暂时镜像落地网关费率为客户费用,客户费率表、利润规则、低余额拦截和 Dashboard 聚合留给后续任务;`pnpm install` 当前受 registry DNS/metadata 影响,后续若新增依赖需优先修复本地包管理器联网或缓存
- 下一任务:`S24 - 录音搬运与播放`
### 2026-06-21 19:34 - S22 CDR Redis Stream
- 状态:已完成
- 操作服务器:A、T、本地;B 仅通过 A 的 Redis AUTH proxy 写入 Redis Stream Consumer Group 元数据,未使用 B sudo,未改 B 服务
- 完成内容:新增 CDR Stream schema v1 和可靠消费库,支持 `stream:cdr_payload``stream:cdr_deadletter``billing-workers` Consumer Group、`XREADGROUP``XACK``XAUTOCLAIM` Pending 重领、死信写入和 `lock:cdr:{event_id}` 幂等占位;`worker-cdr` 从骨架改为 Redis Stream Worker,启动时确保 Consumer Group,循环先处理 Pending 再读取新消息,当前 handler 只接收/记录原始 CDR,不做计费和余额扣减;按多 A 后续演进预留 `node_id``opensips_instance``ingress_a_ip``rtpengine_node` 字段,当前单 A 值为 `a1``opensips-a1``100.90.90.90``a1`;升级 Server A OpenSIPS `route[S20_CDR_XADD]`,从 S20 冒烟 XADD 改为 S22 正式事件字段;在 Redis 创建 `billing-workers` Consumer Group。未调整 SSH 管理端口或密钥登录策略,未写入明文密码。
- 修改文件:`packages/redis/src/index.ts``packages/redis/src/cdr-stream.ts``packages/redis/src/cdr-stream.spec.ts``apps/worker-cdr/src/main.ts``packages/redis/dist/``apps/worker-cdr/dist/``infra/server-a/s22/opensips/opensips.cfg``docs/CDR_REDIS_STREAM_RUNBOOK.md``SOFTSWITCH_PLATFORM_DESIGN_V2.md``IMPLEMENTATION_STATUS.md`A 的 `/etc/opensips/opensips.cfg`Redis `stream:cdr_payload` Consumer Group `billing-workers`
- 安装软件及固定版本:未安装新系统软件;继续使用既有 Node.js/pnpm、OpenSIPS `3.6.7-1`、Redis `7.0.15`。新增代码复用既有 `ioredis 5.8.2`
- 执行的关键命令:本地 `corepack pnpm@10.33.0 exec vitest run packages/redis/src/cdr-stream.spec.ts``corepack pnpm@10.33.0 lint/typecheck/build`A `opensips -C -f /etc/opensips/opensips.cfg``systemctl restart opensips`A 经本地 Redis proxy 执行 `XGROUP CREATE stream:cdr_payload billing-workers 0 MKSTREAM``XINFO GROUPS``XREVRANGE`T 执行 `/opt/lisglosips-s21/lisglosips-s21-sip.py invite --timeout 8`。sudo 凭据通过 `.codex-private/` 读取且未回显。
- 验证结果:Redis Stream 单元测试 6 条通过,覆盖发布/解析、Consumer Group 幂等创建、`XREADGROUP`/`XACK`、重复 `event_id` 幂等、死信、`XAUTOCLAIM` Pending 重领;`lint``typecheck`、全 workspace `build` 均通过;A OpenSIPS 配置语法检查通过并重启后 active;Redis `XINFO GROUPS stream:cdr_payload` 显示 `billing-workers`pending 为 0T 发 INVITE 后 A 返回 `SIP/2.0 503 Config Missing`,A 日志显示 CDR XADD 成功,Redis 最新事件包含 `schema_version=1``idempotency_key``node_id=a1``opensips_instance=opensips-a1``ingress_a_ip=100.90.90.90``rtpengine_node=a1``sip_code=503``hangup_reason=CONFIG_MISSING`。一次使用 S21 默认 3 秒超时的探针未收到响应,但 A 已写入 CDR;随后使用 8 秒超时复测收到预期 `503 Config Missing`
- 回滚方式:A 备份目录为 `/var/backups/lisglosips-s22/20260621T113157Z`,恢复其中 `opensips.cfg` 后执行 `opensips -C -f /etc/opensips/opensips.cfg` 并重启 `opensips`;代码回滚恢复本次新增/修改的 Redis/worker 文件后重新 buildRedis Consumer Group 可保留,停止 worker 即可,不删除 `stream:cdr_payload` 数据。详见 `docs/CDR_REDIS_STREAM_RUNBOOK.md`
- 未解决问题:S22 只完成原始 CDR Stream 契约和可靠消费基础,不写 MySQL raw/rated CDR、不计费、不扣余额,这些留给 S23;A 端当前仍未发布真实路由配置,因此测试 CDR 为 `CONFIG_MISSING` 失败事件;B sudo 凭据仍不可用,本次未执行 B 端服务部署或 systemd worker 验收;多 A 仅完成字段预留和 A1 默认值,真实 3A 部署留到单 A 闭环之后。
- 下一任务:`S23 - 最小计费与余额扣减`
### 2026-06-21 15:28 - S21 Server T 客户模拟配置
- 状态:已完成
- 操作服务器:T、本地;A 仅接收 T 发起的 SIP 探针,未写入;B 未操作
- 完成内容:在 Server T 建立 S21 客户模拟环境;新增测试域 `s21.lisglosips.test`;新增两套 SIP 注册测试账号 `s21-reg-1001``s21-reg-1002`OpenSIPS 改为 HA1 鉴权,`subscriber.password` 为空且不保存 SIP 明文密码;新增两套 IP 认证测试条目 `s21-ip-1001``s21-ip-1002` 指向 T `100.93.185.30/32`;部署 `/opt/lisglosips-s21` 自包含 SIP 脚本,支持注册成功、错误鉴权、向 A 发起 IP/SIP 模拟 INVITE、本地成功/忙/拒绝/超时场景和突发 CPS 探针;收敛 T 的 MI FIFO 权限为 `0660`TLS 私钥权限为 `0600`。未调整 SSH 管理端口或密钥登录策略,未写入明文密码。
- 修改文件:T 的 `/etc/opensips/opensips.cfg`、OpenSIPS `opensips` 数据库 `domain/subscriber/address/location` 测试数据、`/opt/lisglosips-s21/``/etc/lisglosips-s21/sip-accounts.env`;本地 `infra/server-t/s21/``docs/SERVER_T_CUSTOMER_SIMULATION_RUNBOOK.md``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新系统软件;继续使用 T 既有 OpenSIPS `3.6.6`、RTPEngine `11.5.1.18`、MariaDB `10.11.14` 和系统 Python 3。新增 SIP 测试脚本无第三方依赖。
- 执行的关键命令:`mysqldump --single-transaction --routines --events opensips``opensips -C -f /etc/opensips/opensips.cfg``systemctl restart opensips``mysql --protocol=socket -uroot``python3 -m py_compile``/opt/lisglosips-s21/s21-register-*``s21-local-*``s21-call-a-*``s21-over-cps-a`。sudo 凭据通过 `.codex-private/` 读取且未回显。
- 验证结果:备份目录 `/var/backups/lisglosips-s21/20260621T072316Z` 已生成并含 OpenSIPS 配置和数据库 dumpOpenSIPS 配置语法检查通过,`opensips.service` active`s21-register-primary``s21-register-secondary` 均先收到 `401 Unauthorized` challenge 后最终 `200 OK``s21-register-wrong-ha1` 最终 `401 Unauthorized``location` 表已有 `s21-reg-1001``s21-reg-1002` 注册位置;`subscriber.password` 长度为 0`ha1` 长度为 32;本地脚本分别返回 `200 OK``486 Busy Here``603 Decline``NO RESPONSE`;从 T 到 A 的 `s21-call-a-ip``s21-call-a-sip` 与 5 次 `s21-over-cps-a` 均稳定输出 Call-ID 并收到 A 当前预期的 `SIP/2.0 503 Config Missing`MI FIFO 为 `660 opensips:opensips`TLS 私钥文件为 `600 root:root`
- 回滚方式:停止 T 的 `opensips`,从 `/var/backups/lisglosips-s21/20260621T072316Z/opensips` 恢复 `/etc/opensips`,用该目录 `opensips.sql` 恢复 OpenSIPS 数据库,删除 `/opt/lisglosips-s21``/etc/lisglosips-s21`,执行 `opensips -C -f /etc/opensips/opensips.cfg` 后启动 `opensips`。详见 `docs/SERVER_T_CUSTOMER_SIMULATION_RUNBOOK.md`
- 未解决问题:A 端尚未发布真实客户/路由配置,因此 T 到 A 的 INVITE 当前按 S20 逻辑返回 `503 Config Missing`,真实路由、双向媒体、正式 CDR 和扣费留给 S22/S28T 的 RTPEngine 仍无内核加速,防火墙默认放行、Apache/rpcbind 暴露和克隆身份问题仍为历史风险,未在 S21 扩大处理。
- 下一任务:`S22 - CDR Redis Stream`
### 2026-06-21 15:05 - S20 Server A Redis 热路径与 HEP/指标
- 状态:已完成
- 操作服务器:A、B、T、本地
- 完成内容:在 Server A 部署 Redis 热路径 Lua 原子检查,基于 `cfg:active_version` 和版本化 `cfg:v:{version}:*` 配置读取客户、客户网关、IP 认证、策略与线路组;OpenSIPS `INVITE` 接入 Lua 热路径,配置缺失返回 `503 Config Missing`Redis 不可用返回 `503 Redis Unavailable`,策略允许但路由未进入 S21/S28 前返回 `503 Routing Not Ready`;新增 CDR 冒烟 XADD 到 `stream:cdr_payload`;新增本地 Redis AUTH 代理,凭据通过 systemd encrypted credential 加载,OpenSIPS 只连接 `127.0.0.1:6380`;修正 HEP socket 为 `100.90.90.90:9061` 并发送至 B `100.90.90.91:9060`;新增 OpenSIPS Prometheus `script:` 指标和 Node Exporter textfile 指标;新增 S20 nftables 表阻断非本地 Redis 代理和非既有连接访问 HEP socket;未调整 SSH 管理端口或密钥登录策略,未写入明文密码。
- 修改文件:A 的 `/etc/opensips/opensips.cfg``/etc/opensips/lisglosips_hotpath.lua``/usr/local/sbin/lisglosips-redis-auth-proxy``/usr/local/sbin/lisglosips-redis-load-hotpath``/usr/local/sbin/lisglosips-opensips-preprocess``/usr/local/sbin/lisglosips-s20-metrics``/etc/systemd/system/lisglosips-redis-auth-proxy.service``/etc/systemd/system/lisglosips-redis-hotpath-load.service``/etc/systemd/system/opensips.service.d/20-lisglosips-s20.conf``/etc/systemd/system/lisglosips-s20-metrics.service``/etc/systemd/system/lisglosips-s20-metrics.timer``/etc/default/lisglosips-node-exporter``/etc/nftables.d/lisglosips-s20-observability.nft``/etc/systemd/system/lisglosips-a-firewall.service`;本地 `infra/server-a/s20/``docs/SERVER_A_REDIS_HEP_METRICS_RUNBOOK.md``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新系统软件;继续使用 OpenSIPS `3.6.7-1`、Redis Server B 既有 `7.0.15`、Node Exporter `1.7.0`、RTPEngine `11.5.1.18-1ubuntu1.2`。新增 systemd encrypted credential 文件 `/etc/credstore.encrypted/lisglosips-redis-password.cred`,未写明文凭据。
- 执行的关键命令:`opensips -C -f /etc/opensips/opensips.cfg``systemctl daemon-reload/enable/restart/start/stop``nft -c/-f``curl http://127.0.0.1:8888/metrics``curl http://100.90.90.90:9100/metrics`、从 T 发送 SIP `INVITE`A/B Redis RESP 冒烟,A `tcpdump` 观察 HEPB `journalctl -u heplify-server` 查询 HEP 统计。所有凭据均通过 `.codex-private/` 读取并已避免回显。
- 验证结果:A `systemctl is-system-running``running``opensips``lisglosips-redis-auth-proxy``lisglosips-s20-metrics.timer``lisglosips-node-exporter``lisglosips-a-firewall``rtpengine-daemon``rtpengine-recording-daemon` 均 activeS20 新增服务均 enabled`lisglosips-redis-hotpath-load.service` oneshot 结果为 `success`;监听端口为 `100.90.90.90:15060/udp``100.90.90.90:9061/udp``127.0.0.1:6380/tcp``127.0.0.1:8888/tcp``100.90.90.90:9100/tcp`S20 nftables 表存在;A 到 B Redis PING/AUTH 通过;无配置版本时 T 发 INVITE 返回 `SIP/2.0 503 Config Missing`,OpenSIPS 记录热路径拒绝且 CDR XADD 成功;临时 Redis 冒烟配置下 T 发 INVITE 返回 `SIP/2.0 503 Routing Not Ready`,返回字段含配置版本、客户网关、策略和线路组,测试 Redis keys 与 CDR 事件已清理;短暂停止 Redis 代理后 T 发 INVITE 返回 `SIP/2.0 503 Redis Unavailable`,日志明确记录 Redis 热路径不可用和 CDR XADD 失败,代理与 Lua loader 随后恢复;OpenSIPS Prometheus 暴露 `lisglosips_opensips_s20_*`B 可抓取 A Node Exporter textfile 指标 `lisglosips_redis_proxy_up`、录音 tmpfs、ready/part 文件数和 RTPEngine sessionsHEP 绑定修正后 A 侧抓包看到发往 B `9060/udp` 的 HEPB heplify 统计显示 HEP 收包且 Error 为 0,修正后 A 无新增 HEP 发送错误;S20 本地产物敏感扫描未发现明文凭据,命中的仅为 credential 名称或历史文档中的安全术语。
- 回滚方式:A 备份目录为 `/var/backups/lisglosips-s20/20260621T061900Z`,验证阶段的 OpenSIPS 配置副本位于该目录 `post-verify/` 下;回滚时停止 S20 metrics timer 和 Redis AUTH proxy,从备份恢复 `/etc/opensips/opensips.cfg`、S20 systemd/drop-in、Node Exporter 默认文件和 nftables 文件,执行 `opensips -C -f /etc/opensips/opensips.cfg` 后重启 `opensips``lisglosips-node-exporter``lisglosips-a-firewall`,不删除 Redis Stream 历史数据。详细步骤见 `docs/SERVER_A_REDIS_HEP_METRICS_RUNBOOK.md`
- 未解决问题:S20 只完成热路径检查、HEP 和指标,不实现真实落地路由、SIP 注册账号、双向媒体、正式 CDR 契约和计费;这些留给 S21/S22/S28。B sudo 凭据仍无法通过校验,本次 B 侧仅使用免 sudo 的只读 heplify 日志和已有服务凭据做验证;HOMER 数据库未稳定查到本次 OpenSIPS HEP Call-ID 行,但 heplify 已确认收包,后续 S28 端到端联调需继续核对 HOMER UI/库内 Call-ID。
- 下一任务:`S21 - Server T 客户模拟配置`
### 2026-06-21 14:00 - S19 Server A RTPEngine 与录音 tmpfs
- 状态:已完成
- 操作服务器:A、T、本地
- 完成内容:在 Server A 安装并固定 RTPEngine `11.5.1.18-1ubuntu1.2` 及 kernel DKMS/recording 组件;加载 `xt_RTPENGINE` 内核模块,DKMS 已为 `6.17.0-22-generic``6.17.0-35-generic` 构建;配置 RTPEngine 媒体地址 `100.90.90.90`、RTP `30000-40000/udp`、本地 NG/CLI/HTTP 控制口 `127.0.0.1:2223/2224/2225`;创建 `/dev/shm/voip_rec` 3 GiB tmpfs 与 `spool/incoming/ready/failed/tmp` 目录;部署录音 finalizer,按 `incoming -> ready/*.part -> ready/*.ready` 原子规则生成 ready 文件;新增 `lisglosips-a-firewall.service` 保障 S18/S19 nftables 表重启后自动加载;新增 OpenSIPS systemd drop-in 等待 Tailscale 地址后启动;覆盖 `rtpengine-recording-daemon.service`,移除包默认 NFS/rpcbind 依赖;停止并 mask S19 不使用的 `rpcbind.service/socket`。未调整 SSH 管理端口或密钥登录策略。
- 修改文件:A 的 `/etc/rtpengine/rtpengine.conf``/etc/rtpengine/rtpengine-recording.conf``/etc/default/rtpengine-daemon``/etc/default/rtpengine-recording-daemon``/etc/fstab``/etc/nftables.d/lisglosips-s19-rtpengine.nft``/etc/systemd/system/lisglosips-a-firewall.service``/etc/systemd/system/opensips.service.d/10-lisglosips-wait-tailscale.conf``/etc/systemd/system/rtpengine-recording-daemon.service``/etc/systemd/system/lisglosips-recording-finalize.service``/etc/systemd/system/lisglosips-recording-finalize.timer``/etc/tmpfiles.d/lisglosips-voip-rec.conf``/usr/local/sbin/lisglosips-recording-finalize`;本地 `infra/server-a/s19/``docs/SERVER_A_RTPENGINE_RECORDING_RUNBOOK.md``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:`rtpengine``rtpengine-daemon``rtpengine-recording-daemon``rtpengine-iptables``rtpengine-kernel-dkms``rtpengine-utils` 均为 `11.5.1.18-1ubuntu1.2` 并已 `apt-mark hold`;安装匹配内核 headers。未新增明文凭据。
- 执行的关键命令:`apt-get install``apt-mark hold``modprobe xt_RTPENGINE``dkms status``nft -c/-f``systemctl daemon-reload/enable/restart/reboot``systemd-tmpfiles --create``rtpengine-ctl -ip 127.0.0.1 -port 2224 list numsessions/interfaces``tcpdump`、从 T 发送 RTP UDP 与 SIP `OPTIONS`/`REGISTER` 测试包。
- 验证结果:A 二次重启后 `systemctl is-system-running``running` 且 0 failed unit`opensips``rtpengine-daemon``rtpengine-recording-daemon``lisglosips-recording-finalize.timer``lisglosips-a-firewall.service``lisglosips-node-exporter.service` 全部 active/enabled`/dev/shm/voip_rec` 为 3.0 GiB tmpfs,子目录均为 `rtpengine:rtpengine 0750``xt_RTPENGINE` 已加载,DKMS 状态 installed`rtpengine-ctl` 显示接口 `100.90.90.90` 和端口段 `30000 - 40000`nftables S18/S19 表重启后存在;从 T 到 A `30000/udp``40000/udp` 的 RTP 探针在 A `tailscale0` 抓包可见;从 T 发 SIP `OPTIONS` 返回 `SIP/2.0 200 Keepalive``REGISTER` 返回 `SIP/2.0 401 Authentication Required``WWW-Authenticate`;录音 finalizer 冒烟将测试 `.wav` 生成 `ready/...wav.ready`,权限 `0640`,无 `.part` 残留,测试文件已删除;本地 S19 产物敏感信息扫描未命中。
- 回滚方式:A 备份目录为 `/var/backups/lisglosips-s19/20260621T054645Z`;回滚时停止 `rtpengine-recording-daemon``lisglosips-recording-finalize.timer``rtpengine-daemon`,从备份恢复 `/etc/rtpengine/``/etc/default/``/etc/fstab` 等配置,删除 `inet lisglosips_s19_rtpengine` nftables 表,停用并删除 S19 systemd/tmpfiles/finalizer 文件,必要时 `apt-mark unhold` 后卸载 RTPEngine 包;详细步骤见 `docs/SERVER_A_RTPENGINE_RECORDING_RUNBOOK.md`
- 未解决问题:S19 尚未把 OpenSIPS 呼叫路由接入 RTPEngine,真实双向音频、NAT 媒体锚定和完整通话录音需在 S20/S21/S28 完成;Tailscale APT 源在 A 上仍有 DNS 解析失败历史但当前 Tailscale 网络和服务运行正常;B sudo 凭据阻塞仍未解决,不影响本次 A 端 S19。
- 下一任务:`S20 - Server A Redis 热路径与 HEP/指标`
### 2026-06-21 13:29 - S18 Server A OpenSIPS 基线
- 状态:已完成
- 操作服务器:A、本地
- 完成内容:在 Server A 添加 OpenSIPS 官方 APT 源并安装固定 OpenSIPS `3.6.7-1`;安装并 hold `opensips``opensips-cli``opensips-auth-modules``opensips-redis-module``opensips-http-modules``opensips-json-module``opensips-restclient-module``opensips-prometheus-module``opensips-tlsmgm-module``opensips-tls-module``opensips-tls-openssl-module`;部署 `/etc/opensips/opensips.cfg`OpenSIPS 仅监听 `100.90.90.90:15060/udp`HEP transport 占位监听 `127.0.0.1:9061/udp`MI HTTP 仅监听 `127.0.0.1:8888/tcp`;加载 `auth``pike``ratelimit``cachedb_redis``proto_hep``tracer``mi_http` 等模块;实现 S18 保守路由基线:`OPTIONS` 返回 `200 Keepalive``REGISTER` 返回带 Digest challenge 的 `401 Authentication Required``INVITE` 在 S20/S21 前返回 `503 Routing Not Ready`,避免开放中继;新增 `inet lisglosips_s18_opensips` nftables 表,只允许 T `100.93.185.30` 访问 A 的 UDP `15060`,丢弃其他 15060 来源,并阻断非本地 MI HTTP;未调整 SSH 管理端口或密钥登录策略。
- 修改文件:A 的 `/etc/apt/sources.list.d/opensips.list``/etc/apt/sources.list.d/opensips-cli.list``/etc/opensips/opensips.cfg``/etc/default/opensips``/etc/nftables.d/lisglosips-s18-opensips.nft`;本地 `infra/server-a/s18/``docs/SERVER_A_OPENSIPS_BASELINE_RUNBOOK.md``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:OpenSIPS `3.6.7-1`OpenSIPS CLI `0.4.0~20260522~570a9a9-1`;所有 S18 OpenSIPS 包已 `apt-mark hold`。未新增明文凭据。
- 执行的关键命令:`apt-get update``apt-get install``apt-mark hold``opensips -C -f /etc/opensips/opensips.cfg``systemctl enable --now/restart opensips``nft -c -f /etc/nftables.d/lisglosips-s18-opensips.nft``nft -f /etc/nftables.d/lisglosips-s18-opensips.nft``curl -X POST http://127.0.0.1:8888/mi`、从 T 发送 SIP `OPTIONS`/`REGISTER` 测试包。
- 验证结果:A 系统状态 `running``opensips.service` active/enabledOpenSIPS 配置语法检查通过;监听端口为 `100.90.90.90:15060/udp``127.0.0.1:9061/udp``127.0.0.1:8888/tcp`S06 的 `100.90.90.90:9100/tcp` 保持;MI HTTP JSON-RPC `version` 返回 `OpenSIPS (3.6.7 (x86_64/linux))`;从 T `100.93.185.30` 发往 A `15060/udp` 的 SIP `OPTIONS` 返回 `SIP/2.0 200 Keepalive`SIP `REGISTER` 返回 `SIP/2.0 401 Authentication Required` 且包含 `WWW-Authenticate`nftables S18 表加载成功;本地 S18 产物敏感信息扫描未命中。
- 回滚方式:A 备份目录为 `/var/backups/lisglosips-s18/20260621T051403Z`;回滚时执行 `systemctl stop opensips`,从备份恢复 `/etc/opensips/``/etc/default/opensips`,删除 `inet lisglosips_s18_opensips` nftables 表,必要时 `apt-mark unhold` 并卸载 S18 OpenSIPS 包;详细步骤见 `docs/SERVER_A_OPENSIPS_BASELINE_RUNBOOK.md`
- 未解决问题:Tailscale APT 源在 A 上一度 DNS 解析失败,但 Tailscale 服务和当前连接未受影响;A 仍存在 S01 记录的本地 ens18 双地址/路由历史风险,S18 的 OpenSIPS 绑定 Tailscale 地址规避了 SIP 监听歧义;当前 HEP trace 已有目标配置但未查询 B 的 HOMER 入库,完整 HEP/Redis 热路径和指标留给 S20RTPEngine、RTP 端口和录音 tmpfs 留给 S19。
- 下一任务:`S19 - Server A RTPEngine 与录音 tmpfs`
### 2026-06-21 13:06 - S17 落地线路组 API
- 状态:已完成
- 操作服务器:本地;B 未执行写操作
- 完成内容:新增 LandingLineGroupsModule,提供 `GET /landing-line-groups``GET /landing-line-groups/:id``POST /landing-line-groups``PATCH /landing-line-groups/:id``POST /landing-line-groups/:id/enable``POST /landing-line-groups/:id/disable``DELETE /landing-line-groups/:id``POST /landing-line-groups/:id/items``PATCH /landing-line-groups/items/:itemId``DELETE /landing-line-groups/:id/items/:gatewayId``POST /landing-line-groups/:id/items/reorder`;支持线路组启停、软删除、成员增删改、成员优先级重排、权重、成员并发上限和成员状态;返回 `itemCount``enabledItemCount``concurrencyCapSum``gatewayConcurrencyLimitSum``policyCount`,用于组内并发汇总与引用可见性;新增引用校验,成员只能引用未删除落地网关,软删除线路组前阻止仍被未删除客户网关策略引用的线路组删除;读操作绑定 `line_groups.view`,写操作绑定 `line_groups.manage` 和审计动作;线路组与成员变更写入 `line_group_config` OutboxConfig Publisher 扩展线路组快照并发布 `cfg:v:{version}:line_group:*``line_group:*:items`
- 修改文件:`apps/api/src/modules/app.module.ts``apps/api/src/modules/landing-line-groups/``apps/worker-config-publisher/src/publisher.ts``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新系统软件;未新增 npm 包;继续使用 Node.js `v22.22.2`、Prisma `6.19.0`、pnpm `10.33.0`
- 执行的关键命令:`corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 test -- landing-line-groups``corepack pnpm@10.33.0 test``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build`
- 验证结果:typecheck 通过;S17 相关 Vitest 14 个测试文件 44 条测试通过,其中 `landing-line-groups.e2e.spec.ts` 覆盖线路组列表并发汇总、写权限拒绝、创建、成员新增/更新/删除、成员参数校验、优先级重排、启停、软删除和审计记录;全量 Vitest 14 个测试文件 44 条测试通过;lint 通过;workspace build 通过,包含 `apps/worker-config-publisher` 构建通过。
- 回滚方式:移除 `LandingLineGroupsModule` 导入并删除 `apps/api/src/modules/landing-line-groups/`;恢复 `apps/worker-config-publisher/src/publisher.ts` 中 S17 新增的 `line_group_config` Outbox 处理和线路组快照发布逻辑。S17 未新增数据库迁移,B 端未创建临时库、未改服务、未写 Redis 或 MySQL。
- 未解决问题:B 写入式临时库冒烟未执行,因为当前 B sudo 凭据仍无法通过校验,迁移账号也无权创建临时库;S17 不实现 OpenSIPS 呼叫路由或 Server A 配置,这些从 S18 开始。
- 下一任务:`S18 - Server A OpenSIPS 基线`
### 2026-06-21 11:57 - S16 落地网关 API
- 状态:已完成
- 操作服务器:本地;B 未执行写操作
- 完成内容:新增 VendorGatewaysModule,提供 `GET /vendor-gateways``GET /vendor-gateways/:id``POST /vendor-gateways``PATCH /vendor-gateways/:id``POST /vendor-gateways/:id/enable``POST /vendor-gateways/:id/disable`;支持供应商落地网关 `IP``SIP_DIGEST``MIXED` 三种认证模式,创建和更新时校验 host/port/transport、SIP 用户名与密码强度,并以 `username:host:password` 生成 HA1API 响应只返回 `hasSipCredential` 不返回明文密码或 HA1;支持 CPS、并发上限、禁呼时段、编码优先级、主叫/被叫号码前缀转换、计费周期秒数和周期费率,响应补充按分钟折算的 `minuteRate`;写操作绑定 `vendor_gateways.manage` 和审计动作,读操作绑定 `vendor_gateways.view`;落地网关创建、更新、启停写入 `vendor_gateway_config` OutboxConfig Publisher 扩展供应商落地网关快照并发布到 Redis 版本化配置。
- 修改文件:`apps/api/src/modules/app.module.ts``apps/api/src/modules/vendor-gateways/``apps/worker-config-publisher/src/publisher.ts``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新系统软件;未新增 npm 包;继续使用 Node.js `v22.22.2`、Prisma `6.19.0`、pnpm `10.33.0`
- 执行的关键命令:`corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 test -- vendor-gateways``corepack pnpm@10.33.0 test``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build`
- 验证结果:typecheck 通过;S16 相关 Vitest 13 个测试文件 40 条测试通过,其中 `vendor-gateways.e2e.spec.ts` 覆盖列表脱敏、写权限拒绝、完整 SIP Digest 网关创建、审计敏感字段脱敏、切换 IP 模式清空 SIP 凭据、重复编码优先级校验和启停;全量 Vitest 13 个测试文件 40 条测试通过;lint 通过;workspace build 通过,包含 `apps/worker-config-publisher` 构建通过。
- 回滚方式:移除 `VendorGatewaysModule` 导入并删除 `apps/api/src/modules/vendor-gateways/`;恢复 `apps/worker-config-publisher/src/publisher.ts` 中 S16 新增的 `vendor_gateway_config` Outbox 处理和落地网关快照发布逻辑。S16 未新增数据库迁移,B 端未创建临时库、未改服务、未写 Redis 或 MySQL。
- 未解决问题:B 写入式临时库冒烟未执行,因为当前 B sudo 凭据仍无法通过校验,迁移账号也无权创建临时库;S16 不实现落地线路组成员、优先级重排或并发汇总,这些留给 S17。
- 下一任务:`S17 - 落地线路组 API`
### 2026-06-21 11:45 - S15 供应商管理 API
- 状态:已完成
- 操作服务器:本地;B 未执行写操作
- 完成内容:新增 VendorsModule,提供 `GET /vendors``GET /vendors/:id``POST /vendors``PATCH /vendors/:id``POST /vendors/:id/enable``POST /vendors/:id/disable``DELETE /vendors/:id`;供应商返回余额、授信额度、可用余额和落地网关数量;余额保持只读,供应商充值仍通过 S12 的不可变充值流水;支持联系人、电话、邮箱、结算方式、备注、授信额度和启停状态维护;软删除前阻止仍有关联未删除落地网关的供应商删除;读操作绑定 `vendors.view`,写操作绑定 `vendors.manage` 和审计动作。
- 修改文件:`apps/api/src/modules/app.module.ts``apps/api/src/modules/vendors/``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新系统软件;未新增 npm 包;继续使用 Node.js `v22.22.2`、Prisma `6.19.0`、pnpm `10.33.0`
- 执行的关键命令:`corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 test -- vendors``corepack pnpm@10.33.0 test``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build`
- 验证结果:typecheck 通过;S15 相关 Vitest 12 个测试文件 36 条测试通过;全量 Vitest 12 个测试文件 36 条测试通过,覆盖供应商余额/授信/网关数量读取、写权限拒绝、新增、编辑、启停、软删除和审计记录;lint 通过;workspace build 通过。
- 回滚方式:移除 `VendorsModule` 导入并删除 `apps/api/src/modules/vendors/`;S15 未新增数据库迁移。B 端未创建临时库、未改服务、未写 Redis 或 MySQL。
- 未解决问题:B 写入式临时库冒烟未执行,因为当前 B sudo 凭据仍无法通过校验,迁移账号也无权创建临时库;S15 不实现落地网关、费率、CPS/并发或号码转换,这些留给 S16。
- 下一任务:`S16 - 落地网关 API`
### 2026-06-21 11:39 - S14 客户网关策略与 Redis 配置发布
- 状态:已完成
- 操作服务器:本地;B 未执行写操作
- 完成内容:新增 CustomerGatewayPoliciesModule,提供 `GET /customer-gateways/:id/policies``POST /customer-gateways/:id/policies``PATCH /customer-gateway-policies/:id``DELETE /customer-gateway-policies/:id``POST /customer-gateways/:id/policies/reorder`;支持主叫/被叫 `ANY``EQUALS``PREFIX` 匹配,校验非 `ANY` 模式必须提供匹配值;支持显式优先级和自动追加优先级,重排时要求一次提交全部有效策略 ID 并用两阶段负优先级避免唯一索引冲突;客户网关创建、更新、启停以及策略创建、更新、删除、重排均在同一事务内写入 `outbox_events`,不直接双写 RedisConfig Publisher 从骨架升级为处理 `customer_gateway_config` Outbox 的 worker,生成完整 Redis 配置快照,写入 `cfg:v:{version}:customer:*``customer_gateway:*``auth:ip:*``auth:sip:*` 和策略列表,原子切换 `cfg:active_version`,并维护 `cfg:previous_version` 作为回滚点;新增 `rollbackActiveConfig` 函数用于切回上一版本。
- 修改文件:`apps/api/src/modules/app.module.ts``apps/api/src/modules/customer-gateways/customer-gateways.repository.ts``apps/api/src/modules/customer-gateway-policies/``apps/worker-config-publisher/src/main.ts``apps/worker-config-publisher/src/publisher.ts``apps/worker-config-publisher/package.json``apps/worker-config-publisher/tsconfig.json``packages/redis/src/index.ts``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新系统软件;未新增需要联网下载的 npm 包;继续使用 Node.js `v22.22.2`、Prisma `6.19.0`、pnpm `10.33.0`、ioredis `5.8.2`(由 `@lisglosips/redis` 继续提供)。
- 执行的关键命令:`corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 test -- customer-gateway``corepack pnpm@10.33.0 test``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build`;曾尝试 `corepack pnpm@10.33.0 install`,因本机 DNS 无法解析 `registry.npmjs.org` 失败,随后移除新增联网依赖,最终无需更新锁文件。
- 验证结果:typecheck 通过;S14 相关 Vitest 11 个测试文件 33 条测试通过;全量 Vitest 11 个测试文件 33 条测试通过,覆盖策略列表、权限拒绝、策略创建/更新/重排/删除、审计记录和匹配值校验;lint 通过;workspace build 通过,包含 `apps/worker-config-publisher` 构建通过。
- 回滚方式:移除 `CustomerGatewayPoliciesModule` 导入并删除 `apps/api/src/modules/customer-gateway-policies/`;恢复 `apps/api/src/modules/customer-gateways/customer-gateways.repository.ts` 中的 Outbox 写入前版本;恢复 `apps/worker-config-publisher/src/main.ts` 为骨架并删除 `publisher.ts`;恢复 `packages/redis/src/index.ts` 的 S14 新增 Redis 常量和 helper。S14 未新增数据库迁移,B 端未创建临时库、未改服务、未写 Redis。
- 未解决问题:B 写入式临时库/Redis 冒烟未执行,因为当前 B sudo 凭据仍无法通过校验,迁移账号也无权创建临时库;Config Publisher 当前完成客户/客户网关/策略快照发布,落地网关、线路组成员和供应商侧热路径会随 S15-S17 数据模型 API 补齐后扩展到快照。
- 下一任务:`S15 - 供应商管理 API`
### 2026-06-21 11:28 - S13 客户网关 API
- 状态:已完成
- 操作服务器:本地;B 仅读取 SSH/Node/pnpm 状态并尝试临时库验收,未修改正式库或服务
- 完成内容:新增 CustomerGatewaysModule,提供 `GET /customer-gateways``GET /customer-gateways/:id``POST /customer-gateways``PATCH /customer-gateways/:id``POST /customer-gateways/:id/enable``POST /customer-gateways/:id/disable`;支持 `IP``SIP_DIGEST``MIXED` 三种认证模式;创建和更新时校验来源 IP、SIP 用户名、SIP 域和密码强度;SIP 注册密码仅用于生成 OpenSIPS Digest HA1API 响应只返回 `hasSipCredential`,不返回 `sipPassword``sipHa1`;SIP 用户名或域名变更时要求同时提交新密码,避免旧 HA1 与新身份不匹配;所有写操作绑定 `customer_gateways.manage` 和审计动作,读操作绑定 `customer_gateways.view`;日志脱敏路径新增 `sipHa1`
- 修改文件:`apps/api/src/modules/app.module.ts``apps/api/src/modules/customer-gateways/``packages/observability/src/index.ts``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新系统软件;继续使用 Node.js `v22.22.2`、Prisma `6.19.0`、pnpm `10.33.0`
- 执行的关键命令:`corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 test``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build`B 端执行只读 `hostname && node --version && pnpm --version`;尝试通过 SSH 隧道连接 B MySQL 并用 `MYSQL_MIGRATE` 创建 S13 临时库,因账号无 `mysql` 系统库访问权限返回 `P1010`;B sudo 凭据校验失败,未继续执行需要 sudo 的 MySQL 创建/清理命令。
- 验证结果:本地 typecheck 通过;Vitest 10 个测试文件 29 条测试通过,新增覆盖客户网关只读权限、写权限拒绝、IP 网关返回脱敏、SIP Digest 网关创建、审计敏感字段脱敏、SIP 身份变更强制新密码、切换 IP 模式清空 SIP 凭据、启停接口;lint 通过;workspace build 通过;B 的 Node `v22.22.2` 和 pnpm `10.33.0` 只读确认通过;B 临时库冒烟未完成,未创建临时库,SSH 隧道、本地 S13 临时 SQL 和远端 `/tmp/lisglosips-s13-create-*.sql` 已清理。
- 回滚方式:移除 `CustomerGatewaysModule` 导入并删除 `apps/api/src/modules/customer-gateways/`;从 `packages/observability/src/index.ts` 移除 `*.sipHa1` 脱敏项;S13 未新增数据库迁移。B 端未创建临时库、未修改服务,仅使用短时 SSH 隧道和 `/tmp` 临时 SQL 文件,已清理。
- 未解决问题:S13 未执行 B 临时库冒烟,因为当前 `.codex-private` 中的 B sudo 凭据无法通过校验,`MYSQL_MIGRATE` 也无权在 `mysql` 系统库创建临时库;后续需要修复 B sudo 凭据或提供受控临时库创建账号。S13 不实现客户网关策略、Outbox 或 Redis 发布,这些留给 S14。
- 下一任务:`S14 - 客户网关策略与 Redis 配置发布`
### 2026-06-21 11:14 - S12 充值与不可变余额流水
- 状态:已完成
- 操作服务器:本地、B
- 完成内容:新增 RechargesModule,提供 `POST /customers/:id/recharges``POST /vendors/:id/recharges``GET /recharges`;客户和供应商充值在同一数据库事务内完成账户行锁定、余额读取、不可变充值流水写入、余额更新和幂等结果保存;重复相同幂等键返回原充值结果,不重复加余额;同一幂等键不同请求返回冲突;充值写操作绑定 `recharges.manage` 和审计动作,查询绑定 `recharges.view`;并发写冲突和唯一键竞争只对 Prisma `P2034`/`P2002` 做短退避重试。
- 修改文件:`apps/api/src/modules/app.module.ts``apps/api/src/modules/recharges/``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新系统软件;继续使用 Node.js `v22.22.2`、Prisma `6.19.0`、pnpm `10.33.0`
- 执行的关键命令:`corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 test``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build`;B 端通过 SSH 隧道连接临时 MySQL 库执行 `prisma migrate deploy``db:seed``prisma migrate status`,并使用编译后的充值仓库执行客户/供应商充值、幂等重放、幂等冲突和并发充值冒烟。
- 验证结果:本地 typecheck 通过;Vitest 9 个测试文件 24 条测试通过,覆盖充值权限拒绝、客户充值、供应商充值、幂等重放、幂等冲突、流水查询和审计记录;lint 通过;workspace build 通过;B 临时空库 `lisglosips_s12_verify_20260621111322` 成功应用 S08/S09 共 2 个迁移,seed 执行成功,`migrate status` 显示 schema up to date;B 临时库验证客户充值前后余额、供应商充值前后余额、重复幂等不重复入账、幂等冲突拒绝、5 路并发客户充值后余额 `15.000000`、客户流水 6 条、供应商流水 1 条、成功幂等记录 7 条;B 残留检查确认无 S12 临时库、临时用户和 `/tmp/lisglosips-s12-*` 文件。
- 回滚方式:移除 `RechargesModule` 导入并删除 `apps/api/src/modules/recharges/`;S12 未新增数据库迁移。B 端仅创建临时库、临时用户和 SSH 隧道,已删除或关闭。
- 未解决问题:S12 只实现手工充值入账;CDR 扣费、余额扣减和扣费幂等留给 S22/S23;供应商管理 CRUD 仍按任务顺序留给 S15。
- 下一任务:`S13 - 客户网关 API`
### 2026-06-21 10:57 - S11 客户管理 API
- 状态:已完成
- 操作服务器:本地、B
- 完成内容:新增 CustomersModule,提供客户列表、详情、新增、编辑、启用、停用和软删除 API;返回余额、授信额度、最低余额、可用余额和启用网关数量;客户余额只读,避免绕过 S12 充值流水;所有写操作均绑定 `customers.manage` 和审计动作,读操作绑定 `customers.view`;软删除前阻止仍有关联启用网关的客户删除。
- 修改文件:`apps/api/src/modules/app.module.ts``apps/api/src/modules/customers/``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新系统软件;继续使用 Node.js `v22.22.2`、Prisma `6.19.0`、pnpm `10.33.0`
- 执行的关键命令:`corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 test``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build`;B 端通过 SSH 隧道连接临时 MySQL 库执行 `prisma migrate deploy``db:seed``prisma migrate status` 和客户 CRUD 冒烟脚本。
- 验证结果:本地 typecheck 通过;Vitest 8 个测试文件 21 条测试通过,覆盖客户读权限、写权限拒绝、新增、启停、软删除、余额/授信字段和审计记录;lint 通过;workspace build 通过;B 临时空库 `lisglosips_s11_verify_20260621105537` 成功应用 S08/S09 共 2 个迁移,seed 执行成功,`migrate status` 显示 schema up to dateB 临时库客户创建、状态更新、Decimal 精度、网关计数和软删除冒烟通过;B 残留检查确认无 S11 临时库、临时用户和 `/tmp/lisglosips-s11-*` 文件。
- 回滚方式:移除 `CustomersModule` 导入并删除 `apps/api/src/modules/customers/`;S11 未新增数据库迁移。B 端仅创建临时库、临时用户和 SSH 隧道,已删除或关闭。
- 未解决问题:S11 不实现充值入账和余额变更事务,余额仍只能通过后续 S12 不可变流水更新;客户网关数量当前依赖后续 S13 的网关数据。
- 下一任务:`S12 - 充值与不可变余额流水`
### 2026-06-21 10:46 - S10 用户、角色、权限与审计
- 状态:已完成
- 操作服务器:本地、B
- 完成内容:实现全局 Access Token 认证 Guard、RBAC Guard、`@Public``@RequirePermissions` 与当前用户注入;新增用户、角色、权限和审计日志查询 API;实现用户创建、资料更新、重置密码和角色权限维护;内置角色禁止改名、停用和变更权限;重置密码仅保存 Argon2id 哈希并要求下次改密;新增审计拦截器,按接口元数据记录 request_id、操作者、角色、IP、User-Agent、模块、动作、对象、成功/失败、错误码和脱敏后的 before/after 摘要。
- 修改文件:`packages/auth/src/index.ts``packages/database/src/index.ts``apps/api/src/modules/app.module.ts``apps/api/src/modules/auth/auth.controller.ts``apps/api/src/modules/health/health.controller.ts``apps/api/src/modules/security/``apps/api/src/modules/audit/``apps/api/src/modules/audit-logs/``apps/api/src/modules/users/``apps/api/src/modules/roles/``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新系统软件;继续使用 Node.js `v22.22.2`、Prisma `6.19.0`、pnpm `10.33.0`
- 执行的关键命令:`corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 test``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 build`;B 端通过 SSH 隧道连接临时 MySQL 库执行 `prisma migrate deploy``db:seed``prisma migrate status`B 端检查并清理 `lisglosips_s10_verify_*``s10v_*``/tmp/lisglosips-s10-*` 临时残留。
- 验证结果:本地 typecheck 通过;Vitest 7 个测试文件 18 条测试通过,覆盖未认证拒绝、越权拒绝、重置密码审计脱敏、内置角色保护和审计日志查询;lint 通过;workspace build 通过;B 临时空库 `lisglosips_s10_verify_20260621104300` 成功应用 S08/S09 共 2 个迁移,seed 执行成功,`migrate status` 显示 schema up to dateB 残留检查确认无 S10 临时库、临时用户和 `/tmp/lisglosips-s10-*` 文件。
- 回滚方式:恢复上述本地文件到 S10 前版本;S10 未新增数据库迁移,若需回滚只需移除新增模块导入和相关接口代码。B 端仅创建临时库、临时用户和 `/tmp` 临时文件,已删除。
- 未解决问题:S10 只完成后端安全与审计基础;后续业务 API 必须继续显式标注权限和审计动作,客户、充值、网关等业务模块从 S11 开始实现。
- 下一任务:`S11 - 客户管理 API`
### 2026-06-21 10:15 - S09 登录认证基础
- 状态:已完成
- 操作服务器:本地、B
- 完成内容:实现登录、刷新和退出接口;新增 Argon2id 密码哈希与校验,优先使用 Node 原生实现并在 Node 22 上回退到 vendored WASM;实现 HMAC access token、HttpOnly refresh cookie、refresh token SHA-256 摘要入库、会话撤销与轮换、失败计数和临时锁定;新增 Prisma `user_sessions` 模型和迁移;补充 Auth API、服务层、密码/Token 原语和 schema 合同测试。
- 修改文件:`packages/auth/src/index.ts``packages/auth/src/index.spec.ts``packages/auth/vendor/argon2id/``apps/api/src/modules/auth/``apps/api/src/modules/database/``apps/api/src/modules/app.module.ts``apps/api/src/shared/config.ts``apps/api/tsconfig.json``apps/api/package.json``.env.example``eslint.config.mjs``prisma/schema.prisma``prisma/migrations/20260621093000_auth_sessions/migration.sql``packages/database/src/schema-contract.spec.ts``pnpm-lock.yaml``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新系统软件;vendored npm 包 `argon2id` `1.0.1`,MIT 许可证,WASM 哈希记录于 `packages/auth/vendor/argon2id/UPSTREAM.md`;继续使用 Node.js `v22.22.2`、Prisma `6.19.0`、pnpm `10.33.0`
- 执行的关键命令:`corepack pnpm@10.33.0 prisma:validate``prisma:generate``typecheck``test``lint``build`B 端通过临时 `/tmp` 包验证 Node 22 下 Argon2id WASM 哈希与校验;通过 SSH 隧道连接 B 的临时 MySQL 库执行 `prisma migrate deploy``prisma migrate status`
- 验证结果:本地 Prisma schema validate 通过;Prisma Client 生成通过;typecheck 通过;Vitest 6 个测试文件 15 条测试通过,覆盖 Argon2id、Token、登录成功、失败锁定、refresh 轮换、旧 refresh 拒绝、logout 后拒绝和健康检查;lint 通过;workspace build 通过;B Node `v22.22.2` 上 Argon2id WASM 校验结果为正确值通过、随机值拒绝;B 临时空库 `lisglosips_s09_verify_20260621101249` 成功应用 S08 与 S09 共 2 个迁移,`migrate status` 显示 schema up to date;B 残留检查确认临时库、临时用户和 `/tmp/lisglosips-s09-*` 文件已清理。
- 回滚方式:恢复上述本地文件到 S09 前版本;删除 `prisma/migrations/20260621093000_auth_sessions/` 并从 `prisma/schema.prisma` 移除 `UserSession` 关系和模型。B 端仅创建临时库、临时用户和 `/tmp` 临时文件,已删除。
- 未解决问题:生产 access token secret 必须由受控环境变量注入;当前 S09 只实现认证基础,RBAC Guard、内置角色保护、重置密码和审计拦截器留给 S10。
- 下一任务:`S10 - 用户、角色、权限与审计`
### 2026-06-21 09:28 - S08 数据库 Schema 与迁移
- 状态:已完成
- 操作服务器:本地、B
- 完成内容:实现 V2 第 10 章核心数据模型,包括客户、客户网关、策略、供应商、落地网关、线路组、充值、CDR、录音、质检、用户、角色、权限、审计、Outbox 和幂等键;生成初始化迁移;新增内置权限/角色 seed;补充数据库 Schema Runbook 和 schema 合同测试;迁移 Prisma seed 配置到 `prisma.config.ts`
- 修改文件:`prisma/schema.prisma``prisma/migrations/20260621090000_init_v2_schema/migration.sql``prisma/seed.ts``prisma.config.ts``package.json``packages/database/src/schema-contract.spec.ts``docs/DATABASE_SCHEMA_RUNBOOK.md``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未安装新系统软件;继续使用 Prisma `6.19.0`、MySQL `8.0.46`、pnpm `10.33.0`
- 执行的关键命令:`prisma migrate diff --from-empty --to-schema-datamodel prisma/schema.prisma --script``corepack pnpm@10.33.0 prisma:generate``prisma:validate``typecheck``test``lint``build`;通过 SSH 隧道连接 B 的临时 MySQL 库执行 `prisma migrate deploy``prisma db seed`、重复 seed、重复 migrate deploy、`prisma migrate status`;使用 `mysqldump --no-tablespaces` 做临时库备份恢复验证。
- 验证结果:本地 Prisma schema validate 通过;Prisma Client 生成通过;lint 通过;typecheck 通过;Vitest 3 个测试文件 6 条测试通过;workspace build 通过;B 上临时空库 `lisglosips_s08_verify_20260621091537` 成功应用 1 个迁移;seed 重复执行安全;重复 `migrate deploy` 显示无待迁移;`migrate status` 显示 schema up to date;备份恢复后计数为 `permissions=22``roles=5``role_permissions=53``_prisma_migrations=1`
- 回滚方式:恢复上述本地文件到 S08 前版本;删除 `prisma/migrations/20260621090000_init_v2_schema/`。B 端仅创建临时库和临时用户,已删除 `lisglosips_s08_verify_*``s08v_*``/tmp/lisglosips-s08-*.sh` 和临时 dump 文件。
- 未解决问题:生产/准生产迁移仍需在正式发布窗口前做全量备份和恢复演练;本次只验证临时空库初始化、seed 幂等和 dump/restore。
- 下一任务:`S09 - 登录认证基础`
### 2026-06-21 08:55 - S07 V2 后端 Monorepo 骨架完成
- 状态:已完成
- 操作服务器:本地、B
- 完成内容:恢复 B 开机后完成 S07 的 B 端构建验收;补充 `scripts/pnpm-run.mjs`,让根脚本复用当前 pnpm 运行器,兼容本机 Corepack 调用和 B 上已安装的 `/usr/bin/pnpm`;在 `package.json` 中声明允许固定依赖的 pnpm build scripts,并加入 Linux x64 glibc optional dependency 支持,便于 B 端验证 Rollup/esbuild/Prisma。
- 修改文件:`package.json``pnpm-lock.yaml``scripts/pnpm-run.mjs``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:未在 B 安装新系统软件;B 使用既有 Node.js `v22.22.2`、Corepack `0.34.6`、pnpm `10.33.0`。本地和 B 的项目依赖仍按 `pnpm-lock.yaml` 固定。
- 执行的关键命令:本地 `corepack pnpm@10.33.0 lint/typecheck/test/prisma:validate/build`B `/tmp/lisglosips-s07-20260621081708` 中执行 `pnpm install --frozen-lockfile`、离线 store 安装、`pnpm lint``pnpm typecheck``pnpm test``pnpm prisma:validate``pnpm build`、临时 API `node apps/api/dist/main.js` 健康检查。
- 验证结果:本地 lint、typecheck、Vitest 2 个测试文件 3 条测试、Prisma schema validate、workspace build 全部通过;B 端 lint、typecheck、Vitest 2 个测试文件 3 条测试、Prisma schema validate、workspace build 全部通过;B 本机临时 API `/api/v2/health/live` 返回 `ok``/api/v2/health/ready` 返回 `ok``/api/v2/openapi.json` 返回 HTTP 200。
- 回滚方式:恢复本次新增/修改的 monorepo 文件;如需恢复旧前端结构,将 `apps/web/src` 移回根目录 `src``apps/web/index.html` 移回根目录,并恢复旧 `package.json`/`package-lock.json`。B 端仅使用 `/tmp` 临时目录和临时 pnpm store,已删除 `/tmp/lisglosips-s07-*``/tmp/lisglosips-s07-src-*.zip``/tmp/lisglosips-s07-pnpm-store-v10.tar.gz``/tmp/rollup-linux-x64-gnu-4.62.2.tgz` 及 API 冒烟临时文件。
- 未解决问题:B 到 npm registry 的 TLS 连接不稳定,在线安装多次 `ECONNRESET`;本次通过本地 pnpm store 临时上传完成 B 端构建验收。后续正式部署建议使用内网 npm 缓存、制品仓库或在 CI 产出完整 release artifact。
- 下一任务:`S08 - 数据库 Schema 与迁移`
### 2026-06-21 08:10 - S07 V2 后端 Monorepo 骨架
- 状态:已阻塞
- 操作服务器:本地;Server B 仅尝试临时源码包上传和只读 SSH 连通性检查,未确认远端执行
- 完成内容:将现有 React Demo 纳入 pnpm monorepo 的 `apps/web`;创建 `apps/api` NestJS + Fastify 后端骨架;创建 `apps/worker-cdr``apps/worker-recording``apps/worker-config-publisher` worker 入口;创建 `packages/contracts``domain``database``redis``auth``observability` 公共包;建立 Prisma MySQL schema 占位、结构化日志脱敏、配置校验、OpenAPI JSON/Swagger 入口、健康检查、Vitest/Supertest 测试和 lint/typecheck/build/ci 命令。
- 修改文件:`package.json``pnpm-workspace.yaml``pnpm-lock.yaml``tsconfig.base.json``tsconfig.build.json``eslint.config.mjs``vitest.config.ts``.env.example``.gitignore``apps/``packages/``prisma/schema.prisma`;原 `src/``index.html` 已移动到 `apps/web/`
- 安装软件及固定版本:本地通过 Corepack 使用 pnpm `10.33.0`;关键依赖固定包括 NestJS `11.1.9`、Fastify `5.6.2`、Prisma `6.19.0`、ioredis `5.8.2`、pino `9.14.0`、Vitest `4.0.14`、TypeScript `5.9.3`、Vite `5.4.21`
- 执行的关键命令:`corepack pnpm@10.33.0 install``corepack pnpm@10.33.0 prisma:generate``corepack pnpm@10.33.0 lint``corepack pnpm@10.33.0 typecheck``corepack pnpm@10.33.0 test``corepack pnpm@10.33.0 prisma:validate``corepack pnpm@10.33.0 build`、本地 API `node apps/api/dist/main.js` 冒烟检查。
- 验证结果:本地 `lint` 通过;`typecheck` 通过;Vitest 2 个测试文件 3 条测试通过;Prisma schema validate 通过;全 workspace build 通过;本地 API 冒烟 `/api/v2/health/live` 返回 `ok``/api/v2/health/ready` 返回 `ok``/api/v2/openapi.json` 返回 HTTP 200;测试和脚本只使用无密码占位连接串。
- 回滚方式:恢复本次新增/修改的 monorepo 文件;如需恢复旧前端结构,将 `apps/web/src` 移回根目录 `src``apps/web/index.html` 移回根目录,并恢复旧 `package.json`/`package-lock.json`
- 未解决问题:未完成 B 端构建验收。本机到 `100.90.90.91:22` SSH 超时;本机无 `tailscale` CLI`scp` 曾尝试上传 `/tmp/lisglosips-s07-src-20260621080628.zip`,返回码为 0 但随后提示连接关闭,因 SSH 不通无法确认远端是否存在该临时文件,恢复连通后应检查并删除。
- 下一任务:恢复 Server B SSH/Tailscale 可达性后继续完成 `S07 - V2 后端 Monorepo 骨架` 的 B 端构建验收;不要进入 S08。
### 2026-06-21 00:20 - SSH 策略文档修订
- 状态:已完成
- 操作服务器:本地
- 完成内容:将 SSH 加固原则调整为“只调整 SSH 管理端口和允许来源,不禁止 SSH 密钥登录”;端口号统一使用 `<SSH_ADMIN_PORT>` 占位,待用户确认具体端口后再实施。
- 修改文件:`SOFTSWITCH_PLATFORM_DESIGN_V2.md``IMPLEMENTATION_STATUS.md``docs/SSH_ACCESS_RUNBOOK.md``docs/LISGLOSIPS_DEPLOYMENT_GUIDE.md``docs/infra-check.md``docs/SERVER_B_BASELINE_RUNBOOK.md`
- 安装软件及固定版本:无。
- 执行的关键命令:`rg` 文档复查。
- 验证结果:需求/部署文档中已无“仅 Key 登录”“禁用密码登录”作为强制策略的表述;历史盘点文件保留当时实际 `TCP 22` 快照,`docs/infra-check.md` 中历史验收项已标注后续迁移为 `<SSH_ADMIN_PORT>`
- 回滚方式:恢复上述文档至本次修改前版本。
- 未解决问题:尚未确认 `<SSH_ADMIN_PORT>` 的具体端口号,未执行服务器侧 SSH 端口迁移。
- 下一任务:`S07 - V2 后端 Monorepo 骨架`
### 2026-06-20 23:20 - S06 Server B HOMER 与基础监控
- 状态:已完成
- 操作服务器:A、B、T、本地
- 完成内容:在 B 部署 PostgreSQL 16 HOMER 数据库、Heplify HEP 接收、Homer App、Prometheus、Grafana、MySQL/PostgreSQL/Redis/Node/Heplify exporters 和 Nginx 监控入口;在 A 部署 Node Exporter,并用独立 nftables 表限制仅 B 可抓取;重新签发开发证书,增加 `grafana.lisglosips.local``homer.lisglosips.local`;补充 S06 Runbook 和可重复部署脚本。
- 修改文件:B 的 `/etc/homer/``/etc/lisglosips/secrets/``/etc/prometheus/``/etc/grafana/``/etc/nginx/sites-available/monitoring.conf``/etc/systemd/system/` S06 服务、`/data/homer/postgresql`A 的 `/etc/nftables.d/lisglosips-s06-exporter.nft``/etc/systemd/system/lisglosips-exporter-firewall.service``/etc/systemd/system/lisglosips-node-exporter.service`;本地 `infra/server-b/s06/``infra/server-a/s06/``docs/SERVER_B_HOMER_MONITORING_RUNBOOK.md``docs/LISGLOSIPS_DEPLOYMENT_GUIDE.md``.codex-private/SERVICE_CREDENTIALS.clixml`
- 安装软件及固定版本:PostgreSQL `16.14`、Prometheus `2.45.3`、Grafana `13.0.2`、Node Exporter `1.7.0`、MySQL Exporter `0.15.0`、PostgreSQL Exporter `0.15.0`、Redis Exporter `1.86.0`Heplify Server `v1.60.2-0.20260512101233-c74dc3d216ac`Homer App `v0.0.0-20251021161517-9b1336352aa0`Docker `29.1.3` 与 Compose `2.40.3` 已安装但禁用,不作为 S06 运行时。
- 执行的关键命令:`apt-get install`、Go `install/build``psql``mysql``redis-cli``promtool check config/rules``nginx -t``systemctl daemon-reload/enable/restart/reboot``curl``nft -c/-f`、HEPv3 UDP 测试脚本。
- 验证结果:B 重启后系统 `running` 且 0 failed unitA 重启后系统 `running` 且 0 failed unitB 的 PostgreSQL、MySQL、Redis、Nginx、Heplify、Homer、Grafana、Prometheus 和 4 个 exporter 全部 activePrometheus 7 个 target 全部 `up`A Node Exporter 仅监听 `100.90.90.90:9100`,B 抓取返回 200,T 访问超时;HEPv3 `INVITE` 从 A 发往 B 后写入 `homer_data.hep_proto_1_call`,方法和源/目标 IP 正确;Grafana HTTPS health 通过,已加载 `Prometheus``HOMER PostgreSQL` datasource 和 `LisgloSIPS Infrastructure Overview` dashboardHomer HTTPS 返回 200Homer/Grafana/MySQL Exporter 近 10 分钟无错误日志。
- 回滚方式:B 备份位于 `/var/backups/lisglosips-s06/20260620T132440Z`A 备份位于 `/var/backups/lisglosips-s06/20260620225517`;停止 S06 服务、恢复配置、保留并单独备份 `/data/homer/postgresql` 后再处理数据目录;A 回滚只删除 `inet lisglosips_s06` 表和两个 S06 服务,不触碰 OpenSIPS/RTP。
- 未解决问题:外部对象存储下载被代理链路阻断,未继续采用 HOMER10 Docker 架构;当前 HOMER Web 是最小状态页,HOMER API、HEP 入库和 Grafana 可用,完整 HOMER UI 静态包待网络条件稳定后补;A 的 9100 保护只是 S06 局部规则,完整 A 防火墙留到 S18/S20。
- 下一任务:`S07 - V2 后端 Monorepo 骨架`
### 2026-06-20 20:44 - S05 Server B Node.js、Nginx 与 TLS 基线
- 状态:已完成
- 操作服务器:B、T、本地
- 完成内容:固定 Node.js 22 LTS、Corepack、pnpm 和 Nginx 版本;建立 `/opt/lisglosips/releases` 与原子 `current` 发布模型;部署受限 `lisglosips@.service` 模板和无依赖 API 健康占位服务;配置 Nginx 静态站点、API 代理、登录/API 限流、安全响应头、内部录音 Range 入口;签发仅供开发使用的私有 CA 证书;另建全项目独立部署指南。
- 修改文件:B 的 `/etc/lisglosips/web-runtime-versions.env``/etc/lisglosips/api.env``/etc/lisglosips/pki/``/etc/nginx/` LisgloSIPS 配置、`/etc/systemd/system/lisglosips@.service``/usr/local/sbin/lisglosips-issue-dev-cert``/opt/lisglosips/releases/s05-placeholder-20260620`;本地 `infra/server-b/s05/``docs/SERVER_B_WEB_RUNTIME_RUNBOOK.md``docs/LISGLOSIPS_DEPLOYMENT_GUIDE.md``.codex-private/tls/lisglosips-dev-ca.crt`
- 安装软件及固定版本:Node.js `v22.22.2`(包 `22.22.2-1nodesource1`)、Corepack `0.34.6`、pnpm `10.33.0`、Nginx `1.24.0-2ubuntu7.12`Node/Nginx 包已 hold,升级前必须复核安全公告与兼容性。
- 执行的关键命令:`apt-get install``apt-mark hold``openssl``nginx -t``systemctl daemon-reload/enable/restart/reboot``curl``systemd-analyze security``sha256sum`
- 验证结果:内部和管理端 HTTPS 健康检查通过;证书链与 `100.90.90.91` SAN 校验通过;仅 TLS 1.2/1.3;首页 HSTS/CSP/防嵌套等安全头完整;登录限流测试出现预期 429;`/_recordings/` 外部访问为 404;应用只监听 `127.0.0.1:3000`,本地管理机和 T 均不能直连 3000,T 不能访问 443;systemd 安全评级为 OK;重启后 Nginx、API、MySQL、Redis 和备份 timer 正常,0 个 failed unit;11 个仓库配置与服务器文件哈希一致;文档敏感信息扫描通过。
- 回滚方式:切换 `/opt/lisglosips/current` 可回滚应用;S05 变更前备份位于 `/var/backups/lisglosips-s05/20260620T123432Z`,配置/证书/服务恢复步骤见 `docs/SERVER_B_WEB_RUNTIME_RUNBOOK.md`
- 未解决问题:当前是开发私有 CA,迁移阿里云必须替换正式域名证书;80 已监听但仍由 nftables 拒绝,生产是否开放取决于 ACME 方案;NodeSource 在本地代理链路上偶发 TLS 握手中断,现有固定包和运行不受影响;当前 API 仅为 S05 健康占位服务,S07 替换。
- 下一任务:`S06 - Server B HOMER 与基础监控`
### 2026-06-20 20:22 - S04 Server B MySQL 与 Redis
- 状态:已完成
- 操作服务器:B、A、T、本地
- 完成内容:在 B 安装并固定 MySQL 8.0.46 与 Redis 7.0.15;建立 `/data/mysql``/data/redis` bind mount;完成 MySQL binlog、UTC、utf8mb4、慢查询和最小权限账号配置;完成 Redis Tailscale 私网绑定、ACL、AOF/RDB 和 `noeviction` 配置;建立 Tailscale 地址就绪依赖,消除 Redis 开机绑定竞态;配置每日 MySQL/Redis 自动备份及 14 天保留。
- 修改文件:B 的 `/etc/mysql/mysql.conf.d/99-lisglosips.cnf``/etc/redis/redis-lisglosips.conf``/etc/redis/users.acl``/etc/fstab`、相关 systemd unit/drop-in、备份脚本及 `/etc/lisglosips/secrets/`;本地 `docs/SERVER_B_DATA_SERVICES_RUNBOOK.md``infra/server-b/s04/``.codex-private/SERVICE_CREDENTIALS.clixml`
- 安装软件及固定版本:MySQL `8.0.46-0ubuntu0.24.04.2`Redis `5:7.0.15-1ubuntu0.24.04.4`;相关 MySQL/Redis 包已 `apt-mark hold`,升级前须先评估安全公告与兼容性。
- 执行的关键命令:`apt-get install``apt-mark hold``mysql``mysqlcheck``redis-cli``redis-check-rdb``systemctl``findmnt``sha256sum``systemctl reboot`
- 验证结果:MySQL 仅监听本机,应用账号 DML 通过且 DDL 被拒;Redis 未认证访问被拒,应用账号读写通过且危险命令被拒,A 可通过 Tailscale 认证访问,T 到 3306/6379 被防火墙拒绝;MySQL 与 Redis 备份哈希通过,并分别完成隔离恢复和数据一致性验证;重启后 mount、服务、数据和 timer 均正常,Redis `NRestarts=0`0 个 failed unit11 个仓库配置与服务器文件 SHA-256 完全一致;仓库敏感信息、私密文件忽略规则和 ACL 检查通过。
- 回滚方式:变更前及迁移前数据保存在 B 的 `/var/backups/lisglosips-s04/20260620195847`;配置、数据目录和服务恢复步骤见 `docs/SERVER_B_DATA_SERVICES_RUNBOOK.md`
- 未解决问题:开发阶段 `/data` 仍位于根盘,正式上云需改为独立数据盘;固定版本需要纳入安全补丁复核;Tailscale 首次直连协商偶尔产生短时延迟,稳态访问正常。
- 下一任务:`S05 - Server B Node.js、Nginx 与 TLS 基线`
### 2026-06-20 19:01 - S03 Server B 基础系统初始化
- 状态:已完成
- 操作服务器:B、本地
- 完成内容:升级 101 个系统包并启用 6.17.0-35 内核;安装基础工具;建立 chrony、nftables、fail2ban、sysstat;创建三个 LisgloSIPS 系统用户和 `/data` 目录基线;修复 NFS 为 Tailscale 就绪后的 automount;完成重启验收。
- 修改文件:B 的 `/etc/fstab``/etc/nftables.conf``/etc/nftables.d/lisglosips.nft``/etc/chrony/chrony.conf``/etc/chrony/sources.d/lisglosips.sources``/etc/fail2ban/jail.d/lisglosips.local``/etc/tmpfiles.d/lisglosips.conf`;本地 `docs/SERVER_B_BASELINE_RUNBOOK.md``infra/server-b/s03/`
- 安装软件及固定版本:chrony 4.5-1ubuntu4.2、nftables 1.0.9-1ubuntu0.1、fail2ban 1.0.2-3ubuntu0.1;其余基础工具版本见 Runbook 和远端 post-change 包清单。
- 执行的关键命令:`apt-get dist-upgrade/install``chronyc``nft``fail2ban-client``systemd-tmpfiles``findmnt``systemctl reboot`
- 验证结果:新内核启动;0 个 failed unit;无待升级包、无 reboot-requiredchrony Stratum 3/Leap NormalNFS automount 重启后可访问;fail2ban sshd jail activenftables 重启后默认拒绝,开发机与 A 允许路径通过,T 到 B 的 22/443/6379/9060 被拒绝;远端配置与仓库副本哈希一致。
- 回滚方式:变更前备份 `/var/backups/lisglosips-s03/20260620184404`;防火墙可执行其中 `rollback-firewall.sh`;其余恢复方式见 Runbook。
- 未解决问题:开发阶段 `/data` 仍位于根盘;桌面/CUPS/Avahi/rpcbind 尚未精简但已被防火墙隔离;ens18 双地址/双默认路由和克隆 machine-id 仍待单独维护窗口处理。
- 下一任务:`S04 - Server B MySQL 与 Redis`
### 2026-06-20 18:37 - S02 网络与安全组验收
- 状态:已完成
- 操作服务器:A、B、T、本地
- 完成内容:确认本地开发服务器和 Tailscale 专网边界;采集六个方向稳态 RTT;验证 SIP、RTP、Web、HEP、Redis、监控与 SSH 网络路径;形成开发最小暴露矩阵和未来阿里云安全组清单。
- 修改文件:`docs/infra-check.md``docs/inventory-A.md``docs/inventory-B.md``docs/inventory-summary.md``SOFTSWITCH_PLATFORM_DESIGN_V2.md``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:无。
- 执行的关键命令:`tailscale status/ping/netcheck``ping`、Python 短时 TCP/UDP 监听与 ACK、SIP OPTIONS、`ss`、OpenSIPS/MariaDB 只读核验。
- 验证结果:A/B/T 六方向 0% 丢包,预热后平均 RTT 0.434-0.648 ms1200 字节 DF 报文通过;T->A 15060/UDP 与 RTP 30000/40000 双向通过;A->T SIP 5060 返回合法 484;开发机->B 443、A->B 9060/6379、B->A 9100/22 通过;所有临时监听器均已退出。
- 回滚方式:未修改远端持久配置;短时监听器已自动退出,无需回滚。
- 未解决问题:主机防火墙尚未落地;Tailscale direct 路径曾短暂重新协商导致 SSH 超时;ens18 双地址/双默认路由仍存在;T 的权限与 RTPEngine 内核模块问题留待对应任务处理。
- 测试数据:T 的 `sip_trace` 新增 1 条 S02 OPTIONS 记录,`acc``missed_calls` 未增加。
- 下一任务:`S03 - Server B 基础系统初始化`
### 2026-06-20 18:00 - S01 三机只读资产盘点
- 状态:已完成
- 操作服务器:A、B、T、本地
- 完成内容:只读采集三机 OS、内核、CPU、内存、磁盘、网络、路由、时间、端口、服务、软件和防火墙;检查 T 的 OpenSIPS、RTPEngine、MariaDB、录音目录、配置结构与运行统计。
- 修改文件:`docs/inventory-A.md``docs/inventory-B.md``docs/inventory-T.md``docs/inventory-summary.md``IMPLEMENTATION_STATUS.md`
- 安装软件及固定版本:无。
- 执行的关键命令:`hostnamectl``lscpu``lsblk``ip``ss``systemctl``ufw status``nft list ruleset``opensips -C``opensips-cli``rtpengine-ctl`、MariaDB 只读查询。
- 验证结果:A/B/T SSH 复验通过;T 的 OpenSIPS、RTPEngine、录音守护进程、MariaDB、Apache 均 activeOpenSIPS 主配置哈希与盘点时一致;报告完整性及敏感信息扫描通过。
- 回滚方式:S01 未修改远端配置、数据或服务,无需回滚。
- 未解决问题:三机克隆身份相同;均有双 IPv4/双默认路由;UFW 未启用;B 的 NFS mount 失败;T 的 TLS 私钥为 0644、MI FIFO 为 0666、RTPEngine 无内核模块;当前 T 无注册账号和路由数据。
- 下一任务:`S02 - 网络与安全组验收`
### 2026-06-20 15:08 - S00 SSH 安全接入与凭据迁移
- 状态:已完成
- 操作服务器:A、B、T、本地
- 完成内容:生成项目专用 Ed25519 Key;三机安装公钥;发现并修复克隆服务器共享 SSH host key;轮换 `hector` 密码;建立严格 known_hosts 和 SSH 别名。后续 SSH 变更按最新要求只调整管理端口,不禁止 SSH 密钥登录。
- 修改文件:`.codex-private/ssh/lisglosips_codex_ed25519``.codex-private/ssh/config``.codex-private/ssh/known_hosts``.codex-private/SERVER_CREDENTIALS.clixml``docs/SSH_ACCESS_RUNBOOK.md`
- 安装软件及固定版本:本地私密目录安装 Paramiko 5.0.0,仅用于首次安全接入自动化。
- 验证结果:A/B/T 均通过 Key 新会话;`sshd -t` 通过;SSH 密钥登录保持开启;新 sudo 密码逐台验证通过。
- 回滚方式:见 `docs/SSH_ACCESS_RUNBOOK.md`,三台服务器 `/var/backups/lisglosips-s00/` 保留轮换前和加固前备份。
- 未解决问题:尚未盘点三机软件、磁盘、服务和网络;容量参数与 RTP 端口段仍未确认。
- 下一任务:`S01 - 三机只读资产盘点`
### 2026-06-20 - 计划初始化
- 状态:已完成
- 操作服务器:本地
- 完成内容:建立 Codex 原子任务列表、服务器索引和跨会话交接规范。
- 修改文件:`SOFTSWITCH_PLATFORM_DESIGN_V2.md``IMPLEMENTATION_STATUS.md``.gitignore``.codex-private/SERVER_ACCESS.md`
- 验证结果:等待开始 S00。
- 未解决问题:尚未连接服务器;尚未确认容量参数和 RTP 端口段。
- 下一任务:`S00 - SSH 安全接入与凭据迁移`