From 432eb2f9f5725554fdba70bb01f0a537479289ff Mon Sep 17 00:00:00 2001 From: iven Date: Fri, 17 Apr 2026 15:58:31 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=B7=BB=E5=8A=A0=E5=B9=B3=E5=8F=B0?= =?UTF-8?q?=E5=85=A8=E9=9D=A2=E6=88=90=E7=86=9F=E5=BA=A6=E6=8F=90=E5=8D=87?= =?UTF-8?q?=E8=B7=AF=E7=BA=BF=E5=9B=BE=E8=AE=BE=E8=AE=A1=E8=A7=84=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 覆盖安全地基、架构强化、测试覆盖、插件生态四个维度, 按 Q2-Q4 三季度分层推进的全面改进计划。 --- ...-04-17-platform-maturity-roadmap-design.md | 414 ++++++++++++++++++ 1 file changed, 414 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-17-platform-maturity-roadmap-design.md diff --git a/docs/superpowers/specs/2026-04-17-platform-maturity-roadmap-design.md b/docs/superpowers/specs/2026-04-17-platform-maturity-roadmap-design.md new file mode 100644 index 0000000..1a5ab0b --- /dev/null +++ b/docs/superpowers/specs/2026-04-17-platform-maturity-roadmap-design.md @@ -0,0 +1,414 @@ +# ERP 平台底座 — 全面成熟度提升路线图 + +> 创建日期:2026-04-17 +> 状态:已批准 +> 范围:安全、架构、测试、前端体验、插件生态 — 3 季度分层推进 + +--- + +## 1. 背景与目标 + +### 1.1 项目现状 + +ERP 平台底座已完成 Phase 1-6 基础设施建设 + WASM 插件系统集成 + CRM 客户管理插件。当前具备: + +- 6 个业务模块(auth, config, workflow, message, plugin, server) +- 36 个数据库迁移 +- 完整的 WASM 插件运行时 +- Schema 驱动的动态前端(6 种页面类型) +- React 19 + Ant Design 6 + Zustand 5 前端 SPA + +### 1.2 分析发现摘要 + +| 维度 | 评分 | 关键问题 | +|------|------|---------| +| 架构健壮性 | 8/10 | ErpModule trait 死代码、路由注册未自动化 | +| 代码质量 | 7/10 | N+1 查询、错误映射过宽、 oversized 组件 | +| 安全性 | 5/10 | 3 个 CRITICAL(硬编码密钥/密码)、4 个 HIGH | +| 测试覆盖 | 4/10 | 零数据库集成测试、关键流程未覆盖 | +| 前端体验 | 7/10 | 无 i18n、无 Error Boundary、无虚拟滚动 | +| 基础设施 | 4/10 | 无 CI/CD、Wiki 过时、大量未跟踪文件 | + +### 1.3 目标 + +通过 3 个季度的分层改进,将平台从"功能完整"推进到"生产就绪": + +- **Q2(4-5月)**:消除安全风险,建立自动化质量门 +- **Q3(6-8月)**:强化架构,提升前端工程化水平 +- **Q4(9-11月)**:补齐测试覆盖,扩展插件生态 + +### 1.4 约束 + +- **独立开发者** + Claude 辅助 — 每季度聚焦单一维度 +- **SaaS 优先**部署 — 多租户安全是硬性要求 +- **不破坏现有功能** — 所有改进必须向后兼容 + +--- + +## 2. Q2:安全地基 + CI/CD(4-5月) + +### 2.1 密钥外部化与启动强制检查 + +**问题:** +- JWT 密钥 `"change-me-in-production"` 硬编码在 `crates/erp-server/config/default.toml` +- 管理员密码 `"Admin@2026"` 硬编码 + fallback +- 数据库凭据 `postgres://erp:erp_dev_2024@...` 硬编码 +- `.test_token` 含有效 admin JWT 提交到仓库 + +**方案:** + +1. **配置强制化**:`default.toml` 只保留开发环境默认值。生产敏感值通过环境变量 `ERP__` 前缀注入(已有机制) +2. **启动检查**:服务启动时检测 JWT 密钥是否为默认值,若是则 **拒绝启动**(返回错误退出码,不只是警告) +3. **密码初始化**:`seed_tenant_auth` 从环境变量 `ERP__AUTH__ADMIN_PASSWORD` 读取初始密码,未设置则拒绝初始化(移除 fallback 到硬编码值的逻辑) +4. **清理 `.test_token`**:加入 `.gitignore`,从 git 历史中移除 +5. **`default.toml` 占位符**:敏感字段改为 `"__MUST_SET_VIA_ENV__"` 之类的明显占位值 + +**验证标准:** +- 默认配置启动时服务拒绝运行 +- 环境变量设置后正常启动 +- `.test_token` 不再出现在仓库中 + +### 2.2 Gitea Actions CI/CD + +**流水线设计:** + +```yaml +name: CI +on: [push, pull_request] + +jobs: + rust-check: + runs-on: ubuntu-latest + steps: + - cargo fmt --check --all + - cargo clippy -- -D warnings + + rust-test: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:16 + env: { POSTGRES_DB: erp_test, POSTGRES_USER: test, POSTGRES_PASSWORD: test } + ports: ["5432:5432"] + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + steps: + - cargo test --workspace + + frontend-build: + runs-on: ubuntu-latest + steps: + - cd apps/web && pnpm install && pnpm build + + security-audit: + runs-on: ubuntu-latest + steps: + - cargo audit + - cd apps/web && pnpm audit +``` + +**关键决策:** +- 使用 Gitea Actions service 容器提供 PostgreSQL +- 四个 job 并行运行,互不依赖 +- 后续可扩展:Redis service、Playwright E2E、Docker 镜像构建推送 + +### 2.3 审计日志补全 + +**当前缺口与改进:** + +| 缺口 | 改进方案 | +|------|---------| +| 登录/登出只发 DomainEvent,不写审计日志 | 在 `auth_service` 的 login/logout/change_password 中调用 `audit_service::record()` | +| 审计日志缺少 `old_value`/`new_value` | 关键实体(user/role/permission/org)的 update 操作添加 `.with_changes(old, new)` | +| 缺少 IP 地址和 User-Agent | `AuditLogBuilder::with_request_info()` 在 handler 层传入请求上下文 | +| 插件 CRUD 无审计 | `data_service` 的 create/update/delete 操作添加审计日志记录 | +| 登录失败无记录 | 添加失败登录审计(含尝试的用户名/IP),用于入侵检测 | + +**验证标准:** +- 登录成功/失败均写入审计日志 +- 用户更新操作记录变更前后值 +- 审计日志包含 IP 和 User-Agent + +### 2.4 Docker 生产化 + +| 改进项 | 当前 | 目标 | +|--------|------|------| +| PostgreSQL 端口 | `ports: "5432:5432"` 暴露到宿主机 | 移除 `ports:`,使用 Docker 网络内部通信 | +| Redis 端口 | `ports: "6379:6379"` 无认证 | 移除 `ports:`,添加 `--requirepass` | +| 容器资源限制 | 无 | CPU 1核 / 内存 512MB | +| 应用镜像 | 无 Dockerfile | 多阶段构建:Rust build → 精简 runtime 镜像 | +| Redis 宕机时限流 | fail-open(无限流) | fail-closed(拒绝请求) | + +**限流 fail-closed 改动:** +`crates/erp-server/src/middleware/rate_limit.rs` 中 Redis 不可用时,返回 `429 Too Many Requests` 而非放行。 + +### 2.5 多租户安全加固 + +| 问题 | 改进方案 | +|------|---------| +| 登录使用硬编码 `default_tenant_id` | 登录接口增加租户解析(从子域名/请求头 `X-Tenant-ID`) | +| `auth_service::refresh()` 用户查询缺少 tenant_id | `find_by_id` 添加 `.filter(user::Column::TenantId.eq(claims.tenant_id))` | +| 内存级 tenant_id 过滤 | 改为数据库级 `.filter(Column::TenantId.eq(tenant_id))` 查询 | + +**涉及文件:** +- `crates/erp-auth/src/handler/auth_handler.rs` +- `crates/erp-auth/src/service/auth_service.rs` +- `crates/erp-auth/src/service/user_service.rs` +- `crates/erp-auth/src/middleware/jwt_auth.rs` + +--- + +## 3. Q3:架构强化 + 前端体验(6-8月) + +### 3.1 ErpModule Trait 重构 + +**当前问题:** +- `register_event_handlers` 是死代码 — 所有模块实现为空操作 +- 路由注册需在 `main.rs` 手动编辑两处 +- 事件订阅在 `main.rs` 中手动调用,绕过 trait + +**改进方案:** + +重构 `ErpModule` trait,新增路由注册和权限声明: + +```rust +pub trait ErpModule: Send + Sync + 'static { + fn name(&self) -> &str; + fn version(&self) -> &str; + fn dependencies(&self) -> Vec<&str> { vec![] } + + // 新增:路由注册自动化 + fn register_routes(&self, router: Router) -> Router { router } + + // 重构:事件订阅真正生效 + fn register_event_handlers(&self, bus: &EventBus) {} + + // 新增:模块权限声明 + fn permissions(&self) -> Vec { vec![] } + + // 保留已有生命周期钩子 + async fn on_startup(&self, _state: &AppState) -> AppResult<()> { Ok(()) } + async fn on_shutdown(&self) -> AppResult<()> { Ok(()) } + async fn on_tenant_created(&self, _tenant_id: Uuid, _state: &AppState) -> AppResult<()> { Ok(()) } + async fn on_tenant_deleted(&self, _tenant_id: Uuid, _state: &AppState) -> AppResult<()> { Ok(()) } + async fn health_check(&self) -> AppResult<()> { Ok(()) } +} +``` + +`ModuleRegistry::build()` 自动收集路由、事件处理器和权限: + +```rust +let (registry, router) = ModuleRegistry::new() + .register(auth_module) + .register(config_module) + .register(workflow_module) + .register(message_module) + .register(plugin_module) + .build(router); +``` + +**迁移策略:** 逐模块迁移 — 每个模块从静态函数改为 trait 方法实现,`main.rs` 逐步简化。 + +### 3.2 错误映射修正 + N+1 查询优化 + +**错误映射修正:** + +当前 `DbErr` 统一映射为 `AuthError::Validation`,掩盖了连接失败和约束冲突。 + +改为语义化映射: +- `DbErr::ConnectionErr` → `AppError::Internal("数据库连接失败")` +- `DbErr::RecordNotFound` → `AppError::NotFound` +- `DbErr` + 消息含 `"duplicate key"` → `AppError::Conflict("记录已存在")` +- `DbErr` + 消息含 `"foreign key"` → `AppError::Validation("关联数据不存在")` + +移除 `From for AuthError` 的反向映射(lossy wrapping)。 + +**N+1 查询优化:** + +`user_service.rs` 的 `list()` 方法改为批量查询: +1. 先查询当前页用户列表 +2. 收集所有 `user_id` +3. 一次 `WHERE user_id IN (...)` 查询 `user_role` + `role` +4. 内存中按 `user_id` 分组组装 + +从 N+1 查询降为 3 次固定查询(用户列表 + 角色关联 + 角色详情)。 + +### 3.3 前端 Error Boundary + hooks 提取 + +**Error Boundary:** +- `App.tsx` 根组件包裹全局 Error Boundary(捕获未预期崩溃) +- 每个懒加载页面外包裹页面级 Error Boundary(隔离单页面崩溃) +- 失败时展示友好错误页面 + 重试按钮 + +**hooks 提取:** + +| Hook | 提取来源 | 用途 | +|------|---------|------| +| `usePaginatedData` | 6+ 页面的分页加载逻辑 | 统一分页/搜索/加载状态 | +| `useDarkMode` | 8+ 文件的 `token.colorBgContainer` 字符串比较 | 提供可靠的 boolean 暗色模式判断 | +| `useCountUp` | Home.tsx + DashboardWidgets 重复实现 | 计数动画复用 | +| `useDebouncedValue` | Users.tsx 等搜索输入 | 防抖搜索,避免每次按键触发 API | +| `useApiRequest` | 所有页面的 try/catch + message.error | 统一 API 错误处理和消息提示 | + +### 3.4 i18n 基础设施搭建 + +**方案:react-i18next** + +- 安装 `react-i18next` + `i18next` +- 创建 `locales/zh-CN.json`,提取所有硬编码中文为 key +- 配置 i18next 初始化,默认 `zh-CN` +- 用 `useTranslation()` hook 替换硬编码字符串 + +**实施策略:** 增量式 — 新页面强制使用 i18n,旧页面按模块逐步迁移。不强求一次性替换。 + +**命名规范:** +- 页面文案:`{module}.{page}.{element}` 如 `auth.login.username` +- 通用文案:`common.{action}` 如 `common.save`, `common.cancel` +- 错误消息:`error.{type}` 如 `error.network`, `error.unauthorized` + +### 3.5 行级数据权限接线 + +**当前状态:** 数据库列、SQL 条件构建器、manifest 声明已就绪,handler 层有 TODO 未实现。 + +**完成步骤:** +1. JWT 中间件注入 `department_ids`(完成 `jwt_auth.rs:50` 的 TODO) +2. `data_handler` 查询接口注入 data scope 条件 +3. 前端角色权限编辑页添加 `data_scope` 选择控件 +4. 端到端验证:创建测试角色 → 设置数据范围 → 验证查询过滤 + +### 3.6 前端共享类型统一 + +- `PaginatedResponse` 从 `users.ts` 提取到 `api/types.ts` +- 错误提取工具函数 `extractErrorMessage(err: unknown): string` → `api/errors.ts` +- 插件 Schema 类型定义集中到 `types/plugin.ts` +- 移除 `api/client.ts` 中已废弃的 `CancelToken`,改用 `AbortController` + +--- + +## 4. Q4:测试覆盖 + 插件生态(9-11月) + +### 4.1 数据库集成测试框架 + +**方案:Testcontainers + PostgreSQL** + +创建 `crates/erp-server/tests/integration/` 目录,使用 `testcontainers` crate 启动真实 PostgreSQL 容器。 + +**测试基座:** +- 每个测试套件共享一个 PostgreSQL 容器 +- 自动运行所有迁移 +- 提供 `setup_test_db()` 辅助函数返回连接池 +- 测试结束自动清理 + +**覆盖优先级:** + +| 优先级 | 模块 | 测试场景 | +|--------|------|---------| +| P0 | erp-auth | 用户 CRUD、角色权限分配、登录/JWT 完整流程 | +| P0 | erp-auth | 多租户隔离 — 租户 A 数据对租户 B 不可见 | +| P0 | erp-plugin | 插件生命周期(install→enable→disable→uninstall) | +| P1 | erp-auth | 乐观锁并发冲突、软删除恢复 | +| P1 | erp-plugin | 行级数据权限过滤、JSONB 查询/索引 | +| P1 | erp-plugin | 动态表 DDL 正确性(generated column、pg_trgm 索引) | +| P1 | erp-workflow | 流程实例启动、任务完成、网关分支 | +| P1 | erp-core | 事件总线发布/订阅端到端、outbox relay 补偿 | + +### 4.2 核心流程 E2E 测试 + +**方案:Playwright** + +放在 `apps/web/e2e/` 目录,CI 中作为独立 job 运行。 + +**覆盖场景(4 个关键旅程):** + +| 场景 | 步骤 | +|------|------| +| 完整登录流程 | 打开登录页 → 输入密码 → 验证 token → 刷新 token → 登出 → 验证跳转 | +| 用户管理闭环 | 创建用户 → 分配角色 → 搜索用户 → 编辑 → 软删除 → 验证列表不显示 | +| 插件安装流程 | 上传 WASM → 安装 → 验证菜单出现 → 数据 CRUD → 卸载 → 验证菜单消失 | +| 多租户隔离 | 租户 A 创建用户 → 切换租户 B → 验证查询结果为空 | + +### 4.3 第二个行业插件 — 进销存(Inventory) + +**选择理由:** +- 与 CRM 有天然关联(客户 → 订单 → 出库) +- 实体数量适中(5-8 个),复杂度可控 +- 能验证插件系统的复用性和跨实体关联能力 +- 为后续财务模块铺垫 + +**实体设计:** + +| 实体 | 字段 | 关联 | +|------|------|------| +| product 商品 | 名称/编码/规格/单位/分类/售价/成本价 | — | +| warehouse 仓库 | 名称/地址/负责人/状态 | — | +| stock 库存 | 商品/仓库/数量/成本/预警线 | → product, warehouse | +| purchase_order 采购单 | 供应商/总金额/状态/日期 | → supplier(CRM), stock | +| sales_order 销售单 | 客户/总金额/状态/日期 | → customer(CRM), stock | +| supplier 供应商 | 名称/编码/联系方式/地址 | — | + +**需要验证的插件能力:** +- 跨实体关联(订单 → 商品 → 库存联动) +- 事务性事件(库存扣减在订单确认时原子执行) +- 页面间导航(从订单跳转客户详情) +- 报表/统计页面(库存汇总、进销存明细) + +### 4.4 插件热更新能力 + +**当前限制:** 更新插件需要完整 uninstall/reinstall。 + +**改进方案:** +- 新增 `POST /api/v1/admin/plugins/{id}/upgrade` 端点 +- 升级流程:上传新 WASM → 对比 manifest schema → 增量 DDL(ADD COLUMN 等) → 热替换 WASM 模块 +- 数据安全:`tenant_id` 数据不丢失 +- 版本兼容性检查:新版本必须向后兼容或提供迁移脚本 + +### 4.5 文档更新与清理 + +| 项目 | 改进 | +|------|------| +| Wiki 文档 | 全面更新到当前状态(前端路由、测试数量、模块能力、插件系统) | +| CLAUDE.md | 版本号修正(React 19 / Ant Design 6) | +| 根目录清理 | 删除未跟踪的开发临时文件(截图、heap dump、perf trace、agent plan 文件) | +| integration-tests/ | 纳入 Cargo workspace 或合并到 `crates/erp-server/tests/` | +| `erp-common` 引用 | 从文档中移除不存在的 crate 引用 | + +--- + +## 5. 风险与缓解 + +| 风险 | 概率 | 影响 | 缓解措施 | +|------|------|------|---------| +| 安全修复引入新 bug | 中 | 高 | 每个修复配有对应的测试用例 | +| ErpModule trait 重构影响所有模块 | 高 | 中 | 逐模块迁移,每步验证 `cargo test` | +| i18n 迁移工作量大 | 中 | 低 | 增量式,不追求一次性完成 | +| Testcontainers 在 CI 环境不稳定 | 低 | 中 | 本地开发可跳过集成测试,CI 用 service container 兜底 | +| 进销存插件实体设计变更 | 中 | 低 | 先完成最小实体集,后续迭代扩展 | + +--- + +## 6. 成功标准 + +**Q2 完成标准:** +- [ ] 3 个 CRITICAL 安全问题全部修复 +- [ ] Gitea Actions CI/CD 流水线运行通过 +- [ ] 默认配置启动被拒绝 +- [ ] 登录/登出写入审计日志 +- [ ] Docker 生产化配置就绪 + +**Q3 完成标准:** +- [ ] ErpModule trait 路由注册自动化 +- [ ] N+1 查询优化,用户列表查询次数固定为 3 +- [ ] 前端 Error Boundary 覆盖全局 + 页面级 +- [ ] 5 个自定义 hooks 提取完成 +- [ ] i18n 基础设施可用,至少 1 个页面完成迁移 +- [ ] 行级数据权限端到端验证通过 + +**Q4 完成标准:** +- [ ] 集成测试覆盖 auth + plugin 核心流程 +- [ ] 4 个 E2E 测试场景通过 +- [ ] 进销存插件 6 个实体可用 +- [ ] 插件热更新功能可用 +- [ ] Wiki 文档与代码同步