Skip to content

工作流插件

作者:唐亚峰 | battcn
字数统计:1.3k 字

核心信息


概念介绍

什么是工作流?

工作流是将业务流程中的各个环节按照规则串联起来,实现任务的自动流转和协同处理。

典型场景:请假审批、报销审批、合同签订、采购审批等。

核心概念

┌─────────────┐      ┌─────────────┐      ┌─────────────┐
│  流程定义    │  →   │  流程实例    │  →   │   任务      │
│ Definition  │      │  Instance   │      │   Task      │
└─────────────┘      └─────────────┘      └─────────────┘
     模板                运行时               待办项
概念说明类比
流程定义流程的模板,定义节点和流转规则请假单模板
流程实例根据定义发起的具体流程张三的请假单
任务流程中需要人处理的节点等待经理审批

架构设计

┌──────────────────────────────────────────────────────────┐
│                        前端界面                          │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐ │
│  │ 流程设计  │  │ 发起流程  │  │ 我的待办  │  │ 流程监控  │ │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘ │
└──────────────────────────────────────────────────────────┘


┌──────────────────────────────────────────────────────────┐
│                   Workflow Service                       │
│  ┌──────────────────────────────────────────────────┐   │
│  │                  Warm-Flow 引擎                   │   │
│  │  • 流程解析  • 节点流转  • 任务分配  • 监听器    │   │
│  └──────────────────────────────────────────────────┘   │
└──────────────────────────────────────────────────────────┘


┌──────────────────────────────────────────────────────────┐
│                      数据存储层                          │
│         MySQL(流程数据)  +  Redis(分布式锁)          │
└──────────────────────────────────────────────────────────┘

功能模块

流程管理

功能说明
流程分类按业务类型分类(请假、报销、采购等)
流程设计可视化设计器,拖拽配置节点
版本管理支持流程定义的版本控制

任务中心

功能说明
我的待办当前需要处理的任务
我的已办已处理完成的任务
我发起的自己发起的流程

审批操作

操作说明
通过同意,流程流转到下一节点
拒绝不同意,流程终止
驳回退回到之前的节点重新处理
转办将任务转交给其他人
加签临时增加审批人

快速开始

1️⃣ 启动服务

bash
# IDEA 中运行
WorkFlowApplication.java

# 或命令行
mvn spring-boot:run -pl wemirr-plugin/wemirr-platform-workflow

2️⃣ 设计流程

进入【工作流】→【流程定义】→【新建】,使用可视化设计器配置流程。

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

相关链接