JDK21和 Flowable 7.0.0
- 一.Flowable
- 二.项目搭建
- 1.依赖包
- 2.数据库
- 3.资源文件
- 1.YML配置文件
- 2.Drools kbase
- 3.Drools rule
- 4.DMN 决策表
- 5.BPMN 流文件
- 4.BPMN 流程图绘制插件
- 5.测试代码
- 1.启动类
- 2.Flowable 配置
- 3.Camel 配置
- 1.Camel 配置
- 2.Camel Router 定义
- 4.扩展类监听
- 1.外部工作类
- 2.普通Java服务扩展
- 3.Camel Listener
- 4.DmnEndListener
- 5.DmnStartListener
- 6.ExternalWorkerListener
- 7.HttpListener
- 8.MailListener
- 9.ManualTaskListener
- 10.ReceiveTaskListener
- 11.ScriptListener
- 12.ShellListener
- 13.UserTaskListener
- 5.测试接口类
- 三.测试
- 1.测试
- 2.导出流进度
- 3.Postman 配置文件
一.Flowable
Java语言实现的轻量级工作流框架
二.项目搭建
项目结构
1.依赖包
本测试项目包含了 drools 、dmn 等依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.example</groupId><artifactId>flowable-test</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>flowable</artifactId><properties><maven.compiler.source>21</maven.compiler.source><maven.compiler.target>21</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><spring.boot.version>3.1.3</spring.boot.version><flowable.version>7.0.0</flowable.version><drools.version>9.44.0.Final</drools.version></properties><!-- drools--><dependencyManagement><dependencies><dependency><groupId>org.drools</groupId><artifactId>drools-bom</artifactId><type>pom</type><scope>import</scope><version>${drools.version}</version></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>${spring.boot.version}</version></dependency><!-- flowable --><dependency><groupId>org.flowable</groupId><artifactId>flowable-spring-boot-starter</artifactId><version>${flowable.version}</version></dependency><dependency><groupId>org.flowable</groupId><artifactId>flowable-spring-boot-starter-actuator</artifactId><version>${flowable.version}</version></dependency><!-- camel--><dependency><groupId>org.flowable</groupId><artifactId>flowable-camel</artifactId><version>${flowable.version}</version></dependency><!-- dmn--><dependency><groupId>org.flowable</groupId><artifactId>flowable-dmn-engine</artifactId><version>${flowable.version}</version></dependency><dependency><groupId>org.flowable</groupId><artifactId>flowable-dmn-engine-configurator</artifactId><version>${flowable.version}</version></dependency><!-- mq jms--><dependency><groupId>org.flowable</groupId><artifactId>flowable-jms-spring-executor</artifactId><version>${flowable.version}.M1</version><exclusions><exclusion><groupId>org.flowable</groupId><artifactId>flowable-spring</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.apache.activemq</groupId><artifactId>activemq-pool</artifactId><version>6.0.1</version></dependency><!-- httpclient flowable http--><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.14</version></dependency><dependency><groupId>org.apache.camel.springboot</groupId><artifactId>camel-spring-boot-starter</artifactId><version>4.0.0</version><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></exclusion></exclusions></dependency><!-- pgsql --><dependency><groupId>org.postgresql</groupId><artifactId>postgresql</artifactId><version>42.6.0</version></dependency><!-- fastjson --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.44</version></dependency><!--java script execution--><dependency><groupId>org.openjdk.nashorn</groupId><artifactId>nashorn-core</artifactId><version>15.4</version></dependency><!-- drools--><dependency><groupId>org.drools</groupId><artifactId>drools-engine</artifactId><exclusions><exclusion><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.drools</groupId><artifactId>drools-mvel</artifactId><exclusions><exclusion><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.drools</groupId><artifactId>drools-model-compiler</artifactId><exclusions><exclusion><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.drools</groupId><artifactId>drools-xml-support</artifactId><exclusions><exclusion><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId></exclusion></exclusions></dependency></dependencies>
</project>
2.数据库
Flowable 默认使用 H2 数据库,但是可以通过修改配置文件来指定,本文使用的是 Postgres
创建一个名为 flowable 的库
Flowable 首次启动时默认会自动创建表
也可以通过官方SQL脚本手动创建表
3.资源文件
1.YML配置文件
server:port: 8086servlet.context-path: /flowable-rest## 暴露 SpringBoot 除 shutdown 外的所有端点
management:server.port: 7291endpoints:web:exposure:include: "*"health:show-details: when_authorizedroles: access-adminspring:activemq:broker-url: tcp://localhost:61616application:name: flowable-restbanner:location: classpath:/org/flowable/spring/boot/flowable-banner.txtjmx:default-domain: ${spring.application.name}datasource:driver-class-name: org.postgresql.Driverurl: jdbc:postgresql://127.0.0.1:5432/flowableusername: postgrespassword: 123456## 连接池type: com.zaxxer.hikari.HikariDataSourcehikari:poolName: ${spring.application.name}maxLifetime: 600000idleTimeout: 300000minimumIdle: 10maximumPoolSize: 50connection-test-query: select 1## java object serializable
rest:variables:allow:serializable=true:flowable:process-definition-location-prefix: classpath*:/processes/process-definition-location-suffixes: "**.bpmn20.xml,**.bpmn,drools/**.drl,dmn/**.dmn.xml"database-schema-update: trueasync-executor-activate: truehistory-level: fullrest:app:swagger-docs-enabled: truecreate-demo-definitions: trueauthentication-mode: verify-privilegeadmin:user-id: rest-adminpassword: testfirstname: Restlastname: Adminprocess:servlet:path: /servicemail.server:default-from: xxxxxx@qq.comhost: smtp.qq.comusername: xxxxxx@qq.compassword: xxxxxxs-s-l-port: 465use-ssl: trueuse-tls: falselogging:level:org.example.*: debug
2.Drools kbase
<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://jboss.org/kie/6.0.0/kmodule"><!--name: kbase 名称全局唯一packages: 规则文件包目录default: 指定为默认 kbase--><kbase name="my_kbase" packages="processes.drools.*" default="true"><!--name: ksession 名称 kbase 下唯一default: 指定为默认 ksessionclockType: 时钟类型 系统时钟或测试的伪时钟--><ksession name="my_ks" clockType="realtime" default="true"/></kbase>
</kmodule>
3.Drools rule
MyBusinessRule.drl
package droolsrule "myBusiness_1"whenthenSystem.out.println("Step4_1 drools 9.44.0.Final");
endrule "myBusiness_2"whenthenSystem.out.println("Step4_2 drools 9.44.0.Final");
endrule "myBusiness_3"whenthenSystem.out.println("Step4_3 drools 9.44.0.Final");
end
4.DMN 决策表
MyDecision.dmn.xml
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/DMN/20180521/MODEL/"id="simple" name="Simple" namespace="http://activiti.org/dmn"><decision id="myDecision" name="Simple decision"><decisionTable id="myDecisionTest" hitPolicy="UNIQUE"><input id="inputId"><inputExpression id="inputExpression1" typeRef="number"><text>age</text></inputExpression></input><output id="outputId" label="myResult" name="myResult" typeRef="string" /><rule><inputEntry id="inputEntry_1"><text>>18</text></inputEntry><outputEntry id="outputEntry_1"><text>'成年'</text></outputEntry></rule><rule><inputEntry id="inputEntry_2"><text><=18</text></inputEntry><outputEntry id="outputEntry_2"><text>'未成年'</text></outputEntry></rule></decisionTable></decision>
</definitions>
5.BPMN 流文件
leave_approval_process.bpmn20.xml
可通过下面 IDEA 插件查看流节点
<?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="leave_approval_process" name="leave_approval_process" isExecutable="true"><startEvent id="sid-a76e7b82-39bd-4838-b819-2628292d2c2c" name="Start"/><userTask id="sid-604145ee-174b-4864-a973-934078a7d043" name="LineManager" flowable:assignee="admin"><extensionElements><flowable:taskListener event="create" class="org.example.exp.listener.UserTaskListener"/></extensionElements></userTask><sequenceFlow id="sid-4fcf06a4-ba36-486e-a058-d993513403d0" sourceRef="sid-a76e7b82-39bd-4838-b819-2628292d2c2c" targetRef="sid-604145ee-174b-4864-a973-934078a7d043"/><serviceTask id="sid-501c9238-5da1-47f0-8f95-2f458010a970" name="QueryRemainingDays" flowable:delegateExpression="${queryRemainingDays}"/><sequenceFlow id="sid-6a8c97f8-acc2-4a3e-9051-6c78f161365b" sourceRef="sid-604145ee-174b-4864-a973-934078a7d043" targetRef="sid-501c9238-5da1-47f0-8f95-2f458010a970"/><endEvent id="sid-e6b42855-57d6-4e8d-96a7-f5b1a004ed3d"/><scriptTask id="sid-354c035b-2e49-4f5f-88ca-ca1cfd0e27ff" name="VerifyScript" scriptFormat="JavaScript" flowable:autoStoreVariables="false"><extensionElements><flowable:executionListener class="org.example.exp.listener.ScriptListener" event="end"/></extensionElements><script><![CDATA[execution.setVariable("sum",90);]]></script></scriptTask><sequenceFlow id="sid-0da818a4-ea4f-4f85-b9ad-e23e943fc2f0" sourceRef="sid-501c9238-5da1-47f0-8f95-2f458010a970" targetRef="sid-354c035b-2e49-4f5f-88ca-ca1cfd0e27ff"/><businessRuleTask id="sid-42d7188c-58e7-46c1-994c-3004b8d44b6b" name="MyBusiness" flowable:rules="myBusiness_1,myBusiness_3"/><sequenceFlow id="sid-1ec2477c-054e-4611-9c01-fcae871faee5" sourceRef="sid-354c035b-2e49-4f5f-88ca-ca1cfd0e27ff" targetRef="sid-42d7188c-58e7-46c1-994c-3004b8d44b6b"/><receiveTask id="sid-myReceive" name="myReceive"><extensionElements><flowable:executionListener event="end" class="org.example.exp.listener.ReceiveTaskListener"/></extensionElements></receiveTask><sequenceFlow id="sid-2b9661b6-f462-41f9-98d2-556cd0a2c064" sourceRef="sid-42d7188c-58e7-46c1-994c-3004b8d44b6b" targetRef="sid-myReceive"/><manualTask id="sid-5d7dd741-0f16-4100-a21f-3fc2b5808653" flowable:exclusive="true" name="myManualTask"><extensionElements><flowable:executionListener event="start" class="org.example.exp.listener.ManualTaskListener"/></extensionElements></manualTask><sequenceFlow id="sid-2bc20ebd-7970-44d9-b6c0-a8448cb91db3" sourceRef="sid-myReceive" targetRef="sid-5d7dd741-0f16-4100-a21f-3fc2b5808653"/><!-- flowable 单独定义的节点类型 支持按 apache camel 路由规则进行调用 --><serviceTask flowable:type="camel" id="sid-bb78a1a1-3bd8-411a-920b-63ae2eb06949" flowable:exclusive="true" name="CamelServiceTask"><extensionElements><flowable:field name="camelContext"><flowable:string>camelContext</flowable:string></flowable:field><flowable:executionListener event="end" class="org.example.exp.listener.CamelListener"/></extensionElements></serviceTask><sequenceFlow id="sid-e44ef6ef-5e78-4d6b-9fef-45a793579236" sourceRef="sid-5d7dd741-0f16-4100-a21f-3fc2b5808653" targetRef="sid-bb78a1a1-3bd8-411a-920b-63ae2eb06949"/><serviceTask flowable:type="http" id="sid-fe09b981-5d42-4cca-a222-9b1256dea9f6" flowable:exclusive="true" name="HttpServiceTask"><extensionElements><flowable:field name="requestUrl"><flowable:string>http://127.0.0.1:8088/view/hello</flowable:string></flowable:field><flowable:executionListener event="end" class="org.example.exp.listener.HttpListener"/><flowable:field name="requestMethod"><flowable:string>GET</flowable:string></flowable:field><flowable:field name="saveResponseParameters"><flowable:string>true</flowable:string></flowable:field><flowable:field name="responseVariableName"><flowable:string>result</flowable:string></flowable:field></extensionElements></serviceTask><sequenceFlow id="sid-7ed37a45-da59-43d3-ae84-83f57ed56025" sourceRef="sid-bb78a1a1-3bd8-411a-920b-63ae2eb06949" targetRef="sid-fe09b981-5d42-4cca-a222-9b1256dea9f6"/><serviceTask flowable:type="mail" id="sid-5796c07d-53d8-4bd4-a56a-ddff642c06e6" name="MailserviceTask"><extensionElements><flowable:executionListener event="end" class="org.example.exp.listener.MailListener"/><flowable:field name="to"><flowable:string>hongxu_1234@163.com</flowable:string></flowable:field><flowable:field name="subject"><flowable:string>【Flowable 测试】</flowable:string></flowable:field><flowable:field name="text"><flowable:string>Hello</flowable:string></flowable:field></extensionElements></serviceTask><sequenceFlow id="sid-d65dc071-03b3-44b1-b757-e78fc175cb50" sourceRef="sid-fe09b981-5d42-4cca-a222-9b1256dea9f6" targetRef="sid-5796c07d-53d8-4bd4-a56a-ddff642c06e6"/><serviceTask flowable:type="dmn" id="sid-2f1cea1d-b3de-451e-8612-0247f27b3d30" flowable:exclusive="true" name="DmnServiceTask"><extensionElements><flowable:executionListener event="start" class="org.example.exp.listener.DmnStartListener"/><flowable:executionListener event="end" class="org.example.exp.listener.DmnEndListener"/><flowable:field name="decisionTableReferenceKey"><flowable:string>myDecision</flowable:string></flowable:field></extensionElements></serviceTask><sequenceFlow id="sid-389b9aea-15c7-41ed-b509-80c7b90ef6a5" sourceRef="sid-5796c07d-53d8-4bd4-a56a-ddff642c06e6" targetRef="sid-2f1cea1d-b3de-451e-8612-0247f27b3d30"/><serviceTask flowable:type="shell" id="sid-968bc676-cb2a-4e38-ab51-037f174addfb" flowable:exclusive="true" name="ShellServiceTask"><extensionElements><flowable:executionListener event="end" class="org.example.exp.listener.ShellListener"/><flowable:field name="outputVariable"><flowable:string>shellOutput</flowable:string></flowable:field><flowable:field name="command"><flowable:string>ls</flowable:string></flowable:field></extensionElements></serviceTask><sequenceFlow id="sid-285f6ce8-8b6c-4aa0-8a47-ffe9ddcd535f" sourceRef="sid-2f1cea1d-b3de-451e-8612-0247f27b3d30" targetRef="sid-968bc676-cb2a-4e38-ab51-037f174addfb"/><serviceTask flowable:type="external-worker" id="sid-e3e82c89-44df-4beb-89d6-29a5819386d9" flowable:exclusive="true" name="ExternalWorkerTask" flowable:topic="my-external-worker" flowable:async="true"><extensionElements><flowable:executionListener event="end" class="org.example.exp.listener.ExternalWorkerListener"/></extensionElements></serviceTask><sequenceFlow id="sid-e7db60c3-6b77-42b1-8921-7c5b45cf2e52" sourceRef="sid-968bc676-cb2a-4e38-ab51-037f174addfb" targetRef="sid-e3e82c89-44df-4beb-89d6-29a5819386d9"/><sequenceFlow id="sid-fa098a45-db8a-415f-96b2-6b1dc1cd3c26" sourceRef="sid-e3e82c89-44df-4beb-89d6-29a5819386d9" targetRef="sid-e6b42855-57d6-4e8d-96a7-f5b1a004ed3d"/></process><bpmndi:BPMNDiagram id="BPMNDiagram_leave_approval_process"><bpmndi:BPMNPlane bpmnElement="leave_approval_process" id="BPMNPlane_leave_approval_process"><bpmndi:BPMNShape id="shape-678b7128-9f1c-4ebd-a546-3c1337b0ec0d" bpmnElement="sid-a76e7b82-39bd-4838-b819-2628292d2c2c"><omgdc:Bounds x="-700.0" y="-420.0" width="30.0" height="30.0"/></bpmndi:BPMNShape><bpmndi:BPMNShape id="shape-bc8d7db7-870a-4b22-89b6-66f91f51be7f" bpmnElement="sid-604145ee-174b-4864-a973-934078a7d043"><omgdc:Bounds x="-735.0" y="-340.0" width="100.0" height="80.0"/></bpmndi:BPMNShape><bpmndi:BPMNEdge id="edge-04bdc7f3-62dd-49d6-8e1d-bf9ad723c114" bpmnElement="sid-4fcf06a4-ba36-486e-a058-d993513403d0"><omgdi:waypoint x="-685.0" y="-390.0"/><omgdi:waypoint x="-685.0" y="-340.0"/></bpmndi:BPMNEdge><bpmndi:BPMNShape id="shape-1850c5f4-e021-4cfc-8f45-1ed25c94cf67" bpmnElement="sid-501c9238-5da1-47f0-8f95-2f458010a970"><omgdc:Bounds x="-560.0" y="-340.0" width="100.0" height="80.0"/></bpmndi:BPMNShape><bpmndi:BPMNEdge id="edge-77901c58-dc34-403e-8bc1-ed63e71f442c" bpmnElement="sid-6a8c97f8-acc2-4a3e-9051-6c78f161365b"><omgdi:waypoint x="-635.0" y="-300.0"/><omgdi:waypoint x="-560.0" y="-300.0"/></bpmndi:BPMNEdge><bpmndi:BPMNShape id="shape-27dba26e-e25e-42c7-99b1-4a487c854aba" bpmnElement="sid-e6b42855-57d6-4e8d-96a7-f5b1a004ed3d"><omgdc:Bounds x="-165.0" y="115.0" width="30.0" height="30.0"/></bpmndi:BPMNShape><bpmndi:BPMNShape id="shape-fa3bf5cf-c5da-4626-8def-332c1a2f12e9" bpmnElement="sid-354c035b-2e49-4f5f-88ca-ca1cfd0e27ff"><omgdc:Bounds x="-375.0" y="-340.0" width="100.0" height="80.0"/></bpmndi:BPMNShape><bpmndi:BPMNEdge id="edge-757b6ea6-bd95-4eec-b301-c897a784ef2e" bpmnElement="sid-0da818a4-ea4f-4f85-b9ad-e23e943fc2f0"><omgdi:waypoint x="-460.0" y="-300.0"/><omgdi:waypoint x="-375.0" y="-300.0"/></bpmndi:BPMNEdge><bpmndi:BPMNShape id="shape-3cf05ae4-6095-4ded-9107-577ddf81469d" bpmnElement="sid-42d7188c-58e7-46c1-994c-3004b8d44b6b"><omgdc:Bounds x="-205.0" y="-340.0" width="100.0" height="80.0"/></bpmndi:BPMNShape><bpmndi:BPMNEdge id="edge-76ba8768-a486-4cb9-b9dc-1a12eaa0b600" bpmnElement="sid-1ec2477c-054e-4611-9c01-fcae871faee5"><omgdi:waypoint x="-275.0" y="-300.0"/><omgdi:waypoint x="-205.0" y="-300.0"/></bpmndi:BPMNEdge><bpmndi:BPMNShape id="shape-48a1535c-18c4-4749-8e7c-44c5861ba53c" bpmnElement="sid-myReceive"><omgdc:Bounds x="-205.0" y="-195.0" width="100.0" height="80.0"/></bpmndi:BPMNShape><bpmndi:BPMNEdge id="edge-33bc9ca7-a4ff-4d01-8dcd-f82789df32c1" bpmnElement="sid-2b9661b6-f462-41f9-98d2-556cd0a2c064"><omgdi:waypoint x="-155.0" y="-260.0"/><omgdi:waypoint x="-155.0" y="-195.0"/></bpmndi:BPMNEdge><bpmndi:BPMNShape id="shape-9749617d-f42b-4300-8dd3-c7362c2fdcf0" bpmnElement="sid-5d7dd741-0f16-4100-a21f-3fc2b5808653"><omgdc:Bounds x="-375.0" y="-195.0" width="100.0" height="80.0"/></bpmndi:BPMNShape><bpmndi:BPMNEdge id="edge-933e1c6d-abc2-4d32-8d86-66590f3a3fb5" bpmnElement="sid-2bc20ebd-7970-44d9-b6c0-a8448cb91db3"><omgdi:waypoint x="-205.0" y="-155.0"/><omgdi:waypoint x="-275.0" y="-155.0"/></bpmndi:BPMNEdge><bpmndi:BPMNShape id="shape-5b38760c-f447-4bc4-8d91-e1203260de7b" bpmnElement="sid-bb78a1a1-3bd8-411a-920b-63ae2eb06949"><omgdc:Bounds x="-560.0" y="-195.0" width="100.0" height="80.0"/></bpmndi:BPMNShape><bpmndi:BPMNEdge id="edge-8c7b7240-feae-424a-8533-7310817ad8ac" bpmnElement="sid-e44ef6ef-5e78-4d6b-9fef-45a793579236"><omgdi:waypoint x="-375.0" y="-155.0"/><omgdi:waypoint x="-460.0" y="-155.0"/></bpmndi:BPMNEdge><bpmndi:BPMNShape id="shape-4999f21a-5c5a-4aeb-b3a1-d0970216e1ef" bpmnElement="sid-fe09b981-5d42-4cca-a222-9b1256dea9f6"><omgdc:Bounds x="-725.0" y="-195.0" width="100.0" height="80.0"/></bpmndi:BPMNShape><bpmndi:BPMNEdge id="edge-c4376369-11d5-4a49-be3b-abc1d4664bb8" bpmnElement="sid-7ed37a45-da59-43d3-ae84-83f57ed56025"><omgdi:waypoint x="-560.0" y="-155.0"/><omgdi:waypoint x="-625.0" y="-155.0"/></bpmndi:BPMNEdge><bpmndi:BPMNShape id="shape-7e4436fd-c2f0-4f78-8f8c-95705b65ab65" bpmnElement="sid-5796c07d-53d8-4bd4-a56a-ddff642c06e6"><omgdc:Bounds x="-725.0" y="-50.0" width="100.0" height="80.0"/></bpmndi:BPMNShape><bpmndi:BPMNEdge id="edge-0c6a4753-80bb-4b01-9dd1-07a9ecc2eb26" bpmnElement="sid-d65dc071-03b3-44b1-b757-e78fc175cb50"><omgdi:waypoint x="-675.0" y="-115.0"/><omgdi:waypoint x="-675.0" y="-50.0"/></bpmndi:BPMNEdge><bpmndi:BPMNShape id="shape-f1b0d350-1a0e-4db0-940c-0403056ad9ce" bpmnElement="sid-2f1cea1d-b3de-451e-8612-0247f27b3d30"><omgdc:Bounds x="-555.0" y="-50.0" width="100.0" height="80.0"/></bpmndi:BPMNShape><bpmndi:BPMNEdge id="edge-e3edbe78-449d-4e20-8835-6e9d5923657f" bpmnElement="sid-389b9aea-15c7-41ed-b509-80c7b90ef6a5"><omgdi:waypoint x="-625.0" y="-10.0"/><omgdi:waypoint x="-555.0" y="-10.0"/></bpmndi:BPMNEdge><bpmndi:BPMNShape id="shape-85558274-3c5f-4251-91fa-c7929e0b848d" bpmnElement="sid-968bc676-cb2a-4e38-ab51-037f174addfb"><omgdc:Bounds x="-375.0" y="-50.0" width="100.0" height="80.0"/></bpmndi:BPMNShape><bpmndi:BPMNEdge id="edge-847326d8-1d9d-439a-b5fc-fc04d53b7ca5" bpmnElement="sid-285f6ce8-8b6c-4aa0-8a47-ffe9ddcd535f"><omgdi:waypoint x="-455.0" y="-10.0"/><omgdi:waypoint x="-375.0" y="-10.0"/></bpmndi:BPMNEdge><bpmndi:BPMNShape id="shape-ac87169d-593d-4bfa-a0b7-370ae27e1181" bpmnElement="sid-e3e82c89-44df-4beb-89d6-29a5819386d9"><omgdc:Bounds x="-205.0" y="-50.0" width="100.0" height="80.0"/></bpmndi:BPMNShape><bpmndi:BPMNEdge id="edge-d9f11d8b-1d81-43eb-b0ab-1c889868c2d2" bpmnElement="sid-e7db60c3-6b77-42b1-8921-7c5b45cf2e52"><omgdi:waypoint x="-275.0" y="-10.0"/><omgdi:waypoint x="-205.0" y="-10.0"/></bpmndi:BPMNEdge><bpmndi:BPMNEdge id="edge-795e60ae-ca8d-4b57-97c1-44fd8d424451" bpmnElement="sid-ba0aefb1-778c-45d2-92f5-a448479b66cd"><omgdi:waypoint x="135.0" y="280.0"/><omgdi:waypoint x="205.0" y="280.0"/></bpmndi:BPMNEdge><bpmndi:BPMNEdge id="edge-dda2b7be-0018-4e00-a39e-31244236942c" bpmnElement="sid-2acc642a-907d-47dd-bf42-07008e856a0e"><omgdi:waypoint x="255.0" y="320.0"/><omgdi:waypoint x="255.0" y="420.0"/></bpmndi:BPMNEdge><bpmndi:BPMNEdge id="edge-52d51756-b205-4b41-8267-a8142e969843" bpmnElement="sid-fa098a45-db8a-415f-96b2-6b1dc1cd3c26"><omgdi:waypoint x="-155.0" y="30.0"/><omgdi:waypoint x="-149.99998" y="114.99999"/></bpmndi:BPMNEdge></bpmndi:BPMNPlane></bpmndi:BPMNDiagram>
</definitions>
4.BPMN 流程图绘制插件
IDEA 插件:Flowable BPMN visualizer
安装好该插件后即可在 IDEA 中通过右键创建流程模板了
右键可以创建不同节点
右键用该插件打开上面 bpmn 配置文件,如下图,本配置包含了大部分 Flowable节点的简单测试实现
5.测试代码
1.启动类
package org.example;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class WflApp {public static void main(String[] args) {SpringApplication.run(WflApp.class,args);System.out.println("Hello world!");}
}
2.Flowable 配置
package org.example.config;import org.example.exp.external.MyExternalWorker;
import org.flowable.common.engine.impl.EngineDeployer;
import org.flowable.common.engine.impl.history.HistoryLevel;
import org.flowable.dmn.engine.deployer.DmnDeployer;
import org.flowable.engine.impl.rules.RulesDeployer;
import org.flowable.job.service.JobServiceConfiguration;
import org.flowable.job.service.impl.asyncexecutor.DefaultJobManager;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;import java.util.ArrayList;
import java.util.List;/*** @Author zhx & moon* @Since 1.8* @Date 2024-01-02 下午 6:18*/
@Configuration
public class FlowableConfig implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> {@Value("${flowable.async-executor-activate}")boolean asyncExecutorActivate;@Value("${flowable.history-level}")HistoryLevel historyLevel;@Overridepublic void configure(SpringProcessEngineConfiguration engineConfiguration) {//设置字体engineConfiguration.setActivityFontName("宋体");engineConfiguration.setLabelFontName("宋体");engineConfiguration.setAnnotationFontName("宋体");//设置 RULE 依赖List<EngineDeployer> customPostDeployers = new ArrayList<>();customPostDeployers.add(new RulesDeployer());customPostDeployers.add(new DmnDeployer());engineConfiguration.setCustomPostDeployers(customPostDeployers);// JOB 执行器engineConfiguration.setAsyncExecutorActivate(asyncExecutorActivate);engineConfiguration.setHistoryLevel(historyLevel);//设置外部处理器engineConfiguration.addCustomJobHandler(new MyExternalWorker());//设置 JOB MangerString engineName = engineConfiguration.getEngineName();JobServiceConfiguration configuration = new JobServiceConfiguration(engineName);DefaultJobManager jobManager = new DefaultJobManager(configuration);engineConfiguration.setJobManager(jobManager);}
}
3.Camel 配置
1.Camel 配置
package org.example.config;import jakarta.annotation.PostConstruct;
import org.apache.camel.CamelContext;
import org.example.router.CamelRouter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;/*** @Author zhx & moon* @Since 1.8* @Date 2024-01-08 下午 3:06*/
@Component
public class MyCamelContext {@AutowiredCamelContext camelContext;@PostConstructpublic void init() throws Exception {camelContext.addRoutes(new CamelRouter());System.out.println();}
}
2.Camel Router 定义
package org.example.router;import org.apache.camel.builder.RouteBuilder;/*** @Author zhx & moon* @Since 1.8* @Date 2024-01-08 下午 2:26*/
public class CamelRouter extends RouteBuilder {@Overridepublic void configure() throws Exception {from("flowable://leave_approval_process:sid-bb78a1a1-3bd8-411a-920b-63ae2eb06949").setBody(constant("hello world!")).to("log:output");}
}
4.扩展类监听
1.外部工作类
package org.example.exp.external;import org.flowable.common.engine.api.scope.ScopeTypes;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.impl.bpmn.helper.ErrorPropagation;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.impl.util.CountingEntityUtil;
import org.flowable.job.service.JobHandler;
import org.flowable.job.service.impl.persistence.entity.JobEntity;
import org.flowable.variable.api.delegate.VariableScope;
import org.flowable.variable.service.VariableService;
import org.flowable.variable.service.impl.persistence.entity.VariableInstanceEntity;import java.util.List;/*** @Author zhx & moon* @Since 1.8* @Date 2024-01-09 上午 10:07*/
public class MyExternalWorker implements JobHandler {/*** 自定义实现覆盖默认实现*/private static final String type = "async-continuation";/*** key 用于获取唯一的处理器 JobHandlers* @return*/@Overridepublic String getType() {return type;}/*** 定义一个异步任务 用于在流程外执行一些处理逻辑 弱关联性* @param job* @param configuration* @param variableScope* @param commandContext*/@Overridepublic void execute(JobEntity job, String configuration, VariableScope variableScope, CommandContext commandContext) {System.out.println("Step12 My External work take ...");ExecutionEntity executionEntity = (ExecutionEntity) variableScope;ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext);VariableService variableService = processEngineConfiguration.getVariableServiceConfiguration().getVariableService();List<VariableInstanceEntity> jobVariables = variableService.findVariableInstanceBySubScopeIdAndScopeType(executionEntity.getId(), ScopeTypes.BPMN_EXTERNAL_WORKER);for (VariableInstanceEntity jobVariable : jobVariables) {executionEntity.setVariable(jobVariable.getName(), jobVariable.getValue());CountingEntityUtil.handleDeleteVariableInstanceEntityCount(jobVariable, false);variableService.deleteVariableInstance(jobVariable);}if (configuration != null && configuration.startsWith("error:")) {String errorCode;if (configuration.length() > 6) {errorCode = configuration.substring(6);} else {errorCode = null;}ErrorPropagation.propagateError(errorCode, executionEntity);} else {CommandContextUtil.getAgenda(commandContext).planTriggerExecutionOperation(executionEntity);}}
}
2.普通Java服务扩展
package org.example.exp.service;import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.JavaDelegate;
import org.springframework.stereotype.Component;/*** @Author zhx & moon* @Since 1.8* @Date 2024-01-04 下午 10:04*/
@Component("queryRemainingDays")
public class QueryRemainingDays implements JavaDelegate {public void execute(DelegateExecution execution) {System.out.println("Step2 QueryRemainingDays ...");}
}
3.Camel Listener
package org.example.exp.listener;import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.ExecutionListener;/*** @Author zhx & moon* @Since 1.8* @Date 2024-01-08 下午 2:54*/
public class CamelListener implements ExecutionListener {@Overridepublic void notify(DelegateExecution execution) {System.out.println("Step7 ServiceTask Camel ...");}
}
4.DmnEndListener
package org.example.exp.listener;import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.ExecutionListener;/*** @Author zhx & moon* @Since 1.8* @Date 2024-01-09 上午 2:08*/
public class DmnEndListener implements ExecutionListener {@Overridepublic void notify(DelegateExecution execution) {Object object = execution.getVariable("myResult");System.out.println(String.format("Step10 ServiceTask Dmn End ... I'm %s",object));}
}
5.DmnStartListener
package org.example.exp.listener;import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.ExecutionListener;/*** @Author zhx & moon* @Since 1.8* @Date 2024-01-08 下午 11:42*/
public class DmnStartListener implements ExecutionListener {@Overridepublic void notify(DelegateExecution execution) {execution.setVariable("age",20);System.out.println("Step10 ServiceTask Dmn ... ");}
}
6.ExternalWorkerListener
package org.example.exp.listener;import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.ExecutionListener;import java.util.List;
import java.util.Map;/*** @Author zhx & moon* @Since 1.8* @Date 2024-01-09 上午 10:31*/
public class ExternalWorkerListener implements ExecutionListener {@Overridepublic void notify(DelegateExecution execution) {System.out.println("Step12 ServiceTask ExternalWorker ...");}}
7.HttpListener
package org.example.exp.listener;import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.ExecutionListener;/*** @Author zhx & moon* @Since 1.8* @Date 2024-01-08 下午 5:10*/
public class HttpListener implements ExecutionListener {@Overridepublic void notify(DelegateExecution execution) {Object object = execution.getVariable("result");System.out.println(String.format("Step8 ServiceTask Http ... result : %s",object));}
}
8.MailListener
package org.example.exp.listener;import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.ExecutionListener;/*** @Author zhx & moon* @Since 1.8* @Date 2024-01-08 下午 11:02*/
public class MailListener implements ExecutionListener {@Overridepublic void notify(DelegateExecution execution) {System.out.println("Step9 ServiceTask Mail ... ");}
}
9.ManualTaskListener
package org.example.exp.listener;import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.ExecutionListener;/*** @Author zhx & moon* @Since 1.8* @Date 2024-01-08 下午 1:53*/
public class ManualTaskListener implements ExecutionListener {@Overridepublic void notify(DelegateExecution execution) {System.out.println("Step6 ManualTask ...");}
}
10.ReceiveTaskListener
package org.example.exp.listener;import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.ExecutionListener;/*** @Author zhx & moon* @Since 1.8* @Date 2024-01-08 上午 11:59*/
public class ReceiveTaskListener implements ExecutionListener {@Overridepublic void notify(DelegateExecution execution) {System.out.println("Step5 ReceiveTask Trigger ...");}
}
11.ScriptListener
package org.example.exp.listener;import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.ExecutionListener;/*** @Author zhx & moon* @Since 1.8* @Date 2024-01-05 下午 3:33*/
public class ScriptListener implements ExecutionListener {@Overridepublic void notify(DelegateExecution execution) {System.out.println(String.format("Step3 Script: Sum %s",execution.getVariable("sum")));}
}
12.ShellListener
package org.example.exp.listener;import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.ExecutionListener;/*** @Author zhx & moon* @Since 1.8* @Date 2024-01-09 上午 9:42*/
public class ShellListener implements ExecutionListener {@Overridepublic void notify(DelegateExecution execution) {String output = (String) execution.getVariable("shellOutput");System.out.println(String.format("Step11 ServiceTask Shell ... output : %s",output.replace("\n"," ")));}
}
13.UserTaskListener
package org.example.exp.listener;import org.flowable.engine.delegate.TaskListener;
import org.flowable.task.service.delegate.DelegateTask;/*** @Author zhx & moon* @Since 1.8* @Date 2024-01-05 上午 11:07*/
public class UserTaskListener implements TaskListener {@Overridepublic void notify(DelegateTask delegateTask) {System.out.println("Step1 Create ...");}
}
5.测试接口类
包含用户节点的查询和审批,接收节点的触发
package org.example.controller;import com.alibaba.fastjson.JSONObject;
import jakarta.servlet.http.HttpServletResponse;
import org.example.exp.service.QueryRemainingDays;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.*;
import org.flowable.engine.repository.ProcessDefinition;
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.util.StringUtils;
import org.springframework.web.bind.annotation.*;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;/*** @Author zhx & moon* @Since 1.8* @Date 2024-01-02 下午 6:21*/
@RestController
@RequestMapping("/wfl")
public class ProcessController {@AutowiredQueryRemainingDays queryRemainingDays;@AutowiredRuntimeService runtimeService;@AutowiredTaskService taskService;@AutowiredRepositoryService repositoryService;@AutowiredProcessEngine engine;/*** 查询流程定义* @return*/@GetMapping("/queryProcessDefinition")public List<String> queryProcessDefinition(){List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().list();return list.stream().map(ProcessDefinition::getId).collect(Collectors.toList());}/*** 通过 ID 激活流程* @param id*/@GetMapping("/active/{id}")public void active(@PathVariable("id") String id){if (!StringUtils.hasLength(id)){id = "leave_approval_process";}HashMap<String,Object> map = new HashMap<>();map.put("id","111");ProcessInstance instance = runtimeService.startProcessInstanceByKey(id,map);System.out.println(String.format("instance id :%s ",instance.getId()));}/*** 获取任务* 可以根据候选人、组来查询* 认领 taskService.claim(t.getId(),"admin");* 转交 taskService.setAssignee(t.getId(),"admin");* 回退 taskService.setAssignee(t.getId(),null);*/@GetMapping("/queryUserTask")public List<JSONObject> queryUserTask(){List<Task> list = taskService.createTaskQuery().list();List<JSONObject> result = new ArrayList<>(list.size());JSONObject object;for (Task t:list){object = new JSONObject();object.put("taskId",t.getId());object.put("userId",t.getAssignee());result.add(object);}return result;}/*** 认领和处理* @param taskId*/@GetMapping("/claimAndComplete")public void claimAndComplete(@RequestParam("taskId") String taskId,@RequestParam("userCode") String userCode){//获取任务Task task = taskService.createTaskQuery().taskId(taskId).taskAssignee(userCode).singleResult();if (task!=null){//TODO 审批校验,通过或不通过//完成taskService.complete(taskId);}}/*** 等待任务:即任务执行到本节点会阻塞,如本节点前会进行一系列非常复杂的业务处理,需要人为或后台线程单独检测完成情况,并决定是否继续触发* @param receiveTaskId*/@GetMapping("/receiveTaskHandler")public void receiveTaskHandler(@RequestParam("receiveTaskId") String receiveTaskId){//查找当前流实例List<Execution> list = runtimeService.createExecutionQuery().activityId(receiveTaskId).list();for (Execution execution:list){runtimeService.trigger(execution.getId());}}/*** 查询流实例* @return*/@GetMapping("/queryProcessInstance/{key}")public List<JSONObject> queryProcessInstance(@PathVariable("key") String key){List<ProcessInstance> list = runtimeService.createProcessInstanceQuery().processDefinitionKey(key).list();List<JSONObject> result = new ArrayList<>(list.size());JSONObject object;for (ProcessInstance instance:list){object = new JSONObject();object.put("key",instance.getProcessDefinitionKey());object.put("instanceId",instance.getProcessInstanceId());result.add(object);}return result;}/*** 批量删除实例* @param key*/@DeleteMapping("/deleteProcessInstance/{key}")public void deleteProcessInstance(@PathVariable("key") String key){List<ProcessInstance> list = runtimeService.createProcessInstanceQuery().processDefinitionKey(key).list();List<String> ids = list.stream().map(ProcessInstance::getId).collect(Collectors.toList());runtimeService.bulkDeleteProcessInstances(ids,"test");}/*** 绘制流程图* @param response* @param instanceId* @throws IOException*/@GetMapping("/buildFlowChart")public void buildFlowChart(@RequestParam("instanceId") String instanceId,HttpServletResponse response) throws IOException {//查找实例ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(instanceId).singleResult();if (null == pi){return;}//查找已执行节点List<Execution> executions = runtimeService.createExecutionQuery().processInstanceId(instanceId).list();List<String> act = new ArrayList<>();List<String> flows = new ArrayList<>();for (Execution execution:executions){List<String> ids = runtimeService.getActiveActivityIds(execution.getId());act.addAll(ids);}//获取 bpmn 模型并绘制流程图BpmnModel model = repositoryService.getBpmnModel(pi.getProcessDefinitionId());ProcessEngineConfiguration engineConfiguration = engine.getProcessEngineConfiguration();ProcessDiagramGenerator generator = engineConfiguration.getProcessDiagramGenerator();InputStream in = generator.generateDiagram(model,"png",act,flows,engineConfiguration.getActivityFontName(),engineConfiguration.getLabelFontName(),engineConfiguration.getAnnotationFontName(),engineConfiguration.getClassLoader(),1.0,false);int length;byte[] bytes = new byte[4096];OutputStream outputStream = null;try {outputStream = response.getOutputStream();while ((length=in.read(bytes)) != -1){outputStream.write(bytes,0,length);}outputStream.flush();} catch (IOException e) {throw new RuntimeException(e);} finally {if (null != in){in.close();}if (null != outputStream){outputStream.close();}}}
}
三.测试
1.测试
创建流实例、查询待审批节点、审批、触发接收节点
输出日志
2.导出流进度
接口:/flowable-rest/wfl/buildFlowChart?instanceId=0626e0cb-af11-11ee-9530-00155da1d269
3.Postman 配置文件
{"info": {"_postman_id": "a6e5c056-e06d-40d2-b499-6db72d51c413","name": "Flowable","schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json","_exporter_id": "29860655"},"item": [{"name": "启动流","request": {"method": "GET","header": [],"url": {"raw": "127.0.0.1:8086/flowable-rest/wfl/active/leave_approval_process","host": ["127","0","0","1"],"port": "8086","path": ["flowable-rest","wfl","active","leave_approval_process"]}},"response": []},{"name": "查询用户任务","event": [{"listen": "test","script": {"exec": ["pm.test(\"set token\",function(){\r"," var json = pm.response.json()\r"," pm.environment.set(\"instanceId\",json.get(0).taskId)\r","});"],"type": "text/javascript"}}],"request": {"method": "GET","header": [],"url": {"raw": "http://127.0.0.1:8086/flowable-rest/wfl/queryUserTask","protocol": "http","host": ["127","0","0","1"],"port": "8086","path": ["flowable-rest","wfl","queryUserTask"]}},"response": []},{"name": "用户审批","request": {"method": "GET","header": [],"url": {"raw": "http://127.0.0.1:8086/flowable-rest/wfl/claimAndComplete?taskId={{instanceId}}&userCode=admin","protocol": "http","host": ["127","0","0","1"],"port": "8086","path": ["flowable-rest","wfl","claimAndComplete"],"query": [{"key": "taskId","value": "{{instanceId}}"},{"key": "userCode","value": "admin"}]}},"response": []},{"name": "等待触发","request": {"method": "GET","header": [],"url": {"raw": "http://127.0.0.1:8086/flowable-rest/wfl/receiveTaskHandler?instanceId={{instanceId}}&receiveTaskId=sid-myReceive","protocol": "http","host": ["127","0","0","1"],"port": "8086","path": ["flowable-rest","wfl","receiveTaskHandler"],"query": [{"key": "instanceId","value": "{{instanceId}}"},{"key": "receiveTaskId","value": "sid-myReceive"}]}},"response": []},{"name": "查询流实例","request": {"method": "GET","header": [],"url": {"raw": "http://127.0.0.1:8086/flowable-rest/wfl/queryProcessInstance/leave_approval_process","protocol": "http","host": ["127","0","0","1"],"port": "8086","path": ["flowable-rest","wfl","queryProcessInstance","leave_approval_process"]}},"response": []},{"name": "删除流实例","request": {"method": "DELETE","header": [],"url": {"raw": "http://127.0.0.1:8086/flowable-rest/wfl/deleteProcessInstance/leave_approval_process","protocol": "http","host": ["127","0","0","1"],"port": "8086","path": ["flowable-rest","wfl","deleteProcessInstance","leave_approval_process"]}},"response": []},{"name": "根据实例绘制流显示进度","request": {"method": "GET","header": [],"url": {"raw": "http://127.0.0.1:8086/flowable-rest/wfl/buildFlowChart?instanceId={{instanceId}}","protocol": "http","host": ["127","0","0","1"],"port": "8086","path": ["flowable-rest","wfl","buildFlowChart"],"query": [{"key": "instanceId","value": "{{instanceId}}"}]}},"response": []},{"name": "MQ 发数测试","request": {"method": "GET","header": [],"url": {"raw": "127.0.0.1:8085/test/topic?msg=abc","host": ["127","0","0","1"],"port": "8085","path": ["test","topic"],"query": [{"key": "msg","value": "abc"}]}},"response": []}]
}