# 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/enabled,API ready为ok。 - 真实验收:6通受控SIP得到T=6/C=4/A=2/N=2,两个成功场景完成SDP协商并经过RTPEngine,300ms短通话计A;真实API、MySQL状态/分钟桶、重放幂等、RBAC与页面桌面/移动端均通过。Redis重启恢复、毒事件死信、覆盖降级/恢复、维护与轮转检查通过。证据见`docs/CALLER_REALTIME_ANALYTICS_DELIVERY_20260901.md`及`tests/reports/caller-analytics-real-20260901.json`。 - 收口:受控测试入口/出口均已禁用,临时UI会话已清零。没有修改计费、余额扣减、路由选择或旧固定6秒CDR逻辑;工作区独立的代码质量审查文件未纳入本次提交。 - 限制:未取得约定峰值CPS、号码基数和保留期,故不宣称容量上限或统计P95。代码与交付文档已推送到`origin/main`;B机生效代码为`9fa1964`,其后的`367211e`仅增加交付证据与状态文档,无需再次重启服务。 ### 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=success;B `mysql`、`redis-server`、`nginx`、`lisglosips@api`、`lisglosips@cdr-worker`、`lisglosips@recording-worker`、`lisglosips@config-publisher`、`heplify-server`、`lisglosips-prometheus`、`grafana-server` active,preflight ok,API 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` 通过并已 reload;B 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 为 0;B 脚本执行后 current 为 `/opt/lisglosips/releases/s51-customer-gateway-list-prefix-ui-20260629184000`,MySQL/Redis/Nginx/API/CDR Worker/Recording Worker/Config Publisher/HEP/Prometheus/Grafana active,API ready ok,preflight ok,failed units 为 0;T 脚本执行后 `opensips`、`rtpengine-daemon`、`rtpengine-recording-daemon`、`lisglosips-s28-uas`、`apache2`、`mariadb` active,failed 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,已启动并确认 active;T 主服务均已 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` 为 0;B current 为 `/opt/lisglosips/releases/s51-customer-gateway-list-prefix-ui-20260629184000`,MySQL/Redis/Nginx/API/CDR Worker/Recording Worker/Config Publisher/HEP/Prometheus/Grafana 均 active,API ready 返回 ok,preflight 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` 未接收 `children`,导致 `children` 继续透传到原生 `` 并触发 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`,属于原生 `` 收到 children 的运行时错误。为避免任意调用方把 `children` 透传到原生 input,已在公共 `Input` 组件中显式丢弃 `children`。 - 完成内容:修复 `apps/web/src/components/ui.jsx` 的 `Input` 组件,避免 children 透传到 ``;扩展 `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/监控服务 active,API 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 清理。 - 未解决问题:未提交 Git;8.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` 是确定 bug;8.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 artifact:B 上 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` 并上传源码 tar;B 从 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 均 active;preflight ok;API 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` active;T `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 active;API 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` active,S28/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 目标服务最终均 active;S40 最小呼叫 `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 解包、服务重启、preflight;A 备份 Lua/cfg、计算 SHA1、`lisglosips-redis-hotpath-load.service`、`opensips -C -f /etc/opensips/opensips.cfg`、重启 OpenSIPS;T S40 单通呼叫。 - 验证结果:本地 lint/typecheck/全量测试通过,Vitest 25 文件 90 用例通过;B preflight ok,API ready ok,Nginx 首页引用 `index-BBq8iMHt.js`;A OpenSIPS `opensips -C` 通过且 active;T 呼叫 `s40-01-1782657231921-z1w146wc@lisglosips-t` 返回 200 OK/200 BYE;B `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` 后重启 OpenSIPS;MySQL/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 artifact,A 应恢复发布前备份的 `/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` active;API ready 返回 ok;Argon2id 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 返回 ok;HTTPS 首页返回 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 驱动页面内 `