Files
lislgosms/docs/code-quality-continuous-optimization-plan-20260828.md
T

19 KiB
Raw Blame History

CMPP 平台代码质量持续优化整改方案

  • 方案日期:2026-08-28
  • 依据报告:docs/code-quality-reassessment-20260828-v2.md
  • 当前代码基线:3af145abe5eb8cae567bb18e29116b4940889258
  • 适用范围:React/Vite 客户端、NestJS API、依赖治理、测试与工程门禁
  • 实施边界:先在本地完成代码和自动化验证;如需部署,只允许部署到测试环境 100.93.204.60,且部署前必须重新建立并校验恢复资产
  • 禁止范围:未经新的明确授权,不得访问、部署、覆盖或回退预生产环境

1. 背景与当前结论

V2 复评确认上一轮两个 P0 已关闭,代码质量从 58 分提升至 82 分。当前不存在新的运行态 P0,但仍有以下持续优化空间:

  1. 少量客户端写接口仍使用内联 TypeScript 类型,缺少运行时严格校验。
  2. 前端没有自动化测试,登录、权限、懒加载失败和页面异常状态主要依赖人工验证。
  3. API 全源覆盖率刚刚超过门槛,余量不足。
  4. 客户端仍发送 x-tenant-id 并保留默认租户回退,与服务端可信会话租户的最终设计不一致。
  5. 登录保护只有账号维度的主要限流,需要补充 IP 和验证码请求维度。
  6. API 依赖树存在审计公告,需要按真实依赖路径和运行时可利用面逐项治理。
  7. 当前 lint 不是完整 ESLint,格式检查也只依赖 git diff --check
  8. 两个 SendChain Service 仍然过大,但直接拆分具有较高业务回归风险。
  9. 工作区存在双锁文件口径,可能继续误导安装和依赖审计。

本方案遵循“先补小范围安全缺口和测试,再治理供应链和工程门禁,最后拆分发送链”的顺序。

2. 整改目标

本轮持续优化完成后应达到:

  • 所有客户端写接口具有运行时 DTO 校验,不再使用裸内联 body 类型。
  • 客户端租户授权完全依赖服务端认证会话,浏览器不再负责选择或回退租户。
  • 建立可持续运行的前端测试框架,并覆盖核心登录、权限、异步路由和异常状态。
  • API 全源覆盖率形成合理缓冲,新增代码不能依靠全局低门槛掩盖未测试分支。
  • 登录防护具备账号、IP、IP+账号和验证码请求多维度限制与指标。
  • API 依赖公告具有逐项路径、影响、处理方式和回归证据。
  • lintformat:check 成为真实、稳定、可逐步扩展的工程门禁。
  • SendChain 重构有行为锁定测试,拆分过程中不改变事务、锁、幂等和队列语义。
  • 仓库只保留一种正式包管理器和一种正式锁文件。

3. 优先级和最小充分范围

批次 内容 风险 预估工作量 是否建议下一轮立即实施
C1 剩余客户端 DTO 和负向测试 0.51.5 人日
C2 删除客户端租户头和默认租户回退 中低 0.51 人日
C3 最小前端测试框架和核心用例 13 人日
C4 覆盖率缓冲和增量覆盖门禁 0.51 人日 是,依赖 C1/C3
C5 IP/账号/验证码多维登录保护 12 人日 建议
C6 API 依赖公告治理矩阵 13 人日 建议先诊断后升级
C7 ESLint、Prettier 和真实格式门禁 12 人日 建议分阶段启用
C8 SendChain Service 职责拆分 510 人日 单独专项
C9 唯一包管理器和锁文件 0.51 人日 需先确认文件归属

下一轮最小充分范围为 C1~C4。它们投入较小,能够实质关闭 CQ-API-001 的客户端部分并改善 CQ-TEST-001,不需要数据库迁移,也不需要修改发送链业务规则。

4. C1:补齐客户端写接口 DTO

4.1 当前缺口

首批至少覆盖:

  • api/src/open-api/client-open-api.controller.ts
    • 创建 HTTP API 凭据。
    • 新增或更新 Webhook。
  • api/src/operations/client-operations.controller.ts
    • 系统日志导出。
  • api/src/files/client-files.controller.ts
    • Multipart 上传的 purposeprefix

4.2 实施要求

  1. 新增独立 DTO class,不再使用运行时会被擦除的内联 TypeScript 类型。
  2. 对上述接口启用现有 strictValidationPipe
  3. DTO 规则至少包含:
    • 凭据名称长度和空白处理。
    • expiresAt 使用 ISO 日期时间校验,并拒绝已过期时间。
    • Webhook eventType 只允许平台支持的事件。
    • Webhook URL 限制协议、长度和格式;继续由 Service 执行 SSRF 与 HTTPS 策略。
    • Webhook status 使用明确枚举。
    • 日志级别、模块、日期区间使用明确格式和长度限制。
    • 上传 purpose 使用白名单;prefix 限制长度、字符集和路径穿越。
  4. 将当前过宽的 ClientStatusChangeDto 按应用、签名、模板和引流信息拆成更窄的 DTO,避免无关字段被不同接口接受。
  5. scheduledAtrequestedAt 等时间字段使用日期格式校验。
  6. variablesmaterialsreportValues 等对象补充:
    • 最大键数量。
    • 最大嵌套深度。
    • 键名和字符串值长度。
    • 禁止原型污染相关键。

4.3 自动化测试

每个接口至少覆盖:

  • 合法最小请求成功。
  • 额外字段返回 400。
  • 超长字符串返回 400。
  • 非法 URL、日期和状态返回 400。
  • 客户端提交 createdById/operatorId/tenantId 等身份字段不能覆盖服务端身份。
  • 校验失败时 Service 不被调用。
  • 文件 purpose/prefix 校验失败时 MinIO 不发生写入。

4.4 出口标准

  • 客户端 POST/PUT/PATCH 中不再存在未评审的裸 @Body() 内联类型。
  • 对应控制器和 DTO 负向测试通过。
  • 错误请求稳定返回 400,不返回 500。
  • 不改变现有合法前端请求协议。

5. C2:移除客户端租户头和默认租户回退

5.1 目标设计

客户端租户范围只能来自服务端认证会话:

客户端请求
  -> session cookie
  -> 服务端查询会话用户
  -> request.sessionTenantId
  -> CurrentTenantId
  -> 数据库 tenantId 复合条件

浏览器不再通过 localStorage、默认常量或 x-tenant-id 参与客户端授权。

5.2 实施内容

  1. 修改客户端 HTTP 请求封装,客户端路由默认不发送 x-tenant-id
  2. 删除 getSessionTenantId() ?? DEFAULT_CLIENT_TENANT_ID 的客户端回退路径。
  3. 清理客户端 API 方法中仅为请求头服务的 tenantId 参数。
  4. 管理端明确跨企业查询所需的租户参数继续保留,并与客户端请求封装分离。
  5. 服务端暂时保留“收到客户端租户头时做一致性检查”的兼容逻辑,经过一个测试版本确认没有旧客户端后再决定移除。
  6. 质量门禁增加:
    • src/api/client/** 不得设置 x-tenant-id
    • 客户端 API 不得引用 DEFAULT_CLIENT_TENANT_ID
    • 管理端的显式租户参数不受此规则影响。

5.3 必测用例

  • 不带租户头的客户端全部核心页面和接口正常。
  • 浏览器 localStorage 中 tenantId 缺失、错误或过期,不影响服务端租户识别。
  • 运营端跨租户查询仍按权限正常工作。
  • 旧客户端携带正确租户头仍兼容;错误租户头仍返回 403 并记录安全事件。
  • 双租户真实 API 隔离矩阵继续通过。

5.4 出口标准

  • 客户端代码不再选择租户授权范围。
  • 不出现登录后初始化阶段因默认租户错误导致的 403。
  • 服务端可信租户门禁继续通过。

6. C3:建立最小前端测试集

6.1 技术方案

建议采用:

  • Vitest:测试运行器。
  • React Testing Library:组件和页面行为测试。
  • jsdomDOM 环境。
  • MSW:模拟真实 HTTP 协议的成功、空、失败和延迟响应。

测试必须围绕用户可见行为和真实请求契约,不以大量快照替代断言。

6.2 首批核心用例

  1. 客户端登录:成功、密码错误、验证码错误、接口不可用。
  2. 未登录访问客户端深层路由:跳转登录页。
  3. 权限不足:显示稳定提示,不白屏。
  4. RouteLoadBoundary:异步 chunk 加载失败、重试和重新加载。
  5. 列表页:loading、empty、error、成功、分页和筛选。
  6. 高风险操作:删除、重置密钥、创建凭据等确认流程。
  7. API 返回 500:页面显示业务化错误,不直接显示裸 Internal server error
  8. 会话失效:401 后清理展示会话并回登录页。

6.3 工程门禁

新增命令建议:

npm run test:frontend
npm run test:frontend:coverage

并将 test:frontend 纳入 verify:quality。首轮覆盖率门槛以核心模块为主,不为追求全局数字测试纯展示组件。

6.4 出口标准

  • 至少 6~10 个核心行为用例稳定通过。
  • 登录、权限、路由失败和列表异常状态均有自动化覆盖。
  • 测试可以在全新依赖安装后重复执行。
  • CI/质量门禁中前端测试失败会阻止合并。

7. C4:覆盖率缓冲和增量门禁

7.1 当前问题

当前全源覆盖率虽然通过,但接近门槛:

  • statements 59.84%,门槛 59%。
  • branches 51.29%,门槛 50%。
  • functions 60.15%,门槛 60%。
  • lines 62.60%,门槛 62%。

functions 仅有 0.15 个百分点余量,新增少量未测试函数就可能失败。

7.2 实施内容

  1. 先通过 C1 和 C3 增加有业务价值的测试,不立即盲目提高门槛。
  2. 建立修改文件或新增文件覆盖率门禁,建议初始目标:
    • statements/lines 不低于 80%。
    • branches 不低于 70%。
    • functions 不低于 80%。
  3. 全源门槛在覆盖率稳定后分批上调,每次不超过 2~3 个百分点。
  4. 覆盖率报告明确使用全源 collectCoverageFrom 口径,避免再次混用“已加载源码”和“全部源码”。

7.3 出口标准

  • 新增 DTO、校验器和安全逻辑具有负向分支测试。
  • 修改文件覆盖率门禁能够阻止新增无测试逻辑。
  • 文档和质量门禁使用同一统计口径。

8. C5:多维登录和验证码保护

8.1 实施内容

在现有 Redis 原子计数基础上增加:

  • 账号维度失败窗口。
  • 来源 IP 维度失败窗口。
  • IP+账号组合维度。
  • 验证码获取频率限制。
  • 单 IP 随机账号扫描保护。
  • key 数量、失败次数、锁定次数和 Redis 异常指标。

来源 IP 必须基于受信 Nginx/代理链解析,不能直接信任任意客户端 X-Forwarded-For

8.2 验证

  • 多 API 实例共享相同锁定状态。
  • 同账号换 IP、同 IP 换账号均能触发对应保护。
  • 正常用户偶发输错不会被过度锁定。
  • Redis 不可用继续 fail closed,并产生监控告警。
  • 随机账号和验证码请求压测后 Redis key 数量能随 TTL 回落。

9. C6API 依赖公告治理

9.1 原则

依赖审计公告不直接等于业务可利用漏洞。不得直接执行 npm audit fix --force。应先建立矩阵:

字段 内容
公告编号 GHSA/CVE
依赖路径 直接依赖到受影响包的完整路径
实际锁定版本 api/package-lock.json 为准
环境 生产运行时、构建期、开发期、可选工具
不可信输入 是否处理客户输入、文件、URL、模板或命令行参数
当前缓解 override、功能未启用、输入边界、兼容适配
修复方案 补丁升级、依赖替换、隔离或书面风险接受
回归范围 构建、Excel 导出、Swagger、Prisma、数据库和部署

9.2 优先顺序

  1. 直接生产运行时且处理不可信输入的路径。
  2. Excel 导出、压缩、YAML/Swagger 等数据处理路径。
  3. Prisma 的生产客户端路径。
  4. 仅 CLI、Studio、构建或可选工具链路径。

9.3 出口标准

  • 7 项公告均有明确依赖路径和处置结论。
  • 可安全升级的依赖完成升级并通过全量回归。
  • 暂不能升级的项目有代码级缓解门禁和明确复查日期。
  • 根项目和 API 子项目审计口径分别记录,不互相替代。

10. C7:真实 lint 和格式门禁

10.1 分阶段实施

第一阶段只检查新增和修改文件,启用高价值规则:

  • 未使用变量和导入。
  • 未处理 Promise。
  • 不安全的 any 和类型断言。
  • React Hooks 依赖和调用规则。
  • 无效条件、重复分支和不可达代码。
  • Node/NestJS 常见异步错误。

第二阶段再逐目录修复历史问题并扩大到全仓库。

Prettier 首轮只执行 check,不得在同一提交中格式化全部历史文件。大规模格式化必须独立提交,避免掩盖业务改动。

10.2 脚本语义

  • lint:真实 ESLint。
  • typecheck:前端 TypeScript。
  • quality:verify:结构安全门禁。
  • format:checkPrettier check + git diff --check

不要继续用 lint 名称包装结构检查和 TypeScript,使开发者误判实际门禁能力。

11. C8SendChain 超大服务专项拆分

11.1 前置条件

拆分前必须补齐行为锁定测试:

  • 事务边界。
  • advisory lock 和锁顺序。
  • 消息、Submit 和 Outbox 幂等键。
  • 主备路由和补发。
  • 计费冻结、扣费、退款和释放。
  • Inbox claim、租约、超时和恢复。
  • Redis Stream 发布及重复消费。
  • 回执、上行和下游投递。

11.2 建议拆分方向

send-inbound-entry.service.ts

  • 入站协议数据转换。
  • Inbox 持久化和 claim。
  • 长短信聚合。
  • 企业分组微批。
  • 业务校验和消息创建。

send-gateway-submit.service.ts

  • 路由选择。
  • Gateway 命令构造。
  • Submit Outbox。
  • Submit 结果处理。
  • 重试和恢复。

11.3 实施规则

  • 每次只移动一个职责。
  • 先机械提取,再优化逻辑。
  • 不同时修改事务、SQL、幂等键或状态语义。
  • 每个拆分提交都运行 API 全量、Gateway 全量、真实 PostgreSQL/Redis 契约和队列排空检查。

11.4 出口标准

  • 原 Service 只保留编排职责。
  • 行为测试、全链契约和性能基线不下降。
  • 不新增重复查询、跨事务状态漂移或锁顺序变化。

12. C9:唯一包管理器和锁文件

当前正式跟踪文件为根目录和 API 的 package-lock.json,工作区另有未跟踪 pnpm-lock.yaml。该文件属于受保护的既有工作区内容,本方案不授权删除。

需要文件所有者明确选择:

方案 A:统一 npm(建议)

  • 保留两个正式 package-lock.json
  • 确认后删除或归档旧 pnpm-lock.yaml
  • 部署、审计和 CI 全部使用 npm ci
  • 门禁禁止新增 pnpm/yarn 锁文件。

方案 B:统一 pnpm

  • 重新生成受控 pnpm 锁文件。
  • 重写本地、CI、部署和审计命令。
  • 在全新目录验证安装、构建、Prisma、测试和生产部署。
  • 完成后再移除 npm 锁文件。

不得长期同时维护两种锁文件,也不得只改锁文件而不改部署流程。

13. 不建议在下一轮实施的动作

  • 不一次性为全部 API 开启全局严格 ValidationPipe
  • 不直接执行 npm audit fix --force
  • 不为了 Vite 的 500 KiB 原始体积警告替换整个 ECharts;当前 gzip 包体仍在预算内。
  • 不在安全补丁提交中同时拆分 SendChain。
  • 不以大量快照测试提高前端覆盖率。
  • 不擅自删除 =, pnpm-lock.yaml 或其他侧边任务文件。
  • 不把测试环境健康检查等同于登录后页面和 CMPP 全链验收。

14. 验证矩阵

层级 必须验证
静态 TypeScript、ESLint、Prettier、结构安全门禁、git diff --check
API DTO 正负向、租户隔离、密码迁移、登录限流、账务和发送幂等
前端 登录、权限、懒加载、loading/empty/error、确认操作、401/500
数据库 Prisma validate、95 项 migration 一致性、真实 PostgreSQL 关键查询
Redis 验证码 TTL、登录锁定、多实例、Stream pending/lag
Gateway go test ./... -count=1go vet ./...、5 份队列契约
浏览器 登录后页面、控制台、请求、刷新、深层链接和移动端
发布后 部署 commit、服务、端口、健康、日志、队列和临时数据清理

15. 测试环境发布要求

如下一轮包含部署,必须:

  1. 明确目标是测试环境 100.93.204.60,显式绕过 Clash/系统代理进行健康探测。
  2. 发布前重新建立:
    • PostgreSQL custom dump。
    • 当前运行目录归档。
    • 环境、systemd 和 Nginx 配置。
    • Redis RDB 和关键 Stream/队列状态。
    • MinIO 数据或业务对象清单。
    • 当前部署 commit 和 95 项 migration 状态。
  3. 校验 pg_restore --list、tar 目录、Redis RDB 和 SHA-256。
  4. 发布后核对所有相关服务、API/Gateway health、Redis pending/lag 和 error 日志。
  5. 清理临时测试账号、凭据、文件和脚本。
  6. 测试环境通过不自动授权预生产发布。

16. 建议提交拆分

  1. security: validate remaining client write payloads
  2. test: cover client dto rejection paths
  3. security: remove client-controlled tenant headers
  4. test: add frontend auth and route failure baseline
  5. test: enforce changed-file coverage
  6. security: add ip-aware login throttling
  7. build: document and remediate api audit paths
  8. build: add eslint and prettier checks
  9. refactor: extract inbound workflow responsibilities
  10. refactor: extract gateway submit responsibilities
  11. chore: enforce the selected package manager

每个提交只处理一个主题,不混入测试环境部署资产、历史文档改写或其他会话的工作区文件。

17. 最终关闭标准

项目 关闭证据
客户端 DTO 所有写接口使用 class DTO 和严格管道;负向测试通过
租户头清理 客户端不再发送/回退 tenantId;双租户矩阵通过
前端测试 核心用例进入质量门禁并稳定运行
覆盖率 全源口径稳定且修改文件满足增量门槛
登录保护 账号/IP/组合/验证码限流和指标通过多实例测试
依赖公告 每项有路径、影响、处理和回归证据
lint/format ESLint 和 Prettier 真实执行,不再仅使用脚本别名
SendChain 行为锁定后完成职责拆分,全链语义和性能不下降
锁文件 唯一包管理器、唯一正式锁文件、全新安装可重复
测试环境 恢复资产、真实后端、浏览器和发布后状态证据齐全

18. 建议执行顺序

C1 客户端 DTO
  -> C2 租户头清理
  -> C3 前端测试
  -> C4 覆盖率缓冲
  -> C5 登录保护
  -> C6 依赖治理
  -> C7 lint/format
  -> C8 SendChain 专项拆分
  -> C9 锁文件统一
  -> 测试环境完整验收

其中 C1~C4 可作为下一轮独立交付;C8 必须保持为单独的高风险重构任务。