最近工作中有用到工作流的开发,引入了flowable工作流框架,在此记录一下springboot整合flowable工作流框架的过程,以便后续再次使用到时可以做一些参考使用,如果项目中有涉及到流程审批的话,可以使用该框架帮我们实现流程图示化展示的功能,为了快速了解flowable工作流框架的一个使用过程,我们直接步入主题,springboot整合flowable工作流框架的步骤如下:

1、首先创建一个springboot工程,然后引入flowable pom依赖,代码如下:

        <dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.7.0</version>
</dependency>

2、创建流程图定义文档

这里有一个使用flowable-ui可视化的工程来创建流程图定义文档,https://www.wandouip.com/t5i212543/,具体实施过程如下:

先从 https://github.com/flowable/flowable-engine/releases 上下载一个发布文档,这里选择Flowable 6.7.2 release ;然后解压缩文件,将里面的wars文档下的两个jar包(flowable-rest.war、flowable-ui.war)部署到tomcat下,放到webapps文件加下,点击运行,运行存在一个解压缩文件的过程,会产生flowable-rest、flowable-ui文件夹,浏览器输入 http://localhost:8080/flowable-ui,如下图:

用户名密码输入admin/test,如下图:

点击建模器应用程序,点击右上角”创建流程“,填写相关信息,进去后就可以可视化地创建流程,如下图:

上面初步展示了使用可视化制作工具制作流程图的过程,然后导出BPMN2文件,文件内容大概如下:

<?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" exporter="Flowable Open Source Modeler" exporterVersion="6.7.2">
<process id="test" name="test" isExecutable="true">
<startEvent id="startEvent1" flowable:formFieldValidation="true"></startEvent>
<userTask id="sid-EE908BC6-94A5-4C9A-B53A-68890D1241BB" name="初级管理员" flowable:formFieldValidation="true"></userTask>
<sequenceFlow id="sid-BD25A451-66F1-4AF1-B954-5EE5BC5A5782" sourceRef="startEvent1" targetRef="sid-EE908BC6-94A5-4C9A-B53A-68890D1241BB"></sequenceFlow>
<exclusiveGateway id="sid-57C003F8-C0F3-4499-8B1D-0B29223DB541"></exclusiveGateway>
<sequenceFlow id="sid-D9571C82-9071-41EA-9858-D10FF50B4396" sourceRef="sid-EE908BC6-94A5-4C9A-B53A-68890D1241BB" targetRef="sid-57C003F8-C0F3-4499-8B1D-0B29223DB541"></sequenceFlow>
<userTask id="sid-1EF0B969-8092-4EC5-9899-16D066122EC8" name="高级管理员" flowable:formFieldValidation="true"></userTask>
<sequenceFlow id="sid-7E40EF52-4ED7-4785-954B-3530C3400D81" sourceRef="sid-57C003F8-C0F3-4499-8B1D-0B29223DB541" targetRef="sid-1EF0B969-8092-4EC5-9899-16D066122EC8"></sequenceFlow>
<intermediateThrowEvent id="sid-13E2B97C-78C9-4A4B-BAD8-A47038668B5F"></intermediateThrowEvent>
<sequenceFlow id="sid-72B217BF-4F36-446E-B48F-741E3F38B66F" sourceRef="sid-57C003F8-C0F3-4499-8B1D-0B29223DB541" targetRef="sid-13E2B97C-78C9-4A4B-BAD8-A47038668B5F"></sequenceFlow>
<exclusiveGateway id="sid-DB335453-27CB-4992-824A-3C060312F59F"></exclusiveGateway>
<sequenceFlow id="sid-60B0EF34-8DB4-421C-A265-59E8A5B7C35F" sourceRef="sid-1EF0B969-8092-4EC5-9899-16D066122EC8" targetRef="sid-DB335453-27CB-4992-824A-3C060312F59F"></sequenceFlow>
<intermediateThrowEvent id="sid-D6512D07-3215-48FC-A568-227A718EC174"></intermediateThrowEvent>
<sequenceFlow id="sid-0ABE9D33-F08C-4787-A827-27639909C63A" sourceRef="sid-DB335453-27CB-4992-824A-3C060312F59F" targetRef="sid-D6512D07-3215-48FC-A568-227A718EC174"></sequenceFlow>
<intermediateThrowEvent id="sid-3F43BAB4-6D7A-4705-A527-D78CF919EEC2"></intermediateThrowEvent>
<sequenceFlow id="sid-50C31AEE-6B3A-48A2-92D3-7D640E5FF60E" sourceRef="sid-DB335453-27CB-4992-824A-3C060312F59F" targetRef="sid-3F43BAB4-6D7A-4705-A527-D78CF919EEC2"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_test">
<bpmndi:BPMNPlane bpmnElement="test" id="BPMNPlane_test">
<bpmndi:BPMNShape bpmnElement="startEvent1" id="BPMNShape_startEvent1">
<omgdc:Bounds height="30.0" width="30.0" x="90.0" y="166.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-EE908BC6-94A5-4C9A-B53A-68890D1241BB" id="BPMNShape_sid-EE908BC6-94A5-4C9A-B53A-68890D1241BB">
<omgdc:Bounds height="80.0" width="100.0" x="240.0" y="141.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-57C003F8-C0F3-4499-8B1D-0B29223DB541" id="BPMNShape_sid-57C003F8-C0F3-4499-8B1D-0B29223DB541">
<omgdc:Bounds height="40.0" width="40.0" x="385.0" y="161.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-1EF0B969-8092-4EC5-9899-16D066122EC8" id="BPMNShape_sid-1EF0B969-8092-4EC5-9899-16D066122EC8">
<omgdc:Bounds height="80.0" width="100.0" x="470.0" y="141.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-13E2B97C-78C9-4A4B-BAD8-A47038668B5F" id="BPMNShape_sid-13E2B97C-78C9-4A4B-BAD8-A47038668B5F">
<omgdc:Bounds height="30.0" width="30.0" x="390.0" y="270.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-DB335453-27CB-4992-824A-3C060312F59F" id="BPMNShape_sid-DB335453-27CB-4992-824A-3C060312F59F">
<omgdc:Bounds height="40.0" width="40.0" x="615.0" y="161.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-D6512D07-3215-48FC-A568-227A718EC174" id="BPMNShape_sid-D6512D07-3215-48FC-A568-227A718EC174">
<omgdc:Bounds height="30.0" width="30.0" x="700.0" y="166.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-3F43BAB4-6D7A-4705-A527-D78CF919EEC2" id="BPMNShape_sid-3F43BAB4-6D7A-4705-A527-D78CF919EEC2">
<omgdc:Bounds height="30.0" width="30.0" x="620.0" y="270.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sid-0ABE9D33-F08C-4787-A827-27639909C63A" id="BPMNEdge_sid-0ABE9D33-F08C-4787-A827-27639909C63A" flowable:sourceDockerX="20.5" flowable:sourceDockerY="20.5" flowable:targetDockerX="15.0" flowable:targetDockerY="15.0">
<omgdi:waypoint x="654.557806573957" y="181.379746835443"></omgdi:waypoint>
<omgdi:waypoint x="700.0002881553987" y="181.0940234142237"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-72B217BF-4F36-446E-B48F-741E3F38B66F" id="BPMNEdge_sid-72B217BF-4F36-446E-B48F-741E3F38B66F" flowable:sourceDockerX="20.5" flowable:sourceDockerY="20.5" flowable:targetDockerX="15.5" flowable:targetDockerY="3.0">
<omgdi:waypoint x="405.5" y="200.43965611353715"></omgdi:waypoint>
<omgdi:waypoint x="405.5" y="270.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-BD25A451-66F1-4AF1-B954-5EE5BC5A5782" id="BPMNEdge_sid-BD25A451-66F1-4AF1-B954-5EE5BC5A5782" flowable:sourceDockerX="15.0" flowable:sourceDockerY="15.0" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
<omgdi:waypoint x="119.94999946593475" y="181.0"></omgdi:waypoint>
<omgdi:waypoint x="239.9999999999298" y="181.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-D9571C82-9071-41EA-9858-D10FF50B4396" id="BPMNEdge_sid-D9571C82-9071-41EA-9858-D10FF50B4396" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="20.5" flowable:targetDockerY="20.5">
<omgdi:waypoint x="339.9499999999977" y="181.21623376623376"></omgdi:waypoint>
<omgdi:waypoint x="385.4130434782609" y="181.41304347826087"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-7E40EF52-4ED7-4785-954B-3530C3400D81" id="BPMNEdge_sid-7E40EF52-4ED7-4785-954B-3530C3400D81" flowable:sourceDockerX="20.5" flowable:sourceDockerY="20.5" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
<omgdi:waypoint x="424.52473707274277" y="181.41666666666666"></omgdi:waypoint>
<omgdi:waypoint x="469.99999999999386" y="181.21812227074238"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-60B0EF34-8DB4-421C-A265-59E8A5B7C35F" id="BPMNEdge_sid-60B0EF34-8DB4-421C-A265-59E8A5B7C35F" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="20.5" flowable:targetDockerY="20.5">
<omgdi:waypoint x="569.9499999999981" y="181.21623376623378"></omgdi:waypoint>
<omgdi:waypoint x="615.4130434782609" y="181.41304347826087"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-50C31AEE-6B3A-48A2-92D3-7D640E5FF60E" id="BPMNEdge_sid-50C31AEE-6B3A-48A2-92D3-7D640E5FF60E" flowable:sourceDockerX="20.5" flowable:sourceDockerY="20.5" flowable:targetDockerX="15.0" flowable:targetDockerY="15.0">
<omgdi:waypoint x="635.4077669902913" y="200.53271096023283"></omgdi:waypoint>
<omgdi:waypoint x="635.072221397189" y="270.00017140256364"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>

以上初步实现怎么使用可视化工具来创建流程图定义文件,里面更为具体的使用方法还有待探索,上面的步骤执行完毕后,我们就可以运行我们的springboot工程了,在配置文件中配置好数据库链接相关的信息后,然后配置flowable相关信息:

flowable:
aysnc-executor-activate: false
database-schema-update: true
process-definition-location-prefix: classpath*:/processes/
process-definition-location-suffixes: "**.bpmn20.xml, **.bpmn"

上面的信息配置完毕后,就可以运行我们的工程了,在运行工程之前,在工程的resources文件夹下创建一个processes文件夹,我们的流程定义文档就放在这个文件夹下,创建一个process-test.bpmn20.xml放到processes文件夹下,具体内容定义如下:

<?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:activiti="http://activiti.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.activiti.org/test">
<process id="processTest" name="Request Resource Approval " isExecutable="true">
<startEvent id="starter" name="Starter"></startEvent>
<serviceTask id="sendJuniorRejectEmail" name="发送初级审批拒绝邮件"
activiti:class="com.chinaums.web.controller.flowable2.delegate.SendJuniorRejectionMailDelegate">
</serviceTask>
<endEvent id="juniorRejectEnd" name="Junior Reject End"></endEvent>
<sequenceFlow id="flow5" sourceRef="sendJuniorRejectEmail" targetRef="juniorRejectEnd"></sequenceFlow>
<userTask id="seniorApproval" name="高级审批" activiti:assignee="${seniorAdmin}"></userTask>
<userTask id="juniorApproval" name="初级审批" activiti:assignee="${juniorAdmin}"></userTask>
<exclusiveGateway id="exclusivegateway1" name="Exclusive Gateway1"></exclusiveGateway>
<sequenceFlow id="juniorSuccessFlow" name="同意" sourceRef="exclusivegateway1" targetRef="seniorApproval">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[${approved=='Y'}]]>
</conditionExpression>
</sequenceFlow>
<sequenceFlow id="juniorRejectFlow" name="拒绝" sourceRef="exclusivegateway1" targetRef="sendJuniorRejectEmail">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[${approved=='N'}]]>
</conditionExpression>
</sequenceFlow>
<exclusiveGateway id="exclusivegateway2" name="Exclusive Gateway2"></exclusiveGateway>
<sequenceFlow id="flow7" sourceRef="seniorApproval" targetRef="exclusivegateway2"></sequenceFlow>
<endEvent id="approvalSuccessEnd" name="Approval Success End"></endEvent>
<sequenceFlow id="seniorSuccessFlow" name="同意" sourceRef="exclusivegateway2" targetRef="sendApprovalSuccessEmail">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[${approved=='Y'}]]>
</conditionExpression>
</sequenceFlow>
<serviceTask id="sendSeniorRejectEmail" name="发送高级审批拒绝邮件"
activiti:class="com.chinaums.web.controller.flowable2.delegate.SendSeniorRejectionMailDelegate">
</serviceTask>
<endEvent id="seniorRejectEnd" name="Senior Reject End"></endEvent>
<sequenceFlow id="seniorRejectFlow" name="拒绝" sourceRef="exclusivegateway2" targetRef="sendSeniorRejectEmail">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[${approved=='N'}]]>
</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow9" sourceRef="sendSeniorRejectEmail" targetRef="seniorRejectEnd"></sequenceFlow>
<sequenceFlow id="flow11" sourceRef="juniorApproval" targetRef="exclusivegateway1"></sequenceFlow>
<sequenceFlow id="flow12" sourceRef="starter" targetRef="juniorApproval"></sequenceFlow>
<serviceTask id="sendApprovalSuccessEmail" name="发送审批通过邮件"
activiti:class="com.chinaums.web.controller.flowable2.delegate.SendApprovalSuccessEmailDelegate"></serviceTask>
<sequenceFlow id="flow13"
sourceRef="sendApprovalSuccessEmail"
targetRef="approvalSuccessEnd"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_requestResourceApprovalProcess">
<bpmndi:BPMNPlane bpmnElement="requestResourceApprovalProcess" id="BPMNPlane_requestResourceApprovalProcess">
<bpmndi:BPMNShape bpmnElement="starter" id="BPMNShape_starter">
<omgdc:Bounds height="35.0" width="35.0" x="45.0" y="118.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sendJuniorRejectEmail" id="BPMNShape_sendJuniorRejectEmail">
<omgdc:Bounds height="71.0" width="171.0" x="340.0" y="230.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="juniorRejectEnd" id="BPMNShape_juniorRejectEnd">
<omgdc:Bounds height="35.0" width="35.0" x="408.0" y="385.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="seniorApproval" id="BPMNShape_seniorApproval">
<omgdc:Bounds height="78.0" width="121.0" x="575.0" y="95.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="juniorApproval" id="BPMNShape_juniorApproval">
<omgdc:Bounds height="81.0" width="115.0" x="170.0" y="95.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="exclusivegateway1" id="BPMNShape_exclusivegateway1">
<omgdc:Bounds height="40.0" width="40.0" x="405.0" y="113.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="exclusivegateway2" id="BPMNShape_exclusivegateway2">
<omgdc:Bounds height="40.0" width="40.0" x="765.0" y="115.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="approvalSuccessEnd" id="BPMNShape_approvalSuccessEnd">
<omgdc:Bounds height="35.0" width="35.0" x="1140.0" y="117.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sendSeniorRejectEmail" id="BPMNShape_sendSeniorRejectEmail">
<omgdc:Bounds height="71.0" width="192.0" x="690.0" y="230.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="seniorRejectEnd" id="BPMNShape_seniorRejectEnd">
<omgdc:Bounds height="35.0" width="35.0" x="768.0" y="385.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sendApprovalSuccessEmail" id="BPMNShape_sendApprovalSuccessEmail">
<omgdc:Bounds height="75.0" width="141.0" x="920.0" y="96.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5">
<omgdi:waypoint x="425.0" y="301.0"></omgdi:waypoint>
<omgdi:waypoint x="425.0" y="385.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="juniorSuccessFlow" id="BPMNEdge_juniorSuccessFlow">
<omgdi:waypoint x="445.0" y="133.0"></omgdi:waypoint>
<omgdi:waypoint x="575.0" y="134.0"></omgdi:waypoint>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="16.0" width="32.0" x="445.0" y="133.0"></omgdc:Bounds>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="juniorRejectFlow" id="BPMNEdge_juniorRejectFlow">
<omgdi:waypoint x="425.0" y="153.0"></omgdi:waypoint>
<omgdi:waypoint x="425.0" y="230.0"></omgdi:waypoint>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="16.0" width="32.0" x="425.0" y="153.0"></omgdc:Bounds>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow7" id="BPMNEdge_flow7">
<omgdi:waypoint x="696.0" y="134.0"></omgdi:waypoint>
<omgdi:waypoint x="765.0" y="135.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="seniorSuccessFlow" id="BPMNEdge_seniorSuccessFlow">
<omgdi:waypoint x="805.0" y="135.0"></omgdi:waypoint>
<omgdi:waypoint x="920.0" y="133.0"></omgdi:waypoint>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="16.0" width="32.0" x="805.0" y="135.0"></omgdc:Bounds>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="seniorRejectFlow" id="BPMNEdge_seniorRejectFlow">
<omgdi:waypoint x="785.0" y="155.0"></omgdi:waypoint>
<omgdi:waypoint x="786.0" y="230.0"></omgdi:waypoint>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="16.0" width="32.0" x="785.0" y="155.0"></omgdc:Bounds>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow9" id="BPMNEdge_flow9">
<omgdi:waypoint x="786.0" y="301.0"></omgdi:waypoint>
<omgdi:waypoint x="785.0" y="385.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow11" id="BPMNEdge_flow11">
<omgdi:waypoint x="285.0" y="135.0"></omgdi:waypoint>
<omgdi:waypoint x="405.0" y="133.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow12" id="BPMNEdge_flow12">
<omgdi:waypoint x="80.0" y="135.0"></omgdi:waypoint>
<omgdi:waypoint x="170.0" y="135.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow13" id="BPMNEdge_flow13">
<omgdi:waypoint x="1061.0" y="133.0"></omgdi:waypoint>
<omgdi:waypoint x="1140.0" y="134.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>

上面文件中的process标签的id值是该流程定义的唯一标识,在创建流程实例时需要传入processId标识,上面的assignee变量后面定义了一个变量${seniorAdmin},这个是由接口调用时传入的,activiti:class后面的值是一个类,表示执行到这个步骤时会触发执行某个动作,比如id为sendJuniorRectEmail的serviceTask中的class定义如下:

@Slf4j
public class SendJuniorRejectionMailDelegate implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
String requestUser = (String) execution.getVariable("requestUser");
String resourceId = (String) execution.getVariable("resourceId");
System.out.println("SendJuniorRejectionMailDelegate");
log.info("send approval success mail for user [" + requestUser + "] with apply resource [" + resourceId + "]");
}
}

上面的类需要实现JavaDelegate这个接口,上面的内容定义完毕后,就可以定义我们的实现方法了,先创建一个接口,定义一些方法:

public interface IProcess {

    /**
* 创建一个流程实例,创建实例时会登记一些信息,这些信息可以通过调用
* queryProcessVariables方法获取到,调用时需要传递processInstanceId
* @param paramObj
* @return
*/
ProcessInstanceEntity startProcess(ParamObj paramObj); /**
* 获取指定工作人的代办任务
* @param assignee
* @return
*/
List<TaskInstanceEntity> taskInstance(String assignee); /**
* 处理工作
* @param paramObj
*/
void handleTask(ParamObj paramObj); /**
* 获取某个流程实体的状态,各个审批环节所处的状态信息
* @param processInstanceId
* @return
*/
List<ProcessStatusEntity> queryProcessStatus(String processInstanceId); /**
* 查看创建流程实例时登记的变量信息
* @param processInstanceId
* @return
*/
Map<String,Object> queryProcessVariables(String processInstanceId); /**
* 获取某人的历史审批数据
* @param assignee
* @return
*/
List<HistanceInstanceEntity> queryHistoryProcess(String assignee); /**
* 生成流程的图谱
* @param httpServletResponse
* @param processInstanceId
*/
void genProcessDiagram(HttpServletResponse httpServletResponse, String processInstanceId) throws Exception; /**
* 查询是否存在历史数据的流程实例
* @param processInstanceId
* @return
*/
boolean isExistHistoricProcessInstance(String processInstanceId); /**
* 查询指定的流程是否是运行中的流程
* @param processInstanceId
* @return
*/
boolean isExistRunningProcessInstance(String processInstanceId); /**
* 将指定的流程挂起
* @param processInstanceId
*/
void suspendProcessInstance(String processInstanceId); /**
* 终止项目流程
* @param paramObj
*/
void terminateProcessInstance(ParamObj paramObj); /**
* 将指定的流程激活
* @param processInstanceId
*/
void activateProcessInstance(String processInstanceId); /**
* 删除流程实例
* @param paramObj
*/
void deleteProcessInstance(ParamObj paramObj); /**
* 将任务返回到某一步骤
* @param taskId
* @param targetTaskKey 返回到的目标任务ID
*/
void rollbackTask(String taskId, String targetTaskKey); boolean isProcessFinished(String processInstanceId);
}

经网友提示,补充一下上面这个接口涉及到的实体类:

@Data
public class HistoryInstanceEntity {
String processInstanceId;
String taskId;
Date startTime;
Date endTime;
} @Data
public class ParamObj {
//startProcess--the following 5 params
@NotNull(message = "resourceId 不能为空")
String resourceId;
String requestUser;
String juniorAdmin;
String seniorAdmin;
String assignee;
//handleTask--the following 3 params
String comment;
boolean approved;
String taskId;
//delete/get processInstance
String processInstanceId;
String deleteReason; //rollback
String targetTaskKey;
} @Data
public class ProcessInstanceEntity {
String processInstanceId;
String processDeploymentId;
String activityId;
} @Data
public class ProcessStatusEntity {
String taskName;
String taskId;
String assignee;
Date createTime;
String approved;
String comment;
String processInstanceId;
String processDefinitionId;
} @Data
public class TaskInstanceEntity {
private String taskId;
private String taskName;
private String processInstanceId;
private String requestUser;
private String resourceId;
private Date createTime;
}

定义好接口后,再定义一个实现类:

@Service
public class IProcessImpl implements IProcess { @Autowired
private RepositoryService repositoryService; @Autowired
private RuntimeService runtimeService; @Autowired
private TaskService taskService; @Autowired
private HistoryService historyService; @Autowired
private ProcessEngine processEngine; @Autowired
ManagementService managementService; @Override
public ProcessInstanceEntity startProcess(ParamObj paramObj) {
Map<String, Object> variables = new HashMap<>();
// 请求的资源ID
variables.put("resourceId", paramObj.getResourceId());
// 请求发起用户
variables.put("requestUser", paramObj.getRequestUser());
// 初级审批用户
variables.put("juniorAdmin", paramObj.getJuniorAdmin());
// 高级审批用户
variables.put("seniorAdmin", paramObj.getSeniorAdmin());
ProcessInstance processInstance=runtimeService.
startProcessInstanceByKey(ConstantValues.FLOWABLE_PROCESS_TEST, variables);
ProcessInstanceEntity entity=new ProcessInstanceEntity();
entity.setProcessDeploymentId(processInstance.getDeploymentId());
entity.setProcessInstanceId(processInstance.getProcessInstanceId());
entity.setActivityId(processInstance.getActivityId());
return entity;
} @Override
public List<TaskInstanceEntity> taskInstance(String assignee) {
List<TaskInstanceEntity> entities=new ArrayList<>();
List<Task> tasks= taskService.createTaskQuery().taskAssignee(assignee).orderByTaskCreateTime().desc().list();
if(!CollectionUtils.isEmpty(tasks)){
tasks.stream().forEach(task -> {
TaskInstanceEntity entity=new TaskInstanceEntity();
String id=task.getId();
entity.setCreateTime(task.getCreateTime());
entity.setTaskName(task.getName());
entity.setProcessInstanceId(task.getProcessInstanceId());
entity.setTaskId(id);
Map<String, Object> processVariables = taskService.getVariables(id);
entity.setRequestUser(processVariables.get("requestUser").toString());
entity.setResourceId(processVariables.get("resourceId").toString());
entities.add(entity);
});
}
return entities;
} @Override
public void handleTask(ParamObj paramObj) {
Map<String, Object> taskVariables = new HashMap<>();
String approved=paramObj.isApproved()?"Y":"N";
taskVariables.put("approved", approved);
//审核结果和审核意见都封装为JSON然后放在评论里,后续需要进行逆操作。
ObjectMapper objectMapper = new ObjectMapper();
Map<String, String> map= new HashMap<>();
map.put("approved", approved);
map.put("comment", paramObj.getComment());
try {
String json = objectMapper.writeValueAsString(map);
taskService.addComment(paramObj.getTaskId(), null, json);
taskService.complete(paramObj.getTaskId(), taskVariables);
} catch (Exception e) {
throw new RuntimeException(e);
}
} @Override
public List<ProcessStatusEntity> queryProcessStatus(String processInstanceId) {
List<ProcessStatusEntity> result = new ArrayList<>();
List<HistoricTaskInstance> historicTaskInstances = historyService.createHistoricTaskInstanceQuery()
.processInstanceId(processInstanceId).list();
if(CollectionUtils.isEmpty(historicTaskInstances)) {
throw new RuntimeException("Process instance [" + processInstanceId + "] not exist");
}
for (HistoricTaskInstance hti : historicTaskInstances) {
String taskId = hti.getId();
String taskName = hti.getName();
String assignee = hti.getAssignee();
Date createTime = hti.getCreateTime();
String comment = null;
String approved=null;
List<Comment> comments = taskService.getTaskComments(taskId);
if (!CollectionUtils.isEmpty(comments)) {
comment = comments.get(0).getFullMessage();
if(null!=comment) {
//这里进行评论的JSON数据的逆操作提取数据
ObjectMapper mapper = new ObjectMapper();
try {
Map<String,Object> data = mapper.readValue(comment, Map.class);
approved=data.get("approved").toString();
comment=data.get("comment").toString();
} catch (Exception e) {
System.out.println(e.toString());
}
}
}
ProcessStatusEntity pd=new ProcessStatusEntity();
pd.setTaskName(taskName);
pd.setAssignee(assignee);
pd.setCreateTime(createTime);
pd.setApproved(approved);
pd.setComment(comment);
pd.setTaskId(hti.getId());
pd.setProcessInstanceId(hti.getProcessInstanceId());
result.add(pd);
}
return result;
} @Override
public Map<String, Object> queryProcessVariables(String processInstanceId) {
List<HistoricVariableInstance> historicVariableInstances =
historyService.createHistoricVariableInstanceQuery()
.processInstanceId(processInstanceId).list();
if (historicVariableInstances == null) {
throw new RuntimeException("Process instance [" + processInstanceId + "] not exist");
}
Map<String,Object> ret= new HashMap<>();
for(HistoricVariableInstance var: historicVariableInstances) {
ret.put(var.getVariableName(), var.getValue());
}
return ret;
} @Override
public List<HistanceInstanceEntity> queryHistoryProcess(String assignee) {
List<HistanceInstanceEntity> result=new ArrayList<>();
List<HistoricActivityInstance> activities = historyService.createHistoricActivityInstanceQuery()
.taskAssignee(assignee).finished().orderByHistoricActivityInstanceEndTime().desc().list();
for(HistoricActivityInstance h : activities) {
HistanceInstanceEntity d=new HistanceInstanceEntity();
d.setProcessInstanceId(h.getProcessInstanceId());
d.setTaskId(h.getTaskId());
d.setStartTime(h.getStartTime());
d.setEndTime(h.getEndTime());
result.add(d);
}
return result;
} @Override
public void genProcessDiagram(HttpServletResponse httpServletResponse,
String processInstanceId) throws Exception{
ProcessInstance pi = runtimeService.createProcessInstanceQuery().
processInstanceId(processInstanceId).singleResult();
//流程走完的不显示图
if (pi == null) {
// System.out.println("不存在该流程或则流程已经走完");
throw new RuntimeException("不存在该流程或则流程已经走完");
// return;
}
Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
//使用流程实例ID,查询正在执行的执行对象表,返回流程实例对象
String InstanceId = task.getProcessInstanceId();
List<Execution> executions = runtimeService
.createExecutionQuery()
.processInstanceId(InstanceId)
.list();
//得到正在执行的Activity的Id
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 engineConf = processEngine.getProcessEngineConfiguration();
ProcessDiagramGenerator diagramGenerator = engineConf.getProcessDiagramGenerator();
InputStream in = diagramGenerator.generateDiagram(bpmnModel,
"png",
activityIds,
flows,
engineConf.getActivityFontName(),
engineConf.getLabelFontName(),
engineConf.getAnnotationFontName(),
engineConf.getClassLoader(),
1.0,true);
OutputStream out = null;
byte[] buf = new byte[1024];
int length = 0;
try {
out = httpServletResponse.getOutputStream();
while ((length = in.read(buf)) != -1) {
out.write(buf, 0, length);
}
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
} @Override
public boolean isExistHistoricProcessInstance(String processInstanceId) {
HistoricProcessInstance historicProcessInstance =
historyService.createHistoricProcessInstanceQuery().
processInstanceId(processInstanceId).singleResult();
if (historicProcessInstance == null) {
return false;
}
return true;
} @Override
public boolean isExistRunningProcessInstance(String processInstanceId) {
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().
processInstanceId(processInstanceId).singleResult();
if (processInstance == null) {
return false;
}
return true;
} @Override
public void suspendProcessInstance(String processInstanceId) {
runtimeService.suspendProcessInstanceById(processInstanceId);
} @Override
public void terminateProcessInstance(ParamObj paramObj) {
runtimeService.deleteProcessInstance(paramObj.getProcessInstanceId(),paramObj.getDeleteReason());
} @Override
public void activateProcessInstance(String processInstanceId) {
runtimeService.activateProcessInstanceById(processInstanceId);
} @Override
public void deleteProcessInstance(ParamObj paramObj) {
//查询是否操作
long count = runtimeService.createExecutionQuery().processInstanceId(paramObj.getProcessInstanceId()).count();
if(count>0){
DeleteFlowableProcessInstanceCmd cmd=
new DeleteFlowableProcessInstanceCmd(paramObj.getProcessInstanceId(),
paramObj.getDeleteReason(),true);
managementService.executeCommand(cmd);
//runtimeService.deleteProcessInstance(processInstanceId,deleteReason);
}else{
//删除历史数据的流程实体
historyService.deleteHistoricProcessInstance(paramObj.getProcessInstanceId());
}
} @Override
public void rollbackTask(String taskId, String targetTaskKey) {
Task currentTask = taskService.createTaskQuery().taskId(taskId).singleResult();
if (currentTask == null) {
return ;
}
List<String> key = new ArrayList<>();
key.add(currentTask.getTaskDefinitionKey());
runtimeService.createChangeActivityStateBuilder()
.processInstanceId(currentTask.getProcessInstanceId())
.moveActivityIdsToSingleActivityId(key, targetTaskKey)
.changeState();
} @Override
public boolean isProcessFinished(String processInstanceId) {
return historyService.createHistoricProcessInstanceQuery().finished()
.processInstanceId(processInstanceId).count()>0;
}
}

实现类中注入的变量都是flowable框架中的变量,实现类中的方法的作用在接口中都有向相关注释,其中deleteProcessInstance方法中会引用一个类来删除流程实例,DeleteFlowableProcessInstanceCmd类的定义如下:

@Data
public class DeleteProcessInstanceCmd implements Command<Void>, Serializable { String processInstanceId;
String deleteReason;
//是否删除历史
boolean cascade=true; public DeleteProcessInstanceCmd(){ } public DeleteProcessInstanceCmd(String processInstanceId,String deleteReason){
this.deleteReason=deleteReason;
this.processInstanceId=processInstanceId;
} public DeleteProcessInstanceCmd(String processInstanceId,
String deleteReason,
boolean cascade){
this.deleteReason=deleteReason;
this.processInstanceId=processInstanceId;
this.cascade=cascade;
} @Override
public Void execute(CommandContext commandContext) {
ExecutionEntity entity= CommandContextUtil.getExecutionEntityManager(commandContext)
.findById(processInstanceId);
if(entity!=null){
if(entity.isDeleted()){
return null;
}
if(Flowable5Util.isFlowable5ProcessDefinitionId(commandContext,entity.getProcessDefinitionId())){
Flowable5CompatibilityHandler handler=Flowable5Util.getFlowable5CompatibilityHandler();
handler.deleteProcessInstance(processInstanceId,deleteReason);
}else{
CommandContextUtil.getExecutionEntityManager(commandContext).deleteProcessInstance(entity.getProcessInstanceId(),deleteReason,cascade);
}
}
return null;
}
}

上述功能了类定义完毕后,就可以创建我们的controller类了,我们的controller类的定义如下:

@RestController
@RequestMapping("/flowableTest")
public class FlowableController { @Autowired
IProcess process; @PostMapping("/startProcess")
public ProcessInstanceEntity startProcess(@RequestBody ParamObj paramObj){
return process.startProcess(paramObj);
} @GetMapping("/getTaskInstance/{assignee}")
public List<TaskInstanceEntity> taskInstance(@PathVariable("assignee") String assignee){
return process.taskInstance(assignee);
} @PutMapping("/handleTask")
public String handleTask(@RequestBody ParamObj paramObj){
process.handleTask(paramObj);
return "success";
} @GetMapping("/queryProcessStatus")
public List<ProcessStatusEntity> queryProcessStatus(String processInstanceId){
return process.queryProcessStatus(processInstanceId);
} @GetMapping("/queryProcessVariables")
public Map<String, Object> queryProcessVariables(String processInstanceId){
return process.queryProcessVariables(processInstanceId);
} @GetMapping("/queryHistoryProcess")
public List<HistanceInstanceEntity> queryHistoryProcess(String assignee){
return process.queryHistoryProcess(assignee);
} @GetMapping("/genProcessDiagram")
public void genProcessDiagram(HttpServletResponse httpServletResponse,
String processInstanceId) throws Exception {
process.genProcessDiagram(httpServletResponse,processInstanceId);
} @GetMapping("/isExistHistoricProcessInstance")
public boolean isExistHistoricProcessInstance(String processInstanceId){
return process.isExistHistoricProcessInstance(processInstanceId);
} @GetMapping("/isProcessFinished")
public boolean isProcessFinished(String processInstanceId){
return process.isProcessFinished(processInstanceId);
} @GetMapping("/isExistRunningProcessInstance")
public boolean isExistRunningProcessInstance(String processInstanceId){
return process.isExistRunningProcessInstance(processInstanceId);
} @PutMapping("/suspendProcessInstance")
public String suspendProcessInstance(String processInstanceId){
process.suspendProcessInstance(processInstanceId);
return "流程 "+processInstanceId+" 已经挂起";
} @PutMapping("/terminateProcessInstance")
public String terminateProcessInstance(ParamObj paramObj){
process.terminateProcessInstance(paramObj);
return "流程 "+paramObj.getProcessInstanceId()+" 已经终止";
} @PutMapping("/activateProcessInstance")
public String activateProcessInstance(String processInstanceId) {
process.activateProcessInstance(processInstanceId);
return "流程 "+processInstanceId+" 已经激活";
} @PutMapping("/deleteProcessInstance")
public String deleteProcessInstance(ParamObj paramObj){
process.deleteProcessInstance(paramObj);
return "流程 "+paramObj.getProcessInstanceId()+" 已经删除";
} @PutMapping("/rollback")
public String rollbackTask(String taskId, String targetTaskKey){
process.rollbackTask(taskId,targetTaskKey);
return "流程回退成功";
}
}

试运行工程,运行工程后,系统会自动生成一些表单,是一些act_和flw_开头的表单,如下,利用postman创建一个流程实例:

查看流程示例图:

调用查看任务接口,可以查看某个人有哪些任务需要处理:

调用任务处理接口:

查看处理后的结果:

以上是flowable流程框架的简单应用,更为详细的使用等待后续的挖掘......

springboot整合flowable-初步入门的更多相关文章

  1. SpringBoot整合ActiveMQ快速入门

    Spring Boot 具有如下特性: 为基于 Spring 的开发提供更快的入门体验 开箱即用,没有代码生成,也无需 XML 配置.同时也可以修改默认值来满足特定的需求. 提供了一些大型项目中常见的 ...

  2. SpringBoot整合SpringData JPA入门到入坟

    首先创建一个SpringBoot项目,目录结构如下: 在pom.xml中添加jpa依赖,其它所需依赖自行添加 <dependency> <groupId>org.springf ...

  3. SpringBoot整合mybatis快速入门

    一.创建一个SpringBoot项目                 二.引入相关依赖 <!--web核心依赖--> <dependency> <groupId>o ...

  4. SpringBoot整合freemarker 引用基础

    原 ElasticSearch学习笔记Ⅲ - SpringBoot整合ES 新建一个SpringBoot项目.添加es的maven坐标如下: <dependency> <groupI ...

  5. springboot整合elasticsearch入门例子

    springboot整合elasticsearch入门例子 https://blog.csdn.net/tianyaleixiaowu/article/details/72833940 Elastic ...

  6. java springboot整合zookeeper入门教程(增删改查)

    java springboot整合zookeeper增删改查入门教程 zookeeper的安装与集群搭建参考:https://www.cnblogs.com/zwcry/p/10272506.html ...

  7. SpringBoot从入门到精通二(SpringBoot整合myBatis的两种方式)

    前言 通过上一章的学习,我们已经对SpringBoot有简单的入门,接下来我们深入学习一下SpringBoot,我们知道任何一个网站的数据大多数都是动态的,也就是说数据是从数据库提取出来的,而非静态数 ...

  8. RabbitMQ入门到进阶(Spring整合RabbitMQ&SpringBoot整合RabbitMQ)

    1.MQ简介 MQ 全称为 Message Queue,是在消息的传输过程中保存消息的容器.多用于分布式系统 之间进行通信. 2.为什么要用 MQ 1.流量消峰 没使用MQ 使用了MQ 2.应用解耦 ...

  9. SpringBoot 整合 MyBatis-Plus 入门体验

    一.前言 本文小编将基于 SpringBoot 整合 MyBatis-Plus , MyBatis-Plus 是一个 MyBatis 的增强工具,在 MyBatis 的基础上做增强并且不改变原本功能 ...

  10. springboot 整合 mybatis 入门

    springboot整合mybatis 0.yml 配置文件 1.创建数据库表. 2.创建实体类. 3.创建 Mapper 接口 ,添加 @Mapper 注解. 4.创建 Mapper 映射文件. & ...

随机推荐

  1. ABAP SmartForms开发脚本补丁

    当时因为编辑不舒服找了还挺久的Smartforms开发脚本补丁,这里记录一下 补丁下载路径:http://www.drv5.cn/sfinfo/16341.html 当安装补丁后不再以Word形式展现 ...

  2. code的用法

    今天写程序的时候用了const,想到之前遇到的code的用法,那是第一次遇到code的那样的用法,查了一下,解释如下: 在单片机使用C语言进行编程的时候,经常使用到code.code是keil C51 ...

  3. Visaul Studio 快捷方式

    1.删除光标所在行:Ctrl + shift + L : 2.剪切光标所在行:Ctrl + X : 3.在光标上方插入一行:Ctrl + Enter : 4.注释代码:Ctrl + K --> ...

  4. HCIA-ICT实战基础09-远程接入安全管理

    HCIA-ICT实战基础-远程接入安全管理 目录 AAA概述 AAA配置实现 telnet原理与配置 Stelnet(华为ssh的另一种称呼)配置 1 AAA概述 1.1 基本概念 AAA是Authe ...

  5. WebService接口实际场景应用(一)

    背景:要求写一套接口测试工具.过程中遇到了WebService接口的问题,遂写下本篇文章. 阶段问题1: 需要利用数据驱动,然后读取excel中的数据并直接调用.但是webService接口与http ...

  6. flutter TextField 高度问题(包括使用maxlines自适应高度以及改变textfield组件自定义高度)

    先上代码. Container( color: Colors.blue, constraints: BoxConstraints( minHeight: 10, maxHeight: 20 ), ch ...

  7. 前端面试问题整理(html和css部分)

    html5新增属性有哪些? 如何理解语义化标签? 你如何看待前端模块化的? 如何看待前后端分离? 浏览器兼容性问题? 你知道的行内元素.块级元素有哪些? css部分: 1.为什么要初始化css样式? ...

  8. C语言||一作业03

    1.作业头 这个作业属于哪个课程 https://edu.cnblogs.com/campus/zswxy/SE2020-3/ 这个作业目标在哪 https://edu.cnblogs.com/cam ...

  9. react+antd 开发一个可动态增减的复合组件

    需求如图: 与后端协商好的表单数据为: 组件代码: /* 阶梯分成组件 */ import React, { useState, useEffect } from 'react'; import { ...

  10. Calendar设定月份时要注意日期

    先看下代码 public static void main(String[] args) { int dataMonth = 4; DateFormat dateFormat = new Simple ...