- 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
12 KiB
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。
关键决策
- ErpModule trait 路由问题:当前 trait
register_routes使用Router(无状态泛型),但实际路由需要Router<AppState>。Phase 2 采用务实方案:模块暴露独立routes() -> Router<AppState>函数,server 手动 merge。不改动 trait,避免核心层依赖 server 类型。 - Token 存储:JWT 的 SHA-256 哈希存入
user_tokens表,支持吊销。前端用 localStorage 存储 access/refresh token(httpOnly cookie 为 Phase 6 优化项)。 - 中间件方案:使用
axum::middleware::from_fn_with_state实现 JWT 认证中间件,将TenantContext注入req.extensions()。
Task 1: 数据库迁移(10 张表)
目标:创建所有 Auth 相关的数据库表。
创建文件(crates/erp-server/migration/src/):
m20260411_000002_create_users.rsm20260411_000003_create_user_credentials.rsm20260411_000004_create_user_tokens.rsm20260411_000005_create_roles.rsm20260411_000006_create_permissions.rsm20260411_000007_create_role_permissions.rsm20260411_000008_create_user_roles.rsm20260411_000009_create_organizations.rsm20260411_000010_create_departments.rsm20260411_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 for AppErrorservice/mod.rsservice/password.rs— Argon2 hash/verifyservice/token_service.rs— JWT sign/validate, token DB CRUDservice/auth_service.rs— login/refresh/logoutservice/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, permissions: Vec, 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 哈希到 DBvalidate_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 → 签发新双 tokenlogout(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.rshandler/auth_handler.rs— login/refresh/logouthandler/user_handler.rs— user CRUD + 角色分配module.rs— AuthModule struct + ErpModule impl
修改文件:
crates/erp-server/Cargo.toml— 添加erp-auth.workspace = truecrates/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.rsmiddleware/jwt_auth.rs—jwt_auth_middleware(State, Request, Next)→ 从 Bearer token 解码 Claims → 注入 TenantContext 到 extensionsmiddleware/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_createdcrates/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 APIstores/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 + 创建/编辑/角色分配 Modalapps/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 过滤