最近工作中有用到工作流的开发,引入了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. react native android9 axios network error

    react native 发布成apk后网络请求会报 network error 是因为android9以后http协议不能用,要用htts协议.需要改成配置能兼容http协议,修改信息如下: and ...

  2. Something Just Like This

    I've been reading books of old我遍读旧籍 The legends and the myths那些古老传奇和无边神秘 Achilles and his gold如阿喀琉斯和 ...

  3. feign的工作原理

    1.开发微服务时,我们会在微服务的主程序入口添加EnableFignClient注解开启对Feign Client扫描加载处理,根据FignClient接口规范,定义接口并加上FignClient注解 ...

  4. mybatis动态生成sql示例

  5. 计蒜客(Stone Game)01背包

    题意:在集合中挑一些数,形成一个集合S,剩下的数形成另一个集合P,使得S>= P ,并且对于S中任意元素ai,S-ai<=P 问有多少种方案. 题目链接:https://nanti.jis ...

  6. Windows 分层窗口 桌面上透明 Direct3D

    Windows 分层窗口 桌面上透明 Direct3D 1 //IDirect3DSurface9 GetDC UpdateLayeredWindow 2 3 #include <Windows ...

  7. js遍历出数组重复的数据,及重复的个数(简单有效)

    const res={} ["s","s","a"].forEach((key)=>{ if(res[key]){ res[key]+ ...

  8. php excel导出列超过26个字母处理

    /** * String from columnindex * * @param int $pColumnIndex Column index (base 0 !!!) * @return strin ...

  9. module 'tensorflow_core._api.v2.config' has no attribute 'experimental_list_devices'

    module 'tensorflow_core._api.v2.config' has no attribute 'experimental_list_devices' kearsPython 报错如 ...

  10. C#之List、Queue、Stack使用EnsureCapacity方法预设数组大小

    简介 List.Queue 和 Stack 集合中的 EnsureCapacity方法预设数组大小. 为什么以及何时使用EnsureCapacity 方法 这里我们将首先了解为什么需要使用这种方法以及 ...