Files
erp/docs/superpowers/specs/2026-04-17-platform-maturity-roadmap-design.md
iven 432eb2f9f5 docs: 添加平台全面成熟度提升路线图设计规格
覆盖安全地基、架构强化、测试覆盖、插件生态四个维度,
按 Q2-Q4 三季度分层推进的全面改进计划。
2026-04-17 15:58:31 +08:00

415 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 个季度的分层改进,将平台从"功能完整"推进到"生产就绪"
- **Q24-5月**:消除安全风险,建立自动化质量门
- **Q36-8月**:强化架构,提升前端工程化水平
- **Q49-11月**:补齐测试覆盖,扩展插件生态
### 1.4 约束
- **独立开发者** + Claude 辅助 — 每季度聚焦单一维度
- **SaaS 优先**部署 — 多租户安全是硬性要求
- **不破坏现有功能** — 所有改进必须向后兼容
---
## 2. Q2安全地基 + CI/CD4-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<PermissionDef> { 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<AppError> 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<T>` | 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<T>``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 → 增量 DDLADD 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 文档与代码同步