refactor: strengthen client boundaries and quality gates

This commit is contained in:
hectorzhao
2026-08-28 14:26:58 +08:00
parent 3af145abe5
commit ad27acad7e
51 changed files with 7703 additions and 697 deletions
@@ -0,0 +1,31 @@
# API 依赖公告治理矩阵(2026-08-28
## 结论
本轮重新执行 `npm audit --omit=dev --json`,审计对象从 9 项降至 5 项:Swagger、`js-yaml``fast-uri``brace-expansion` 的可安全升级路径已完成;剩余 5 项全部属于 Prisma CLI 的同一条开发/迁移工具链,不进入 API 运行时请求处理路径,且审计给出的唯一自动修复是把 Prisma 7.9.0 降级到 6.12.0,属于破坏性主版本变更,因此本轮不执行 `audit fix --force`
## 已修复公告
| 公告/包 | 原路径 | 处理 | 回归要求 |
|---|---|---|---|
| `js-yaml` / GHSA-5p4m-2wfm-xmqj | `@nestjs/swagger -> js-yaml < 4.3.1` | 升级 `@nestjs/swagger` 至 11.4.7,并锁定 `js-yaml` 4.3.1 | API build、Swagger 启动、全量 Jest |
| `fast-uri` / GHSA-7p8r-x3mc-p8w7 | OpenAPI/JSON schema 依赖树 | override 至 3.1.5 | API build、全量 Jest |
| `brace-expansion` / GHSA-rgw5-rvv9-x895 | Excel/归档工具的 minimatch 兼容链 | 兼容适配器升级到有界实现 5.0.9 | `security:verify` 中真实 require 与 legacy minimatch 验证 |
| `@nestjs/swagger` 聚合项 | 由 `js-yaml` 引起 | 随上述升级关闭 | 同上 |
## 暂留公告
| 审计项 | 实际路径 | 运行时暴露面 | 决策 |
|---|---|---|---|
| `prisma` | 根开发依赖 `prisma@7.9.0` | 仅生成客户端和执行迁移;API 运行时使用 `@prisma/client` | 暂留,等待 Prisma 7/8 提供无破坏升级 |
| `@prisma/config` | `prisma -> @prisma/config -> deepmerge-ts` | CLI 解析受控本地配置,不接受 HTTP 用户递归对象 | 暂留并监控上游 |
| `deepmerge-ts` / GHSA-ggr8-5vv4-36mx | 同上 | 不在 API 请求路径调用 | 暂留;禁止用不可信配置执行 Prisma CLI |
| `@prisma/dev` | `prisma -> @prisma/dev -> valibot` | Prisma CLI 开发工具链 | 暂留并监控上游 |
| `valibot` / GHSA-5qjj-4xww-7phc | 同上 | 不在 API 请求路径调用 | 暂留并监控上游 |
## 门禁与复核
- `tools/security/verify-dependency-mitigations.mjs` 固定验证修复版本及 brace CommonJS 兼容行为。
- 部署仍使用受控 `package-lock.json``npm ci`,不执行 `npm audit fix --force`
- 每次依赖升级重新运行两份 lock 的审计、API/前端构建、API 全量测试和真实测试环境健康检查。
- Prisma 上游出现保持主版本兼容的修复后,优先在独立分支验证 `prisma generate`、95 项迁移、PostgreSQL 连接和全量回归,再关闭暂留项。
@@ -0,0 +1,457 @@
# 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.51.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 职责拆分 | 高 | 510 人日 | 单独专项 |
| 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:组件和页面行为测试。
- 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 工程门禁
新增命令建议:
```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. 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:check`Prettier 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=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 必须保持为单独的高风险重构任务。
@@ -0,0 +1,190 @@
# CMPP 平台代码质量复评报告 V2
- 报告日期:2026-08-28
- 复评时间:2026-08-28 12:3812:57Asia/Shanghai
- 复评基线:`3af145abe5eb8cae567bb18e29116b4940889258`
- 对比基线:`171c7d38e8f17de0dd83570603da316d47c0d015`
- 分支状态:`main`,本地 `HEAD``origin/main` 一致
- 变更规模:58 个文件,新增 1,953 行、删除 487 行
- 复评范围:React/Vite 前端、NestJS API、Prisma/PostgreSQL、Redis、Go Gateway、自动化测试、依赖与仓库门禁、测试环境只读健康探测
- 明确未执行:提交、推送、部署、服务重启、数据库写入、短信发送、压力测试、MinIO 业务对象写入、预生产或生产环境操作
## 1. 复评结论
本次采用两个相互独立的结论,避免把“代码整改完成”和“环境可发布”混为一谈:
1. **代码整改:有条件通过。** 上一版的两个 P0 已在源码、自动化测试和结构门禁层面关闭;前端分包、覆盖率门槛、Redis 状态存储、数据库配置 fail closed、mock 解耦等整改有效。
2. **发布就绪:待完整验收。** 2026-08-28 12:50 在 Clash 虚拟网卡/代理开启状态下探测测试环境时曾连续返回 HTTP 502;关闭 Clash 后使用 `curl --noproxy '*'` 绕过代理直连,12:56 连续 3 次访问 `/api/health` 均返回 HTTP 200、`status=ok`,TCP 12026 端口可达,连接耗时约 10 ms、总响应约 19~28 ms。此前 502 判定属于本地网络代理干扰,不是测试环境运行故障。
当前代码质量综合参考分:**82/100**,较上一版 **58/100** 提升 24 分。该分数只表示仓库代码治理水平,不等价于功能验收通过率。
测试环境外部 API 健康探测已经恢复为通过;但在重新核对部署版本、内部服务/队列状态并完成真实后端与登录后浏览器复验前,**仍不建议进入预生产发布**。
## 2. 质量评级
| 维度 | 上版 | 本次 | 复评结论 |
|---|---:|---:|---|
| 业务正确性与并发设计 | B | B+ | API/Gateway 回归通过,核心幂等与队列逻辑保持;未重跑真实 CMPP 全链。 |
| 安全性 | D | B+ | 可信租户上下文和强密码哈希已落地;重点写接口校验仍非全覆盖。 |
| 自动化测试 | C | B- | API 47 套 543 项通过并建立覆盖率阈值;前端测试仍为 0。 |
| 前端性能 | C- | B+ | 路由懒加载和 ECharts 按需加载生效,入口 gzip 降至 107.38 KiB。 |
| 可维护性 | C | C+ | mock 解耦和页面辅助逻辑拆分完成;两个 SendChain Service 仍超大,未建立 ESLint/Prettier。 |
| 数据库工程 | B | B+ | Prisma 校验通过,生产/测试缺少 `DATABASE_URL` 时 fail closed;本轮未做真实数据执行计划复测。 |
| 依赖与仓库卫生 | C- | B- | 根正式锁文件审计为 0;API 依赖树仍有 7 项公告,且未跟踪的旧 pnpm 锁文件会造成错误审计口径。 |
| 运行与发布就绪 | 未评 | B- | 绕过本地代理后测试环境外部 `/api/health` 连续 3 次返回 200;本次未独立核对内部服务、队列和登录后业务链。 |
## 3. 上一版问题关闭状态
| 编号 | 原级别 | 本次状态 | 判定依据 |
|---|---:|---|---|
| CQ-SEC-001 客户端租户可伪造 | P0 | **已关闭(代码级)** | 会话中间件从数据库用户写入 `sessionTenantId`;客户端装饰器只读取可信上下文;伪造 header 返回 `CLIENT_TENANT_MISMATCH`;相关租户隔离测试通过。 |
| CQ-SEC-002 无盐 SHA-256 密码 | P0 | **已关闭(代码级)** | 使用随机 salt 的版本化 scrypt、恒定时间比较、参数上限解析;旧 SHA-256 只读兼容并在正确登录后透明升级;写路径和工具门禁已覆盖。 |
| CQ-API-001 缺少运行时输入校验 | P1 | **部分关闭** | 已新增严格 `ValidationPipe` 和重点客户端写 DTO;但未全局注册,部分凭据、Webhook、日志导出等写接口仍使用内联类型和裸 `@Body()`。 |
| CQ-AUTH-001 验证码/失败计数进程内 Map | P1 | **基本关闭** | 已迁移 Redis TTL、`GETDEL` 和 Lua 原子计数,Redis 异常 fail closed;仍建议补 IP+账号双维度限流与容量指标。 |
| CQ-FE-001 主包过大、无路由分包 | P1 | **已关闭** | 全部业务页采用懒加载;ECharts 按需注册;入口 gzip 107.38 KiB,低于 250 KiB 门槛,最大异步图表包 181.64 KiB,低于 190 KiB 门槛。 |
| CQ-TEST-001 测试与验收层级不足 | P1 | **部分关闭** | API 覆盖率阈值已建立且本轮通过;Gateway 测试通过;前端仍无测试,登录后浏览器和真实全链未完成。 |
| CQ-DEP-001 两个 high advisory | P2 | **原问题已关闭** | 正式 `package-lock.json` 使用 React Router 7.18.2、NanoID 3.3.18;按该锁文件重建审计结果为 0。 |
| CQ-MAINT-001 超大文件、无静态风格门禁 | P2 | **部分关闭** | 页面辅助逻辑已拆分,增加结构质量脚本;两个 SendChain Service 仍为 86,253/53,421 字节,`lint` 实际为结构检查加 TypeScript,不是 ESLint/格式门禁。 |
| CQ-CONFIG-001 数据库默认凭据 | P2 | **已关闭** | 非 development/test 环境缺少 `DATABASE_URL` 时启动失败,不再静默使用默认生产连接。 |
| CQ-ARCH-001 生产代码依赖 mock | P2 | **已关闭** | 类型已迁至 `src/api/types`,结构门禁禁止生产页面重新导入 mock。 |
| CQ-REPO-001 仓库产物与审计基线不稳定 | P2 | **部分关闭** | `*.tsbuildinfo` 已停止跟踪并加入忽略;但工作区仍有未跟踪的 `=` 和过期 `pnpm-lock.yaml`。 |
## 4. 关键整改证据
### 4.1 可信租户上下文
- `api/src/auth/session-validation.middleware.ts:70-72`:客户端请求携带的租户头与登录用户租户不一致时返回 403,并将数据库用户租户写入 `request.sessionTenantId`
- `api/src/auth/current-tenant-id.decorator.ts:10-13`:只允许 client portal 读取可信租户上下文,不再回退到请求头。
- 客户端认证、短信配置、账务、发送链、风险复核、用户等控制器已改用 `@CurrentTenantId()`
- 管理端 `@TenantId()` 仍存在于 `admin/files``admin/operation-logs``admin/billing` 等管理路由中,不属于客户端授权依据;结构门禁已禁止客户端控制器回退使用旧装饰器。
判定:原 P0 的根因已消除。测试环境真实双租户结果在整改记录中有描述;本次只复核外部健康接口,没有重跑双租户矩阵,因此仍按代码级关闭记录。
### 4.2 密码存储与迁移
- `api/src/auth/password-hasher.ts:1-82`:版本化 scrypt、随机 salt、`timingSafeEqual`、参数边界和旧 SHA-256 识别均已实现。
- 旧哈希兼容写窗口被限制为最长 2 小时;正常写路径默认生成新格式。
- 登录成功后通过条件更新透明迁移旧哈希,错误密码不迁移。
- 单测覆盖随机盐、错误密码、旧哈希验证/迁移、畸形参数和兼容窗口边界。
判定:满足上一版“强哈希、版本参数、恒定时间比较、透明迁移”的最小关闭标准。测试环境仍保留的历史 SHA-256 数量来自整改记录,本次未直接查询数据库复核。
### 4.3 登录状态与输入校验
- `api/src/auth/session.service.ts:129-163`:验证码使用 Redis TTL,消费使用 `GETDEL`;失败计数使用 Redis Lua 原子逻辑。
- `api/src/common/strict-validation.pipe.ts`:启用 transform、whitelist 和 forbidNonWhitelisted。
- 重点客户端写接口已增加 class-validator DTO 和严格管道。
- `api/src/open-api/client-open-api.controller.ts:15-18` 的凭据/Webhook 写入,以及 `api/src/operations/client-operations.controller.ts:78-84` 的日志导出仍使用内联 body,未套用严格校验管道。
判定:验证码多实例一致性问题已关闭;输入校验只能判为阶段性完成,不能写成全 API 闭环。
### 4.4 前端性能
- `src/routes/AppRoutes.tsx:10-74`:业务页面统一路由级懒加载。
- `src/components/ui/Chart.tsx:3-8`:只注册实际使用的 ECharts 图表、组件和 Canvas renderer。
- 本次生产构建:入口 `index-*.js` 370.54 KiBgzip 107.38 KiB;图表异步包 553.06 KiBgzip 181.64 KiB。
- 包体预算脚本通过:入口不高于 250 KiB gzip,异步包不高于 190 KiB gzip。
判定:原主包 626.18 KiB gzip 的问题已实质关闭。Vite 仍提示图表 chunk 的未压缩体积超过 500 KiB,但其 gzip 体积在当前预算内,作为后续优化项而非阻断项。
### 4.5 数据库与工程配置
- API 正式构建通过。
- Prisma Schema validate 通过;仓库存在 95 个 migration 目录。
- 非开发/测试环境缺少 `DATABASE_URL` 时 fail closed。
- 本轮没有连接真实 PostgreSQL 执行 `EXPLAIN (ANALYZE, BUFFERS)`,也没有独立核对测试库 95/95 migration;相关运行态数据仅见整改记录。
## 5. 本次独立复测结果
| 检查项 | 本次结果 |
|---|---|
| Git 基线 | `HEAD == origin/main == 3af145a...` |
| 工作区 | 仅发现既有未跟踪 `=``pnpm-lock.yaml`;未修改或删除 |
| 前端 TypeScript | 通过 |
| Vite production build | 通过 |
| Bundle budget | 通过;入口 107.38 KiB gzip,最大异步包 181.64 KiB gzip |
| API TypeScript build | 通过 |
| Prisma validate | 通过 |
| API Jest | 47/47 套、543/543 项通过 |
| API 全源覆盖率 | statements 59.84%branches 51.29%functions 60.15%lines 62.60% |
| API 覆盖率门槛 | 通过;门槛分别为 59%、50%、60%、62% |
| Gateway `go test ./... -count=1` | 通过 |
| Gateway `go vet ./...` | 通过 |
| Gateway 队列契约 | 5/5 通过 |
| 代码质量结构检查 | 通过 |
| 依赖缓解/安全部署检查 | 通过 |
| 根正式锁文件生产依赖审计 | 0 个公告 |
| API 正式锁文件生产依赖审计 | 7 个公告:2 moderate、5 high |
| 前端自动化测试 | 0 个测试文件,未通过验收出口标准 |
| 测试环境 `/api/health` | **通过;关闭 Clash 后绕过代理直连,连续 3 次 HTTP 200、`status=ok`** |
| 测试环境 TCP 12026 | **通过;可连接** |
| 测试环境 SSH 只读核验 | 未完成;当前凭据认证失败 |
覆盖率说明:整改结果文档记录的 69.77%/53.47%/71.08%/73.05% 使用 Jest 默认“已加载源码”口径;本报告显式使用 `collectCoverageFrom=src/**/*.ts` 的全源口径,得到 59.84%/51.29%/60.15%/62.60%。两个结果不是同一统计口径,不应直接比较。本次全源结果仍满足已设门槛,并略高于上一版全源基线。
依赖审计说明:工作区未跟踪的 `pnpm-lock.yaml` 固定了旧 React Router 7.18.1 和 NanoID 3.3.16,直接运行 pnpm audit 会错误报出上一版两个 high。按正式、已跟踪的 `package-lock.json` 在临时目录重建锁文件后,根项目审计为 0。该旧锁文件仍应由文件所有者确认后清理或更新,避免 CI/开发者误用。
## 6. 当前阻断项与剩余风险
### 运行探测校正:此前 502 为本地代理干扰,不构成缺陷
**证据链**
- 2026-08-28 12:50,在 Clash 虚拟网卡/代理开启状态下,PowerShell Web 请求曾连续返回 HTTP 502。
- 用户关闭 Clash 后,首次 PowerShell `-NoProxy` 请求出现一次 10 秒超时,随后请求开始恢复。
- 2026-08-28 12:56,使用 `curl --noproxy '*'` 明确绕过代理连续请求 3 次,均返回 HTTP 200 和 `{"status":"ok","service":"cmpp-platform-api"}`
- 三次直连总响应时间分别约 28 ms、23 ms、19 msTCP 12026 检测为可连接。
**校正结论**
此前新增的 `CQ-OPS-001 / P0` 撤销,不计入缺陷和发布阻断。测试环境外部 API 当前可用,原整改记录中的 API 健康结论没有被本次复评推翻。
**仍保留的验证边界**
本次 SSH 凭据认证失败,因此没有独立读取部署标记、11 项服务状态、内部 Gateway health、Redis Stream pending/lag、migration 或服务器日志。这些项目继续引用整改发布记录,预生产前仍应在可用只读凭据下重新核对。后续私网健康探测应显式绕过系统代理,避免 Clash 等本地网络工具造成误判。
### CQ-API-001 / P1:严格输入校验尚未覆盖全部写接口
当前采取“重点接口逐步接入”而非全局管道。凭据创建、Webhook 更新和日志导出等入口仍使用运行时会被擦除的内联 TypeScript 类型。建议先覆盖所有 client POST/PUT/PATCH/DELETE,再评估 admin 高风险写接口;为字段长度、URL、枚举、数组大小和额外字段补负向测试。
### CQ-TEST-001 / P1:前端与真实环境验收未闭环
前端测试文件仍为 0;本轮没有可用登录态,无法验证登录后页面、控制台、请求、加载/空/错误/刷新状态。本次仅确认外部 API health,未重新执行真实 PostgreSQL、Redis、MinIO、Gateway 和 CMPP 联动复验。
### CQ-DEP-002 / P1API 依赖树仍有 7 项生产审计公告
`api/package-lock.json` 重建审计后为 2 moderate、5 high,涉及 ExcelJS 间接 UUID、Prisma 可选工具链、brace-expansion、Swagger 间接 js-yaml 等。公告并不等于当前业务路径全部可利用,但应建立“依赖路径、运行时是否打包/调用、可升级版本、兼容回归”的矩阵,不能只以根项目审计为 0 宣告供应链闭环。
### CQ-MAINT-001 / P2:超大服务和风格门禁仍未治理
`send-inbound-entry.service.ts` 约 84.2 KiB`send-gateway-submit.service.ts` 约 52.2 KiB;职责仍集中。建议先补行为锁定测试,再按入站持久化、关联/去重、重试路由、下游投递和计费拆分。另应增加 ESLint 与 format check;当前 `npm run lint` 名称实际只执行结构脚本和 TypeScript 检查。
### CQ-REPO-001 / P2:双锁文件口径仍可能误导审计
正式锁文件是 `package-lock.json`,但未跟踪 `pnpm-lock.yaml` 仍存在且版本过期。应先确认其归属,再选择唯一包管理器和唯一锁文件;本报告没有擅自删除或更新用户保留文件。
## 7. 发布建议
当前建议为:**代码可进入下一轮验证,但暂停预生产发布。**
最小充分关闭顺序:
1. 使用显式绕过系统代理的方式复核测试环境,并在可用只读凭据下核对实际部署标记、内部服务和队列状态。
2. 在测试环境重跑双租户隔离和旧密码透明迁移,不发送真实短信。
3. 补齐剩余客户端写接口 DTO/严格校验,并增加负向测试。
4. 建立最小前端测试集:登录、权限、路由懒加载失败、列表加载/空/错误、关键确认操作。
5. 对 API 7 项公告建立升级兼容矩阵;优先处理直接运行时路径。
6. 在单独任务中治理 SendChain 超大服务、ESLint/格式门禁和唯一锁文件。
7. 只有在真实 PostgreSQL、Redis、MinIO、Gateway/CMPP 模拟器和登录后浏览器证据齐全后,再申请预生产发布授权。
## 8. 最终判定
- 原 P0 代码问题:**2/2 已关闭**。
- 原 P1:**2 项关闭或基本关闭,2 项部分关闭**。
- 原 P2:**3 项关闭,2 项部分关闭**。
- 新增运行态阻断:**0 项**;此前 502 已确认是本地 Clash 代理干扰并撤销。
- 自动化门禁:除 API 子树依赖审计和前端测试缺失外,其余本次执行项通过。
- 代码质量:**82/100,有条件通过**。
- 当前发布就绪:**待完整验收;无 502 运行态阻断**。
本报告是对固定提交的独立复评和一次当前测试环境只读探测,不替代完整功能验收、生产安全评估或预生产发布审批。
+16
View File
@@ -4104,3 +4104,19 @@ git diff --check
- 采用两阶段密码安全发布并在每次覆盖前重新建恢复资产:`code-quality-before-d85ff859-20260828T035007Z``code-quality-compatible-d85ff859-20260828T035426Z``code-quality-before-fc4a6a7-20260828T040150Z`。三份均含PostgreSQL custom dump、运行目录、配置、Redis RDB、MinIO数据、状态快照、回退说明和SHA-256;恢复清单、pg_restore、tar和Redis校验通过。
- 最终收尾为95/95迁移、11项服务active、入口HTTP 200、API/Gateway健康、Redis PONG、`gateway.submit.results pending=0/lag=0`,发布后7项核心服务error级日志均0。浏览器真实登录页完整且控制台error/warning为0;未自动输入账号密码/验证码,登录后深层页面仍保留为人工浏览器验收边界。
- 本轮没有发送短信、压测、修改余额/应用/通道或写入MinIO业务对象,也没有执行新的CMPP模拟器全链;两个SendChain超大Service未在安全发布中做高风险重构。完整证据和保留项见`docs/code-quality-remediation-result-20260828.md`。只操作测试环境`100.93.204.60`,预生产未访问或修改。
## 2026-08-28 代码质量 V2 复评与测试环境健康探测校正
- 固定`3af145abe5eb8cae567bb18e29116b4940889258`基线重新复评,原两个P0在代码、单测和结构门禁层面均已关闭;API 47套543项、全源覆盖率59.84/51.29/60.15/62.60、前后端构建、bundle budget、Gateway全包测试/go vet、Prisma及队列契约通过。正式报告为`docs/code-quality-reassessment-20260828-v2.md`
- 首次外部健康探测时本机开启Clash虚拟网卡/代理,`100.93.204.60:12026/api/health`曾连续返回HTTP 502。关闭Clash后使用`curl --noproxy '*'`明确绕过代理,连续3次均返回HTTP 200、`status=ok`,总响应约1928ms,TCP 12026可连接;因此撤销“测试环境502”P0判定,确认为本地代理干扰,不是服务端故障。
- 本轮SSH只读认证未成功,未独立读取部署标记、内部服务、Redis Stream、migration和服务器日志;这些运行态项目继续保留为预生产前复核边界。未部署、未重启服务、未写库、未发送短信、未压测,预生产未访问或修改。
## 2026-08-28 代码质量持续优化执行
- 按`docs/code-quality-continuous-optimization-plan-20260828.md`实施非“暂不建议投入”项:补齐客户端凭据、Webhook、导出、文件、用户等运行时DTO与负向校验;浏览器端不再为客户端请求发送或回退租户ID,服务端继续只从认证会话取得客户端租户。
- 登录保护扩展为账号、来源IP和账号+IP三维失败计数,并增加IP维度验证码频率限制;Redis键只保存规范化值的SHA-256摘要。账号锁定仍为24小时,IP阈值为15分钟窗口30次,组合阈值为24小时5次;Prometheus新增有界事件/范围指标。
- 建立Vitest、Testing Library、jsdom、MSW前端测试基座,覆盖登录错误中文化、异步路由异常、客户端租户头禁止、401/403/500、文件/表单请求、用户列表loading/empty/error及删除确认。33项前端测试通过,纳入覆盖范围的核心模块为statements 88.19%、branches 84.51%、functions 84%、lines 87.94%,门禁为80/70/80/80。
- API增量覆盖门禁覆盖本轮新增DTO、动态JSON校验及登录安全逻辑,28项定向测试通过,覆盖率为86.78/78.29/95.83/89.84。API全量51套563项通过,全源覆盖率为70.44/53.77/71.76/73.61,较V2复评继续形成缓冲。
- 引入ESLint flat config、Prettier和实际增量文件门禁;`packageManager`固定为npm 11.6.2,正式提交仍只维护根目录/API两份`package-lock.json`。历史受保护的未跟踪`pnpm-lock.yaml`和空文件`=`保持原状且不纳入提交。
- 重新审计API依赖并将9项公告降至5项:Swagger、`js-yaml``fast-uri``brace-expansion`安全升级完成;剩余5项属于Prisma CLI同一开发/迁移链,唯一自动方案为破坏性降级Prisma 7至6,未执行`audit fix --force`。逐项路径和运行时判断见`docs/api-dependency-advisory-matrix-20260828.md`
- 本地前后端TypeScript、Vite生产构建、bundle budget、Gateway全包测试与`go vet`、95项Prisma schema、代码质量、安全部署及依赖缓解门禁均通过。按方案明确排除全局ValidationPipe一次性切换、强制audit修复、ECharts替换和SendChain高风险重构;发布与真实环境证据在后续条目补齐。