feat: 初始化ERP平台底座项目结构

- 添加基础crate结构(erp-core, erp-common)
- 实现核心模块trait和事件总线
- 配置Docker开发环境(PostgreSQL+Redis)
- 添加Tauri桌面端基础框架
- 设置CI/CD工作流
- 编写项目协作规范文档(CLAUDE.md)
This commit is contained in:
iven
2026-04-10 23:40:38 +08:00
commit eb856b1d73
32 changed files with 8049 additions and 0 deletions

27
.gitignore vendored Normal file
View File

@@ -0,0 +1,27 @@
# Rust
/target/
**/*.rs.bk
# Node
node_modules/
dist/
# Tauri
apps/desktop/src-tauri/target/
# IDE
.vscode/
.idea/
*.swp
# Environment
.env
*.env.local
# OS
.DS_Store
Thumbs.db
# Docker data
docker/postgres_data/
docker/redis_data/

583
CLAUDE-1.md Normal file
View File

@@ -0,0 +1,583 @@
# ZCLAW 协作与实现规则
> **ZCLAW 是一个独立成熟的 AI Agent 桌面客户端**,专注于提供真实可用的 AI 能力,而不是演示 UI。
> **当前阶段: 发布前管家模式实施。** 稳定化基线已达成管家模式6交付物已完成。
## 1. 项目定位
### 1.1 ZCLAW 是什么
ZCLAW 是面向中文用户的 AI Agent 桌面端,核心能力包括:
- **智能对话** - 多模型支持8 Provider、流式响应、上下文管理
- **自主能力** - 9 个启用的 Hands另有 Predictor/Lead 已禁用)
- **技能系统** - 75 个 SKILL.md 技能定义
- **工作流编排** - Pipeline DSL + 10 行业模板
- **安全审计** - 完整的操作日志和权限控制
### 1.2 决策原则
**任何改动都要问:这对 ZCLAW 用户今天能产生价值吗?**
- ✅ 修复已知的 P0/P1 缺陷 → 最高优先
- ✅ 接通"写了没接"的断链 → 高优先
- ✅ 清理死代码和孤立文件 → 应该做
- ❌ 新增功能/页面/端点 → 稳定化完成前禁止
- ❌ 增加复杂度但无实际价值 → 永远不做
- ❌ 折中方案掩盖根因 → 永远不做
### 1.3 稳定化铁律
**稳定化基线达成后仍需遵守以下约束:**
| 禁止行为 | 原因 |
|----------|------|
| 新增 SaaS API 端点 | 已有 140 个(含 2 个 dev-only前端未全部接通 |
| 新增 SKILL.md 文件 | 已有 75 个,大部分未执行验证 |
| 新增 Tauri 命令 | 已有 189 个70 个无前端调用且无 @reserved |
| 新增中间件/Store | 已有 13 层中间件 + 18 个 Store |
| 新增 admin 页面 | 已有 15 页 |
### 1.4 系统真实状态
参见 [docs/TRUTH.md](docs/TRUTH.md) — 这是唯一的真相源,所有其他文档中的数字如果与此冲突,以 TRUTH.md 为准。
***
## 2. 项目结构
```text
ZCLAW/
├── crates/ # Rust Workspace (10 crates)
│ ├── zclaw-types/ # L1: 基础类型 (AgentId, Message, Error)
│ ├── zclaw-memory/ # L2: 存储层 (SQLite, KV, 会话管理)
│ ├── zclaw-runtime/ # L3: 运行时 (4 Driver, 7 工具, 12 层中间件)
│ ├── zclaw-kernel/ # L4: 核心协调 (182 Tauri 命令)
│ ├── zclaw-skills/ # 技能系统 (75 SKILL.md 解析, 语义路由)
│ ├── zclaw-hands/ # 自主能力 (9 启用, 106 Rust 测试)
│ ├── zclaw-protocols/ # 协议支持 (MCP 完整, A2A feature-gated)
│ ├── zclaw-pipeline/ # Pipeline DSL (v1/v2, 10 行业模板)
│ ├── zclaw-growth/ # 记忆增长 (FTS5 + TF-IDF)
│ └── zclaw-saas/ # SaaS 后端 (130 API, Axum + PostgreSQL)
├── admin-v2/ # 管理后台 (Vite + Ant Design Pro, 13 页)
├── desktop/ # Tauri 桌面应用
│ ├── src/
│ │ ├── components/ # React UI 组件 (含 SaaS 集成)
│ │ ├── store/ # Zustand 状态管理 (含 saasStore)
│ │ └── lib/ # 客户端通信 / 工具函数 (含 saas-client)
│ └── src-tauri/ # Tauri Rust 后端 (集成 Kernel)
├── skills/ # SKILL.md 技能定义
├── hands/ # HAND.toml 自主能力配置
├── config/ # TOML 配置文件
├── saas-config.toml # SaaS 后端配置 (PostgreSQL 连接等)
├── docker-compose.yml # PostgreSQL 容器配置
├── docs/ # 架构文档和知识库
└── tests/ # Vitest 回归测试
```
### 2.1 核心数据流
```text
用户操作 → React UI → Zustand Store → Tauri Commands → zclaw-kernel → LLM/Tools/Skills/Hands
```
### 2.2 技术栈
| 层级 | 技术 |
| ---- | --------------------- |
| 前端框架 | React 19 + TypeScript |
| 状态管理 | Zustand 5 |
| 桌面框架 | Tauri 2.x |
| 样式方案 | Tailwind 4 |
| 配置格式 | TOML |
| 后端核心 | Rust Workspace (10 crates, ~66K 行) |
| SaaS 后端 | Axum + PostgreSQL (zclaw-saas) |
| 管理后台 | Vite + Ant Design Pro (admin-v2/) |
### 2.3 Crate 依赖关系
```text
zclaw-types (无依赖)
zclaw-memory (→ types)
zclaw-runtime (→ types, memory)
zclaw-kernel (→ types, memory, runtime)
zclaw-saas (→ types, 独立运行于 8080 端口)
desktop/src-tauri (→ kernel, skills, hands, protocols)
```
***
## 3. 工作风格
### 3.1 交付导向
- **先做最高杠杆问题** - 解决用户最痛的点
- **真实能力优先** - 不做假数据占位
- **完整闭环** - 每个功能都要能真正使用
### 3.2 根因优先
遇到问题时,先确认属于哪一类:
1. **协议问题** - API 端点、请求格式、响应解析
2. **状态问题** - Store 更新、组件同步
3. **UI 问题** - 交互逻辑、样式显示
4. **配置问题** - TOML 解析、环境变量
5. **运行时问题** - 服务启动、端口占用
不在根因未明时盲目堆补丁。
### 3.3 闭环工作法(强制)
每次改动**必须**按顺序完成以下步骤,不允许跳过:
1. **定位问题** — 理解根因,不盲目堆补丁
2. **最小修复** — 只改必要的代码
3. **自动验证**`tsc --noEmit` / `cargo check` / `vitest run` 必须通过
4. **提交推送** — 按 §11 规范提交,**立即 `git push`**,不积压
5. **文档同步** — 按 §8.3 检查并更新相关文档,提交并推送
**铁律:步骤 4 和 5 是任务完成的硬性条件。不允许"等一下再提交"或"最后一起推送"。**
***
## 4. 实现规则
### 4.1 通信层
所有与后端的通信必须通过统一的客户端层:
- `desktop/src/lib/gateway-client.ts` - 主要通信客户端
- `desktop/src/lib/tauri-gateway.ts` - Tauri 原生命令
**禁止**在组件内直接创建 WebSocket 或拼装 HTTP 请求。
### 4.2 分层职责
```
UI 组件 → 只负责展示和交互
Store → 负责状态组织和流程编排
Client → 负责网络通信和协议转换
```
### 4.3 代码自检规则
**每次修改代码前必须检查:**
1. **是否已有相同能力的代码?** — 先搜索再写,避免重复
2. **前端是否有人调用?** — 没有 Rust 调用者的 Tauri 命令,先标注 `@reserved`
3. **错误是否静默吞掉?**`let _ =` 必须替换为 `log::warn!` 或更高级别处理
4. **文档数字是否需要更新?** — 改了数量就要改文档```
---
### 4.4 代码规范
**TypeScript:**
- 避免 `any`,优先 `unknown + 类型守卫`
- 外部数据必须做容错解析
- 不假设 API 响应永远只有一种格式
**React:**
- 使用函数组件 + hooks
- 复杂副作用收敛到 store
- 组件保持"展示层"职责
**配置处理:**
- 使用 TOML 解析器
- 支持环境变量插值 `${VAR_NAME}`
- 写回时保持格式一致
---
## 5. UI 完成度标准
### 5.1 允许存在的 UI
- 已接入真实后端能力的 UI
- 明确标注"开发中 / 只读"的 UI
- 有降级方案的 UI
### 5.2 不允许存在的 UI
- 看似可编辑但不会生效的设置
- 展示假状态的面板
- 用 mock 数据掩盖未完成能力
### 5.3 核心功能 UI
| 功能 | 状态 | 说明 |
|------|------|------|
| 聊天界面 | ✅ 完成 | 流式响应、多模型切换 |
| 分身管理 | ✅ 完成 | 创建、配置、切换 Agent |
| 自动化面板 | ✅ 完成 | Hands 触发、参数配置 |
| 技能市场 | 🚧 进行中 | 技能浏览和安装 |
| 工作流编辑 | 🚧 进行中 | Pipeline 工作流编辑器 |
---
## 6. 自主能力系统 (Hands)
ZCLAW 提供 11 个自主能力包9 启用 + 2 禁用):
| Hand | 功能 | 状态 |
|------|------|------|
| Browser | 浏览器自动化 | ✅ 可用 |
| Collector | 数据收集聚合 | ✅ 可用 |
| Researcher | 深度研究 | ✅ 可用 |
| Predictor | 预测分析 | ❌ 已禁用 (enabled=false),无 Rust 实现 |
| Lead | 销售线索发现 | ❌ 已禁用 (enabled=false),无 Rust 实现 |
| Clip | 视频处理 | ⚠️ 需 FFmpeg |
| Twitter | Twitter 自动化 | ✅ 可用12 个 API v2 真实调用,写操作需 OAuth 1.0a |
| Whiteboard | 白板演示 | ✅ 可用(导出功能开发中,标注 demo |
| Slideshow | 幻灯片生成 | ✅ 可用 |
| Speech | 语音合成 | ✅ 可用Browser TTS 前端集成完成) |
| Quiz | 测验生成 | ✅ 可用 |
**触发 Hand 时:**
1. 检查依赖是否满足
2. 收集必要参数
3. 处理 `needs_approval` 状态
4. 记录执行日志
---
## 7. 测试与验证
### 7.1 必测场景
修改以下内容后必须验证:
- 聊天 / 流式响应
- Store 状态更新
- 配置读写
- Hand 触发
### 7.2 前端调试优先使用 WebMCP
ZCLAW 注册了 WebMCP 结构化调试工具(`desktop/src/lib/webmcp-tools.ts`AI 代理可直接查询应用状态而无需 DOM 截图。
**原则:能用 WebMCP 工具完成的调试,优先使用 WebMCP 而非 DevTools MCP`take_snapshot`/`evaluate_script`),以减少约 67% 的 token 消耗。**
已注册的 WebMCP 工具:
| 工具名 | 用途 |
|--------|------|
| `get_zclaw_state` | 综合状态概览(连接、登录、流式、模型) |
| `check_connection` | 连接状态检查 |
| `send_message` | 发送聊天消息 |
| `cancel_stream` | 取消当前流式响应 |
| `get_streaming_state` | 流式响应详细状态 |
| `list_conversations` | 列出最近对话 |
| `get_current_conversation` | 获取当前对话完整消息 |
| `switch_conversation` | 切换到指定对话 |
| `get_token_usage` | Token 用量统计 |
| `get_offline_queue` | 离线消息队列 |
| `get_saas_account` | SaaS 账户和订阅信息 |
| `get_available_models` | 可用 LLM 模型列表 |
| `get_current_agent` | 当前 Agent 详情 |
| `list_agents` | 所有 Agent 列表 |
| `get_console_errors` | 应用日志中的错误 |
**使用前提**Chrome 146+ 并启用 `chrome://flags/#enable-webmcp-testing`。仅在开发模式注册。
**何时仍需 DevTools MCP**UI 布局/样式问题、点击交互、截图对比、网络请求检查。
### 7.3 验证命令
```bash
# TypeScript 类型检查
pnpm tsc --noEmit
# 前端单元测试
cd desktop && pnpm vitest run
# Rust 全量测试(排除 SaaS
cargo test --workspace --exclude zclaw-saas
# SaaS 集成测试(需要 PostgreSQL
export TEST_DATABASE_URL="postgresql://postgres:123123@localhost:5432/zclaw"
cargo test -p zclaw-saas -- --test-threads=1
# 启动开发环境
pnpm start:dev
````
### 7.4 人工验证清单
- [ ] 能否正常连接后端服务
- [ ] 能否发送消息并获得流式响应
- [ ] 模型切换是否生效
- [ ] Hand 触发是否正常执行
- [ ] 配置保存是否持久化
***
## 8. 文档管理
### 8.1 文档结构
```text
docs/
├── features/ # 功能文档
│ ├── README.md # 功能索引
│ └── */ # 各功能详细文档
├── knowledge-base/ # 技术知识库
│ ├── troubleshooting.md
│ └── *.md
└── archive/ # 归档文档
```
### 8.2 文档更新原则
- **修完就记** - 解决问题后立即更新文档
- **面向未来** - 文档要帮助未来的开发者快速理解
- **中文优先** - 所有面向用户的文档使用中文
### 8.3 完成工作后的收尾流程(强制,不可跳过)
每次完成功能实现、架构变更、问题修复后,**必须立即执行以下收尾**
#### 步骤 A文档同步代码提交前
检查以下文档是否需要更新,有变更则立即修改:
1. **CLAUDE.md** — 项目结构、技术栈、工作流程、命令变化时
2. **CLAUDE.md §13 架构快照** — 涉及子系统变更时,更新 `<!-- ARCH-SNAPSHOT-START/END -->` 标记区域(可执行 `/sync-arch` 技能自动分析)
3. **docs/ARCHITECTURE_BRIEF.md** — 架构决策或关键组件变更时
4. **docs/features/** — 功能状态变化时
5. **docs/knowledge-base/** — 新的排查经验或配置说明
6. **docs/TRUTH.md** — 数字命令数、Store 数、crates 数等)变化时
#### 步骤 B提交按逻辑分组
```
代码变更 → 一个或多个逻辑提交
文档变更 → 独立提交(如果和代码分开更清晰)
```
#### 步骤 C推送立即
```
git push
```
**不允许积压。** 每次完成一个独立工作单元后立即推送。不要留到"最后一起推"。
**判断标准:** 如果工作目录有未提交文件,说明收尾流程没完成。
***
## 9. 常见问题排查
### 9.1 连接问题
1. 检查后端服务是否启动(端口 50051
2. 检查 Vite 代理配置
3. 检查防火墙设置
### 9.2 状态问题
1. 检查 Store 是否正确订阅
2. 检查组件是否在正确的 Store 获取数据
3. 检查是否有多个 Store 实例
### 9.3 配置问题
1. 检查 TOML 语法
2. 检查环境变量是否设置
3. 检查配置文件路径
***
## 10. 常用命令
```bash
# 安装依赖
pnpm install
# 开发模式
pnpm start:dev
# 仅启动桌面端
pnpm desktop
# 构建生产版本
pnpm build
# 类型检查
pnpm tsc --noEmit
# 运行测试
pnpm vitest run
# 停止所有服务
pnpm start:stop
```
***
## 11. 提交规范
```
<type>(<scope>): <description>
```
**类型:**
- `feat` - 新功能
- `fix` - 修复问题
- `refactor` - 重构
- `docs` - 文档更新
- `test` - 测试相关
- `chore` - 杂项
**示例:**
```
feat(hands): 添加参数预设保存功能
fix(chat): 修复流式响应中断问题
refactor(store): 统一 Store 数据获取方式
```
***
## 12. 安全注意事项
- 不在代码中硬编码密钥
- 用户输入必须验证
- 敏感操作需要确认
- 保留操作审计日志
- 环境变量 `ZCLAW_SAAS_DEV` 模式放宽安全限制(开发环境设 `ZCLAW_SAAS_DEV=true`
### 认证安全
- **JWT password_version**: 密码修改后自动使所有已签发的 JWT 失效Claims 含 `pwv`,中间件比对 DB
- **账户锁定**: 5 次登录失败后锁定 15 分钟
- **邮箱验证**: RFC 5322 正则 + 254 字符长度限制
- **JWT 密钥**: `#[cfg(debug_assertions)]` 保护 fallbackrelease 模式 `bail` 拒绝启动
- **TOTP 加密密钥**: 生产环境强制独立 `ZCLAW_TOTP_ENCRYPTION_KEY`64 字符 hex不从 JWT 密钥派生
- **TOTP/API Key 加密**: AES-256-GCM + 随机 Nonce
- **密码存储**: Argon2id + OsRng 随机盐
- **Refresh Token 轮换**: 单次使用Logout 时撤销到 DBrotation 校验已撤销的旧 token
### 网络安全
- **Cookie**: HttpOnly + Secure + SameSite=Strict + 路径作用域
- **Cookie Secure**: 开发环境 false生产 true
- **CORS**: 生产强制白名单,缺失拒绝启动
- **TLS**: 反向代理nginx/caddy提供 HTTPS 终止Axum 不负责 TLS
- **Docker**: SaaS 端口绑定 `127.0.0.1`,仅通过 nginx 反代访问
- **XFF**: 仅信任配置的代理 IP
### 限流
- `/api/auth/login` — 5次/分钟/IP防暴力破解+ 持久化到 PostgreSQL
- `/api/auth/register` — 3次/小时/IP防刷注册
- 公共端点默认 20次/分钟/IP防滥用
### 前端安全
- **Admin Token**: HttpOnly Cookie 传递JS 不存储/读取 token
- **Tauri CSP**: 移除 `unsafe-inline` script`connect-src` 限制为 `http://localhost:*` + `https://*`
- **Pipeline 日志**: Debug 日志截断 + 仅记录 keys 不记录 values
### 环境变量
| 变量 | 用途 |
|------|------|
| `DB_PASSWORD` | 数据库密码 |
| `ZCLAW_DATABASE_URL` | 完整数据库连接 URL优先级最高 |
| `ZCLAW_SAAS_JWT_SECRET` | JWT 签名密钥 (>= 32 字符) |
| `ZCLAW_TOTP_ENCRYPTION_KEY` | TOTP/API Key 加密密钥 (64 hex) |
| `ZCLAW_ADMIN_USERNAME` | 初始管理员用户名 |
| `ZCLAW_ADMIN_PASSWORD` | 初始管理员密码 |
| `ZCLAW_SAAS_DEV` | 开发模式标志 (true=开发, false=生产) |
`saas-config.toml` 支持 `${ENV_VAR}` 模式环境变量插值。
### 生产环境清单
- [ ] nginx/caddy 配置反向代理 + HTTPS
- [ ] 确保设置 `ZCLAW_SAAS_DEV=false`(或不设置)
- [ ] 启用 CORS 白名单(`cors_origins` 配置实际域名)
- [ ] Cookie Secure=true + HttpOnly=true + SameSite=Strict
- [ ] JWT 签名密钥 >= 32 字符随机字符串
- [ ] `ZCLAW_TOTP_ENCRYPTION_KEY` 独立设置
- [ ] 数据库密码通过 `${DB_PASSWORD}` 引用
### 完整审计报告
参见 `docs/features/SECURITY_PENETRATION_TEST_V1.md`
***
<!-- ARCH-SNAPSHOT-START -->
<!-- 此区域由 auto-sync 自动更新,请勿手动编辑。更新时间: 2026-04-09 -->
## 13. 当前架构快照
### 活跃子系统
| 子系统 | 状态 | 最新变更 |
|--------|------|----------|
| 管家模式 (Butler) | ✅ 活跃 | 04-09 ButlerRouter + 双模式UI + 痛点持久化 + 冷启动 |
| Hermes 管线 | ✅ 活跃 | 04-09 4 Chunk: 自我改进+用户建模+NL Cron+轨迹压缩 (684 tests) |
| 聊天流 (ChatStream) | ✅ 稳定 | 04-02 ChatStore 拆分为 4 Store (stream/conversation/message/chat) |
| 记忆管道 (Memory) | ✅ 稳定 | 04-02 闭环修复: 对话→提取→FTS5+TF-IDF→检索→注入 |
| SaaS 认证 (Auth) | ✅ 稳定 | Token池 RPM/TPM 轮换 + JWT password_version 失效机制 |
| Pipeline DSL | ✅ 稳定 | 04-01 17 个 YAML 模板 + DAG 执行器 |
| Hands 系统 | ✅ 稳定 | 9 启用 (Browser/Collector/Researcher/Twitter/Whiteboard/Slideshow/Speech/Quiz/Clip) |
| 技能系统 (Skills) | ✅ 稳定 | 75 个 SKILL.md + 语义路由 |
| 中间件链 | ✅ 稳定 | 14 层 (含 DataMasking@90, ButlerRouter, TrajectoryRecorder@650) |
### 关键架构模式
- **Hermes 管线**: 4模块闭环 — ExperienceStore(FTS5经验存取) + UserProfiler(结构化用户画像) + NlScheduleParser(中文时间→cron) + TrajectoryRecorder+Compressor(轨迹记录压缩)。通过中间件链+intelligence hooks调用
- **管家模式**: 双模式UI (默认简洁/解锁专业) + ButlerRouter 4域关键词分类 (healthcare/data_report/policy/meeting) + 冷启动4阶段hook (idle→greeting→waiting→completed) + 痛点双写 (内存Vec+SQLite)
- **聊天流**: 3种实现 → GatewayClient(WebSocket) / KernelClient(Tauri Event) / SaaSRelay(SSE) + 5min超时守护。详见 [ARCHITECTURE_BRIEF.md](docs/ARCHITECTURE_BRIEF.md)
- **客户端路由**: `getClient()` 4分支决策树 → Admin路由 / SaaS Relay(可降级到本地) / Local Kernel / External Gateway
- **SaaS 认证**: JWT→OS keyring 存储 + HttpOnly cookie + Token池 RPM/TPM 限流轮换 + SaaS unreachable 自动降级
- **记忆闭环**: 对话→extraction_adapter→FTS5全文+TF-IDF权重→检索→注入系统提示
- **LLM 驱动**: 4 Rust Driver (Anthropic/OpenAI/Gemini/Local) + 国内兼容 (DeepSeek/Qwen/Moonshot 通过 base_url)
### 最近变更
1. [04-09] Hermes Intelligence Pipeline 4 Chunk: ExperienceStore+Extractor, UserProfileStore+Profiler, NlScheduleParser, TrajectoryRecorder+Compressor (684 tests, 0 failed)
2. [04-09] 管家模式6交付物完成: ButlerRouter + 冷启动 + 简洁模式UI + 桥测试 + 发布文档
3. [04-08] 侧边栏 AnimatePresence bug + TopBar 重复 Z 修复 + 发布评估报告
3. [04-07] @reserved 标注 5 个 butler Tauri 命令 + 痛点持久化 SQLite
4. [04-06] 4 个发布前 bug 修复 (身份覆盖/模型配置/agent同步/自动身份)
<!-- ARCH-SNAPSHOT-END -->
<!-- ANTI-PATTERN-START -->
<!-- 此区域由 auto-sync 自动更新,请勿手动编辑。更新时间: 2026-04-09 -->
## 14. AI 协作注意事项
### 反模式警告
- ❌ **不要**建议新增 SaaS API 端点 — 已有 140 个,稳定化约束禁止新增
- ❌ **不要**忽略管家模式 — 已上线且为默认模式,所有聊天经过 ButlerRouter
- ❌ **不要**假设 Tauri 直连 LLM — 实际通过 SaaS Token 池中转SaaS unreachable 时降级到本地 Kernel
- ❌ **不要**建议从零实现已有能力 — 先查 Hand(9个)/Skill(75个)/Pipeline(17模板) 现有库
- ❌ **不要**在 CLAUDE.md 以外创建项目级配置或规则文件 — 单一入口原则
### 场景化指令
- 当遇到**聊天相关** → 记住有 3 种 ChatStream 实现,先用 `getClient()` 判断当前路由模式
- 当遇到**认证相关** → 记住 Tauri 模式用 OS keyring 存 JWTSaaS 模式用 HttpOnly cookie
- 当遇到**新功能建议** → 先查 [TRUTH.md](docs/TRUTH.md) 确认可用能力清单,避免重复建设
- 当遇到**记忆/上下文相关** → 记住闭环已接通: FTS5+TF-IDF+embedding不是空壳
- 当遇到**管家/Butler** → 管家模式是默认模式ButlerRouter 在中间件链中做关键词分类+system prompt 增强
<!-- ANTI-PATTERN-END -->

485
CLAUDE.md Normal file
View File

@@ -0,0 +1,485 @@
# ERP 平台底座 — 协作与实现规则
> **ERP Platform Base** 是一个模块化的商业 SaaS ERP 底座,目标是提供核心基础设施(身份权限、工作流、消息、配置),使行业业务模块(进销存、生产、财务等)可以快速插接。
> **当前阶段: Phase 1 基础设施搭建。** 从零构建 Rust workspace + Web 前端 + 核心共享层。
## 1. 项目定位
### 1.1 这是什么
一个 **底座 + 行业插件** 架构的商业 SaaS ERP 平台:
- **全功能底座** — 身份权限、工作流引擎、消息中心、系统配置
- **渐进式扩展** — 从小微企业起步,逐步支持中大型企业
- **多租户 + 私有化** — 默认 SaaS 共享数据库隔离,支持独立 schema 私有部署
- **Web 优先** — 浏览器 SPA 是主力,可选 Tauri 桌面端用于特定行业场景
### 1.2 决策原则
**任何改动都要问:这对 ERP 底座的模块化和可扩展性有帮助吗?**
- ✅ 完善模块接口和 trait 定义 → 最高优先
- ✅ 确保多租户隔离的正确性 → 最高优先
- ✅ 按计划推进 Phase 交付物 → 高优先
- ✅ 清晰的模块边界和事件契约 → 高优先
- ❌ 跳过 Phase 顺序提前实现远期功能 → 禁止
- ❌ 在模块间创建直接耦合 → 永远不做
- ❌ 硬编码租户 ID 或绕过多租户中间件 → 永远不做
- ❌ 过度设计未来才需要的能力 → 永远不做
### 1.3 架构铁律
| 约束 | 原因 |
|------|------|
| 模块间只通过事件总线和 trait 通信 | 保证模块可独立拆分为微服务 |
| 所有数据表必须含 `tenant_id` | 多租户是核心能力,不可事后补 |
| 使用 UUID v7 作为主键 | 时间排序 + 唯一性,分布式友好 |
| 软删除,不硬删除 | ERP 数据不可丢失,审计追溯需要 |
| 所有 API 使用 `/api/v1/` 前缀 | 版本化是 SaaS 产品的基本要求 |
---
## 2. 项目结构
```text
erp/
├── crates/ # Rust Workspace
│ ├── erp-core/ # L1: 基础类型、错误、事件、模块 trait
│ ├── erp-common/ # L1: 共享工具、宏
│ ├── erp-auth/ # L2: 身份与权限模块
│ ├── erp-workflow/ # L2: 工作流引擎模块
│ ├── erp-message/ # L2: 消息中心模块
│ ├── erp-config/ # L2: 系统配置模块
│ └── erp-server/ # L3: Axum 服务入口,组装所有模块
│ └── migration/ # SeaORM 数据库迁移
├── apps/
│ └── web/ # Vite + React 18 SPA (主力前端)
├── packages/
│ └── ui-components/ # React 共享组件库
├── desktop/ # (可选) Tauri 桌面端,行业需要时启用
├── docker/ # Docker 开发环境配置
├── docs/ # 文档
│ └── superpowers/
│ ├── specs/ # 设计规格文档
│ └── plans/ # 实施计划
├── Cargo.toml # Workspace root
└── CLAUDE.md # 本文件 — 协作规则
```
### 2.1 Crate 依赖关系
```text
erp-core (无业务依赖)
erp-common (无业务依赖)
erp-auth (→ core)
erp-config (→ core)
erp-workflow (→ core)
erp-message (→ core)
erp-server (→ 所有 crate组装入口)
```
**规则:**
- `erp-core``erp-common` 不依赖任何业务 crate
- 业务 crate 之间**禁止**直接依赖,只通过事件总线和 `erp-core` trait 通信
- `erp-server` 是唯一的组装点
### 2.2 技术栈
| 层级 | 技术 |
|------|------|
| 后端框架 | Axum 0.8 + Tokio |
| ORM | SeaORM (异步、类型安全) |
| 数据库 | PostgreSQL 16+ |
| 缓存 | Redis 7+ |
| 前端框架 | React 18 + TypeScript (Vite) |
| UI 组件库 | Ant Design 5 |
| 状态管理 | Zustand |
| 路由 | React Router 7 |
| 样式 | TailwindCSS + CSS Variables |
| API 文档 | utoipa (OpenAPI 3) |
---
## 3. 工作风格
### 3.1 按计划推进
- **严格按 Phase 顺序执行** — Phase 2 依赖 Phase 1 的基础设施
- **每个 Task 完成后立即提交** — 不积压,保持可追溯
- **先测试后实现** — TDD 流程:写失败测试 → 实现 → 通过 → 提交
### 3.2 模块化思维
开发任何功能时先问:
1. **它属于哪个模块?** — 不确定就放到 `erp-core` 共享层
2. **它的接口是什么?** — 先定义 trait再实现
3. **它需要发什么事件?** — 跨模块通知必须走事件总线
4. **其他模块怎么发现它?** — 通过 `ErpModule` trait 注册
### 3.3 闭环工作法(强制)
每次改动**必须**按顺序完成以下步骤,不允许跳过:
1. **理解需求** — 确认改动的目标模块和影响范围
2. **最小实现** — 只改必要的代码,保持模块边界
3. **自动验证**`cargo check` / `cargo test` / `pnpm dev` 必须通过
4. **提交** — 按 §10 规范提交
5. **文档同步** — 更新相关文档(如果涉及架构变化)
**铁律:步骤 4 是任务完成的硬性条件。不允许"等一下再提交"。**
---
## 4. 实现规则
### 4.1 错误处理
- **跨 crate 边界**:使用 `thiserror` 定义类型化错误,转换为 `AppError`
- **crate 内部**:可以使用 `anyhow`,但**永远不**跨越 crate 边界
- **数据库错误**:通过 `From<sea_orm::DbErr>` 自动转换为 `AppError`
- **验证错误**:包含字段级详情,方便 UI 渲染
### 4.2 数据库操作
- 所有 SeaORM Entity 必须包含:`id`, `tenant_id`, `created_at`, `updated_at`, `created_by`, `updated_by`, `deleted_at`, `version`
- 查询时**始终**带 `tenant_id` 过滤(中间件自动注入)
- 更新时检查 `version` 字段实现乐观锁
- 删除使用软删除(设置 `deleted_at`
### 4.3 API 设计
- 所有端点使用 `/api/v1/` 前缀
- 响应统一使用 `ApiResponse<T>` 包装
- 分页使用 `Pagination` + `PaginatedResponse<T>`
- utoipa 自动生成 OpenAPI 文档
- 租户 ID 从 JWT 中间件注入,**不在** API 路径中传递(管理员接口除外)
### 4.4 事件总线
- 模块间通信**只能**通过 `EventBus`
- 事件必须持久化到 `domain_events`outbox 模式)
- 事件处理失败记录到 dead-letter 存储
- 事件类型命名:`{模块}.{动作}``user.created`, `workflow.task.completed`
### 4.5 Rust 代码规范
```rust
// 命名snake_case (函数/变量), PascalCase (类型/trait), SCREAMING_SNAKE (常量)
// 模块公开接口通过 lib.rs 统一导出
// 每个 public 函数和 trait 必须有文档注释
// 异步函数返回 Result 时使用 AppResult<T> 类型别名
// 数据库操作使用 SeaORM 的 Entity + Model + Relation 模式
```
### 4.6 TypeScript / React 代码规范
```typescript
// 避免 any优先 unknown + 类型守卫
// 函数组件 + hooks
// 复杂状态收敛到 Zustand store
// API 调用封装到独立的 service 层,不在组件中直接 fetch
// 使用 Ant Design 组件,不自行实现已有组件
// 国际化文案使用 i18n key不硬编码中文
```
---
## 5. 模块开发规范
### 5.1 新建业务模块清单
每个新模块(如 erp-auth, erp-workflow**必须**包含:
1. `Cargo.toml` — 依赖 `erp-core`
2. `src/lib.rs` — 模块入口,实现 `ErpModule` trait
3. `src/error.rs` — 模块错误类型wrap `AppError`
4. `src/entity/` — SeaORM Entity 定义
5. `src/service/` — 业务逻辑层
6. `src/handler/` — Axum 路由处理器
7. `src/event.rs` — 模块事件定义和处理器
### 5.2 ErpModule trait 实现
```rust
pub struct AuthModule;
impl ErpModule for AuthModule {
fn name(&self) -> &str { "auth" }
fn version(&self) -> &str { env!("CARGO_PKG_VERSION") }
fn dependencies(&self) -> Vec<&str> { vec![] } // auth 是基础模块,无依赖
fn register_routes(&self, router: Router) -> Router {
router.nest("/api/v1", auth_routes())
}
fn register_event_handlers(&self, bus: &EventBus) {
// 订阅其他模块的事件
}
async fn on_tenant_created(&self, tenant_id: Uuid) -> AppResult<()> {
// 初始化默认角色、管理员等
Ok(())
}
}
```
### 5.3 数据库迁移
- 迁移文件放在 `crates/erp-server/migration/src/`
- 命名格式:`m{YYYYMMDD}_{6位序号}_{描述}.rs`
- 迁移必须可回滚(实现 `down` 方法)
- 新增表必须包含所有标准字段§1.3
- 迁移必须幂等(使用 `if_not_exists`
---
## 6. 测试与验证
### 6.1 测试要求
| 测试类型 | 覆盖目标 | 工具 |
|----------|---------|------|
| 单元测试 | 每个 service 函数 | `#[cfg(test)]` + `tokio::test` |
| 集成测试 | API 端点 → 数据库 | `cargo test` + 真实 PostgreSQL |
| 多租户测试 | 数据隔离验证 | 独立测试 crate |
| 前端测试 | 组件交互 | Vitest (未来) |
### 6.2 验证命令
```bash
# Rust 编译检查
cargo check
# Rust 全量测试
cargo test --workspace
# 后端服务启动
cd crates/erp-server && cargo run
# Docker 环境
cd docker && docker compose up -d
# 桌面端开发
cd apps/desktop && pnpm tauri dev
# 数据库迁移检查
docker exec erp-postgres psql -U erp -c "\dt"
```
### 6.3 Phase 完成标准
每个 Phase 完成时必须满足:
- [ ] `cargo check` 全 workspace 通过
- [ ] `cargo test` 全部通过
- [ ] Docker 环境正常启动
- [ ] 所有迁移可正/反向执行
- [ ] API 端点可通过 Swagger UI 测试
- [ ] 桌面端可正常启动并展示对应 UI
- [ ] 所有代码已提交
---
## 7. 安全注意事项
### 7.1 认证安全
- **密码存储**: Argon2 哈希,禁止明文
- **JWT**: access token 15min + refresh token 7d
- **Refresh Token 轮换**: 每次使用后签发新的,旧的作废
- **Token 存储**: 桌面端使用 Tauri secure store
- **密码修改**: 使所有已签发的 JWT 失效
### 7.2 多租户安全
- **中间件注入**: `tenant_id` 从 JWT 中提取,应用层不可伪造
- **数据隔离**: 所有查询自动过滤 `tenant_id`
- **越权防护**: 禁止跨租户数据访问
- **租户 provisioning**: `on_tenant_created` 钩子初始化数据
### 7.3 通用安全
- **不硬编码密钥** — 使用环境变量或配置文件
- **用户输入验证** — 所有 API 端点验证输入
- **SQL 注入防护** — SeaORM 参数化查询
- **限流** — Redis token bucket登录等敏感接口限流
- **CORS** — 白名单制,默认拒绝
- **审计日志** — 所有关键操作记录变更前后状态
---
## 8. 桌面端 UI 规范
### 8.1 布局结构
经典 SaaS 后台管理布局(响应式,支持移动端):
```
┌─────────────────────────────────────────────┐
│ LOGO 搜索... 🔔 5 👤 Admin ▾ │ ← 顶部导航栏
├─────────┬───────────────────────────────────┤
│ 📊 首页 │ │
│ 👥 用户 │ 主内容区域 │
│ 🔐 权限 │ (多标签页切换) │
│ 📋 流程 │ │
│ 💬 消息 │ │
│ ⚙️ 设置 │ │
│─────────│ │
│ 📦 进销存│ │
│ 🏭 生产 │ │
│ 💰 财务 │ │
│─────────│ │
│ ▸ 更多 │ │
└─────────┴───────────────────────────────────┘
```
### 8.2 UI 规则
- 使用 Ant Design 组件库,不自造轮子
- 中文优先,所有文案通过 i18n key 引用
- 支持暗色/亮色主题切换
- 侧边栏按模块分组:基础模块 / 行业模块
- 表单验证使用 Ant Design Form 的 validateRules
---
## 9. 常用命令
```bash
# === Rust ===
cargo check # 编译检查
cargo test --workspace # 运行所有测试
cargo run -p erp-server # 启动后端服务
cargo fmt --check # 检查格式
cargo clippy -- -D warnings # Lint 检查
# === Docker ===
cd docker && docker compose up -d # 启动 PostgreSQL + Redis
docker compose -f docker/docker-compose.yml ps # 查看服务状态
docker compose -f docker/docker-compose.yml down # 停止服务
# === 前端 ===
cd apps/web && pnpm install # 安装依赖
cd apps/web && pnpm dev # 开发模式
cd apps/web && pnpm build # 构建生产版本
# === 数据库 ===
docker exec -it erp-postgres psql -U erp # 连接数据库
```
---
## 10. 提交规范
```
<type>(<scope>): <description>
```
**类型:**
- `feat` — 新功能
- `fix` — 修复问题
- `refactor` — 重构
- `docs` — 文档更新
- `test` — 测试相关
- `chore` — 杂项(构建、配置等)
- `perf` — 性能优化
**Scope 对应 crate 或模块名:**
| scope | 范围 |
|-------|------|
| `core` | erp-core |
| `common` | erp-common |
| `auth` | erp-auth |
| `workflow` | erp-workflow |
| `message` | erp-message |
| `config` | erp-config |
| `server` | erp-server |
| `web` | Web 前端 |
| `ui` | React 组件 |
| `db` | 数据库迁移 |
| `docker` | Docker 配置 |
**示例:**
```
feat(auth): 添加用户管理 CRUD
feat(core): 实现事件总线和模块注册
fix(server): 修复数据库连接池配置
refactor(auth): 拆分 RBAC 和 ABAC 权限模型
chore(docker): 添加 PostgreSQL 健康检查
```
---
## 11. 设计文档索引
| 文档 | 说明 |
|------|------|
| `docs/superpowers/specs/2026-04-10-erp-platform-base-design.md` | 平台底座设计规格 |
| `docs/superpowers/plans/2026-04-10-erp-platform-base-plan.md` | 平台底座实施计划 |
所有设计决策以设计规格文档为准。实施计划按阶段拆分,每阶段开始前细化。
---
<!-- ARCH-SNAPSHOT-START -->
<!-- 此区域随开发进度更新 -->
## 12. 当前架构快照
### 开发进度
| Phase | 内容 | 状态 |
|-------|------|------|
| Phase 1 | 基础设施 (workspace + core + Docker + 桌面端) | 🚧 进行中 |
| Phase 2 | 身份与权限 (Auth) | ⏳ 待开始 |
| Phase 3 | 系统配置 (Config) | ⏳ 待开始 |
| Phase 4 | 工作流引擎 (Workflow) | ⏳ 待开始 |
| Phase 5 | 消息中心 (Message) | ⏳ 待开始 |
| Phase 6 | 整合与打磨 | ⏳ 待开始 |
### 已实现模块
| Crate | 功能 | 状态 |
|-------|------|------|
| erp-core | 错误类型、共享类型、事件总线、ErpModule trait | 🚧 进行中 |
| erp-common | 共享工具 | 🚧 进行中 |
| erp-server | Axum 服务入口、配置、数据库连接 | 🚧 进行中 |
| erp-auth | 身份与权限 | ⏳ 待开始 |
| erp-workflow | 工作流引擎 | ⏳ 待开始 |
| erp-message | 消息中心 | ⏳ 待开始 |
| erp-config | 系统配置 | ⏳ 待开始 |
<!-- ARCH-SNAPSHOT-END -->
---
<!-- ANTI-PATTERN-START -->
## 13. 反模式警告
- ❌ **不要**在业务 crate 之间创建直接依赖 — 只通过事件和 trait 通信
- ❌ **不要**跳过多租户中间件 — 所有数据操作必须带 `tenant_id` 过滤
- ❌ **不要**硬编码配置值 — 使用 config.toml + 环境变量
- ❌ **不要**跳过迁移直接建表 — 所有 schema 变更通过 SeaORM Migration
- ❌ **不要**在前端组件中直接调用 HTTP — 封装到 service 层
- ❌ **不要**使用 `anyhow` 跨越 crate 边界 — 内部可用,对外必须转 `AppError`
- ❌ **不要**假设只有单租户 — 从第一天就按多租户设计
- ❌ **不要**提前实现远期功能 — 严格按 Phase 计划推进
- ❌ **不要**忽略 `version` 字段 — 所有更新操作必须检查乐观锁
### 场景化指令
- 当遇到**新增模块** → 实现 `ErpModule` trait`erp-server` 注册
- 当遇到**跨模块通信** → 定义事件类型,通过 `EventBus` 发布/订阅
- 当遇到**数据查询** → 确保包含 `tenant_id` 过滤,检查软删除条件
- 当遇到**新增 API** → 添加 utoipa 注解,确保 OpenAPI 文档同步
- 当遇到**新增表** → 创建 SeaORM migration + Entity包含所有标准字段
- 当遇到**新增页面** → 使用 Ant Design 组件i18n key 引用文案
<!-- ANTI-PATTERN-END -->

4069
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

74
Cargo.toml Normal file
View File

@@ -0,0 +1,74 @@
[workspace]
resolver = "2"
members = [
"crates/erp-core",
"crates/erp-common",
"crates/erp-server",
"crates/erp-auth",
"crates/erp-workflow",
"crates/erp-message",
"crates/erp-config",
]
[workspace.package]
version = "0.1.0"
edition = "2024"
license = "MIT"
[workspace.dependencies]
# Async
tokio = { version = "1", features = ["full"] }
# Web
axum = "0.8"
tower = "0.5"
tower-http = { version = "0.6", features = ["cors", "trace", "compression-gzip"] }
# Database
sea-orm = { version = "1.1", features = [
"sqlx-postgres", "runtime-tokio-rustls", "macros", "with-uuid", "with-chrono", "with-json"
] }
sea-orm-migration = { version = "1.1", features = ["sqlx-postgres", "runtime-tokio-rustls"] }
# Serialization
serde = { version = "1", features = ["derive"] }
serde_json = "1"
# UUID & Time
uuid = { version = "1", features = ["v7", "serde"] }
chrono = { version = "0.4", features = ["serde"] }
# Error handling
thiserror = "2"
anyhow = "1"
# Logging
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
# Config
config = "0.14"
# Redis
redis = { version = "0.27", features = ["tokio-comp", "connection-manager"] }
# JWT
jsonwebtoken = "9"
# Password hashing
argon2 = "0.5"
# API docs
utoipa = { version = "5", features = ["axum_extras", "uuid", "chrono"] }
utoipa-swagger-ui = { version = "8", features = ["axum"] }
# Validation
validator = { version = "0.19", features = ["derive"] }
# Internal crates
erp-core = { path = "crates/erp-core" }
erp-common = { path = "crates/erp-common" }
erp-auth = { path = "crates/erp-auth" }
erp-workflow = { path = "crates/erp-workflow" }
erp-message = { path = "crates/erp-message" }
erp-config = { path = "crates/erp-config" }

View File

@@ -0,0 +1,16 @@
[package]
name = "erp-auth"
version.workspace = true
edition.workspace = true
[dependencies]
erp-core.workspace = true
tokio.workspace = true
serde.workspace = true
serde_json.workspace = true
uuid.workspace = true
chrono.workspace = true
axum.workspace = true
sea-orm.workspace = true
tracing.workspace = true
anyhow.workspace = true

View File

@@ -0,0 +1 @@
// erp-auth: 身份与权限模块 (Phase 2)

View File

@@ -0,0 +1,11 @@
[package]
name = "erp-common"
version.workspace = true
edition.workspace = true
[dependencies]
uuid.workspace = true
chrono.workspace = true
serde.workspace = true
serde_json.workspace = true
tracing.workspace = true

View File

@@ -0,0 +1 @@
pub mod utils;

View File

@@ -0,0 +1 @@
/// Shared utility functions for the ERP platform.

View File

@@ -0,0 +1,16 @@
[package]
name = "erp-config"
version.workspace = true
edition.workspace = true
[dependencies]
erp-core.workspace = true
tokio.workspace = true
serde.workspace = true
serde_json.workspace = true
uuid.workspace = true
chrono.workspace = true
axum.workspace = true
sea-orm.workspace = true
tracing.workspace = true
anyhow.workspace = true

View File

@@ -0,0 +1 @@
// erp-config: 系统配置模块 (Phase 3)

View File

@@ -0,0 +1,16 @@
[package]
name = "erp-core"
version.workspace = true
edition.workspace = true
[dependencies]
tokio.workspace = true
serde.workspace = true
serde_json.workspace = true
uuid.workspace = true
chrono.workspace = true
thiserror.workspace = true
anyhow.workspace = true
tracing.workspace = true
axum.workspace = true
sea-orm.workspace = true

View File

@@ -0,0 +1,78 @@
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum::Json;
use serde::Serialize;
/// 统一错误响应格式
#[derive(Debug, Serialize)]
pub struct ErrorResponse {
pub error: String,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub details: Option<serde_json::Value>,
}
/// 平台级错误类型
#[derive(Debug, thiserror::Error)]
pub enum AppError {
#[error("资源未找到: {0}")]
NotFound(String),
#[error("验证失败: {0}")]
Validation(String),
#[error("未授权")]
Unauthorized,
#[error("禁止访问: {0}")]
Forbidden(String),
#[error("冲突: {0}")]
Conflict(String),
#[error("内部错误: {0}")]
Internal(String),
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, message) = match &self {
AppError::NotFound(_) => (StatusCode::NOT_FOUND, self.to_string()),
AppError::Validation(_) => (StatusCode::BAD_REQUEST, self.to_string()),
AppError::Unauthorized => (StatusCode::UNAUTHORIZED, "未授权".to_string()),
AppError::Forbidden(_) => (StatusCode::FORBIDDEN, self.to_string()),
AppError::Conflict(_) => (StatusCode::CONFLICT, self.to_string()),
AppError::Internal(_) => (StatusCode::INTERNAL_SERVER_ERROR, "内部错误".to_string()),
};
let body = ErrorResponse {
error: status.canonical_reason().unwrap_or("Error").to_string(),
message,
details: None,
};
(status, Json(body)).into_response()
}
}
impl From<anyhow::Error> for AppError {
fn from(err: anyhow::Error) -> Self {
AppError::Internal(err.to_string())
}
}
impl From<sea_orm::DbErr> for AppError {
fn from(err: sea_orm::DbErr) -> Self {
match err {
sea_orm::DbErr::RecordNotFound(msg) => AppError::NotFound(msg),
sea_orm::DbErr::Query(sea_orm::RuntimeErr::SqlxError(e))
if e.to_string().contains("duplicate key") =>
{
AppError::Conflict("记录已存在".to_string())
}
_ => AppError::Internal(err.to_string()),
}
}
}
pub type AppResult<T> = Result<T, AppError>;

View File

@@ -0,0 +1,61 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use tokio::sync::broadcast;
use tracing::{error, info};
use uuid::Uuid;
/// 领域事件
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DomainEvent {
pub id: Uuid,
pub event_type: String,
pub tenant_id: Uuid,
pub payload: serde_json::Value,
pub timestamp: DateTime<Utc>,
pub correlation_id: Uuid,
}
impl DomainEvent {
pub fn new(event_type: impl Into<String>, tenant_id: Uuid, payload: serde_json::Value) -> Self {
Self {
id: Uuid::now_v7(),
event_type: event_type.into(),
tenant_id,
payload,
timestamp: Utc::now(),
correlation_id: Uuid::now_v7(),
}
}
}
/// 事件处理器 trait
pub trait EventHandler: Send + Sync {
fn event_types(&self) -> Vec<String>;
fn handle(&self, event: &DomainEvent) -> impl std::future::Future<Output = anyhow::Result<()>> + Send;
}
/// 进程内事件总线
#[derive(Clone)]
pub struct EventBus {
sender: broadcast::Sender<DomainEvent>,
}
impl EventBus {
pub fn new(capacity: usize) -> Self {
let (sender, _) = broadcast::channel(capacity);
Self { sender }
}
/// 发布事件
pub fn publish(&self, event: DomainEvent) {
info!(event_type = %event.event_type, event_id = %event.id, "Event published");
if let Err(e) = self.sender.send(event) {
error!("Failed to publish event: {}", e);
}
}
/// 订阅所有事件,返回接收端
pub fn subscribe(&self) -> broadcast::Receiver<DomainEvent> {
self.sender.subscribe()
}
}

View File

@@ -0,0 +1,4 @@
pub mod error;
pub mod events;
pub mod module;
pub mod types;

View File

@@ -0,0 +1,76 @@
use axum::Router;
use uuid::Uuid;
use crate::error::AppResult;
use crate::events::EventBus;
/// 模块注册接口
/// 所有业务模块Auth, Workflow, Message, Config, 行业模块)都实现此 trait
pub trait ErpModule: Send + Sync {
/// 模块名称(唯一标识)
fn name(&self) -> &str;
/// 模块版本
fn version(&self) -> &str {
env!("CARGO_PKG_VERSION")
}
/// 依赖的其他模块名称
fn dependencies(&self) -> Vec<&str> {
vec![]
}
/// 注册 Axum 路由
fn register_routes(&self, router: Router) -> Router;
/// 注册事件处理器
fn register_event_handlers(&self, _bus: &EventBus) {}
/// 租户创建时的初始化钩子
fn on_tenant_created(
&self,
_tenant_id: Uuid,
) -> impl std::future::Future<Output = AppResult<()>> + Send {
async { Ok(()) }
}
/// 租户删除时的清理钩子
fn on_tenant_deleted(
&self,
_tenant_id: Uuid,
) -> impl std::future::Future<Output = AppResult<()>> + Send {
async { Ok(()) }
}
}
/// 模块注册器
pub struct ModuleRegistry {
modules: Vec<Box<dyn ErpModule>>,
}
impl ModuleRegistry {
pub fn new() -> Self {
Self { modules: vec![] }
}
pub fn register(&mut self, module: Box<dyn ErpModule>) {
tracing::info!(module = module.name(), version = module.version(), "Module registered");
self.modules.push(module);
}
pub fn build_router(&self, base: Router) -> Router {
self.modules
.iter()
.fold(base, |router, m| m.register_routes(router))
}
pub fn register_handlers(&self, bus: &EventBus) {
for module in &self.modules {
module.register_event_handlers(bus);
}
}
pub fn modules(&self) -> &[Box<dyn ErpModule>] {
&self.modules
}
}

View File

@@ -0,0 +1,70 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
/// 所有数据库实体的公共字段
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BaseFields {
pub id: Uuid,
pub tenant_id: Uuid,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub created_by: Uuid,
pub updated_by: Uuid,
pub deleted_at: Option<DateTime<Utc>>,
pub version: i32,
}
/// 分页请求
#[derive(Debug, Deserialize)]
pub struct Pagination {
pub page: Option<u64>,
pub page_size: Option<u64>,
}
impl Pagination {
pub fn offset(&self) -> u64 {
(self.page.unwrap_or(1).saturating_sub(1)) * self.limit()
}
pub fn limit(&self) -> u64 {
self.page_size.unwrap_or(20).min(100)
}
}
/// 分页响应
#[derive(Debug, Serialize)]
pub struct PaginatedResponse<T> {
pub data: Vec<T>,
pub total: u64,
pub page: u64,
pub page_size: u64,
pub total_pages: u64,
}
/// API 统一响应
#[derive(Debug, Serialize)]
pub struct ApiResponse<T: Serialize> {
pub success: bool,
pub data: Option<T>,
pub message: Option<String>,
}
impl<T: Serialize> ApiResponse<T> {
pub fn ok(data: T) -> Self {
Self {
success: true,
data: Some(data),
message: None,
}
}
}
/// 租户上下文(中间件注入)
#[derive(Debug, Clone)]
pub struct TenantContext {
pub tenant_id: Uuid,
pub user_id: Uuid,
pub roles: Vec<String>,
pub permissions: Vec<String>,
}

View File

@@ -0,0 +1,16 @@
[package]
name = "erp-message"
version.workspace = true
edition.workspace = true
[dependencies]
erp-core.workspace = true
tokio.workspace = true
serde.workspace = true
serde_json.workspace = true
uuid.workspace = true
chrono.workspace = true
axum.workspace = true
sea-orm.workspace = true
tracing.workspace = true
anyhow.workspace = true

View File

@@ -0,0 +1 @@
// erp-message: 消息中心模块 (Phase 5)

View File

@@ -0,0 +1,25 @@
[package]
name = "erp-server"
version.workspace = true
edition.workspace = true
[[bin]]
name = "erp-server"
path = "src/main.rs"
[dependencies]
erp-core.workspace = true
erp-common.workspace = true
tokio.workspace = true
axum.workspace = true
tower.workspace = true
tower-http.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
config.workspace = true
sea-orm.workspace = true
redis.workspace = true
utoipa.workspace = true
utoipa-swagger-ui.workspace = true
serde_json.workspace = true
serde.workspace = true

View File

@@ -0,0 +1,19 @@
[server]
host = "0.0.0.0"
port = 3000
[database]
url = "postgres://erp:erp_dev_2024@localhost:5432/erp"
max_connections = 20
min_connections = 5
[redis]
url = "redis://localhost:6379"
[jwt]
secret = "change-me-in-production"
access_token_ttl = "15m"
refresh_token_ttl = "7d"
[log]
level = "info"

View File

@@ -0,0 +1,50 @@
use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct AppConfig {
pub server: ServerConfig,
pub database: DatabaseConfig,
pub redis: RedisConfig,
pub jwt: JwtConfig,
pub log: LogConfig,
}
#[derive(Debug, Deserialize)]
pub struct ServerConfig {
pub host: String,
pub port: u16,
}
#[derive(Debug, Deserialize)]
pub struct DatabaseConfig {
pub url: String,
pub max_connections: u32,
pub min_connections: u32,
}
#[derive(Debug, Deserialize)]
pub struct RedisConfig {
pub url: String,
}
#[derive(Debug, Deserialize)]
pub struct JwtConfig {
pub secret: String,
pub access_token_ttl: String,
pub refresh_token_ttl: String,
}
#[derive(Debug, Deserialize)]
pub struct LogConfig {
pub level: String,
}
impl AppConfig {
pub fn load() -> anyhow::Result<Self> {
let config = config::Config::builder()
.add_source(config::File::with_name("config/default"))
.add_source(config::Environment::with_prefix("ERP").separator("__"))
.build()?;
Ok(config.try_deserialize()?)
}
}

View File

@@ -0,0 +1,16 @@
use sea_orm::{ConnectOptions, Database, DatabaseConnection};
use std::time::Duration;
use crate::config::DatabaseConfig;
pub async fn connect(config: &DatabaseConfig) -> anyhow::Result<DatabaseConnection> {
let mut opt = ConnectOptions::new(&config.url);
opt.max_connections(config.max_connections)
.min_connections(config.min_connections)
.connect_timeout(Duration::from_secs(10))
.idle_timeout(Duration::from_secs(600));
let db = Database::connect(opt).await?;
tracing::info!("Database connected successfully");
Ok(db)
}

View File

@@ -0,0 +1,41 @@
mod config;
mod db;
use axum::Router;
use config::AppConfig;
use tracing_subscriber::EnvFilter;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Load config
let config = AppConfig::load()?;
// Initialize tracing
tracing_subscriber::fmt()
.with_env_filter(
EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new(&config.log.level)),
)
.json()
.init();
tracing::info!("ERP Server starting...");
// Connect to database
let db = db::connect(&config.database).await?;
// Connect to Redis
let _redis_client = redis::Client::open(&config.redis.url[..])?;
tracing::info!("Redis client created");
// Build app
let app = Router::new()
.fallback(|| async { axum::Json(serde_json::json!({"error": "Not found"})) });
let addr = format!("{}:{}", config.server.host, config.server.port);
let listener = tokio::net::TcpListener::bind(&addr).await?;
tracing::info!("Server listening on {}", addr);
axum::serve(listener, app).await?;
Ok(())
}

View File

@@ -0,0 +1,16 @@
[package]
name = "erp-workflow"
version.workspace = true
edition.workspace = true
[dependencies]
erp-core.workspace = true
tokio.workspace = true
serde.workspace = true
serde_json.workspace = true
uuid.workspace = true
chrono.workspace = true
axum.workspace = true
sea-orm.workspace = true
tracing.workspace = true
anyhow.workspace = true

View File

@@ -0,0 +1 @@
// erp-workflow: 工作流引擎模块 (Phase 4)

5
docker/.env.example Normal file
View File

@@ -0,0 +1,5 @@
POSTGRES_USER=erp
POSTGRES_PASSWORD=erp_dev_2024
POSTGRES_DB=erp
POSTGRES_PORT=5432
REDIS_PORT=6379

36
docker/docker-compose.yml Normal file
View File

@@ -0,0 +1,36 @@
version: "3.8"
services:
postgres:
image: postgres:16-alpine
container_name: erp-postgres
environment:
POSTGRES_USER: ${POSTGRES_USER:-erp}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-erp_dev_2024}
POSTGRES_DB: ${POSTGRES_DB:-erp}
ports:
- "${POSTGRES_PORT:-5432}:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-erp}"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
container_name: erp-redis
ports:
- "${REDIS_PORT:-6379}:6379"
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 5
volumes:
postgres_data:
redis_data:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,618 @@
# ERP Platform Base - Design Specification
**Date:** 2026-04-10
**Status:** Draft (Review Round 2)
**Author:** Claude + User
---
## Context
Build a commercial SaaS ERP product from scratch using a "platform base + industry plugins" architecture. The base provides core infrastructure (auth, workflow, messaging, configuration), enabling rapid deployment of industry-specific modules (inventory, manufacturing, finance, HR, etc.) on top.
The system targets progressive scaling: start with small businesses, expand to mid and large enterprises. Multi-tenant SaaS deployment is the default, with private deployment as an option.
---
## Architecture
### Overall: Modular Monolith (Progressive)
Start as a single Rust backend service with well-defined module boundaries. Modules communicate through an internal event bus and shared traits. When a module needs independent scaling, it can be extracted into a standalone service without changing interfaces.
### System Layers
```
Web Frontend (Vite + React 18 + Ant Design 5)
├── Shell / Layout / Navigation
└── Module UI (dynamically loaded per tenant config)
API Layer (REST + WebSocket)
Rust Backend Service (Axum + Tokio)
├── Auth Module (identity, roles, permissions, tenants)
├── Workflow Engine (BPMN processes, tasks, approvals)
├── Message Center (notifications, templates, channels)
└── Config Module (menus, dictionaries, settings, numbering)
Core Shared Layer (tenant context, audit, events, caching)
PostgreSQL (primary) + Redis (cache + session + pub/sub)
```
> **Note:** Tauri 桌面端为可选方案,未来行业模块(如工厂仓库)需要硬件集成时启用。主力前端为 Web SPA。
### Design Principles
1. **Module isolation**: Each business module is an independent Rust crate, interfaces defined via traits
2. **Multi-tenant built-in**: All data tables include `tenant_id`, middleware auto-injects tenant context
3. **Event-driven**: Modules communicate via event bus, no direct coupling
4. **Plugin extensibility**: Industry modules register through standard interfaces, support dynamic enable/disable
### Error Handling Strategy
```rust
// erp-core defines the unified error hierarchy
// Uses thiserror for typed errors across crate boundaries
#[derive(Debug, thiserror::Error)]
pub enum AppError {
#[error("Not found: {0}")]
NotFound(String),
#[error("Validation error: {0}")]
Validation(String),
#[error("Unauthorized")]
Unauthorized,
#[error("Forbidden: {0}")]
Forbidden(String),
#[error("Conflict: {0}")]
Conflict(String),
#[error("Internal error: {0}")]
Internal(String),
}
// Axum IntoResponse impl maps to HTTP status codes
// Validation errors include field-level detail for UI rendering
```
**Decision**: `thiserror` for crate boundaries (typed, catchable), `anyhow` never crosses crate boundaries (internal use only for prototyping). Each module defines its own error variants that wrap `AppError`.
### Event Bus Specification
```
EventBus (tokio::sync::broadcast based, in-process)
Event {
id: UUID v7
event_type: String (e.g., "user.created", "workflow.task.completed")
tenant_id: UUID
payload: serde_json::Value
timestamp: DateTime<Utc>
correlation_id: UUID (for tracing)
}
Delivery Guarantees:
- At-least-once delivery within the process
- Events persisted to `domain_events` table before dispatch (outbox pattern)
- Failed handlers log to dead-letter storage, trigger alert
- No cross-process delivery in Phase 1 (single binary)
```
### Plugin / Module Registration Interface
```rust
// erp-core defines the plugin trait
pub trait ErpModule: Send + Sync {
fn name(&self) -> &str;
fn version(&self) -> &str;
fn dependencies(&self) -> Vec<&str>; // required modules
fn register_routes(&self, router: Router) -> Router;
fn register_event_handlers(&self, bus: &EventBus);
fn on_tenant_created(&self, tenant_id: Uuid) -> Result<()>;
fn on_tenant_deleted(&self, tenant_id: Uuid) -> Result<()>;
}
// erp-server assembles modules at startup
fn build_app(modules: Vec<Box<dyn ErpModule>>) -> Router { ... }
```
Industry modules implement `ErpModule` and are discovered via configuration, not compile-time.
### API Versioning & Contract Governance
- Code-first with utoipa: derive OpenAPI from Rust types
- Auto-generated Swagger UI at `/docs` in development
- `/api/v1/` prefix for all endpoints; v2 only when breaking changes needed
- Client sends `X-API-Version: 1` header; server rejects unsupported versions
- Tauri client version and server version must be compatible (checked on connect)
### Concurrency & Transaction Strategy
- **Optimistic locking**: All mutable entities carry `version` column; updates fail on mismatch
- **Idempotency**: Write endpoints accept optional `Idempotency-Key` header
- **Cross-module transactions**: Avoided by design; event bus + saga pattern for consistency
- **Numbering sequences**: PostgreSQL sequences with `advisory_lock` per tenant per rule
### Audit Logging
```
AuditLog {
id: UUID v7
tenant_id: UUID
user_id: UUID
action: String (e.g., "user.update", "role.create")
resource_type: String
resource_id: UUID
changes: JSONB { before: {}, after: {} }
ip_address: String
user_agent: String
timestamp: DateTime<Utc>
}
Retention: 90 days hot, archive to cold storage after
Query API: GET /api/v1/audit-logs?resource_type=user&from=...
```
### Frontend-Backend Communication
| Aspect | Decision |
|--------|----------|
| Auth flow | Login via REST → JWT stored in httpOnly cookie (web) → sent as Bearer header |
| REST calls | Standard fetch/axios from browser to backend |
| WebSocket | Connect on page load, auth via first message with JWT, auto-reconnect with exponential backoff |
| File upload/download | Standard HTTP multipart + blob download |
| CORS | Whitelist per tenant, deny by default |
### Security Measures
- CORS: Whitelist per tenant, deny by default
- Rate limiting: Per-IP + per-user via Redis token bucket
- Secret management: Environment variables + vault (HashiCorp Vault for production)
- Data encryption: TLS in transit, AES-256 at rest for PII fields (optional per tenant config)
- Input validation: Schema-based (JSON Schema for complex inputs, types for simple)
- SQL injection: Prevented by SeaORM parameterized queries
---
## Tech Stack
### Backend (Rust)
| Component | Choice | Rationale |
|-----------|--------|-----------|
| Web framework | Axum 0.8 | Tokio-team maintained, best ecosystem |
| Async runtime | Tokio | Rust async standard |
| ORM | SeaORM | Async, type-safe, migration support |
| DB migration | SeaORM Migration | Versioned schema management |
| Cache | redis-rs | Official Redis client |
| JWT | jsonwebtoken | Lightweight, reliable |
| Serialization | serde + serde_json | Rust standard |
| Logging | tracing + tracing-subscriber | Structured logging |
| Config | config-rs | Multi-format support |
| API docs | utoipa (OpenAPI 3) | Auto-generate Swagger |
| Testing | Built-in + tokio-test | Unit + integration |
### Web Frontend
| Layer | Technology |
|-------|-----------|
| Build tool | Vite 6 |
| UI framework | React 18 + TypeScript |
| Component library | Ant Design 5 |
| State management | Zustand |
| Routing | React Router 7 |
| Styling | TailwindCSS + CSS Variables |
### Infrastructure
| Component | Technology |
|-----------|-----------|
| Primary database | PostgreSQL 16+ |
| Cache / Session / PubSub | Redis 7+ |
| Containerization | Docker + Docker Compose (dev) |
---
## Crate Structure
```
erp/
├── crates/
│ ├── erp-core/ # Shared: error handling, types, traits, events
│ ├── erp-auth/ # Identity & permissions module
│ ├── erp-workflow/ # Workflow engine module
│ ├── erp-message/ # Message center module
│ ├── erp-config/ # System configuration module
│ ├── erp-server/ # Axum server entry, assembles all modules
│ └── erp-common/ # Shared utilities, macros
├── apps/
│ └── web/ # Vite + React SPA (primary frontend)
├── desktop/ # (Optional) Tauri desktop, enabled per industry need
├── packages/
│ └── ui-components/ # React shared component library
├── migrations/ # Database migrations
├── docs/ # Documentation
└── docker/ # Docker configurations
```
---
## Module 1: Identity & Permissions (Auth)
### Data Model
```
Tenant
├── Organization
│ └── Department
│ └── Position
├── User
│ ├── UserCredential (password / OAuth / SSO)
│ ├── UserProfile
│ └── UserToken (session)
├── Role
│ └── Permission
└── Policy (ABAC rules)
```
### Permission Model: RBAC + ABAC Hybrid
- **RBAC**: User -> Role -> Permission, for standard scenarios
- **ABAC**: Attribute-based rules (e.g., "department manager can only approve own department's requests")
- **Data-level**: Row filtering (e.g., "only see own department's data")
### Authentication Methods
| Method | Description |
|--------|------------|
| Username/Password | Basic auth, Argon2 hash |
| OAuth 2.0 | Third-party login (WeChat, DingTalk, WeCom) |
| SSO (SAML/OIDC) | Enterprise SSO, required for private deployment |
| TOTP | Two-factor authentication |
### Key APIs
```
POST /api/v1/auth/login
POST /api/v1/auth/logout
POST /api/v1/auth/refresh
POST /api/v1/auth/revoke # Revoke a specific token
GET /api/v1/users
POST /api/v1/users
PUT /api/v1/users/:id
DELETE /api/v1/users/:id (soft delete)
GET /api/v1/roles
POST /api/v1/roles
PUT /api/v1/roles/:id
DELETE /api/v1/roles/:id
POST /api/v1/roles/:id/permissions
GET /api/v1/permissions # List all available permissions
GET /api/v1/tenants/:id/users
GET /api/v1/organizations
POST /api/v1/organizations
PUT /api/v1/organizations/:id
DELETE /api/v1/organizations/:id
GET /api/v1/organizations/:id/departments
POST /api/v1/organizations/:id/departments
GET /api/v1/positions
POST /api/v1/positions
GET /api/v1/policies
POST /api/v1/policies
PUT /api/v1/policies/:id
DELETE /api/v1/policies/:id
```
### Multi-tenant Isolation
- **Default**: Shared database + `tenant_id` column isolation (cost-optimal)
- **Switchable**: Independent schema per tenant (for private deployment)
- Middleware auto-injects `tenant_id`, application code is tenant-agnostic
### Multi-tenant Migration Strategy
- Schema migrations run once globally, affect all tenants' rows
- New tenant provisioning: seed data script (default roles, admin user, org structure, menus)
- Migrations are versioned and idempotent; failed migrations halt startup
- Per-tenant data migrations (e.g., adding default config) trigger on `on_tenant_created` hook
---
## Module 2: Workflow Engine
### Design Goals
- BPMN 2.0 **subset** compatible visual process designer
- Low latency, high throughput (Rust advantage)
- Support conditional branches, parallel gateways, sub-processes
- Embeddable into any business module
### BPMN Subset Scope (Phase 4)
**Included in Phase 4:**
- Start/End events
- User Tasks (with assignee, candidate groups)
- Service Tasks (HTTP call, script execution)
- Exclusive Gateways (conditional branching)
- Parallel Gateways (fork/join)
- Sequence Flows with conditions
- Process variables (basic types: string, number, boolean, date)
**Deferred to later phases:**
- Inclusive Gateways
- Sub-Processes (call activity)
- Timer events (intermediate, boundary)
- Signal/Message events
- Error boundary events
- Multi-instance (loop) activities
- Data objects and stores
### Core Concepts
```
ProcessDefinition
├── Node Types
│ ├── StartNode
│ ├── EndNode
│ ├── UserTask (human task)
│ ├── ServiceTask (system task)
│ ├── Gateway (exclusive / parallel / inclusive)
│ └── SubProcess
├── Flow (connections)
│ └── Condition (expressions)
└── ProcessInstance
├── Token (tracks execution position)
├── Task (pending tasks)
└── Variable (process variables)
```
### Key Features
| Feature | Description |
|---------|------------|
| Visual designer | React flowchart editor, drag-and-drop |
| Condition expressions | EL expressions: `amount > 10000 && dept == "finance"` |
| Countersign / Or-sign | Multi-person approval: all approve / any approve |
| Delegate / Transfer | Tasks can be delegated to others |
| Reminder / Timeout | Auto-remind, auto-handle on timeout |
| Version management | Process definitions versioned, running instances use old version |
### Key APIs
```
POST /api/v1/workflow/definitions
GET /api/v1/workflow/definitions/:id
PUT /api/v1/workflow/definitions/:id
POST /api/v1/workflow/instances
GET /api/v1/workflow/instances/:id
GET /api/v1/workflow/tasks (my pending)
POST /api/v1/workflow/tasks/:id/approve
POST /api/v1/workflow/tasks/:id/reject
POST /api/v1/workflow/tasks/:id/delegate
GET /api/v1/workflow/instances/:id/diagram (highlighted)
```
### Integration Points
- **Auth**: Task assignment based on roles/org structure
- **Message**: Pending task notifications, reminders, approval results
- **Config**: Process categories, numbering rules
---
## Module 3: Message Center
### Message Channels
| Channel | Use Case |
|---------|----------|
| In-app notifications | Foundation for all messages |
| WebSocket | Real-time push, instant desktop alerts |
| Email | Important approvals, scheduled reports |
| SMS | Verification codes, urgent alerts |
| WeCom / DingTalk | Enterprise messaging integration |
### Data Model
```
MessageTemplate
├── Channel type
├── Template content (variable interpolation: {{user_name}})
└── Multi-language versions
Message
├── Sender (system / user)
├── Recipient (user / role / department / all)
├── Priority (normal / important / urgent)
├── Read status
└── Business reference (deep link to specific page)
MessageSubscription
├── User notification preferences
├── Do-not-disturb periods
└── Channel preferences (e.g., approvals via in-app + WeCom, reports via email)
```
### Key Features
- **Message aggregation**: Group similar messages (e.g., "You have 5 pending approvals")
- **Read/unread**: Read receipts, unread count query
- **Message recall**: Sender can recall unread messages
- **Scheduled sending**: Set delivery time
- **Message archive**: Auto-archive history, searchable
### Key APIs
```
GET /api/v1/messages (list with pagination)
GET /api/v1/messages/unread-count
PUT /api/v1/messages/:id/read
PUT /api/v1/messages/read-all
DELETE /api/v1/messages/:id
POST /api/v1/messages/send
GET /api/v1/message-templates
POST /api/v1/message-templates
PUT /api/v1/message-subscriptions (update preferences)
WS /ws/v1/messages (real-time push)
```
---
## Module 4: System Configuration
### Configuration Hierarchy
```
Platform (global)
└── Tenant
└── Organization
└── User
```
Lower-level overrides higher-level. Priority: User > Organization > Tenant > Platform.
### Capabilities
| Capability | Description |
|-----------|------------|
| Dynamic menus | Tenants customize menu structure, display by role |
| Data dictionaries | System-level and tenant-level enum management |
| Numbering rules | Document number generation with concurrency-safe sequences |
| Multi-language | i18n resource management, runtime switching |
| System parameters | Key-value general configuration |
| Theme customization | Tenant-level UI theme (colors, logo) |
### Key APIs
```
GET /api/v1/config/menus # Tenant from middleware
PUT /api/v1/config/menus
GET /api/v1/config/dictionaries
POST /api/v1/config/dictionaries
PUT /api/v1/config/dictionaries/:id
GET /api/v1/config/settings/:key
PUT /api/v1/config/settings/:key
GET /api/v1/config/numbering-rules
POST /api/v1/config/numbering-rules
PUT /api/v1/config/numbering-rules/:id
GET /api/v1/config/languages
PUT /api/v1/config/languages/:code
GET /api/v1/config/themes # Tenant theme
PUT /api/v1/config/themes
```
---
## Database Design Principles
- All tables include: `tenant_id`, `created_at`, `updated_at`, `created_by`, `updated_by`
- Soft delete via `deleted_at` (no hard deletes)
- UUID v7 as primary keys (time-sortable + unique)
- JSONB columns for flexible extension data
- Indexes on `tenant_id` + business keys for multi-tenant queries
---
## Web UI Design
### Layout
Classic SaaS admin panel layout (responsive, mobile-friendly):
```
+----------------------------------------------+
| LOGO Search... 🔔 5 👤 Admin ▾ | ← Top nav bar
+--------+-------------------------------------+
| Home | |
| Users | Main Content Area |
| Roles | (Dynamic per menu selection) |
| Flows | Multi-tab support |
| Messages| |
| Settings| |
|--------| |
| Inv. | |
| Mfg. | |
| Finance| |
|--------| |
| More > | |
+--------+-------------------------------------+
```
### Key UI Features
- **Collapsible sidebar**: Multi-level menus, grouped (base modules / industry modules)
- **Multi-tab content**: Switch between open pages like browser tabs
- **Global search**: Search menus, users, documents
- **Notification panel**: Click bell icon to expand message list
- **Dark/Light theme**: Toggle support, follow system preference
- **Responsive**: Mobile/tablet adaptive layout
- **Browser notifications**: Web Notification API for real-time alerts
---
## Development Roadmap
### Phase 1 - Foundation (2-3 weeks)
- Rust workspace scaffolding + Vite + React setup
- erp-core: error types, shared types, trait definitions, event bus
- ErpModule trait + module registration system
- Database migration framework (SeaORM) with tenant provisioning
- Docker dev environment (PostgreSQL + Redis)
- CI/CD pipeline setup
### Phase 2 - Identity & Permissions (2-3 weeks)
- User, Role, Organization, Department, Position CRUD
- RBAC + ABAC permission model
- JWT auth (access + refresh tokens, token revocation)
- httpOnly cookie for web JWT storage
- Multi-tenant middleware
- Login page UI + user management pages
### Phase 3 - System Configuration (1-2 weeks)
- Data dictionaries
- Dynamic menus
- System parameters (hierarchical override)
- Numbering rules (concurrency-safe PostgreSQL sequences)
- i18n framework
- Settings pages UI
### Phase 4 - Workflow Engine (4-6 weeks)
- Process definition storage and versioning
- BPMN subset parser (start/end, user/service tasks, exclusive/parallel gateways)
- Execution engine with token tracking
- Task assignment, countersign, delegation
- Condition expression evaluator
- React visual flowchart designer
- Process diagram viewer (highlighted current node)
- Reminder and timeout handling
### Phase 5 - Message Center (2 weeks)
- Message templates with variable interpolation
- In-app notification CRUD
- WebSocket real-time push (auth, reconnect)
- Notification panel UI
- Message aggregation and read tracking
### Phase 6 - Integration & Polish (2-3 weeks)
- Cross-module integration testing
- Audit logging verification
- Web app deployment and optimization
- Performance optimization
- Documentation
---
## Verification Plan
1. **Unit tests**: Each module has comprehensive unit tests (80%+ coverage target)
2. **Integration tests**: API endpoint tests against real PostgreSQL/Redis
3. **E2E tests**: Desktop client test automation via Tauri WebDriver
4. **Multi-tenant tests**: Verify data isolation between tenants
5. **Workflow tests**: Full process lifecycle (define -> start -> approve -> complete)
6. **Performance benchmarks**: API response time < 100ms (p99), WebSocket push < 50ms
7. **Security audit**: OWASP top 10 check before release

View File

@@ -0,0 +1,275 @@
# Code Review: ERP Platform Base Design Specification
**Document reviewed:** `G:\erp\docs\superpowers\specs\2026-04-10-erp-platform-base-design.md`
**Verdict:** ISSUES FOUND
---
## What Is Done Well
The spec demonstrates several strengths before I get into the issues:
1. **Clear architectural vision.** The modular monolith with progressive extraction is a sound strategy for a product targeting small-to-large enterprises. It avoids premature microservice complexity while preserving the extraction path.
2. **Crate structure is well-considered.** Separating `erp-core` (traits, types, events) from `erp-common` (utilities, macros) and keeping each business module as its own crate is the right granularity for Rust workspace management and future extraction.
3. **RBAC + ABAC hybrid permission model.** This is the correct approach for a multi-tenant ERP. Pure RBAC breaks down at scale; ABAC alone is hard to administer. The hybrid is pragmatic.
4. **UUID v7 as primary keys.** Time-sortable UUIDs are the right choice for distributed systems and work well with PostgreSQL's B-tree indexes.
5. **Soft delete via `deleted_at`.** Appropriate for an ERP where audit trails are non-negotiable.
6. **Database design principles.** The universal columns (`tenant_id`, `created_at`, `updated_at`, `created_by`, `updated_by`) are a solid foundation.
---
## CRITICAL Issues (Must Fix Before Implementation)
### C1. No Error Handling Strategy Defined
The spec lists `erp-core` as containing "error handling" but provides zero detail on the error model. For a Rust codebase, this is an immediate blocker because error types pervade every crate boundary.
**What is missing:**
- A unified error type hierarchy (domain errors vs infrastructure errors vs API errors)
- How module-level errors map to HTTP responses (status codes, error envelopes)
- Whether a single `erp-core::Error` enum or per-module error types with `From` conversions
- Error chain propagation strategy (anyhow vs thiserror vs custom)
- How validation errors are represented and returned to the client
**Recommendation:** Add a section defining the error architecture. The standard Rust approach for this kind of project is `thiserror` for library crates with per-module error enums, converting to a unified API error type in `erp-server`. Define the HTTP error response envelope (status, code, message, details).
### C2. Event Bus Specification Is Absent
The spec states "Modules communicate via event bus, no direct coupling" as a core design principle but provides zero detail on:
- The event bus implementation (in-process? tokio channels? trait-object dispatch?)
- Event schema and versioning strategy
- Event delivery guarantees (at-least-once? exactly-once?)
- Event persistence (are events stored for audit/replay?)
- Dead letter handling
- How cross-module event subscriptions are registered
This is not an implementation detail -- it is a load-bearing architectural decision. The wrong choice here is extremely expensive to change later.
**Recommendation:** Add an "Event System" subsection under Architecture that specifies the event bus mechanism, event schema, delivery semantics, and at minimum the key events exchanged between the four core modules.
### C3. API Versioning and Contract Strategy Is Undefined
The APIs all use `/api/v1/` prefixes, but there is no statement on:
- How breaking changes are managed across versions
- Whether API versioning is URL-path-based (current) or header-based
- How the Tauri client and backend version compatibility is maintained
- Whether API contracts are formally defined (OpenAPI generation from code vs. design-first)
**Recommendation:** Add an API governance section. At minimum: the versioning strategy, the OpenAPI generation approach (utoipa is listed, but is it code-first or spec-first?), and the client-server compatibility contract.
### C4. No Data Migration Strategy for Multi-Tenant Schema Changes
The spec mentions SeaORM Migrations but does not address the multi-tenant reality:
- How are schema migrations applied across tenants?
- For the "independent schema per tenant" option, how is per-tenant migration managed?
- How are tenant-specific data migrations (dictionary changes, menu changes) handled separately from schema migrations?
- What happens when a migration fails for one tenant but succeeds for others?
**Recommendation:** Add a migration strategy section that covers schema migration in the shared-database model, per-tenant schema model, and data seeding for new tenants.
### C5. Workflow Engine BPMN 2.0 Compatibility Is Underspecified for Implementation
The workflow engine is listed as Phase 4 (3-4 weeks) and claims "BPMN 2.0 compatible," but implementing a BPMN engine from scratch in 3-4 weeks is not realistic. BPMN 2.0 is a massive specification (~500 pages). The spec does not specify:
- Which BPMN elements are in scope for Phase 4 vs. later phases
- How BPMN XML is parsed, stored, and rendered
- The expression language for conditions (mentions "EL expressions" but does not specify the engine)
- How subprocesses and call activities interact with multi-tenancy
**Recommendation:** Define a BPMN subset for Phase 4. A realistic Phase 4 scope would be: Start/End nodes, UserTask, ServiceTask, ExclusiveGateway, ParallelGateway, sequence flows with simple conditions. SubProcesses, InclusiveGateways, timers, and signals should be deferred.
---
## IMPORTANT Issues (Should Fix Before Implementation)
### I1. No Deployment Architecture or Operations Section
For a commercial SaaS product, the spec lacks:
- Production deployment topology (containers, orchestration, load balancing)
- How Tauri desktop clients connect to the backend (direct? reverse proxy? CDN?)
- Database connection pooling strategy
- Redis clustering strategy for production
- Backup and disaster recovery
- Monitoring, alerting, and observability stack
- Rate limiting strategy (mentioned in security checklist but not in the spec)
**Recommendation:** Add an "Operations" or "Infrastructure" section. Even if production deployment is future work, the development Docker Compose topology should be defined, and the backend should be designed to support horizontal scaling from day one (stateless server, sticky-session considerations for WebSocket, etc.).
### I2. Authentication Token Lifecycle Is Incomplete
The spec mentions JWT and refresh tokens but does not address:
- Token format and claims structure
- Access token TTL vs. refresh token TTL
- Refresh token rotation strategy
- Token revocation mechanism (critical for logout and security incidents)
- How tokens are transmitted (authorization header? cookies? both?)
- Tauri-specific secure token storage on the client side
- Concurrent session management (can a user be logged in on multiple devices?)
- Token handling across tenant boundaries (if a user belongs to multiple tenants)
**Recommendation:** Add a token lifecycle section covering issuance, rotation, revocation, storage, and multi-tenant user scenarios.
### I3. No Concurrency and Transaction Strategy
An ERP handles concurrent edits to shared data. The spec does not address:
- Optimistic vs. pessimistic locking strategy for business entities
- How SeaORM transactions are managed across module boundaries
- Idempotency for API operations (especially workflow actions)
- Concurrent workflow task handling (two people approving the same task simultaneously)
**Recommendation:** Add a concurrency section. For ERP, optimistic locking with version columns is the typical baseline. Define how cross-module transactions are handled (saga pattern? eventual consistency?).
### I4. Audit Logging Is Mentioned But Not Specified
The core shared layer includes "audit" but there is no specification for:
- What events are audited (all mutations? login/logout? data access?)
- Audit log storage (same database? separate? append-only?)
- Audit log retention policy
- Audit log query API
- How audit logs relate to multi-tenancy
For a commercial ERP, audit logging is not optional. It is a compliance requirement.
**Recommendation:** Add an audit specification section. Define the audit event schema, what triggers audit events, storage, and retention.
### I5. No Module Registration and Plugin Interface Defined
One of the four design principles is "Plugin extensibility: Industry modules register through standard interfaces, support dynamic enable/disable." But the spec provides:
- No trait definitions for module registration
- No lifecycle hooks (init, start, stop, health-check)
- No mechanism for modules to register their routes, menu items, or event handlers
- No tenant-level module enable/disable data model
This is the mechanism that makes the "platform base + industry plugins" architecture work. Without it, the industry modules cannot be built.
**Recommendation:** Add a "Module System" section that defines the module trait interface, the registration mechanism, and the per-tenant module configuration model.
### I6. Numbering Rules Are Too Simplistic
The spec lists "Numbering rules: Document number generation (e.g., PO-2024-001)" as a Config capability but does not address:
- Concurrency-safe sequence generation (multiple users creating POs simultaneously)
- Sequence reset rules (yearly? monthly? daily? never?)
- Sequence gaps (are gaps allowed? this has legal implications in some jurisdictions)
- Multi-tenant sequence isolation
- How different document types get different sequences
**Recommendation:** Expand the numbering rules section with the sequence generation strategy. PostgreSQL sequences or a dedicated sequence table with row-level locking are the typical approaches.
### I7. Desktop-Backend Communication Protocol Undefined
The spec shows REST APIs and a WebSocket endpoint but does not specify:
- How the Tauri app authenticates WebSocket connections
- WebSocket message format and protocol
- Reconnection and offline queueing strategy
- How Tauri IPC interacts with HTTP calls (does the frontend call the backend directly, or go through Tauri commands?)
- File upload/download handling (common in ERP: attachments, exports)
- How the desktop app handles backend version mismatches
**Recommendation:** Add a "Client-Server Communication" section covering HTTP API patterns, WebSocket protocol, Tauri IPC strategy, and offline behavior.
---
## SUGGESTIONS (Nice to Have)
### S1. Consider Adding a `erp-infra` Crate
The current structure has `erp-core` for shared types/traits and `erp-common` for utilities. Consider a third crate, `erp-infra`, for infrastructure adapters (database connection pool, Redis client, event bus implementation). This keeps `erp-core` purely abstract and makes testing easier since modules depend on `erp-core` traits, not `erp-infra` implementations.
### S2. Define the Configuration File Format
`config-rs` is listed but no application configuration schema is defined. What does `config.toml` or `config.yaml` look like? Database URL, Redis URL, JWT secret, server port, log level, etc. should be enumerated.
### S3. Consider API Response Envelope Standardization
Define a standard API response format (as mentioned in the project's own patterns rules). Example:
```json
{
"success": true,
"data": { ... },
"error": null,
"meta": { "total": 100, "page": 1, "limit": 20 }
}
```
### S4. Add Database Index Strategy
While the spec mentions "Indexes on `tenant_id` + business keys," a more detailed index strategy would help. Consider composite indexes for common query patterns, partial indexes for soft-deleted rows, and index-only scans for high-frequency queries.
### S5. Consider Health Check and Readiness Endpoints
For containerized deployment and monitoring, define `/health` and `/ready` endpoints. These are essential for Docker orchestration and load balancer configuration.
### S6. The `apps/admin` Directory Implies a Web Admin Panel
The spec mentions it as "Optional web admin panel" but does not define its relationship to the Tauri desktop client. If both exist, is it a separate SPA build? Does it share the `packages/ui-components` library? Clarify whether this is in scope or should be removed to avoid confusion.
### S7. Timeline Estimates May Be Tight
The total roadmap is 10-15 weeks. For a solo developer or small team building a BPMN workflow engine from scratch in Rust, Phase 4 (3-4 weeks) is extremely aggressive. Consider:
- Phase 4 should be 5-8 weeks for a usable workflow engine
- Phase 1 should include CI/CD pipeline as a deliverable, which can take 3-5 days alone
- Total realistic estimate: 14-22 weeks for a solid base
---
## Consistency Check
| Check | Result |
|-------|--------|
| Module API prefixes consistent | PASS - all use `/api/v1/{module}/` |
| Data model matches API surface | PARTIAL - no API for Organization CRUD, Policy CRUD, or Position CRUD in Auth |
| Crate structure matches module list | PASS |
| Tech stack choices are compatible | PASS - Axum + Tokio + SeaORM + redis-rs are a well-tested stack |
| Multi-tenant strategy is applied consistently | PARTIAL - some APIs use `/:tenant_id` in path (Config menus) while Auth relies on middleware injection. Should be consistent. |
| UI features match backend capabilities | PASS |
| Roadmap phases align with module dependencies | PASS - Auth first, then Config, then Workflow (which depends on both) |
---
## Security Gap Analysis
| Area | Status | Notes |
|------|--------|-------|
| Password hashing (Argon2) | Covered | Good |
| JWT authentication | Partially covered | Missing token revocation, rotation |
| SQL injection | Implicitly covered | SeaORM parameterized queries |
| XSS | Not addressed | Should specify CSP policy for Tauri |
| CSRF | Not applicable | Tauri desktop, not browser-based |
| Rate limiting | Not specified | Mentioned in verification plan but not in design |
| Input validation | Not specified | No validation strategy defined |
| CORS | Not specified | Needed if web admin panel is built |
| Secret management | Not specified | Where do JWT secrets, DB credentials, Redis passwords live? |
| Audit trail | Mentioned but not specified | See I4 above |
| Data encryption at rest | Not addressed | Relevant for private deployment customers |
| Backup/restore security | Not addressed | |
| Tenant data isolation verification | Covered | In verification plan |
---
## Summary
The specification provides a strong architectural vision and reasonable technology choices. However, it has five critical gaps that would block or severely impair implementation:
1. **C1** - No error handling strategy
2. **C2** - No event bus specification (despite being a core design principle)
3. **C3** - No API versioning and contract governance
4. **C4** - No multi-tenant migration strategy
5. **C5** - Workflow engine scope is unrealistic for the stated timeline
And seven important issues that should be addressed before coding begins:
1. **I1** - No deployment/operations architecture
2. **I2** - Incomplete authentication token lifecycle
3. **I3** - No concurrency and transaction strategy
4. **I4** - Audit logging mentioned but not specified
5. **I5** - No module registration/plugin interface defined
6. **I6** - Numbering rules too simplistic for ERP use
7. **I7** - Desktop-backend communication protocol undefined
**Recommendation:** Address all five CRITICAL issues and the IMPORTANT issues before beginning Phase 1. The CRITICAL issues represent architectural foundations that are extremely expensive to retrofit. The IMPORTANT issues represent functionality that will be needed within the first three phases of development.
The spec should move from `Status: Draft` to `Status: Under Review` until these issues are resolved.