- 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
14 KiB
Phase 4: 工作流引擎模块 — 实施计划
Context
Phase 1(基础设施)、Phase 2(身份与权限)和 Phase 3(系统配置)已完成。Phase 4 需要实现工作流引擎模块(erp-workflow),提供流程定义、流程实例管理、任务审批、Token 驱动的执行引擎和可视化流程设计器。当前 erp-workflow 仅为 placeholder。
用户选择"完整实施"方案,包括 BPMN 子集解析器、Token 驱动执行引擎、完整 CRUD 端点和 React Flow 可视化设计器。
Task 1: erp-workflow 骨架 + WorkflowState + Error
目标: 创建可编译的最小 crate,注册到 erp-server。
创建/修改文件:
- 修改:
crates/erp-workflow/Cargo.toml— 添加完整依赖 - 修改:
crates/erp-workflow/src/lib.rs— 模块声明 + re-export - 创建:
crates/erp-workflow/src/workflow_state.rs—WorkflowState { db, event_bus } - 创建:
crates/erp-workflow/src/error.rs— WorkflowError 枚举 - 创建:
crates/erp-workflow/src/module.rs— WorkflowModule + ErpModule trait - 创建:
crates/erp-workflow/src/dto.rs— 占位 - 创建:
crates/erp-workflow/src/entity/mod.rs— 占位 - 创建:
crates/erp-workflow/src/service/mod.rs— 占位 - 创建:
crates/erp-workflow/src/handler/mod.rs— 占位 - 创建:
crates/erp-workflow/src/engine/mod.rs— 占位 - 修改:
crates/erp-server/Cargo.toml— 确认 erp-workflow 依赖 - 修改:
crates/erp-server/src/state.rs— 添加FromRef<AppState>for WorkflowState - 修改:
crates/erp-server/src/main.rs— 注册 WorkflowModule
依赖:
erp-core.workspace = true
tokio = { workspace = true, features = ["full"] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
uuid = { workspace = true, features = ["v7", "serde"] }
chrono = { workspace = true, features = ["serde"] }
axum = { workspace = true }
sea-orm = { workspace = true, features = ["sqlx-postgres", "runtime-tokio-rustls", "with-uuid", "with-chrono", "with-json"] }
tracing = { workspace = true }
thiserror = { workspace = true }
utoipa = { workspace = true, features = ["uuid", "chrono"] }
async-trait = { workspace = true }
验证: cargo check 通过
Task 2: 数据库迁移(5 张表)
创建文件(crates/erp-server/migration/src/):
m20260412_000018_create_process_definitions.rsm20260412_000019_create_process_instances.rsm20260412_000020_create_tokens.rsm20260412_000021_create_tasks.rsm20260412_000022_create_process_variables.rs- 修改:
lib.rs— 注册新迁移
process_definitions
| 列 | 类型 | 说明 |
|---|---|---|
| id | uuid PK | UUIDv7 |
| tenant_id | uuid NOT NULL | 租户 ID |
| name | string NOT NULL | 流程名称 |
| key | string NOT NULL | 流程唯一编码 |
| version | int NOT NULL DEFAULT 1 | 版本号 |
| category | string NULL | 分类(如 leave, expense) |
| description | text NULL | 描述 |
| nodes | jsonb NOT NULL DEFAULT '[]' | 节点定义(BPMN 子集) |
| edges | jsonb NOT NULL DEFAULT '[]' | 连线定义 |
| status | string NOT NULL DEFAULT 'draft' | draft/published/deprecated |
| + 标准审计字段 | ||
| 唯一索引: | (tenant_id, key, version) WHERE deleted_at IS NULL |
process_instances
| 列 | 类型 | 说明 |
|---|---|---|
| id | uuid PK | UUIDv7 |
| tenant_id | uuid NOT NULL | |
| definition_id | uuid NOT NULL FK → process_definitions | |
| business_key | string NULL | 业务关联键(如请假单 ID) |
| status | string NOT NULL DEFAULT 'running' | running/suspended/completed/terminated |
| started_by | uuid NOT NULL | 发起人 user_id |
| started_at | timestamptz NOT NULL DEFAULT NOW() | |
| completed_at | timestamptz NULL | |
| + 标准审计字段 | ||
| 索引: | idx_instances_status (tenant_id, status) |
tokens
| 列 | 类型 | 说明 |
|---|---|---|
| id | uuid PK | UUIDv7 |
| tenant_id | uuid NOT NULL | |
| instance_id | uuid NOT NULL FK → process_instances | |
| node_id | string NOT NULL | 当前所在节点 ID |
| status | string NOT NULL DEFAULT 'active' | active/consumed/terminated |
| created_at | timestamptz NOT NULL DEFAULT NOW() | |
| consumed_at | timestamptz NULL | |
| 索引: | idx_tokens_instance (instance_id) |
tasks
| 列 | 类型 | 说明 |
|---|---|---|
| id | uuid PK | UUIDv7 |
| tenant_id | uuid NOT NULL | |
| instance_id | uuid NOT NULL FK → process_instances | |
| token_id | uuid NOT NULL FK → tokens | |
| node_id | string NOT NULL | 对应的流程节点 |
| node_name | string NULL | 节点名称(冗余,便于查询) |
| assignee_id | uuid NULL | 指定处理人 |
| candidate_groups | jsonb NULL | 候选角色组 |
| status | string NOT NULL DEFAULT 'pending' | pending/approved/rejected/delegated |
| outcome | string NULL | 审批结果 |
| form_data | jsonb NULL | 表单数据 |
| due_date | timestamptz NULL | 到期时间 |
| completed_at | timestamptz NULL | |
| + 标准审计字段 | ||
| 索引: | idx_tasks_assignee (tenant_id, assignee_id, status) |
|
| 索引: | idx_tasks_instance (instance_id) |
process_variables
| 列 | 类型 | 说明 |
|---|---|---|
| id | uuid PK | UUIDv7 |
| tenant_id | uuid NOT NULL | |
| instance_id | uuid NOT NULL FK → process_instances | |
| name | string NOT NULL | 变量名 |
| var_type | string NOT NULL DEFAULT 'string' | string/number/boolean/date/json |
| value_string | text NULL | |
| value_number | double precision NULL | |
| value_boolean | boolean NULL | |
| value_date | timestamptz NULL | |
| 唯一索引: | (instance_id, name) |
验证: cargo run -p erp-server 启动后 \dt 可见新表
Task 3: SeaORM Entity
创建文件(crates/erp-workflow/src/entity/):
mod.rs— 导出所有实体process_definition.rsprocess_instance.rstoken.rstask.rsprocess_variable.rs
模式: 参考 erp-config/src/entity/numbering_rule.rs,包含 Relation 和 Related。
验证: cargo check 通过
Task 4: DTO 定义
修改文件: crates/erp-workflow/src/dto.rs
包含:
- 流程定义:
ProcessDefinitionResp,CreateProcessDefinitionReq,UpdateProcessDefinitionReq,PublishDefinitionReq - 流程实例:
ProcessInstanceResp,StartInstanceReq - 任务:
TaskResp,CompleteTaskReq(含 outcome + form_data) - 流程变量:
ProcessVariableResp,SetVariableReq - 流程图:
NodeDef(BPMN 节点),EdgeDef(连线),FlowDiagram(完整图)
节点类型: StartEvent, EndEvent, UserTask, ServiceTask, ExclusiveGateway, ParallelGateway
连线条件: condition 字段为可选表达式字符串(如 amount > 1000)
验证: cargo check 通过
Task 5: BPMN 解析器 + 表达式引擎
创建文件(crates/erp-workflow/src/engine/):
model.rs— 流程图内存模型(FlowDiagram, FlowNode, FlowEdge, NodeType 枚举)parser.rs— 解析 JSON nodes/edges 为内存模型,验证流程图合法性expression.rs— 简单表达式求值器(支持比较运算和流程变量引用)
关键逻辑:
FlowDiagram::validate()— 检查:恰好 1 个 StartEvent,至少 1 个 EndEvent,无悬空连线,网关分支/汇合配对ExpressionEvaluator::eval(expr, variables) -> bool— 支持var > 1000,status == "approved",amount <= budget格式- 解析器将
nodes和edgesjsonb 反序列化为Vec<FlowNode>和Vec<FlowEdge>
验证: 单元测试覆盖:合法流程验证、缺少 StartEvent 报错、表达式求值
Task 6: Token 驱动执行引擎
创建文件: crates/erp-workflow/src/engine/executor.rs
核心逻辑:
start(instance_id, definition) → 在 StartEvent 创建 token
advance(token_id, instance_id, definition, variables) → 消费当前 token,在下一节点创建新 token
- 到达 EndEvent → 消费 token,检查实例是否所有 token 都完成 → 完成实例
- 到达 UserTask → 创建 token + 创建 task 记录
- 到达 ServiceTask → 创建 token + 执行动作(占位,发布事件)
- 到达 ExclusiveGateway → 求值条件,选择一条分支
- 到达 ParallelGateway(分支)→ 为每条出边创建 token
- 到达 ParallelGateway(汇合)→ 消费当前 token,等待所有入边 token 到达后创建新 token
并发安全: 使用 pg_advisory_xact_lock 保护 token 操作(参考 NumberingService 模式)
验证: 单元测试覆盖:直线流程、排他网关分支、并行网关分支与汇合
Task 7: Service 层
创建文件(crates/erp-workflow/src/service/):
definition_service.rs— 流程定义 CRUD + 发布 + 版本管理instance_service.rs— 启动实例 + 查询 + 挂起/恢复/终止task_service.rs— 查询待办 + 完成任务 + 委派 + 查询已办
关键逻辑:
DefinitionService::publish— draft → published,验证流程图合法性InstanceService::start— 创建实例 + 初始化变量 + 调用 executor.startTaskService::complete— 更新 task 状态 + 调用 executor.advance + 处理下一节点
Task 8: Handler 层
创建文件(crates/erp-workflow/src/handler/):
definition_handler.rs— 5 个端点instance_handler.rs— 4 个端点task_handler.rs— 4 个端点
端点映射:
GET/POST /workflow/definitions
GET /workflow/definitions/{id}
PUT /workflow/definitions/{id}
POST /workflow/definitions/{id}/publish
POST /workflow/instances
GET /workflow/instances
GET /workflow/instances/{id}
POST /workflow/instances/{id}/suspend
POST /workflow/instances/{id}/terminate
GET /workflow/tasks/pending — 我的待办
GET /workflow/tasks/completed — 我的已办
POST /workflow/tasks/{id}/complete — 完成任务
POST /workflow/tasks/{id}/delegate — 委派任务
RBAC: 所有端点使用 require_permission(&ctx, "workflow:xxx")
Task 9: 模块注册 + 种子数据
修改文件:
crates/erp-workflow/src/module.rs— 填充真实路由(12 个端点)crates/erp-auth/src/service/seed.rs— 添加工作流权限crates/erp-server/src/main.rs— 注册 WorkflowModule,合并路由
新增种子权限(8 个):
- workflow:create, workflow:list, workflow:read, workflow:update
- workflow:publish, workflow:start, workflow:approve, workflow:delegate
验证: cargo check + cargo test 通过
Task 10: 前端 API 层 + 工作流页面
创建文件(apps/web/src/):
api/workflowDefinitions.ts— 流程定义 APIapi/workflowInstances.ts— 流程实例 APIapi/workflowTasks.ts— 任务 APIpages/Workflow.tsx— Tab 壳页面(流程定义 | 我的待办 | 我的已办 | 流程监控)
修改文件:
App.tsx— 添加 workflow 路由MainLayout.tsx— 侧边栏添加工作流菜单
Task 11: React Flow 可视化设计器
创建文件(apps/web/src/pages/workflow/):
ProcessDesigner.tsx— React Flow 画布 + 节点面板 + 属性面板nodes/— 自定义节点组件(StartEvent, EndEvent, UserTask, ServiceTask, Gateway)edges/— 条件标签连线组件hooks/useFlowValidation.ts— 流程图前端验证
依赖: @xyflow/react npm 包
功能:
- 拖拽添加节点到画布
- 连线编辑(含条件表达式)
- 节点属性编辑面板
- 导出为 JSON nodes/edges 格式(匹配后端 DTO)
- 流程图合法性前端验证
Task 12: 流程图查看器 + 超时框架
创建文件(apps/web/src/pages/workflow/):
ProcessViewer.tsx— 只读 React Flow 渲染,高亮当前活跃节点InstanceDetail.tsx— 实例详情页(流程图 + 变量 + 任务历史)
超时框架(后端占位):
crates/erp-workflow/src/engine/timeout.rs— 超时检查接口- Task 表
due_date字段已支持
验证: pnpm dev 启动,工作流设计器可拖拽节点、连线、保存
依赖图
Task 1(骨架)
|
Task 2(迁移)→ Task 3(Entity)→ Task 4(DTO)
|
+---------------+---------------+
| |
Task 5(BPMN 解析器) Task 6(执行引擎)
| |
+---------------+---------------+
|
Task 7(Service)
|
Task 8(Handler)
|
Task 9(集成+种子)
|
+---------------+---------------+
| |
Task 10(前端页面) Task 11(可视化设计器)
| |
+---------------+---------------+
|
Task 12(查看器+超时)
验证清单
cargo check全 workspace 通过cargo test --workspace全部通过- Docker 环境正常启动
- 所有迁移可正/反向执行
- 12 个工作流 API 端点可测试
- 前端工作流设计器可拖拽节点和连线
- 流程图保存和加载正常
- 所有代码已提交
关键参考文件
| 用途 | 文件路径 |
|---|---|
| Service 模式 | crates/erp-config/src/service/numbering_service.rs |
| Handler 模式 | crates/erp-config/src/handler/numbering_handler.rs |
| State 桥接 | crates/erp-server/src/state.rs |
| 模块注册 | crates/erp-config/src/module.rs |
| 迁移模式 | crates/erp-server/migration/src/m20260412_000016_create_settings.rs |
| Advisory Lock | crates/erp-config/src/service/numbering_service.rs (generate_number) |
| 前端 Table CRUD | apps/web/src/pages/Roles.tsx |
| 前端树形展示 | apps/web/src/pages/Organizations.tsx |
| RBAC | crates/erp-core/src/rbac.rs |