工作流插件
作者:唐亚峰 | battcn
字数统计:1.3k 字
核心信息
- 引擎:—— 国产轻量级工作流引擎
Warm-Flow https://warm-flow.cn/ - 服务:
wemirr-platform-workflow - 端口:
5004
概念介绍
什么是工作流?
工作流是将业务流程中的各个环节按照规则串联起来,实现任务的自动流转和协同处理。
典型场景:请假审批、报销审批、合同签订、采购审批等。
核心概念
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 流程定义 │ → │ 流程实例 │ → │ 任务 │
│ Definition │ │ Instance │ │ Task │
└─────────────┘ └─────────────┘ └─────────────┘
模板 运行时 待办项| 概念 | 说明 | 类比 |
|---|---|---|
| 流程定义 | 流程的模板,定义节点和流转规则 | 请假单模板 |
| 流程实例 | 根据定义发起的具体流程 | 张三的请假单 |
| 任务 | 流程中需要人处理的节点 | 等待经理审批 |
架构设计
┌──────────────────────────────────────────────────────────┐
│ 前端界面 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 流程设计 │ │ 发起流程 │ │ 我的待办 │ │ 流程监控 │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└──────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ Workflow Service │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Warm-Flow 引擎 │ │
│ │ • 流程解析 • 节点流转 • 任务分配 • 监听器 │ │
│ └──────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ 数据存储层 │
│ MySQL(流程数据) + Redis(分布式锁) │
└──────────────────────────────────────────────────────────┘功能模块
流程管理
| 功能 | 说明 |
|---|---|
| 流程分类 | 按业务类型分类(请假、报销、采购等) |
| 流程设计 | 可视化设计器,拖拽配置节点 |
| 版本管理 | 支持流程定义的版本控制 |
任务中心
| 功能 | 说明 |
|---|---|
| 我的待办 | 当前需要处理的任务 |
| 我的已办 | 已处理完成的任务 |
| 我发起的 | 自己发起的流程 |
审批操作
| 操作 | 说明 |
|---|---|
| 通过 | 同意,流程流转到下一节点 |
| 拒绝 | 不同意,流程终止 |
| 驳回 | 退回到之前的节点重新处理 |
| 转办 | 将任务转交给其他人 |
| 加签 | 临时增加审批人 |
快速开始
1️⃣ 启动服务
bash
# IDEA 中运行
WorkFlowApplication.java
# 或命令行
mvn spring-boot:run -pl wemirr-plugin/wemirr-platform-workflow2️⃣ 设计流程
进入【工作流】→【流程定义】→【新建】,使用可视化设计器配置流程。
3️⃣ 发起流程
进入【发起流程】,选择流程模板,填写表单后提交。
4️⃣ 处理待办
进入【我的待办】,查看并处理待审批的任务。
API 接口(真实代码)
以下接口来自 wemirr-platform-workflow 模块的真实实现。
流程任务 API
java
// 来自 FlowTaskController.java
@RestController
@RequestMapping("/flow-tasks")
@Tag(name = "流程任务", description = "流程任务管理")
public class FlowTaskController {
@PostMapping("/todo/mine")
@Operation(summary = "我的待办")
public IPage<TodoTaskPageResp> myTodo(@RequestBody TaskPageReq req);
@PostMapping("/done/mine")
@Operation(summary = "我的已办")
public IPage<DoneTaskPageResp> myDone(@RequestBody TaskPageReq req);
@PostMapping("/{id}/approve")
@RedisLock(prefix = "workflow:task:handle", message = "请稍后重试")
@Operation(summary = "审批通过")
public void approve(@PathVariable Long id, @RequestBody WorkflowTaskReq req);
@PostMapping("/{id}/reject")
@Operation(summary = "审批拒绝")
public void reject(@PathVariable Long id, @RequestBody WorkflowTaskReq req);
@PostMapping("/{id}/return")
@Operation(summary = "任务驳回")
public void returnTask(@PathVariable Long id, @RequestBody WorkflowTaskReq req);
@PostMapping("/{id}/terminate")
@Operation(summary = "任务终止")
public void terminate(@PathVariable Long id, @RequestBody WorkflowTaskReq req);
@PostMapping("/{id}/transfer")
@Operation(summary = "任务转办")
public void transfer(@PathVariable Long id, @RequestBody WorkflowTaskReq req);
@PostMapping("/{id}/sign/add")
@Operation(summary = "任务加签")
public void addSign(@PathVariable Long id, @RequestBody WorkflowTaskReq req);
@PostMapping("/{id}/sign/remove")
@Operation(summary = "任务减签")
public void removeSign(@PathVariable Long id, @RequestBody WorkflowTaskReq req);
}流程实例 API
java
// 来自 FlowInstanceController.java
@RestController
@RequestMapping("/flow-instances")
@Tag(name = "流程实例", description = "流程实例管理")
public class FlowInstanceController {
@GetMapping
@Operation(summary = "分页查询")
public IPage<InstancePageResp> page(InstancePageReq req);
@GetMapping("/{id}/detail")
@Operation(summary = "实例详情")
public InstanceExtDetailResp detail(@PathVariable Long id);
@GetMapping("/mine")
@Operation(summary = "我的流程")
public IPage<InstancePageResp> mine(InstancePageReq req);
@PutMapping("/{id}/active")
@Operation(summary = "激活实例")
public void active(@PathVariable Long id);
@PutMapping("/{id}/suspend")
@Operation(summary = "挂起实例")
public void suspend(@PathVariable Long id);
@GetMapping("/{id}/tasks")
@Operation(summary = "任务列表")
public List<Task> tasks(@PathVariable Long id);
@GetMapping("/{id}/tasks/history")
@Operation(summary = "历史任务")
public List<FlowTaskApproveListResp> taskHistory(@PathVariable Long id);
@GetMapping("/{id}/form")
@Operation(summary = "表单预览")
public ProcessInstanceFormPreviewResp form(@PathVariable String id);
}任务处理请求
java
// 来自 WorkflowTaskReq.java
@Data
public class WorkflowTaskReq {
@NotBlank(message = "任务处理意见不能为空")
@Schema(description = "任务处理意见")
private String message;
@Schema(description = "处理对象", example = "1,role:1,role:2")
private String handlerObject;
@Schema(description = "流程变量")
private Map<String, Object> variable = new HashMap<>();
}Service 实现
审批通过
java
// 来自 TaskExtServiceImpl.java
@Override
@Transactional(rollbackFor = Exception.class)
public void pass(Long id, WorkflowTaskReq req) {
Map<String, Object> variable = ObjUtil.defaultIfNull(req.getVariable(), Maps.newHashMap());
variable.put(VariableConstant.VAR_APPROVE_USER, context.nickName());
FlowParams flowParams = FlowParams.build()
.skipType(ApprovalType.PASS.getValue())
.message(req.getMessage())
.hisTaskExt(ApprovalAction.PASS.getValue())
.variable(variable);
taskService.skip(id, flowParams);
}任务驳回
java
@Override
@Transactional(rollbackFor = Exception.class)
public void taskReturn(Long id, WorkflowTaskReq req) {
FlowParams flowParams = FlowParams.build();
flowParams.message(req.getMessage());
taskService.rejectLast(id, flowParams); // 驳回到上一节点
}转办/加签/减签
java
// 转办
@Override
@Transactional(rollbackFor = Exception.class)
public void transfer(Long taskId, WorkflowTaskReq req) {
List<String> list = handlerChangeObject(req);
taskService.transfer(taskId, FlowParams.build()
.message(req.getMessage())
.addHandlers(list));
}
// 加签
@Override
public void addSignature(Long taskId, WorkflowTaskReq req) {
taskService.addSignature(taskId, FlowParams.build()
.addHandlers(handlerChangeObject(req)));
}
// 减签
@Override
public void removeSignature(Long taskId, WorkflowTaskReq req) {
taskService.reductionSignature(taskId, FlowParams.build()
.reductionHandlers(handlerChangeObject(req)));
}
// handlerObject 格式:用户ID 或 role:角色ID
private List<String> handlerChangeObject(WorkflowTaskReq req) {
if (StrUtil.isBlank(req.getHandlerObject())) {
throw CheckedException.badRequest("需要修改的对象不能为空!");
}
return StrUtil.split(req.getHandlerObject(), ",");
}分布式锁保护
审批操作使用 @RedisLock 防止并发处理:
java
@PostMapping("/{id}/approve")
@RedisLock(prefix = "workflow:task:handle", message = "当前已有任务处理中,请稍后重试")
public void approve(@PathVariable @RedisParam Long id, @RequestBody WorkflowTaskReq req) {
taskExtService.pass(id, req);
}扩展开发
自定义表单
业务表单需在前端注册映射关系:
typescript
// apps/web-antd/src/views/wemirr/workflow/register.ts
export const flowComponentsMap = {
'/workflow/leave/index': LeaveForm, // 请假表单
'/workflow/expense/index': ExpenseForm, // 报销表单
};监听器扩展
可通过 Warm-Flow 的监听器在流程节点执行前后插入业务逻辑。
处理人格式
| 格式 | 说明 | 示例 |
|---|---|---|
{userId} | 指定用户 | 1001 |
role:{roleId} | 指定角色 | role:1 |
| 多人 | 逗号分隔 | 1001,1002,role:1 |
