- Collapse nested if-let in user_service.rs search filter - Suppress dead_code warning on ApiDoc struct - Refactor server routing: nest all routes under /api/v1 prefix - Simplify health check route path - Improve workflow ProcessDesigner with edit mode and loading states - Update workflow pages with enhanced UX - Add Phase 2 implementation plan document
270 lines
12 KiB
Markdown
270 lines
12 KiB
Markdown
# Phase 2: 身份与权限模块实施计划
|
||
|
||
## Context
|
||
|
||
Phase 1 基础设施已完成(workspace、core types、EventBus、ErpModule trait、AppState、health check、graceful shutdown)。现在需要构建 Phase 2 身份与权限模块,这是所有后续模块的基础——工作流、消息、配置都依赖认证和权限中间件。
|
||
|
||
**目标**:实现完整的用户认证(JWT)、RBAC 权限模型、多租户中间件、用户/角色/组织管理 CRUD,以及对应的前端页面。
|
||
|
||
**范围界定**:Phase 2 仅实现用户名/密码认证 + RBAC。OAuth/SSO/TOTP/ABAC 延后到后续 Phase。
|
||
|
||
---
|
||
|
||
## 关键决策
|
||
|
||
1. **ErpModule trait 路由问题**:当前 trait `register_routes` 使用 `Router`(无状态泛型),但实际路由需要 `Router<AppState>`。Phase 2 采用**务实方案**:模块暴露独立 `routes() -> Router<AppState>` 函数,server 手动 merge。不改动 trait,避免核心层依赖 server 类型。
|
||
2. **Token 存储**:JWT 的 SHA-256 哈希存入 `user_tokens` 表,支持吊销。前端用 localStorage 存储 access/refresh token(httpOnly cookie 为 Phase 6 优化项)。
|
||
3. **中间件方案**:使用 `axum::middleware::from_fn_with_state` 实现 JWT 认证中间件,将 `TenantContext` 注入 `req.extensions()`。
|
||
|
||
---
|
||
|
||
## Task 1: 数据库迁移(10 张表)
|
||
|
||
**目标**:创建所有 Auth 相关的数据库表。
|
||
|
||
**创建文件**(`crates/erp-server/migration/src/`):
|
||
- `m20260411_000002_create_users.rs`
|
||
- `m20260411_000003_create_user_credentials.rs`
|
||
- `m20260411_000004_create_user_tokens.rs`
|
||
- `m20260411_000005_create_roles.rs`
|
||
- `m20260411_000006_create_permissions.rs`
|
||
- `m20260411_000007_create_role_permissions.rs`
|
||
- `m20260411_000008_create_user_roles.rs`
|
||
- `m20260411_000009_create_organizations.rs`
|
||
- `m20260411_000010_create_departments.rs`
|
||
- `m20260411_000011_create_positions.rs`
|
||
|
||
**修改文件**:`migration/src/lib.rs` 注册所有新迁移
|
||
|
||
**表结构要点**:
|
||
- 所有表含标准字段:id(UUID PK), tenant_id, created_at, updated_at, created_by, updated_by, deleted_at, version
|
||
- **users**: username(VARCHAR, 复合唯一 tenant_id+username), email, phone, display_name, avatar_url, status(CHECK active/disabled/locked), last_login_at
|
||
- **user_credentials**: user_id FK, credential_type(CHECK password/oauth/sso), credential_data(JSONB), verified(bool)
|
||
- **user_tokens**: user_id FK, token_hash(VARCHAR UNIQUE), token_type(CHECK access/refresh), expires_at, revoked_at, device_info
|
||
- **roles**: name, code(复合唯一 tenant_id+code), description, is_system(bool)
|
||
- **permissions**: code(复合唯一 tenant_id+code), name, resource, action, description
|
||
- **role_permissions**: role_id + permission_id 复合 PK
|
||
- **user_roles**: user_id + role_id 复合 PK
|
||
- **organizations**: name, code, parent_id(自引用 FK), path(TEXT), level, sort_order
|
||
- **departments**: org_id FK, name, code, parent_id(自引用), manager_id FK users, path, sort_order
|
||
- **positions**: dept_id FK, name, code, level, sort_order
|
||
|
||
**验证**:`cargo run -p erp-server` → `\dt` 显示全部 11 张表
|
||
|
||
---
|
||
|
||
## Task 2: SeaORM Entity 定义
|
||
|
||
**目标**:为所有 Auth 表创建类型安全的 Entity/Model/Relation。
|
||
|
||
**创建文件**(`crates/erp-auth/src/entity/`):
|
||
- `mod.rs`, `user.rs`, `user_credential.rs`, `user_token.rs`, `role.rs`, `permission.rs`, `role_permission.rs`, `user_role.rs`, `organization.rs`, `department.rs`, `position.rs`
|
||
|
||
**修改文件**:
|
||
- `crates/erp-auth/Cargo.toml` — 添加 workspace 依赖:jsonwebtoken, argon2, validator, thiserror, utoipa, erp-common
|
||
|
||
**同时创建 DTO 文件**:`crates/erp-auth/src/dto.rs`
|
||
- `LoginReq` { username, password } — #[derive(Validate)]
|
||
- `LoginResp` { access_token, refresh_token, expires_in, user }
|
||
- `RefreshReq` { refresh_token }
|
||
- `CreateUserReq` { username, password, email?, phone?, display_name? }
|
||
- `UpdateUserReq` { email?, phone?, display_name?, status? }
|
||
- `UserResp`, `RoleResp`, `PermissionResp` 等
|
||
- `CreateRoleReq`, `UpdateRoleReq`, `AssignPermissionsReq`, `AssignRolesReq`
|
||
- 所有 DTO 添加 `#[derive(utoipa::ToSchema)]`
|
||
|
||
**验证**:`cargo check -p erp-auth` 通过
|
||
|
||
---
|
||
|
||
## Task 3: 核心服务层(密码 + JWT + 认证)
|
||
|
||
**目标**:实现密码哈希、JWT 签发/验证、登录/刷新/登出逻辑。
|
||
|
||
**创建文件**(`crates/erp-auth/src/`):
|
||
- `error.rs` — AuthError 枚举 + From<AuthError> for AppError
|
||
- `service/mod.rs`
|
||
- `service/password.rs` — Argon2 hash/verify
|
||
- `service/token_service.rs` — JWT sign/validate, token DB CRUD
|
||
- `service/auth_service.rs` — login/refresh/logout
|
||
- `service/user_service.rs` — user CRUD
|
||
|
||
**修改文件**:`crates/erp-auth/src/lib.rs` — 声明模块
|
||
|
||
**关键实现**:
|
||
- `password.rs`: `hash_password(plain) -> Result<String>`, `verify_password(plain, hash) -> Result<bool>`
|
||
- `token_service.rs`:
|
||
- JWT Claims: { sub: Uuid, tid: Uuid, roles: Vec<String>, permissions: Vec<String>, exp, iat, token_type: String }
|
||
- `sign_access_token(user_id, tenant_id, roles, permissions, jwt_config) -> Result<String>`
|
||
- `sign_refresh_token(user_id, tenant_id, db, jwt_config) -> Result<(String, Uuid)>` — 存 SHA-256 哈希到 DB
|
||
- `validate_refresh_token(token, db, jwt_config) -> Result<(Uuid, Claims)>` — 检查吊销状态
|
||
- `revoke_all_user_tokens(user_id, tenant_id, db)`
|
||
- `auth_service.rs`:
|
||
- `login(tenant_id, username, password, device_info, db, token_svc, event_bus)` → 查用户 → 验证密码 → 签发双 token → 更新 last_login_at → 发 user.login 事件
|
||
- `refresh(refresh_token, db, token_svc)` → 验证 → 吊销旧 refresh → 签发新双 token
|
||
- `logout(user_id, tenant_id, db, token_svc)` → 吊销全部 token
|
||
- `user_service.rs`: CRUD,查询始终带 tenant_id 过滤 + deleted_at IS NULL
|
||
|
||
**验证**:`cargo check -p erp-auth` 通过,password hash/verify 单元测试通过
|
||
|
||
---
|
||
|
||
## Task 4: Handler 路由 + AuthModule 注册
|
||
|
||
**目标**:创建 Axum handler,注册到 server。
|
||
|
||
**创建文件**(`crates/erp-auth/src/`):
|
||
- `handler/mod.rs`
|
||
- `handler/auth_handler.rs` — login/refresh/logout
|
||
- `handler/user_handler.rs` — user CRUD + 角色分配
|
||
- `module.rs` — AuthModule struct + ErpModule impl
|
||
|
||
**修改文件**:
|
||
- `crates/erp-server/Cargo.toml` — 添加 `erp-auth.workspace = true`
|
||
- `crates/erp-server/src/main.rs` — 注册 auth 模块路由
|
||
- `crates/erp-server/src/config.rs` — 添加 `AuthConfig { super_admin_password }`
|
||
- `crates/erp-server/config/default.toml` — 添加 `[auth]` 段
|
||
|
||
**路由设计**:
|
||
- 公开(无需 JWT):`POST /api/v1/auth/login`, `POST /api/v1/auth/refresh`
|
||
- 受保护(需 JWT):`POST /api/v1/auth/logout`, 全部 users/roles/permissions/orgs 路由
|
||
|
||
**main.rs 路由结构**:
|
||
```
|
||
Router::new()
|
||
.merge(public_routes) // health + login + refresh
|
||
.merge(protected_routes) // logout + user/role CRUD, 后续加 JWT 中间件
|
||
.with_state(state)
|
||
```
|
||
|
||
**验证**:server 启动,`POST /api/v1/auth/login` 返回 400(无 body)而非 404
|
||
|
||
---
|
||
|
||
## Task 5: JWT 认证中间件 + RBAC 权限检查
|
||
|
||
**目标**:JWT 验证中间件注入 TenantContext,RBAC 权限检查辅助函数。
|
||
|
||
**创建文件**(`crates/erp-auth/src/`):
|
||
- `middleware/mod.rs`
|
||
- `middleware/jwt_auth.rs` — `jwt_auth_middleware(State, Request, Next)` → 从 Bearer token 解码 Claims → 注入 TenantContext 到 extensions
|
||
- `middleware/rbac.rs` — `require_permission(perm: &str, ctx: &TenantContext) -> AppResult<()>`
|
||
|
||
**修改文件**:
|
||
- `crates/erp-server/src/main.rs` — public/protected 路由分离,protected 路由层加 JWT 中间件
|
||
|
||
**关键实现**:
|
||
- 中间件用 `axum::middleware::from_fn_with_state(state, jwt_auth_middleware)`
|
||
- 从 `Authorization: Bearer xxx` 提取 token → decode → 检查过期 → 注入 `TenantContext { tenant_id, user_id, roles, permissions }`
|
||
- RBAC: 简单检查 `ctx.permissions.contains(&required_permission)`
|
||
- login/refresh/health 路由跳过 JWT 中间件
|
||
|
||
**验证**:无 token 访问 `/api/v1/users` 返回 401;login/refresh 不受影响
|
||
|
||
---
|
||
|
||
## Task 6: 租户初始化钩子 + 种子数据
|
||
|
||
**目标**:实现 `on_tenant_created` 创建默认角色/权限/管理员。
|
||
|
||
**创建文件**:`crates/erp-auth/src/service/seed.rs`
|
||
|
||
**修改文件**:
|
||
- `crates/erp-auth/src/module.rs` — 实现 `on_tenant_created`
|
||
- `crates/erp-server/src/main.rs` — server 启动时自动创建默认租户(开发用)
|
||
|
||
**种子数据**:
|
||
- 20 个默认权限(user:crud, role:crud, permission:read, organization:crud, department:crud, position:crud)
|
||
- "admin" 角色(is_system=true)绑定所有权限
|
||
- "viewer" 角色(is_system=true)绑定只读权限
|
||
- super admin 用户(username="admin",密码从配置读取)
|
||
|
||
**验证**:启动后查询 DB 有 admin 用户、admin/viewer 角色、20 个权限
|
||
|
||
---
|
||
|
||
## Task 7: 前端登录页 + Auth Store + API 层
|
||
|
||
**目标**:登录 UI、token 管理、路由守卫。
|
||
|
||
**创建文件**(`apps/web/src/`):
|
||
- `api/client.ts` — axios 实例 + 请求/响应拦截器(附加 token、401 自动 refresh)
|
||
- `api/auth.ts` — login/refresh/logout API 调用
|
||
- `api/users.ts` — 用户 CRUD API
|
||
- `stores/auth.ts` — auth Zustand store(user, permissions, tokens, login/logout)
|
||
- `pages/Login.tsx` — 登录表单页
|
||
- `pages/Home.tsx` — 首页(从 App.tsx 抽取)
|
||
|
||
**修改文件**:
|
||
- `apps/web/src/App.tsx` — 添加 PrivateRoute 守卫、/login 路由
|
||
- `apps/web/src/stores/app.ts` — 移除 isLoggedIn stub(由 auth store 接管)
|
||
|
||
**验证**:打开 `/#/` 自动跳转 `/login`,输入 admin/Admin@2026 登录后跳转到首页,刷新保持登录状态
|
||
|
||
---
|
||
|
||
## Task 8: 角色/权限管理
|
||
|
||
**目标**:角色 CRUD + 权限分配后端 + 前端页面。
|
||
|
||
**创建文件**:
|
||
- Backend: `crates/erp-auth/src/service/role_service.rs`, `service/permission_service.rs`, `handler/role_handler.rs`, `handler/permission_handler.rs`
|
||
- Frontend: `apps/web/src/pages/Roles.tsx`, `apps/web/src/api/roles.ts`
|
||
|
||
**修改文件**:`module.rs` 注册新路由, `App.tsx` 添加路由
|
||
|
||
**验证**:admin 登录 → 角色管理 → 创建角色 → 分配权限 → DB 验证
|
||
|
||
---
|
||
|
||
## Task 9: 组织/部门/岗位管理
|
||
|
||
**目标**:树形组织架构管理。
|
||
|
||
**创建文件**:
|
||
- Backend: `service/org_service.rs`, `service/dept_service.rs`, `service/position_service.rs`, `handler/org_handler.rs`, `handler/dept_handler.rs`, `handler/position_handler.rs`
|
||
- Frontend: `apps/web/src/pages/Organizations.tsx`, `apps/web/src/api/orgs.ts`
|
||
|
||
**关键**:树形结构用 parent_id + path 列实现祖先查询
|
||
|
||
**验证**:创建根组织 → 子组织 → 部门 → 岗位,验证 path 正确
|
||
|
||
---
|
||
|
||
## Task 10: 用户管理页面 + 整合
|
||
|
||
**目标**:完整的用户 CRUD 界面 + 角色分配 + 主布局用户信息。
|
||
|
||
**创建文件**:
|
||
- `apps/web/src/pages/Users.tsx` — Ant Design Table + 创建/编辑/角色分配 Modal
|
||
- `apps/web/src/layouts/MainLayout.tsx` — 更新:显示当前用户名、登出菜单
|
||
|
||
**验证**:admin 登录 → 用户管理 → 创建用户 → 分配角色 → 禁用/启用 → 所有操作即时反映
|
||
|
||
---
|
||
|
||
## 依赖关系
|
||
|
||
```
|
||
Task 1 (迁移) → Task 2 (Entity) → Task 3 (服务层) → Task 4 (Handler+注册)
|
||
↓
|
||
Task 5 (JWT 中间件)
|
||
Task 6 (种子数据)
|
||
↓
|
||
Task 7 (前端登录)
|
||
↓
|
||
Task 8 (角色权限) Task 9 (组织部门)
|
||
↓
|
||
Task 10 (用户管理 UI)
|
||
```
|
||
|
||
## 验证标准
|
||
|
||
- [ ] `cargo check --workspace` 零错误零警告
|
||
- [ ] `cargo test --workspace` 全部通过
|
||
- [ ] server 启动后可 login → 获得 JWT → 带 JWT 访问 /api/v1/users
|
||
- [ ] 无 JWT 访问受保护端点返回 401
|
||
- [ ] 前端登录 → 跳转首页 → 刷新保持 → 登出清除
|
||
- [ ] 用户/角色/组织 CRUD 页面功能完整
|
||
- [ ] 多租户隔离:每个查询自动带 tenant_id 过滤
|