Files
erp/plans/lively-tickling-engelbart.md
iven 97d3c9026b fix: resolve remaining clippy warnings and improve workflow frontend
- 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
2026-04-11 12:59:43 +08:00

12 KiB
Raw Blame History

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 tokenhttpOnly 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 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, 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 哈希到 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]

路由设计

  • 公开(无需 JWTPOST /api/v1/auth/login, POST /api/v1/auth/refresh
  • 受保护(需 JWTPOST /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 验证中间件注入 TenantContextRBAC 权限检查辅助函数。

创建文件crates/erp-auth/src/

  • middleware/mod.rs
  • middleware/jwt_auth.rsjwt_auth_middleware(State, Request, Next) → 从 Bearer token 解码 Claims → 注入 TenantContext 到 extensions
  • middleware/rbac.rsrequire_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 返回 401login/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 storeuser, 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 过滤