SpringBoot搭Flowable搞工作流的实现示例
作者:星辰聊技术
一、先把环境整明白:准备工作不踩坑
1. 新建 Spring Boot 项目(手残党也能秒懂)
打开你的 IDEA,新建一个 Spring Boot 项目。记得选 Web 模块,毕竟咱后续可能要搞点接口测试啥的。如果用命令行的话,一行命令搞定:
spring init --name=flowable-demo --groupId=com.example --artifactId=flowable-demo --version=2.7.12 --packaging=jar --dependencies=web,mysql,flowable-spring-boot-starter-process flowable-demo
这里重点说下依赖 flowable-spring-boot-starter-process,这可是 Flowable 和 Spring Boot 联姻的关键信物,自带自动配置功能,能让我们少写一大堆繁琐配置。
2. 数据库配置:和 MySQL 做好朋友
咱先给项目找个「仓库」存流程数据。打开 application.properties,加上数据库配置:
spring.datasource.url=jdbc:mysql://localhost:3306/flowable_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 1.2.3.4.
注意这里用了 MySQL 8.0 的驱动,如果你的数据库是 5.x 版本,记得把驱动换成 com.mysql.jdbc.Driver,不然可能会报「找不到驱动」的玄学错误哦。
3. Flowable 核心配置:让引擎听指挥
接着告诉 Flowable 咱们的需求:
# 关闭自动部署(开发阶段建议关闭,避免每次启动都重新部署流程) flowable.engine.auto-deploy-process-definitions=false # 开启流程模型编辑器(后面画流程图全靠它) flowable.ui.modeler.enabled=true # 设置历史记录级别(记录所有操作,方便排查问题) flowable.history.level=full
这里重点说下 history.level,默认是 audit,只会记录关键事件,咱们设为 full 后,连任务的创建、分配、完成时间都会记录下来,调试的时候简直不要太方便。
二、流程设计初体验:用可视化工具画流程图
1. 启动项目,打开 Modeler 界面
把项目跑起来,访问 http://localhost:8080/flowable-modeler,会看到一个登录界面。别慌,Flowable 默认的账号密码是 admin/admin,输进去就能看到高大上的流程设计器了。
这个界面有点像 Visio,左边是各种流程元素,什么开始事件、用户任务、结束事件,拖出来就能用。咱们来画一个最简单的请假流程:
- 拖一个「开始事件」放在画布上,这就是流程的起点。
- 拖一个「用户任务」,改名叫「部门经理审批」,这一步需要人来处理。
- 再拖一个「用户任务」,改名叫「HR 审批」。
- 最后拖一个「结束事件」。
然后用连接线把它们按顺序连起来,一个简单的串行审批流程就画好了。
2. 给任务节点加属性:告诉引擎找谁干活
双击「部门经理审批」节点,在右边的属性面板里,找到「Assignee」(任务负责人),这里可以写固定的用户 ID,比如 manager,不过实际项目中肯定是动态指定的,后面咱会用代码实现。先记住这个地方,后面会用到。
3. 导出流程定义:让代码认识你的流程图
画好流程图后,点击左上角的保存,会让你输入流程名称、key 等信息。流程 key 很重要,后面启动流程的时候要靠它来识别,比如咱们设为 leaveProcess。保存后,点击右上角的「导出为 XML」,会得到一个 .bpmn20.xml 文件,这个文件就是流程定义的核心,后面要部署到引擎里。
三、代码实现:让流程跑起来才是硬道理
1. 引入流程引擎对象:Flowable 的核心工具
在 Spring Boot 里,Flowable 会自动注入一个 ProcessEngine 对象,里面封装了所有操作流程的方法。咱们可以在需要的地方直接注入:
@Autowired private ProcessEngine processEngine;
这个对象就像一个超级管家,能帮我们部署流程、启动流程、查询任务等等,后面咱会频繁用到它。
2. 部署流程定义:把流程图交给引擎管理
有两种方式部署流程,一种是直接读取本地的 .bpmn20.xml 文件,另一种是通过 Modeler 界面上传部署。咱们先试试代码部署:
@RestController @RequestMapping("/process") public class ProcessController { @Autowired private RepositoryService repositoryService; // 流程定义相关服务 @PostMapping("/deploy") public String deployProcess() { // 读取流程文件 ClassPathResource resource = new ClassPathResource("processes/leaveProcess.bpmn20.xml"); try { InputStream inputStream = resource.getInputStream(); // 部署流程 Deployment deployment = repositoryService.createDeployment() .name("请假流程") .addInputStream("leaveProcess.bpmn20.xml", inputStream) .deploy(); return "部署成功,部署 ID:" + deployment.getId(); } catch (IOException e) { e.printStackTrace(); return "部署失败"; } } }
这里用到了 RepositoryService,它是专门管理流程定义和部署的服务。部署成功后,数据库里的 ACT_RE_DEPLOYMENT、ACT_RE_PROCDEF 等表就会有数据了,这就是流程定义的元数据。
3. 启动流程实例:让流程开始跑起来
部署完流程后,就可以启动流程实例了。比如小明要请假,就需要启动一个请假流程的实例:
@PostMapping("/start") public String startProcess(@RequestParam String userId) { // 使用流程 key 启动流程,并传递参数(这里传递申请人 ID) Map<String, Object> variables = new HashMap<>(); variables.put("applicant", userId); // 申请人 ID,后面审批时可能会用到 ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("leaveProcess", variables); return "流程启动成功,流程实例 ID:" + processInstance.getId(); }
这里用到了 RuntimeService,它是管理运行中的流程实例和执行对象的服务。启动流程时可以传递变量,这些变量可以在流程节点中使用,比如在任务分配时,根据变量动态指定负责人。
4. 查询用户任务:让用户知道该干啥了
当流程走到用户任务节点时,需要查询当前用户有哪些待办任务。比如部门经理登录系统,需要看到自己待审批的任务:
@GetMapping("/tasks") public List<Task> getTasks(@RequestParam String userId) { List<Task> tasks = taskService.createTaskQuery() .taskAssignee(userId) // 根据任务负责人查询 .orderByTaskCreateTime().desc() // 按创建时间倒序排列 .list(); return tasks; }
这里用到了 TaskService,专门管理任务的创建、分配、完成等操作。taskAssignee 就是咱们之前在流程图里设置的任务负责人,不过实际项目中,更多是通过候选人或者候选组来分配任务,后面咱会讲到。
5. 完成任务:推动流程往下走
用户处理完任务后,需要完成任务,让流程走到下一个节点。比如部门经理审批通过,任务就会到 HR 那里:
@PostMapping("/complete") public String completeTask(@RequestParam String taskId, @RequestParam Map<String, Object> variables) { taskService.complete(taskId, variables); // 完成任务时可以传递变量,比如审批意见 return "任务完成,流程继续"; }
这里的 variables 可以传递审批结果、意见等信息,这些信息可以在后续节点中使用,比如 HR 审批时可以看到部门经理的意见。
四、进阶操作:让流程更灵活更强大
1. 动态分配任务:别再写死负责人了
前面咱们在流程图里写死了任务负责人 manager,这在实际项目中肯定不行,毕竟不同的流程可能有不同的审批人。Flowable 支持多种任务分配方式:
- 候选人(Candidate Users):任务可以分配给多个候选人,任何一个候选人都可以认领任务。
// 分配候选人 taskService.addCandidateUser(taskId, "user1"); taskService.addCandidateUser(taskId, "user2"); // 查询候选人任务 List<Task> candidateTasks = taskService.createTaskQuery() .taskCandidateUser(userId) .list();
- 候选组(Candidate Groups):把用户分组,任务分配给组,组内成员都可以处理。
taskService.addCandidateGroup(taskId, "managerGroup");
- 表达式分配:在流程图里用 EL 表达式动态指定负责人,比如根据流程变量 managerId 来分配。
assignee="${managerId}"
2. 流程变量的妙用:让流程更智能
流程变量就像流程里的「全局变量」,可以在各个节点之间传递数据。比如请假流程里的请假天数、请假原因,都可以作为流程变量存储,还可以在流程条件中使用。比如添加一个条件分支,请假天数超过 3 天需要总经理审批:
在流程图里加一个「排他网关」,连接线的条件表达式可以写:
${days > 3}
然后在启动流程时传递 days 变量,流程就会根据这个条件自动选择走向。
3. 历史数据查询:出了问题别慌,有记录可查
前面咱们设置了 history.level=full,现在可以用 HistoryService 来查询历史数据了。比如查询某个流程实例的所有历史任务:
@Autowired private HistoryService historyService; public void queryHistoryTasks(String processInstanceId) { List<HistoricTaskInstance> tasks = historyService.createHistoricTaskInstanceQuery() .processInstanceId(processInstanceId) .orderByHistoricTaskInstanceStartTime().asc() .list(); for (HistoricTaskInstance task : tasks) { System.out.println("任务名称:" + task.getName() + ",负责人:" + task.getAssignee() + ",状态:" + task.getTaskStatus()); } }
还可以查询历史流程变量、历史审批意见等,简直是排查问题的神器。
4. 与 Spring Security 集成:安全问题不能忽视
如果你的项目用了 Spring Security 做权限管理,Flowable 的 Modeler 界面也需要做权限控制。可以自定义一个认证处理器,实现 FlowableAuthenticationProvider 接口,把 Spring Security 的用户信息传递给 Flowable。具体代码有点多,这里就不展开了,后面咱们可以专门写一篇讲权限集成的文章。
五、实战案例:搞一个完整的请假流程
1. 流程图设计(升级版)
咱们来设计一个更真实的请假流程:
- 开始事件 -> 申请人填写请假单(用户任务,收集请假天数、原因等信息)
- -> 排他网关(判断请假天数是否超过 3 天)
是 -> 部门经理审批 -> HR 审批 -> 总经理审批(如果是管理层请假,跳过总经理审批)
否 -> HR 审批
- 结束事件
这里用到了网关和条件分支,让流程更灵活。画流程图的时候,记得给每个节点起有意义的名字,方便后续代码处理。
2. 服务层代码实现(关键逻辑)
@Service publicclass LeaveProcessService { @Autowired private RuntimeService runtimeService; @Autowired private TaskService taskService; // 启动请假流程 publicString startLeaveProcess(String applicant, int days, String reason) { Map<String, Object> variables = new HashMap<>(); variables.put("applicant", applicant); variables.put("days", days); variables.put("reason", reason); ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("leaveProcess", variables); return processInstance.getId(); } // 审批任务(支持通过/拒绝) publicvoid approveTask(String taskId, String approver, boolean approved, String comment) { Task task = taskService.createTaskQuery().taskId(taskId).singleResult(); if (task == null) { thrownew RuntimeException("任务不存在"); } // 先认领任务(如果是候选人) taskService.claim(taskId, approver); Map<String, Object> variables = new HashMap<>(); variables.put("approved", approved); variables.put("comment", comment); taskService.complete(taskId, variables); } // 查询当前用户的待办任务(包括候选任务) public List<Task> get待办任务(String userId) { return taskService.createTaskQuery() .taskAssignee(userId) .or() .taskCandidateUser(userId) .endOr() .orderByTaskCreateTime().desc() .list(); } }
这里重点处理了候选人任务的认领、审批意见的记录,以及复杂的任务查询条件。
3. 测试流程(Postman 走起)
- 启动流程:POST /process/start?userId=xiaoming,请求体传 {"days": 5, "reason": "去看偶像演唱会"}
- 查询小明的任务:此时没有任务,因为第一个任务是申请人填写请假单,这里咱们简化了,假设启动流程后直接到部门经理审批。
- 部门经理审批:POST /process/complete?taskId=123&variables={"approved": true, "comment": "同意"}
- 查看流程状态:通过 runtimeService.createProcessInstanceQuery().processInstanceId(xxx).singleResult() 查看是否走到下一个节点。
六、常见问题与解决方案:踩过的坑都给你填上
1. 依赖冲突:找不到 Flowable 的类
有时候引入 Flowable 依赖后,会和 Spring Boot 的某些版本冲突,比如出现 ClassNotFoundException: org.flowable.engine.ProcessEngine。这时候可以检查 pom.xml 里的依赖版本,确保 Flowable 的版本和 Spring Boot 兼容,或者排除冲突的依赖:
<dependency> <groupId>org.flowable</groupId> <artifactId>flowable-spring-boot-starter-process</artifactId> <version>6.8.0</version> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency>
2. 流程定义版本问题:改了流程图没生效
每次部署流程定义时,Flowable 会生成新的版本。如果启动流程时还是用旧的 key,可能会走到旧版本的流程。可以在部署时加上 deploymenyName 和 key,或者在启动流程时指定版本:
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("leaveProcess", "1.0"); // 指定版本 1.0
或者直接用最新版本:
ProcessInstance processInstance = runtimeService.createProcessInstanceBuilder() .processDefinitionKey("leaveProcess") .latestVersion() .start();
3. 任务查询性能问题:数据量大了查不动
当任务表数据量很大时,taskService.createTaskQuery().list() 会很慢。可以给 ACT_RU_TASK 表的 ASSIGNEE、CANDIDATE_USER 等字段加索引,或者分页查询:
Page<Task> tasks = taskService.createTaskQuery() .taskAssignee(userId) .orderByTaskCreateTime().desc() .listPage(pageable.getPageNumber(), pageable.getPageSize());
4. 中文乱码问题:流程节点名称显示乱码
如果在流程图里用了中文,部署后数据库里显示乱码,需要检查数据库连接是否设置了正确的字符集,以及 Modeler 保存时是否编码正确。确保数据库、连接 URL、表结构都是 UTF-8 编码。
七、Flowable 对比其他工作流引擎:为啥选它?
1. 对比 Activiti
Flowable 其实是从 Activiti 团队分裂出来的,兼容性很好,Activiti 的流程定义可以直接拿到 Flowable 里用。但 Flowable 在性能和扩展性上做了优化,比如支持分布式部署、更好的异步处理,而且社区活跃度更高,文档更详细。
2. 对比 Camunda
Camunda 功能很强大,支持云部署,但商业版收费较高,社区版功能有限。Flowable 保持了开源免费策略,同时提供了企业版支持,性价比更高,适合中小团队使用。
3. 为啥和 Spring Boot 是绝配?
- 自动配置:不用手动写一堆配置类,开箱即用。
- 无缝集成:和 Spring 的依赖注入、事务管理、安全框架完美结合。
- 生态丰富:可以和 Spring Cloud、MyBatis 等框架轻松搭配,拓展性强。
八、总结:爽在哪里?
用 Spring Boot 搭 Flowable 搞工作流,爽就爽在这几个地方:
- 开发效率高:不用从头写工作流逻辑,可视化设计器让流程建模像搭积木。
- 维护成本低:流程变更只需要改流程图,部署新版本即可,代码几乎不用动。
- 扩展性强:支持各种复杂流程场景,无论是串行、并行、分支、循环,都能轻松搞定。
- 调试方便:丰富的历史记录和查询接口,让你随时知道流程走到哪一步,出了问题分分钟定位。
到此这篇关于SpringBoot搭Flowable搞工作流的实现示例的文章就介绍到这了,更多相关SpringBoot搭Flowable工作流内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!