# 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 依赖公告具有逐项路径、影响、处理方式和回归证据。 - `lint` 和 `format:check` 成为真实、稳定、可逐步扩展的工程门禁。 - SendChain 重构有行为锁定测试,拆分过程中不改变事务、锁、幂等和队列语义。 - 仓库只保留一种正式包管理器和一种正式锁文件。 ## 3. 优先级和最小充分范围 | 批次 | 内容 | 风险 | 预估工作量 | 是否建议下一轮立即实施 | |---|---|---:|---:|---| | C1 | 剩余客户端 DTO 和负向测试 | 低 | 0.5~1.5 人日 | 是 | | C2 | 删除客户端租户头和默认租户回退 | 中低 | 0.5~1 人日 | 是 | | C3 | 最小前端测试框架和核心用例 | 中 | 1~3 人日 | 是 | | C4 | 覆盖率缓冲和增量覆盖门禁 | 低 | 0.5~1 人日 | 是,依赖 C1/C3 | | C5 | IP/账号/验证码多维登录保护 | 中 | 1~2 人日 | 建议 | | C6 | API 依赖公告治理矩阵 | 中 | 1~3 人日 | 建议先诊断后升级 | | C7 | ESLint、Prettier 和真实格式门禁 | 中 | 1~2 人日 | 建议分阶段启用 | | C8 | SendChain Service 职责拆分 | 高 | 5~10 人日 | 单独专项 | | C9 | 唯一包管理器和锁文件 | 中 | 0.5~1 人日 | 需先确认文件归属 | 下一轮最小充分范围为 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 上传的 `purpose` 和 `prefix`。 ### 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. 对 `scheduledAt`、`requestedAt` 等时间字段使用日期格式校验。 6. 对 `variables`、`materials`、`reportValues` 等对象补充: - 最大键数量。 - 最大嵌套深度。 - 键名和字符串值长度。 - 禁止原型污染相关键。 ### 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 目标设计 客户端租户范围只能来自服务端认证会话: ```text 客户端请求 -> 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:组件和页面行为测试。 - jsdom:DOM 环境。 - 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 工程门禁 新增命令建议: ```text 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. C6:API 依赖公告治理 ### 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:check`:Prettier check + `git diff --check`。 不要继续用 `lint` 名称包装结构检查和 TypeScript,使开发者误判实际门禁能力。 ## 11. C8:SendChain 超大服务专项拆分 ### 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=1`、`go 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. 建议执行顺序 ```text C1 客户端 DTO -> C2 租户头清理 -> C3 前端测试 -> C4 覆盖率缓冲 -> C5 登录保护 -> C6 依赖治理 -> C7 lint/format -> C8 SendChain 专项拆分 -> C9 锁文件统一 -> 测试环境完整验收 ``` 其中 C1~C4 可作为下一轮独立交付;C8 必须保持为单独的高风险重构任务。