Java语言实现的轻量级工作流框架
项目结构
本测试项目包含了 drools 、dmn 等依赖
<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.0modelVersion>
<parent>
<groupId>org.examplegroupId>
<artifactId>flowable-testartifactId>
<version>1.0-SNAPSHOTversion>
parent>
<artifactId>flowableartifactId>
<properties>
<maven.compiler.source>21maven.compiler.source>
<maven.compiler.target>21maven.compiler.target>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<spring.boot.version>3.1.3spring.boot.version>
<flowable.version>7.0.0flowable.version>
<drools.version>9.44.0.Finaldrools.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.droolsgroupId>
<artifactId>drools-bomartifactId>
<type>pomtype>
<scope>importscope>
<version>${drools.version}version>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<version>${spring.boot.version}version>
dependency>
<dependency>
<groupId>org.flowablegroupId>
<artifactId>flowable-spring-boot-starterartifactId>
<version>${flowable.version}version>
dependency>
<dependency>
<groupId>org.flowablegroupId>
<artifactId>flowable-spring-boot-starter-actuatorartifactId>
<version>${flowable.version}version>
dependency>
<dependency>
<groupId>org.flowablegroupId>
<artifactId>flowable-camelartifactId>
<version>${flowable.version}version>
dependency>
<dependency>
<groupId>org.flowablegroupId>
<artifactId>flowable-dmn-engineartifactId>
<version>${flowable.version}version>
dependency>
<dependency>
<groupId>org.flowablegroupId>
<artifactId>flowable-dmn-engine-configuratorartifactId>
<version>${flowable.version}version>
dependency>
<dependency>
<groupId>org.flowablegroupId>
<artifactId>flowable-jms-spring-executorartifactId>
<version>${flowable.version}.M1version>
<exclusions>
<exclusion>
<groupId>org.flowablegroupId>
<artifactId>flowable-springartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.apache.activemqgroupId>
<artifactId>activemq-poolartifactId>
<version>6.0.1version>
dependency>
<dependency>
<groupId>org.apache.httpcomponentsgroupId>
<artifactId>httpclientartifactId>
<version>4.5.14version>
dependency>
<dependency>
<groupId>org.apache.camel.springbootgroupId>
<artifactId>camel-spring-boot-starterartifactId>
<version>4.0.0version>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.postgresqlgroupId>
<artifactId>postgresqlartifactId>
<version>42.6.0version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>2.0.44version>
dependency>
<dependency>
<groupId>org.openjdk.nashorngroupId>
<artifactId>nashorn-coreartifactId>
<version>15.4version>
dependency>
<dependency>
<groupId>org.droolsgroupId>
<artifactId>drools-engineartifactId>
<exclusions>
<exclusion>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.droolsgroupId>
<artifactId>drools-mvelartifactId>
<exclusions>
<exclusion>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.droolsgroupId>
<artifactId>drools-model-compilerartifactId>
<exclusions>
<exclusion>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.droolsgroupId>
<artifactId>drools-xml-supportartifactId>
<exclusions>
<exclusion>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
exclusion>
exclusions>
dependency>
dependencies>
project>
Flowable 默认使用 H2 数据库,但是可以通过修改配置文件来指定,本文使用的是 Postgres
创建一个名为 flowable 的库
Flowable 首次启动时默认会自动创建表
也可以通过官方SQL脚本手动创建表
server:
port: 8086
servlet.context-path: /flowable-rest
## 暴露 SpringBoot 除 shutdown 外的所有端点
management:
server.port: 7291
endpoints:
web:
exposure:
include: "*"
health:
show-details: when_authorized
roles: access-admin
spring:
activemq:
broker-url: tcp://localhost:61616
application:
name: flowable-rest
banner:
location: classpath:/org/flowable/spring/boot/flowable-banner.txt
jmx:
default-domain: ${spring.application.name}
datasource:
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://127.0.0.1:5432/flowable
username: postgres
password: 123456
## 连接池
type: com.zaxxer.hikari.HikariDataSource
hikari:
poolName: ${spring.application.name}
maxLifetime: 600000
idleTimeout: 300000
minimumIdle: 10
maximumPoolSize: 50
connection-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: true
async-executor-activate: true
history-level: full
rest:
app:
swagger-docs-enabled: true
create-demo-definitions: true
authentication-mode: verify-privilege
admin:
user-id: rest-admin
password: test
firstname: Rest
lastname: Admin
process:
servlet:
path: /service
mail.server:
default-from: [email protected]
host: smtp.qq.com
username: [email protected]
password: xxxxxx
s-s-l-port: 465
use-ssl: true
use-tls: false
logging:
level:
org.example.*: debug
<kmodule xmlns="http://jboss.org/kie/6.0.0/kmodule">
<kbase name="my_kbase" packages="processes.drools.*" default="true">
<ksession name="my_ks" clockType="realtime" default="true"/>
kbase>
kmodule>
MyBusinessRule.drl
package drools
rule "myBusiness_1"
when
then
System.out.println("Step4_1 drools 9.44.0.Final");
end
rule "myBusiness_2"
when
then
System.out.println("Step4_2 drools 9.44.0.Final");
end
rule "myBusiness_3"
when
then
System.out.println("Step4_3 drools 9.44.0.Final");
end
MyDecision.dmn.xml
<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>agetext>
inputExpression>
input>
<output id="outputId" label="myResult" name="myResult" typeRef="string" />
<rule>
<inputEntry id="inputEntry_1">
<text>>18text>
inputEntry>
<outputEntry id="outputEntry_1">
<text>'成年'text>
outputEntry>
rule>
<rule>
<inputEntry id="inputEntry_2">
<text><=18text>
inputEntry>
<outputEntry id="outputEntry_2">
<text>'未成年'text>
outputEntry>
rule>
decisionTable>
decision>
definitions>
leave_approval_process.bpmn20.xml
可通过下面 IDEA 插件查看流节点
<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>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"/>
<serviceTask flowable:type="camel" id="sid-bb78a1a1-3bd8-411a-920b-63ae2eb06949" flowable:exclusive="true" name="CamelServiceTask">
<extensionElements>
<flowable:field name="camelContext">
<flowable:string>camelContextflowable: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/helloflowable:string>
flowable:field>
<flowable:executionListener event="end" class="org.example.exp.listener.HttpListener"/>
<flowable:field name="requestMethod">
<flowable:string>GETflowable:string>
flowable:field>
<flowable:field name="saveResponseParameters">
<flowable:string>trueflowable:string>
flowable:field>
<flowable:field name="responseVariableName">
<flowable:string>resultflowable: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>[email protected]flowable:string>
flowable:field>
<flowable:field name="subject">
<flowable:string>【Flowable 测试】flowable:string>
flowable:field>
<flowable:field name="text">
<flowable:string>Helloflowable: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>myDecisionflowable: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>shellOutputflowable:string>
flowable:field>
<flowable:field name="command">
<flowable:string>lsflowable: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>
IDEA 插件:Flowable BPMN visualizer
安装好该插件后即可在 IDEA 中通过右键创建流程模板了
右键可以创建不同节点
右键用该插件打开上面 bpmn 配置文件,如下图,本配置包含了大部分 Flowable节点的简单测试实现
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!");
}
}
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;
@Override
public 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 Manger
String engineName = engineConfiguration.getEngineName();
JobServiceConfiguration configuration = new JobServiceConfiguration(engineName);
DefaultJobManager jobManager = new DefaultJobManager(configuration);
engineConfiguration.setJobManager(jobManager);
}
}
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 {
@Autowired
CamelContext camelContext;
@PostConstruct
public void init() throws Exception {
camelContext.addRoutes(new CamelRouter());
System.out.println();
}
}
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 {
@Override
public void configure() throws Exception {
from("flowable://leave_approval_process:sid-bb78a1a1-3bd8-411a-920b-63ae2eb06949")
.setBody(constant("hello world!"))
.to("log:output");
}
}
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
*/
@Override
public String getType() {
return type;
}
/**
* 定义一个异步任务 用于在流程外执行一些处理逻辑 弱关联性
* @param job
* @param configuration
* @param variableScope
* @param commandContext
*/
@Override
public 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);
}
}
}
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 ...");
}
}
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 {
@Override
public void notify(DelegateExecution execution) {
System.out.println("Step7 ServiceTask Camel ...");
}
}
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 {
@Override
public void notify(DelegateExecution execution) {
Object object = execution.getVariable("myResult");
System.out.println(String.format("Step10 ServiceTask Dmn End ... I'm %s",object));
}
}
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 {
@Override
public void notify(DelegateExecution execution) {
execution.setVariable("age",20);
System.out.println("Step10 ServiceTask Dmn ... ");
}
}
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 {
@Override
public void notify(DelegateExecution execution) {
System.out.println("Step12 ServiceTask ExternalWorker ...");
}
}
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 {
@Override
public void notify(DelegateExecution execution) {
Object object = execution.getVariable("result");
System.out.println(String.format("Step8 ServiceTask Http ... result : %s",object));
}
}
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 {
@Override
public void notify(DelegateExecution execution) {
System.out.println("Step9 ServiceTask Mail ... ");
}
}
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 {
@Override
public void notify(DelegateExecution execution) {
System.out.println("Step6 ManualTask ...");
}
}
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 {
@Override
public void notify(DelegateExecution execution) {
System.out.println("Step5 ReceiveTask Trigger ...");
}
}
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 {
@Override
public void notify(DelegateExecution execution) {
System.out.println(String.format("Step3 Script: Sum %s",execution.getVariable("sum")));
}
}
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 {
@Override
public void notify(DelegateExecution execution) {
String output = (String) execution.getVariable("shellOutput");
System.out.println(String.format("Step11 ServiceTask Shell ... output : %s",output.replace("\n"," ")));
}
}
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 {
@Override
public void notify(DelegateTask delegateTask) {
System.out.println("Step1 Create ...");
}
}
包含用户节点的查询和审批,接收节点的触发
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 {
@Autowired
QueryRemainingDays queryRemainingDays;
@Autowired
RuntimeService runtimeService;
@Autowired
TaskService taskService;
@Autowired
RepositoryService repositoryService;
@Autowired
ProcessEngine 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();
}
}
}
}
创建流实例、查询待审批节点、审批、触发接收节点
输出日志
接口:/flowable-rest/wfl/buildFlowChart?instanceId=0626e0cb-af11-11ee-9530-00155da1d269
{
"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": []
}
]
}