# 质量问题系统性预防方案 > 日期: 2026-05-08 | 参与者: iven, Claude > 触发: 4 轮角色测试发现 27 个问题,归纳出 5 种高频模式,分析根因后制定根治方案 ## 背景 2026-05-06 至 2026-05-07 进行了 4 轮角色测试(5 角色 × 220+ API 项 + 175 前端交互项),共发现 27 个独立问题,已修复 22 个,剩余 5 个待处理。 归纳出 5 种高频模式: 1. 权限配置散落(9/27 = 33%) 2. 后端类型/状态不一致(5/27 = 19%) 3. 前后端 API 路径分叉(4/27 = 19%) 4. 缺少全局容错(3/27 = 11%) 5. 默认放行模式(6/27 = 22%) 共性深层问题:**系统缺乏"定义一次,到处生效"的约束机制**。 ## 讨论要点 ### 方案 1:权限注册表(单一真相源) **问题**:权限码散落在 4 处(handler 字符串 / seed 迁移 SQL / 前端路由 meta / 菜单可见性),每加一个页面需手动同步 4 处。 **方案**:创建 `permissions.yaml` 作为唯一定义源,构建时自动生成: - Rust 侧权限常量 - seed 迁移 INSERT 语句 - 前端路由权限映射 - 菜单可见性配置 **优先级**:P2(投入 3-5 天,最彻底但成本最高) ### 方案 2:API 契约自动同步 **问题**:后端 utoipa 生成 OpenAPI spec,前端完全不用,手动硬编码路径。 **方案**:从 OpenAPI spec 自动生成前端 API 客户端(openapi-typescript),消除路径拼写错误和类型不匹配。 **优先级**:P2(投入 2-3 天,需改造 service 层) ### 方案 3:状态机 + 类型安全 Seed **问题**:告警 seed 数据 `status=active`,代码状态机只认 `pending` → 全部操作 422;`escalation_level` INT2 vs Entity i32 → 查询 500。 **方案**: - 3a:状态机定义从代码注释提升为一等公民(宏定义 + 编译期校验) - 3b:Seed 数据校验测试(验证 seed 的 status 字段值在合法状态列表中) **优先级**:3b P1(投入 1 天),3a P2(投入 2-3 天) ### 方案 4:聚合接口防御性模式 **问题**:仪表盘统计 API 内部查 5 个子指标,任一失败 → 整个 500。 **方案**:在 `erp-core` 封装 `safe_aggregate` 工具函数,单个子查询失败返回零值/默认值。 **优先级**:P1(投入半天) ### 方案 5:默认拒绝 + 强制守卫 **问题**:路由守卫和菜单渲染默认放行,开发者需要主动加限制 → 容易遗漏。 **方案**: - 5a:`createProtectedRoute()` wrapper — 不声明权限码则无法创建路由(编译期强制) - 5b:CI 扫描未注册权限的后端端点 - 5c:新端点 lint 检查 **优先级**:P0(5a 半天,5b 1-2 天) ## 实施优先级 | 优先级 | 方案 | 投入 | 收益 | |--------|------|------|------| | P0 | 5a — `createProtectedRoute` wrapper | 半天 | 杜绝路由守卫遗漏 | | P0 | 5b — CI 扫描未注册权限的端点 | 1-2 天 | 立即堵住权限遗漏 | | P1 | 4 — `safe_aggregate` 工具函数 | 半天 | 杜绝聚合接口 500 | | P1 | 3b — Seed 数据状态校验测试 | 1 天 | 杜绝 seed/代码不一致 | | P2 | 2 — OpenAPI 生成前端客户端 | 2-3 天 | 杜绝路径分叉 | | P2 | 1 — 权限注册表 YAML | 3-5 天 | 统一权限管理 | ## 结论 核心思路:让"对的事情"变成"唯一能做的事情"。靠人记得加权限、记得同步路径、记得容错,注定会遗漏。把约束嵌入构建流程和代码框架,错自然就犯不了。 先做 P0+P1(2-3 天见效),P2 作为下一阶段质量基建。