Springboot集成Flowable
1、maven依赖
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.2</version></dependency><!-- MySQL Driver --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version><scope>runtime</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><!-- Flowable Spring Boot Starter (核心依赖) --><dependency><groupId>org.flowable</groupId><artifactId>flowable-spring-boot-starter</artifactId><version>6.8.1</version> <!-- 推荐使用最新稳定版 --></dependency>
2、application.yml配置
spring:# ------------------------ 数据库配置 ------------------------datasource:# MySQL JDBC 驱动类名driver-class-name: com.mysql.cj.jdbc.Driver# 数据库连接 URL# 解释:# jdbc:mysql:// -> 协议# localhost:3306 -> 主机和端口 (如果 MySQL 不在本地或端口不是 3306,请修改)# /your_database_name -> 你的数据库名 (必须先在 MySQL 中创建好)# ? -> 参数开始# useSSL=false -> 禁用 SSL (生产环境建议配置正确的 SSL)# serverTimezone=UTC -> 设置服务器时区为 UTC (强烈建议,避免时区问题)# characterEncoding=utf8 -> 设置字符编码为 UTF-8,支持中文# useUnicode=true -> 使用 Unicode 编码url: jdbc:mysql://192.168.128.154:3306/flowable?useSSL=false&serverTimezone=UTC&characterEncoding=utf8&useUnicode=true# 数据库用户名username: root# 数据库密码password: changeme# ------------------------ Flowable 配置 ------------------------flowable:# 流程引擎配置# 是否在启动时检查并自动创建/更新 Flowable 所需的数据库表# 可选值: false, true, create-drop# - false: 不做任何操作 (推荐生产环境)# - true: 如果表不存在则创建,存在则不修改 (推荐开发环境)# - create-drop: 启动时创建表,关闭时删除表 (仅用于测试)database-schema-update: false# 是否启用 Flowable 的 REST API (通常不需要,自己写 Controller 更好)rest-enabled: false# 是否启用历史记录 (强烈建议开启,用于查询历史任务、流程实例等)history-enabled: true# 历史级别 (决定存储多少历史数据)# 可选: none, activity, audit, full# - none: 不记录历史# - activity: 记录流程实例、任务的开始/结束时间等 (推荐)# - audit: activity + 变量、任务评论等 (常用)# - full: audit + 所有细节 (占用空间大)history-level: audit
3、bpmn.xml
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.flowable.org/processdef"><process id="leaveProcess" name="请假审批流程" isExecutable="true"><!--流程开始--><startEvent id="startEvent" name="开始"/><!--用户任务:员工填写请假申请--><userTask id="applyTask" name="填写请假申请" flowable:assignee="${employee}"/><!--用户任务:直属经理审批--><userTask id="managerTask" name="直属经理审批" flowable:assignee="${manager}"/><!--排他网关:判断请假天数--><exclusiveGateway id="decisionGateway" name="请假天数>3天?"/><!--用户任务:部门经理审批--><userTask id="directorTask" name="部门经理审批" flowable:assignee="${director}"/><!--结束事件:审批通过--><endEvent id="approvedEnd" name="审批通过"/><!--结束事件:审批拒绝--><endEvent id="rejectedEnd" name="审批拒绝"/><!--流程连线--><sequenceFlow id="flow1" sourceRef="startEvent" targetRef="applyTask"/><sequenceFlow id="flow2" sourceRef="applyTask" targetRef="decisionGateway"/><sequenceFlow id="flow3" sourceRef="decisionGateway" targetRef="managerTask"><conditionExpression xsi:type="tFormalExpression"><![CDATA[${leaveDays <= 3}]]></conditionExpression></sequenceFlow><sequenceFlow id="flow4" sourceRef="decisionGateway" targetRef="directorTask"><conditionExpression xsi:type="tFormalExpression"><![CDATA[${leaveDays > 3}]]></conditionExpression></sequenceFlow><sequenceFlow id="flow5" sourceRef="managerTask" targetRef="approvedEnd" name="直属经理通过"><conditionExpression xsi:type="tFormalExpression"><![CDATA[${approvalResult == 'approved'}]]></conditionExpression></sequenceFlow><sequenceFlow id="flow6" sourceRef="managerTask" targetRef="rejectedEnd" name="直属经理拒绝"><conditionExpression xsi:type="tFormalExpression"><![CDATA[${approvalResult == 'rejected'}]]></conditionExpression></sequenceFlow><sequenceFlow id="flow7" sourceRef="directorTask" targetRef="approvedEnd" name="部门经理通过"><conditionExpression xsi:type="tFormalExpression"><![CDATA[${approvalResult == 'approved'}]]></conditionExpression></sequenceFlow><sequenceFlow id="flow8" sourceRef="directorTask" targetRef="rejectedEnd" name="部门经理拒绝"><conditionExpression xsi:type="tFormalExpression"><![CDATA[${approvalResult == 'rejected'}]]></conditionExpression></sequenceFlow></process><bpmndi:BPMNDiagram id="BPMNDiagram_leave"><bpmndi:BPMNPlane bpmnElement="leaveProcess" id="BPMNPlane_leave"><bpmndi:BPMNShape id="shape-88302f20-a91f-42d5-8d08-b122392cec21" bpmnElement="startEvent"><omgdc:Bounds x="-80.25" y="-163.25" width="30.0" height="30.0"/></bpmndi:BPMNShape><bpmndi:BPMNShape id="shape-684e076a-33a8-4da9-9552-098391f4328a" bpmnElement="applyTask"><omgdc:Bounds x="-101.74999" y="-107.5" width="73.0" height="29.5"/></bpmndi:BPMNShape><bpmndi:BPMNShape id="shape-b5d93391-dd1b-4806-9d30-96b85c8cd55f" bpmnElement="managerTask"><omgdc:Bounds x="-186.75" y="15.75" width="60.0" height="28.0"/></bpmndi:BPMNShape><bpmndi:BPMNShape id="shape-0671d532-8840-417a-8175-de2a5c872724" bpmnElement="exclusiveGateway"><omgdc:Bounds x="-85.25" y="-46.25" width="40.0" height="40.0"/></bpmndi:BPMNShape><bpmndi:BPMNShape id="sid-9312e982-dcbb-4747-a927-3d7583c39f7e" bpmnElement="directorTask"><omgdc:Bounds x="-5.25" y="15.749998" width="70.0" height="28.0"/></bpmndi:BPMNShape><bpmndi:BPMNShape id="shape-ad34cbab-fe1c-4e14-9935-c69492da746e" bpmnElement="approvedEnd"><omgdc:Bounds x="-80.25" y="14.75" width="30.0" height="30.0"/></bpmndi:BPMNShape><bpmndi:BPMNShape id="sid-90272d6e-f892-4076-8a6d-d8caa4b634ad" bpmnElement="rejectedEnd"><omgdc:Bounds x="-80.25" y="89.75" width="30.0" height="30.0"/></bpmndi:BPMNShape><bpmndi:BPMNEdge id="edge-7619d941-446a-4163-bf43-ec70fbfc01c7" bpmnElement="flow1"><omgdi:waypoint x="-65.25" y="-133.25"/><omgdi:waypoint x="-65.24999" y="-107.5"/></bpmndi:BPMNEdge><bpmndi:BPMNEdge id="edge-c6119ab5-d7cf-4aaf-95e1-a0a45e0cdd93" bpmnElement="flow2"><omgdi:waypoint x="-65.24999" y="-78.0"/><omgdi:waypoint x="-65.25" y="-46.25"/></bpmndi:BPMNEdge><bpmndi:BPMNEdge id="edge-4dab1629-ba1e-442e-a58f-98ebfde38981" bpmnElement="flow3"><omgdi:waypoint x="-85.25" y="-26.25"/><omgdi:waypoint x="-156.75" y="15.749998"/></bpmndi:BPMNEdge><bpmndi:BPMNEdge id="edge-9ff0151e-e3e4-4984-a1f8-6600f8e92bb6" bpmnElement="flow4"><omgdi:waypoint x="-45.25" y="-26.25"/><omgdi:waypoint x="29.75" y="15.749998"/></bpmndi:BPMNEdge><bpmndi:BPMNEdge id="edge-f9603b09-3997-4108-aa2e-45b20a63042b" bpmnElement="flow5"><omgdi:waypoint x="-126.75" y="29.75"/><omgdi:waypoint x="-80.25" y="29.75"/></bpmndi:BPMNEdge><bpmndi:BPMNEdge id="edge-ea8de88d-da00-4de6-9b10-c8a471e3b393" bpmnElement="flow6"><omgdi:waypoint x="-126.75" y="36.75"/><omgdi:waypoint x="-72.75" y="89.75"/></bpmndi:BPMNEdge><bpmndi:BPMNEdge id="edge-435f472f-ff80-4fa5-9aed-62402a7a3f81" bpmnElement="flow7"><omgdi:waypoint x="-5.25" y="29.749998"/><omgdi:waypoint x="-50.25" y="29.75"/></bpmndi:BPMNEdge><bpmndi:BPMNEdge id="edge-2ff38d3e-f92b-4dd0-a967-f75c9a5ddc21" bpmnElement="flow8"><omgdi:waypoint x="12.25" y="43.749996"/><omgdi:waypoint x="-57.75" y="89.75"/></bpmndi:BPMNEdge></bpmndi:BPMNPlane></bpmndi:BPMNDiagram>
</definitions>
对应的流程图
4、Service代码
import org.example.springboot.starter.common.LeaveRequest;
import org.example.springboot.starter.common.TaskDTO;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.variable.api.history.HistoricVariableInstance;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.util.*;@Service
public class FlowableTaskService {@Autowiredprivate TaskService taskService; // Flowable 的 TaskService@Autowiredprivate RuntimeService runtimeService;@Autowiredprivate HistoryService historyService;@Transactionalpublic LeaveRequest startLeaveProcess(LeaveRequest leave) {// 1. 构建业务对象leave.setStatus("PENDING");// 2. 保存业务数据//this.save(leave);// 3. 设置流程变量Map<String, Object> variables = new HashMap<>();variables.put("employee", leave.getEmployee());variables.put("manager", leave.getManager());variables.put("director", leave.getDirector());variables.put("leaveDays", leave.getLeaveDays());variables.put("approvalResult", ""); // 初始化variables.put("rejectionReason", ""); // 初始化// 4. 启动流程(businessKey = leave_123)String businessKey = "leave_" + UUID.randomUUID();ProcessInstance leaveProcess = runtimeService.startProcessInstanceByKey("leaveProcess",businessKey,variables);// 自动完成“填写申请”任务Task task = taskService.createTaskQuery().processInstanceId(leaveProcess.getProcessInstanceId()).singleResult();if (task != null) {taskService.complete(task.getId()); // 完成任务,触发流程往下走}// 5. 更新流程实例IDleave.setProcessInstanceId(leaveProcess.getId());//this.updateById(leave);return leave;}/*** 查询指定用户的待办任务列表** @param assignee 用户ID (如: "zhangsan")* @return 任务列表*/public List<TaskDTO> getTasksByAssignee(String assignee) {// 创建任务查询对象TaskQuery taskQuery = taskService.createTaskQuery();// 设置查询条件: 任务负责人(assignee) 为指定用户,且任务未完成List<Task> tasks = taskQuery.taskAssignee(assignee) // 关键:查询分配给该用户的所有任务.active() // 只查询未完成的任务.orderByTaskCreateTime().desc() // 按创建时间倒序排列.list(); // 执行查询并返回列表List<TaskDTO> taskDTOs = new ArrayList<>();for (Task task : tasks) {TaskDTO taskDTO = new TaskDTO();BeanUtils.copyProperties(task, taskDTO);taskDTOs.add(taskDTO);}return taskDTOs;}/*** 完成一个任务(审批)* 这是核心的审批方法** @param taskId 任务ID (从 getTasksByAssignee 返回的 Task 对象中获取)* @param approvalResult 审批结果 ("approved" 或 "rejected")* @param variables 其他需要设置的流程变量 (可选,如 rejectionReason)*/public void completeTask(String taskId, String approvalResult, Map<String, Object> variables) {// 如果 variables 为 null,创建一个空的 map,避免空指针if (variables == null) {variables = new HashMap<>();}// 将 approvalResult 也放入变量中,供 sequenceFlow 的条件判断使用variables.put("approvalResult", approvalResult);// 调用 Flowable API 完成任务,并传入所有变量taskService.complete(taskId, variables);// 注意:complete() 方法执行后,流程会根据条件自动流转// 如果 approvalResult='approved',流程会走向 "审批通过" 结束事件// 如果 approvalResult='rejected',流程会走向 "审批拒绝" 结束事件}/*** 获取单个任务的详细信息* (可选方法,用于在审批前查看任务上下文)** @param taskId 任务ID* @return Task 对象*/public Task getTaskById(String taskId) {return taskService.createTaskQuery().taskId(taskId).singleResult(); // 因为 taskId 唯一,使用 singleResult()}/*** 查询审批记录*/public List<Map<String, Object>> getApprovalHistory(String processInstanceId) {// 查询该流程实例中所有已完成的任务List<HistoricTaskInstance> historicTasks = historyService.createHistoricTaskInstanceQuery().processInstanceId(processInstanceId).finished() // 只查已完成的任务.orderByHistoricTaskInstanceEndTime().asc().list();List<Map<String, Object>> result = new ArrayList<>();for (HistoricTaskInstance task : historicTasks) {Map<String, Object> dto = new HashMap<>();dto.put("taskName", task.getName());dto.put("assignee", task.getAssignee());dto.put("startTime", task.getStartTime());dto.put("endTime", task.getEndTime());dto.put("duration", task.getDurationInMillis() / 1000 + "秒");// 查询该任务相关的所有历史变量List<HistoricVariableInstance> variableInstances = historyService.createHistoricVariableInstanceQuery().taskId(task.getId()) // 关键:按任务ID查询.list();// 转为 MapMap<String, Object> variables = new HashMap<>();for (HistoricVariableInstance var : variableInstances) {variables.put(var.getVariableName(), var.getValue());}// 查询该任务执行时的流程变量(如 approvalResult)dto.put("approvalResult", variables.get("approvalResult"));dto.put("rejectionReason", variables.get("rejectionReason"));dto.put("comment", variables.get("comment")); // 可选:审批意见result.add(dto);}return result;}}
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;import java.time.LocalDateTime;@Data
//@TableName("leave_request")
public class LeaveRequest {@TableId(type = IdType.AUTO)private Long id;private String employee; // 申请人IDprivate String employeeName; // 申请人姓名private Integer leaveDays; // 请假天数private String reason; // 事由private String manager; // 直属经理IDprivate String director; // 部门总监IDprivate String status; // PENDING, APPROVED, REJECTEDprivate String processInstanceId; // 关联的流程实例ID@TableField(fill = FieldFill.INSERT)private LocalDateTime createTime;@TableField(fill = FieldFill.UPDATE)private LocalDateTime updateTime;