我们在开发自动化办公软件时经常会遇到各种审批流程功能,这个使用就需要使用到工作流引擎。目前主流的工作流引擎有Activiti、Flowable、camunda,其中Flowable是在Activiti的基础上开发出来的,基于BPMN2.0协议,它包括 BPMN(Business Process Model and Notation)引擎、CMMN(Case Management Model and Notation)引擎、DMN(Decision Model and Notation)引擎、表单引擎(Form Engine)等模块。
这里我们基于springboot和PostgreSQL数据库,介绍一下如何搭建一个基础的Flowable工作流框架。
创建项目,添加依赖
- 下图是我在线创建的springboot项目,只添加了Spring Web和Postgre SQL Driver两个依赖。
- 添加Flowable依赖
<dependency><groupId>org.flowable</groupId><artifactId>flowable-spring-boot-starter</artifactId><version>6.7.2</version>
</dependency>
-
配置数据库链接
这里要解释一下,网上有的博文说,添加完数据库,启动服务,就会在数据库里自动创建一堆表,本人测试需要创建bpmn20.xml文件后,数据库才会自动生成对应的表。
spring:datasource:username: postgrespassword: postgresurl: jdbc:postgresql://192.168.2.172/test
到这里已经完成了项目的基础配置。
bpmn20.xml文件制作
在整个工作流框架中,比较核心的是bpmn20.xml文件的制作,如果之前没有一定的基础,制作整个文件还是有点难度的。idea的插件市场里有一个Flowable BPMN visualizer插件,可以在idea中直接用来制作bpmn20.xml文件。
安装插件后,在resources文件夹下创建processes文件夹,流程文件默认放在此文件夹下。idea中右键创建文件,选择BPMN 2.0 file。
注意文件名要以**.bpmn20.xml**结尾。在创建的文件上,右键选择View 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="ask_for_leave" name="ask_for_leave" isExecutable="true"><startEvent id="sid-8f75290b-6d4f-4304-98e3-592be1f19a4e"/><endEvent id="sid-4d3f3115-90f9-4cad-a8d3-df415f8aaa9d"/><userTask id="sid-ca4fe38f-af1d-47ad-8646-d045a82fcb6a" name="请假"/><userTask id="sid-7b7c9585-5913-49fe-910f-fea79e53518c" name="组长审批" flowable:assignee="${taskUser}"/><userTask id="sid-627ef23c-3f54-4536-bda9-bbf0d3243230" name="经理审批" flowable:assignee="${taskUser}"/><exclusiveGateway id="sid-becd3cfd-2ede-4df6-8916-83d9e4b2eb5f" /><sequenceFlow id="sid-b1bdf216-3433-4052-ae80-ad753e16aed7" sourceRef="sid-becd3cfd-2ede-4df6-8916-83d9e4b2eb5f" targetRef="sid-627ef23c-3f54-4536-bda9-bbf0d3243230" name="通过"><conditionExpression xsi:type="tFormalExpression">${finishFlag==\"YES\"}</conditionExpression></sequenceFlow><sequenceFlow id="sid-0b327947-1e7f-48c3-8047-e8afc3d15005" sourceRef="sid-8f75290b-6d4f-4304-98e3-592be1f19a4e" targetRef="sid-ca4fe38f-af1d-47ad-8646-d045a82fcb6a"/><sequenceFlow id="sid-e5703760-35e2-497f-9501-7b616d5c1d02" sourceRef="sid-ca4fe38f-af1d-47ad-8646-d045a82fcb6a" targetRef="sid-7b7c9585-5913-49fe-910f-fea79e53518c"/><sequenceFlow id="sid-17b98be9-ef29-48a8-8cf6-cfde4be9d7c9" sourceRef="sid-7b7c9585-5913-49fe-910f-fea79e53518c" targetRef="sid-becd3cfd-2ede-4df6-8916-83d9e4b2eb5f"/><serviceTask id="sid-ddda9b4d-414e-40d0-aaa1-1a8bbac4d49a" flowable:exclusive="true" name="发送失败消息" flowable:class="com.work.flow.listener.TestServiceListener"/><sequenceFlow id="sid-7f8d7cc2-ddd0-4cb4-9ce6-81e8b42f4717" sourceRef="sid-becd3cfd-2ede-4df6-8916-83d9e4b2eb5f" targetRef="sid-ddda9b4d-414e-40d0-aaa1-1a8bbac4d49a" name="不通过"><conditionExpression xsi:type="tFormalExpression">${finishFlag==\"NO\"}</conditionExpression></sequenceFlow><sequenceFlow id="sid-95ed6745-c15f-4489-a9a5-93f4aeea1f43" sourceRef="sid-ddda9b4d-414e-40d0-aaa1-1a8bbac4d49a" targetRef="sid-4d3f3115-90f9-4cad-a8d3-df415f8aaa9d"/><exclusiveGateway id="sid-03e9886b-d80c-4a5a-8916-76f2b6683727" /><sequenceFlow id="sid-e8f7bf08-9b5b-4f0b-b0aa-c1a74d42e870" sourceRef="sid-03e9886b-d80c-4a5a-8916-76f2b6683727" targetRef="sid-ddda9b4d-414e-40d0-aaa1-1a8bbac4d49a" name="不通过"><conditionExpression xsi:type="tFormalExpression">${finishFlag==\"NO\"}</conditionExpression></sequenceFlow><sequenceFlow id="sid-fc8f3bf6-d817-4d6b-9dbd-f15d11032fae" sourceRef="sid-627ef23c-3f54-4536-bda9-bbf0d3243230" targetRef="sid-03e9886b-d80c-4a5a-8916-76f2b6683727"/><endEvent id="sid-f7bc1956-c2b1-4036-99fa-0318f6da4221"/><sequenceFlow id="sid-2a97e964-4e84-4936-9356-14b7b7b6ecb4" sourceRef="sid-03e9886b-d80c-4a5a-8916-76f2b6683727" targetRef="sid-f7bc1956-c2b1-4036-99fa-0318f6da4221" name="通过"><conditionExpression xsi:type="tFormalExpression">${finishFlag==\"YES\"}</conditionExpression></sequenceFlow></process><bpmndi:BPMNDiagram id="BPMNDiagram_ask_for_leave"><bpmndi:BPMNPlane bpmnElement="ask_for_leave" id="BPMNPlane_ask_for_leave"><bpmndi:BPMNShape id="shape-44c54737-7c91-4844-8210-7db7f6853bb6" bpmnElement="sid-8f75290b-6d4f-4304-98e3-592be1f19a4e"><omgdc:Bounds x="-245.0" y="-65.0" width="30.0" height="30.0"/></bpmndi:BPMNShape><bpmndi:BPMNShape id="shape-12f82f19-3220-442d-893b-b139db4834e4" bpmnElement="sid-4d3f3115-90f9-4cad-a8d3-df415f8aaa9d"><omgdc:Bounds x="-155.0" y="15.0" width="30.0" height="30.0"/></bpmndi:BPMNShape><bpmndi:BPMNShape id="shape-146cee26-7a5b-4da7-9bbb-6c392e645b94" bpmnElement="sid-ca4fe38f-af1d-47ad-8646-d045a82fcb6a"><omgdc:Bounds x="-180.0" y="-75.0" width="55.0" height="50.0"/></bpmndi:BPMNShape><bpmndi:BPMNShape id="shape-bd5bc59b-ab57-4e3c-8dfa-cb2827d9a331" bpmnElement="sid-7b7c9585-5913-49fe-910f-fea79e53518c"><omgdc:Bounds x="-75.0" y="-80.0" width="80.0" height="60.0"/></bpmndi:BPMNShape><bpmndi:BPMNShape id="shape-cc2b2bce-04fd-4641-ae8b-419d9f79c2b0" bpmnElement="sid-627ef23c-3f54-4536-bda9-bbf0d3243230"><omgdc:Bounds x="185.0" y="-97.5" width="100.0" height="55.0"/></bpmndi:BPMNShape><bpmndi:BPMNShape id="shape-43f90d29-b7d6-4a74-8acb-1fc7f3f29289" bpmnElement="sid-becd3cfd-2ede-4df6-8916-83d9e4b2eb5f"><omgdc:Bounds x="55.0" y="-80.0" width="40.0" height="40.0"/></bpmndi:BPMNShape><bpmndi:BPMNEdge id="edge-9eca388f-9baf-4a0b-8201-892c738b6ad0" bpmnElement="sid-b1bdf216-3433-4052-ae80-ad753e16aed7"><omgdi:waypoint x="95.0" y="-60.0"/><omgdi:waypoint x="185.0" y="-70.0"/></bpmndi:BPMNEdge><bpmndi:BPMNEdge id="edge-a5e04944-60fb-4696-ad16-c8092d6c0bb5" bpmnElement="sid-0b327947-1e7f-48c3-8047-e8afc3d15005"><omgdi:waypoint x="-215.0" y="-50.0"/><omgdi:waypoint x="-180.0" y="-50.0"/></bpmndi:BPMNEdge><bpmndi:BPMNEdge id="edge-6fb6a4c1-3b9a-4238-a66b-10f3d8571af1" bpmnElement="sid-e5703760-35e2-497f-9501-7b616d5c1d02"><omgdi:waypoint x="-125.0" y="-50.0"/><omgdi:waypoint x="-75.0" y="-50.0"/></bpmndi:BPMNEdge><bpmndi:BPMNEdge id="edge-e23fcf4e-1309-4639-a40e-9f0fe3fb42b7" bpmnElement="sid-17b98be9-ef29-48a8-8cf6-cfde4be9d7c9"><omgdi:waypoint x="5.0" y="-50.0"/><omgdi:waypoint x="55.0" y="-60.0"/></bpmndi:BPMNEdge><bpmndi:BPMNShape id="shape-d6c43ede-95db-49fe-a468-0c8678deff6f" bpmnElement="sid-ddda9b4d-414e-40d0-aaa1-1a8bbac4d49a"><omgdc:Bounds x="-20.0" y="7.5" width="110.0" height="75.0"/></bpmndi:BPMNShape><bpmndi:BPMNEdge id="edge-80b91b72-184a-4add-878d-a0553d8fdd04" bpmnElement="sid-7f8d7cc2-ddd0-4cb4-9ce6-81e8b42f4717"><omgdi:waypoint x="75.0" y="-40.0"/><omgdi:waypoint x="62.5" y="7.5"/></bpmndi:BPMNEdge><bpmndi:BPMNEdge id="edge-92accc38-c426-45f8-bf82-4ab1d8b6625b" bpmnElement="sid-95ed6745-c15f-4489-a9a5-93f4aeea1f43"><omgdi:waypoint x="-20.0" y="45.0"/><omgdi:waypoint x="-125.0" y="30.0"/></bpmndi:BPMNEdge><bpmndi:BPMNShape id="shape-aed10d1f-11b7-4c70-91ca-63514182f93f" bpmnElement="sid-03e9886b-d80c-4a5a-8916-76f2b6683727"><omgdc:Bounds x="225.0" y="15.0" width="40.0" height="40.0"/></bpmndi:BPMNShape><bpmndi:BPMNEdge id="edge-1a9651dc-930e-4642-b2f3-4ba0b47fe97d" bpmnElement="sid-e8f7bf08-9b5b-4f0b-b0aa-c1a74d42e870"><omgdi:waypoint x="225.0" y="35.0"/><omgdi:waypoint x="90.0" y="45.0"/></bpmndi:BPMNEdge><bpmndi:BPMNEdge id="edge-5e55bb58-f07a-45d5-bb8a-8c13a6c0b9dd" bpmnElement="sid-fc8f3bf6-d817-4d6b-9dbd-f15d11032fae"><omgdi:waypoint x="260.0" y="-42.5"/><omgdi:waypoint x="245.0" y="15.0"/></bpmndi:BPMNEdge><bpmndi:BPMNShape id="shape-12be209b-f581-4e87-b480-41c60acb8d8e" bpmnElement="sid-f7bc1956-c2b1-4036-99fa-0318f6da4221"><omgdc:Bounds x="255.0" y="100.0" width="30.0" height="30.0"/></bpmndi:BPMNShape><bpmndi:BPMNEdge id="edge-7aba8c58-63e5-4c57-b3d7-679ad58b6388" bpmnElement="sid-2a97e964-4e84-4936-9356-14b7b7b6ecb4"><omgdi:waypoint x="245.0" y="55.0"/><omgdi:waypoint x="270.0" y="100.0"/></bpmndi:BPMNEdge></bpmndi:BPMNPlane></bpmndi:BPMNDiagram>
</definitions>
接口开发
-
防止流程图乱码配置
package com.work.flow.config;import org.flowable.spring.SpringProcessEngineConfiguration; import org.flowable.spring.boot.EngineConfigurationConfigurer; import org.springframework.context.annotation.Configuration;@Configuration public class FlowableConfig implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> {@Overridepublic void configure(SpringProcessEngineConfiguration engineConfiguration) {engineConfiguration.setActivityFontName("宋体");engineConfiguration.setLabelFontName("宋体");engineConfiguration.setAnnotationFontName("宋体");} }
-
发起流程接口
package com.work.flow.controller;import org.flowable.bpmn.model.BpmnModel; import org.flowable.engine.*; import org.flowable.engine.runtime.Execution; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.image.ProcessDiagramGenerator; import org.flowable.task.api.Task; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletResponse; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List;@RestController public class TestController {@AutowiredRuntimeService runtimeService;@AutowiredTaskService taskService;@AutowiredRepositoryService repositoryService;@AutowiredProcessEngine processEngine;@GetMapping( "/add")public String addExpense(@RequestParam String userId) {//启动流程HashMap<String, Object> map = new HashMap<>(4);//name="客服代表" flowable:candidateGroups="${customerServiceId}"map.put("userId", userId);//<process id="adviceApply" name="投诉建议" isExecutable="true">ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("ask_for_leave", map);return "提交成功.流程Id为:" + processInstance.getId();} }
-
获取流程图接口
@GetMapping("/pic")public void showPic(HttpServletResponse resp, @RequestParam String processId) throws Exception {System.out.println("收到请求"+processId);List<ProcessInstance> instances =runtimeService.createProcessInstanceQuery().list();for (ProcessInstance instance : instances) {System.out.println(instance.getId());}ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();if (pi == null) {return;}List<Execution> executions = runtimeService.createExecutionQuery().processInstanceId(processId).list();List<String> activityIds = new ArrayList<>();List<String> flows = new ArrayList<>();for (Execution exe : executions) {List<String> ids = runtimeService.getActiveActivityIds(exe.getId());activityIds.addAll(ids);}/*** 生成流程图*/BpmnModel bpmnModel = repositoryService.getBpmnModel(pi.getProcessDefinitionId());ProcessEngineConfiguration engconf = processEngine.getProcessEngineConfiguration();ProcessDiagramGenerator diagramGenerator = engconf.getProcessDiagramGenerator();InputStream in = diagramGenerator.generateDiagram(bpmnModel, "png", activityIds, flows, engconf.getActivityFontName(), engconf.getLabelFontName(), engconf.getAnnotationFontName(), engconf.getClassLoader(), 1.0, false);OutputStream out = null;byte[] buf = new byte[1024];int legth = 0;try {out = resp.getOutputStream();while ((legth = in.read(buf)) != -1) {out.write(buf, 0, legth);}out.flush();} catch (Exception e) {e.printStackTrace();} finally {if (in != null) {in.close();}if (out != null) {out.close();}System.out.println("运行结束");}}
- 流程推进接口
/*** 流转** @param processId 流程id*/@GetMapping("/apply")public String apply(@RequestParam String processId,@RequestParam String userId,@RequestParam boolean isPass) {//查询当前办理人的任务IDTask task = taskService.createTaskQuery()//使用流程实例ID.processInstanceId(processId)//任务办理人.singleResult();if (task == null) {throw new RuntimeException("流程不存在");}//通过审核HashMap<String, Object> map = new HashMap<>();map.put("taskUser", userId);String pass = "NO";if(isPass){pass = "YES";}map.put("finishFlag", pass);taskService.complete(task.getId(), map);return "processed ok!";}
走流程后,再次查看流程图
总结
- 总体bpmn20.xml的配置比较麻烦,需要一定的专业知识
- 上手还是有一定的难度的