记录一下flowable 工作流的使用
导入依赖
项目使用了mybatis-plus和flowable会有冲突,需要排除flowable中的mybatis依赖
<!-- flowable工作流-->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.3.0</version>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--mysql依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
</dependency>
配置
项目使用了springsecurity ,和flowable自带的有冲突,需要关闭 flowable的idm模块,idm提供了 用户管理,权限管理功能, 项目已经有security,不需要idm的这些功能.
flowable:
#开发时关闭定时任务JOB
async-executor-activate: false
#flase: 默认值。在启动时,会对比数据库表中保存的版本,如果没有表或者版本不匹配,将抛出异常。(生产环境常用)
#true: 会对数据库中所有表进行更新操作。如果表不存在,则自动创建。(开发时常用)
#create_drop: 在启动时创建表,在关闭时删除表(必须手动关闭引擎,才能删除表)。(单元测试常用)
#drop-create: 在启动时删除原来的旧表,然后在创建新表(不需要手动关闭引擎)。
database-schema-update: true
# 在项目启动时,去检查文件目录是否有对应的流程文件,默认值为 true 表示如果有流程文件就自动部署,false 表示不检查,那么也就不会自动部署
check-process-definitions: true
#指定流程文件的位置
process-definition-location-prefix: classpath*:/processes/
idm:
enabled: false #关闭flowable的权限认证模块 解决和springsecurity的冲突
额外配置,网上找的
# ===================================================================
# Common Flowable Spring Boot Properties
# 通用Flowable Spring Boot参数
#
# This sample file is provided as a guideline. Do NOT copy it in its
# entirety to your own application. ^^^
# 本示例文件只作为指导。请不要直接拷贝至你自己的应用中。
# ===================================================================
# Core (Process) FlowableProperties
# 核心(流程)
flowable.check-process-definitions=true # 是否需要自动部署流程定义。
flowable.custom-mybatis-mappers= # 需要添加至引擎的自定义Mybatis映射的FQN。
flowable.custom-mybatis-x-m-l-mappers= # 需要添加至引擎的自定义Mybatis XML映射的路径。
flowable.database-schema= # 如果数据库返回的元数据不正确,可以在这里设置schema用于检测/生成表。
flowable.database-schema-update=true # 数据库schema更新策略。
flowable.db-history-used=true # 是否要使用db历史。
flowable.deployment-name=SpringBootAutoDeployment # 自动部署的名称。
flowable.history-level= # 要使用的历史级别。
flowable.process-definition-location-prefix=classpath*:/processes/ # 自动部署时查找流程的目录。
flowable.process-definition-location-suffixes=**.bpmn20.xml,**.bpmn # 'processDefinitionLocationPrefix'路径下需要部署的文件的后缀(扩展名)。
# Process FlowableProcessProperties
# 流程
flowable.process.definition-cache-limit=-1 # 流程定义缓存中保存流程定义的最大数量。默认值为-1(缓存所有流程定义)。
flowable.process.enable-safe-xml=true # 在解析BPMN XML文件时进行额外检查。参见 https://www.flowable.org/docs/userguide/index.html#advanced.safe.bpmn.xml 。不幸的是,部分平台(JDK 6,JBoss)上无法使用这个功能,因此如果你所用的平台在XML解析时不支持StaxSource,需要禁用这个功能。
flowable.process.servlet.load-on-startup=-1 # 启动时加载Process servlet。
flowable.process.servlet.name=Flowable BPMN Rest API # Process servlet的名字。
flowable.process.servlet.path=/process-api # Process servelet的context path。
# Process Async Executor
# 流程异步执行器
flowable.process.async-executor-activate=true # 是否启用异步执行器。
flowable.process.async.executor.async-job-lock-time-in-millis=300000 # 异步作业在被异步执行器取走后的锁定时间(以毫秒计)。在这段时间内,其它异步执行器不会尝试获取及锁定这个任务。
flowable.process.async.executor.default-async-job-acquire-wait-time-in-millis=10000 # 异步作业获取线程在进行下次获取查询前的等待时间(以毫秒计)。只在当次没有取到新的异步作业,或者只取到很少的异步作业时生效。默认值 = 10秒。
flowable.process.async.executor.default-queue-size-full-wait-time-in-millis=0 # 异步作业(包括定时器作业与异步执行)获取线程在队列满时,等待执行下次查询的等待时间(以毫秒计)。默认值为0(以向后兼容)
flowable.process.async.executor.default-timer-job-acquire-wait-time-in-millis=10000 # 定时器作业获取线程在进行下次获取查询前的等待时间(以毫秒计)。只在当次没有取到新的定时器作业,或者只取到很少的定时器作业时生效。默认值 = 10秒。
flowable.process.async.executor.max-async-jobs-due-per-acquisition=1 # (译者补)单次查询的异步作业数量。默认值为1,以降低乐观锁异常的可能性。除非你知道自己在做什么,否则请不要修改这个值。
flowable.process.async.executor.retry-wait-time-in-millis=500 # ???(译者补不了了)
flowable.process.async.executor.timer-lock-time-in-millis=300000 # 定时器作业在被异步执行器取走后的锁定时间(以毫秒计)。在这段时间内,其它异步执行器不会尝试获取及锁定这个任务。
# CMMN FlowableCmmnProperties
flowable.cmmn.deploy-resources=true # 是否部署资源。默认值为'true'。
flowable.cmmn.deployment-name=SpringBootAutoDeployment # CMMN资源部署的名字。
flowable.cmmn.enable-safe-xml=true # 在解析CMMN XML文件时进行额外检查。参见 https://www.flowable.org/docs/userguide/index.html#advanced.safe.bpmn.xml 。不幸的是,部分平台(JDK 6,JBoss)上无法使用这个功能,因此如果你所用的平台在XML解析时不支持StaxSource,需要禁用这个功能。
flowable.cmmn.enabled=true # 是否启用CMMN引擎。
flowable.cmmn.resource-location=classpath*:/cases/ # CMMN资源的路径。
flowable.cmmn.resource-suffixes=**.cmmn,**.cmmn11,**.cmmn.xml,**.cmmn11.xml # 需要扫描的资源后缀名。
flowable.cmmn.servlet.load-on-startup=-1 # 启动时加载CMMN servlet。
flowable.cmmn.servlet.name=Flowable CMMN Rest API # CMMN servlet的名字。
flowable.cmmn.servlet.path=/cmmn-api # CMMN servlet的context path。
# CMMN Async Executor
# CMMN异步执行器
flowable.cmmn.async-executor-activate=true # 是否启用异步执行器。
flowable.cmmn.async.executor.async-job-lock-time-in-millis=300000 # 异步作业在被异步执行器取走后的锁定时间(以毫秒计)。在这段时间内,其它异步执行器不会尝试获取及锁定这个任务。
flowable.cmmn.async.executor.default-async-job-acquire-wait-time-in-millis=10000 # 异步作业获取线程在进行下次获取查询前的等待时间(以毫秒计)。只在当次没有取到新的异步作业,或者只取到很少的异步作业时生效。默认值 = 10秒。
flowable.cmmn.async.executor.default-queue-size-full-wait-time-in-millis=0 # 异步作业(包括定时器作业与异步执行)获取线程在队列满时,等待执行下次查询的等待时间(以毫秒计)。默认值为0(以向后兼容)
flowable.cmmn.async.executor.default-timer-job-acquire-wait-time-in-millis=1000 # 定时器作业获取线程在进行下次获取查询前的等待时间(以毫秒计)。只在当次没有取到新的定时器作业,或者只取到很少的定时器作业时生效。默认值 = 10秒。
flowable.cmmn.async.executor.max-async-jobs-due-per-acquisition=1 # (译者补)单次查询的异步作业数量。默认值为1,以降低乐观锁异常的可能性。除非你知道自己在做什么,否则请不要修改这个值。
flowable.cmmn.async.executor.retry-wait-time-in-millis=500 #(译者补不了了)
flowable.cmmn.async.executor.timer-lock-time-in-millis=300000 # 定时器作业在被异步执行器取走后的锁定时间(以毫秒计)。在这段时间内,其它异步执行器不会尝试获取及锁定这个任务。
# Content FlowableContentProperties
flowable.content.enabled=true # 是否启动Content引擎。
flowable.content.servlet.load-on-startup=-1 # 启动时加载Content servlet。
flowable.content.servlet.name=Flowable Content Rest API # Content servlet的名字。
flowable.content.servlet.path=/content-api # Content servlet的context path。
flowable.content.storage.create-root=true # 如果根路径不存在,是否需要创建?
flowable.content.storage.root-folder= # 存储content文件(如上传的任务附件,或表单文件)的根路径。
# DMN FlowableDmnProperties
flowable.dmn.deploy-resources=true # 是否部署资源。默认为'true'。
flowable.dmn.deployment-name=SpringBootAutoDeployment # DMN资源部署的名字。
flowable.dmn.enable-safe-xml=true # 在解析DMN XML文件时进行额外检查。参见 https://www.flowable.org/docs/userguide/index.html#advanced.safe.bpmn.xml 。不幸的是,部分平台(JDK 6,JBoss)上无法使用这个功能,因此如果你所用的平台在XML解析时不支持StaxSource,需要禁用这个功能。
flowable.dmn.enabled=true # 是否启用DMN引擎。
flowable.dmn.history-enabled=true # 是否启用DMN引擎的历史。
flowable.dmn.resource-location=classpath*:/dmn/ # DMN资源的路径。
flowable.dmn.resource-suffixes=**.dmn,**.dmn.xml,**.dmn11,**.dmn11.xml # 需要扫描的资源后缀名。
flowable.dmn.servlet.load-on-startup=-1 # 启动时加载DMN servlet。
flowable.dmn.servlet.name=Flowable DMN Rest API # DMN servlet的名字。
flowable.dmn.servlet.path=/dmn-api # DMN servlet的context path。
flowable.dmn.strict-mode=true # 如果希望避免抉择表命中策略检查导致失败,可以将本参数设置为false。如果检查发现了错误,会直接返回错误前一刻的中间结果。
# Form FlowableFormProperties
flowable.form.deploy-resources=true # 是否部署资源。默认为'true'。
flowable.form.deployment-name=SpringBootAutoDeployment # Form资源部署的名字。
flowable.form.enabled=true # 是否启用Form引擎。
flowable.form.resource-location=classpath*:/forms/ # Form资源的路径。
flowable.form.resource-suffixes=**.form # 需要扫描的资源后缀名。
flowable.form.servlet.load-on-startup=-1 # 启动时加载Form servlet。
flowable.form.servlet.name=Flowable Form Rest API # Form servlet的名字。
flowable.form.servlet.path=/form-api # Form servlet的context path。
# IDM FlowableIdmProperties
flowable.idm.enabled=true # 是否启用IDM引擎。
flowable.idm.password-encoder= # 使用的密码编码类型。
flowable.idm.servlet.load-on-startup=-1 # 启动时加载IDM servlet。
flowable.idm.servlet.name=Flowable IDM Rest API # IDM servlet的名字。
flowable.idm.servlet.path=/idm-api # IDM servlet的context path。
# IDM Ldap FlowableLdapProperties
flowable.idm.ldap.attribute.email= # 用户email的属性名。
flowable.idm.ldap.attribute.first-name= # 用户名字的属性名。
flowable.idm.ldap.attribute.group-id= # 用户组ID的属性名。
flowable.idm.ldap.attribute.group-name= # 用户组名的属性名。
flowable.idm.ldap.attribute.group-type= # 用户组类型的属性名。
flowable.idm.ldap.attribute.last-name= # 用户姓的属性名。
flowable.idm.ldap.attribute.user-id= # 用户ID的属性名。
flowable.idm.ldap.base-dn= # 查找用户与组的DN(标志名称 distinguished name)。
flowable.idm.ldap.cache.group-size=-1 # 设置{@link org.flowable.ldap.LDAPGroupCache}的大小。这是LRU缓存,用于缓存用户及组,以避免每次都查询LDAP系统。
flowable.idm.ldap.custom-connection-parameters= # 用于设置所有没有专用setter的LDAP连接参数。查看 http://docs.oracle.com/javase/tutorial/jndi/ldap/jndi.html 介绍的自定义参数。参数包括配置链接池,安全设置,等等。
flowable.idm.ldap.enabled=false # 是否启用LDAP IDM 服务。
flowable.idm.ldap.group-base-dn= # 组查找的DN。
flowable.idm.ldap.initial-context-factory=com.sun.jndi.ldap.LdapCtxFactory # 初始化上下文工厂的类名。
flowable.idm.ldap.password= # 连接LDAP系统的密码。
flowable.idm.ldap.port=-1 # LDAP系统的端口。
flowable.idm.ldap.query.all-groups= # 查询所有组所用的语句。
flowable.idm.ldap.query.all-users= # 查询所有用户所用的语句。
flowable.idm.ldap.query.groups-for-user= # 按照指定用户查询所属组所用的语句
flowable.idm.ldap.query.user-by-full-name-like= # 按照给定全名查找用户所用的语句。
flowable.idm.ldap.query.user-by-id= # 按照userId查找用户所用的语句。
flowable.idm.ldap.search-time-limit=0 # 查询LDAP的超时时间(以毫秒计)。默认值为'0',即“一直等待”。
flowable.idm.ldap.security-authentication=simple # 连接LDAP系统所用的'java.naming.security.authentication'参数的值。
flowable.idm.ldap.server= # LDAP系统的主机名。如'ldap://localhost'。
flowable.idm.ldap.user= # 连接LDAP系统的用户ID。
flowable.idm.ldap.user-base-dn= # 查找用户的DN。
# Flowable Mail FlowableMailProperties
flowable.mail.server.default-from=flowable@localhost # 发送邮件时使用的默认发信人地址。
flowable.mail.server.host=localhost # 邮件服务器。
flowable.mail.server.password= # 邮件服务器的登录密码。
flowable.mail.server.port=1025 # 邮件服务器的端口号。
flowable.mail.server.use-ssl=false # 是否使用SSL/TLS加密SMTP传输连接(即SMTPS/POPS)。
flowable.mail.server.use-tls=false # 使用或禁用STARTTLS加密。
flowable.mail.server.username= # 邮件服务器的登录用户名。如果为空,则不需要登录。
# Actuator
management.endpoint.flowable.cache.time-to-live=0ms # 缓存响应的最大时间。
management.endpoint.flowable.enabled=true # 是否启用flowable端点。
简单的工作流实战
需求是: 公司财务接收到系统中上传的发票,在审核过发票信息无误后,可以提交发票报销流程---->首先是财务总监审核—>然后是公司老板审核—>最后是可打款
画流程图
tomcat 运行flowable-ui.war 包
访问localhost:8080/flowable-ui
默认账号admin 密码 test
选择建模器应用程序
新建流程文件
根据需求画图,详细的这里就不过多说明
定义流程变量
分配该节点的所有人,选择固定值,在分配输入框 填入表达式${xxxx}
设置任务监听器
Flowable任务监听器的事件及其作用如下:
- create:当任务被创建时触发。
- assignment:当任务分配给某个用户时触发。
- complete:当任务已完成时触发。
- delete:当任务被删除时触发。
使用委托表达式,可以很方便的使用springboot项目中创建的监听器
老板审核和总监审核的设置一样,根据你的需求自行设置
添加排他网关和设置条件
注意:条件是在排他网关分出的"箭头"上面设置的
根据流程中携带的条件变量进入不同的节点
我这里拒绝的话是直接流程结束,
我这里的两个排他网关的设置完全一致,根据需求自行设置
结束节点
这里设置了执行监听器,方式和前面的任务监听器类似
我这里的3个结束节点设置都是一样的,根据你的需求自行设置
Flowable执行监听器的事件及其作用如下:
- start:开始(相当于进入该节点时触发)
- end:结束
- take:在用
同样适用委托表达式
部署流程
流程图画完后保存,然后生成bpmn20.xml文件并下载
如果你按照我上面的配置 设置flowable,在springboot项目中,只需在resources目录下创建processes文件夹,
把你的bpmn20.xml文件放在processes目录下,流程就会自动部署,
如果是首次运行,flowable还会根据你配置的数据库url,在数据库中创建相关的工作流表
我的完整bpmn20.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.flowable.org/processdef" exporter="Flowable Open Source Modeler" exporterVersion="6.7.2">
<process id="invoiceProcess" name="发票报销流程" isExecutable="true">
<startEvent id="startEvent1" name="发票报销" flowable:formFieldValidation="true"></startEvent>
<userTask id="sid-75D626F6-AAA4-48C0-A30A-EC0E54A90A2D" name="财务总监审核" flowable:assignee="${assignee}" flowable:formFieldValidation="true">
<extensionElements>
<flowable:taskListener event="create" delegateExpression="${invoiceProcessTaskListener}"></flowable:taskListener>
<modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
</extensionElements>
</userTask>
<exclusiveGateway id="sid-F40D3324-08EC-49A6-A5D7-134F3C8161B9"></exclusiveGateway>
<userTask id="sid-7FBBC2DF-119D-4E45-8B53-4247FCCE0B51" name="老板审核" flowable:assignee="${assignee}" flowable:formFieldValidation="true">
<extensionElements>
<flowable:taskListener event="create" delegateExpression="${invoiceProcessTaskListener}"></flowable:taskListener>
<modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
</extensionElements>
</userTask>
<endEvent id="sid-E504FF4C-9593-4BE7-8C77-CCE3454BBC21" name="财务总监退回">
<extensionElements>
<flowable:executionListener event="start" delegateExpression="${invoiceProcessEventListener}"></flowable:executionListener>
</extensionElements>
</endEvent>
<exclusiveGateway id="sid-04165EB7-C595-49AD-8DD3-F747FA097C54"></exclusiveGateway>
<sequenceFlow id="sid-905216E3-CB13-4925-AD93-0F2C16E62FEC" sourceRef="sid-7FBBC2DF-119D-4E45-8B53-4247FCCE0B51" targetRef="sid-04165EB7-C595-49AD-8DD3-F747FA097C54"></sequenceFlow>
<endEvent id="sid-A15C17DE-4858-46D7-9F57-472A864DD555" name="老板退回">
<extensionElements>
<flowable:executionListener event="start" delegateExpression="${invoiceProcessEventListener}"></flowable:executionListener>
</extensionElements>
</endEvent>
<endEvent id="sid-70588DC2-2C9F-426B-82C5-90344C881530" name="修改状态为可打款">
<extensionElements>
<flowable:executionListener event="start" delegateExpression="${invoiceProcessEventListener}"></flowable:executionListener>
</extensionElements>
</endEvent>
<sequenceFlow id="sid-C59BA984-D60E-4DF5-BEB9-9DEDCBF5E274" name="拒绝" sourceRef="sid-04165EB7-C595-49AD-8DD3-F747FA097C54" targetRef="sid-A15C17DE-4858-46D7-9F57-472A864DD555">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${result=='fail'}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="sid-39027013-B136-4AF1-BED8-C517EB3FE323" sourceRef="sid-75D626F6-AAA4-48C0-A30A-EC0E54A90A2D" targetRef="sid-F40D3324-08EC-49A6-A5D7-134F3C8161B9"></sequenceFlow>
<sequenceFlow id="sid-453685A6-9775-40C7-A477-13DE625470CB" name="通过" sourceRef="sid-F40D3324-08EC-49A6-A5D7-134F3C8161B9" targetRef="sid-7FBBC2DF-119D-4E45-8B53-4247FCCE0B51">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${result=='ok'}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="sid-2FEB315C-7C5F-4E85-94FC-313E7738C2D1" name="拒绝" sourceRef="sid-F40D3324-08EC-49A6-A5D7-134F3C8161B9" targetRef="sid-E504FF4C-9593-4BE7-8C77-CCE3454BBC21">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${result=='fail'}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="sid-1ADCBAE3-7C5D-4C44-8F05-4646A7669835" name="通过" sourceRef="sid-04165EB7-C595-49AD-8DD3-F747FA097C54" targetRef="sid-70588DC2-2C9F-426B-82C5-90344C881530">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${result=='ok'}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="sid-FBDC3C0C-DFB4-4491-B7A5-895D8703E1E7" sourceRef="startEvent1" targetRef="sid-75D626F6-AAA4-48C0-A30A-EC0E54A90A2D"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_invoiceProcess">
<bpmndi:BPMNPlane bpmnElement="invoiceProcess" id="BPMNPlane_invoiceProcess">
<bpmndi:BPMNShape bpmnElement="startEvent1" id="BPMNShape_startEvent1">
<omgdc:Bounds height="30.0" width="30.0" x="75.0" y="160.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-75D626F6-AAA4-48C0-A30A-EC0E54A90A2D" id="BPMNShape_sid-75D626F6-AAA4-48C0-A30A-EC0E54A90A2D">
<omgdc:Bounds height="80.0" width="100.0" x="240.0" y="135.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-F40D3324-08EC-49A6-A5D7-134F3C8161B9" id="BPMNShape_sid-F40D3324-08EC-49A6-A5D7-134F3C8161B9">
<omgdc:Bounds height="40.0" width="40.0" x="465.0" y="155.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-7FBBC2DF-119D-4E45-8B53-4247FCCE0B51" id="BPMNShape_sid-7FBBC2DF-119D-4E45-8B53-4247FCCE0B51">
<omgdc:Bounds height="80.0" width="100.0" x="630.0" y="135.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-E504FF4C-9593-4BE7-8C77-CCE3454BBC21" id="BPMNShape_sid-E504FF4C-9593-4BE7-8C77-CCE3454BBC21">
<omgdc:Bounds height="28.0" width="28.0" x="471.0" y="345.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-04165EB7-C595-49AD-8DD3-F747FA097C54" id="BPMNShape_sid-04165EB7-C595-49AD-8DD3-F747FA097C54">
<omgdc:Bounds height="40.0" width="40.0" x="850.0" y="155.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-A15C17DE-4858-46D7-9F57-472A864DD555" id="BPMNShape_sid-A15C17DE-4858-46D7-9F57-472A864DD555">
<omgdc:Bounds height="28.0" width="28.0" x="856.0" y="330.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-70588DC2-2C9F-426B-82C5-90344C881530" id="BPMNShape_sid-70588DC2-2C9F-426B-82C5-90344C881530">
<omgdc:Bounds height="28.0" width="28.0" x="1065.0" y="161.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sid-905216E3-CB13-4925-AD93-0F2C16E62FEC" id="BPMNEdge_sid-905216E3-CB13-4925-AD93-0F2C16E62FEC" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="20.5" flowable:targetDockerY="20.5">
<omgdi:waypoint x="729.9499999999908" y="175.1311023622047"></omgdi:waypoint>
<omgdi:waypoint x="850.447368421052" y="175.44736842105263"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-453685A6-9775-40C7-A477-13DE625470CB" id="BPMNEdge_sid-453685A6-9775-40C7-A477-13DE625470CB" flowable:sourceDockerX="20.5" flowable:sourceDockerY="20.5" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
<omgdi:waypoint x="504.4939335394127" y="175.45103092783506"></omgdi:waypoint>
<omgdi:waypoint x="629.9999999999867" y="175.12840616966582"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-C59BA984-D60E-4DF5-BEB9-9DEDCBF5E274" id="BPMNEdge_sid-C59BA984-D60E-4DF5-BEB9-9DEDCBF5E274" flowable:sourceDockerX="20.5" flowable:sourceDockerY="20.5" flowable:targetDockerX="14.0" flowable:targetDockerY="14.0">
<omgdi:waypoint x="870.4434523809524" y="194.5007287328971"></omgdi:waypoint>
<omgdi:waypoint x="870.0413944779516" y="330.00006113058237"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-FBDC3C0C-DFB4-4491-B7A5-895D8703E1E7" id="BPMNEdge_sid-FBDC3C0C-DFB4-4491-B7A5-895D8703E1E7" flowable:sourceDockerX="15.0" flowable:sourceDockerY="15.0" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
<omgdi:waypoint x="104.94999954302153" y="175.0"></omgdi:waypoint>
<omgdi:waypoint x="240.0" y="175.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-39027013-B136-4AF1-BED8-C517EB3FE323" id="BPMNEdge_sid-39027013-B136-4AF1-BED8-C517EB3FE323" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="20.5" flowable:targetDockerY="20.5">
<omgdi:waypoint x="339.95000000000005" y="175.12774936061382"></omgdi:waypoint>
<omgdi:waypoint x="465.44871794871796" y="175.44871794871796"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-1ADCBAE3-7C5D-4C44-8F05-4646A7669835" id="BPMNEdge_sid-1ADCBAE3-7C5D-4C44-8F05-4646A7669835" flowable:sourceDockerX="20.5" flowable:sourceDockerY="20.5" flowable:targetDockerX="14.0" flowable:targetDockerY="14.0">
<omgdi:waypoint x="889.4909778952386" y="175.45432692307693"></omgdi:waypoint>
<omgdi:waypoint x="1065.0000360964646" y="175.03345328133196"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-2FEB315C-7C5F-4E85-94FC-313E7738C2D1" id="BPMNEdge_sid-2FEB315C-7C5F-4E85-94FC-313E7738C2D1" flowable:sourceDockerX="20.5" flowable:sourceDockerY="20.5" flowable:targetDockerX="14.0" flowable:targetDockerY="14.0">
<omgdi:waypoint x="485.448087431694" y="194.49657291097765"></omgdi:waypoint>
<omgdi:waypoint x="485.03801075760134" y="345.0000521192875"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
启动流程
所用到的常量类
package com.bjhtzckj.commom.constants;
import java.io.File;
/**
* 常量类
*/
public class Constants {
/**
* 工作流相关
*/
public final static String PROCESS_INVOICE_KEY = "invoiceProcess"; //发票流程key(模型key)新建模型时的key
public final static String PERMISSION_SUBMIT_INVOICE = "submit:payment:request"; //财务权限
public final static String PERMISSION_RECHECK_INVOICE = "recheck:payment:request"; //财务总监权限
public final static String PERMISSION_BOSS_INVOICE = "examine:payment:request";//老板权限
//这里就是画流程图时设置的流程变量
public final static String PROCESS_INVOICE_OK = "ok"; //通过
public final static String PROCESS_INVOICE_FAIL = "fail";//拒绝
public final static String PROCESS_INVOICE_RESULT = "result";//审核结果
public final static String ASSIGNEE = "assignee";//流程所属的企业
//这两个是上面bpmn20.xml文件中,userTask标签的id 可以根据这个id查询任务
/**
* <userTask id="sid-75D626F6-AAA4-48C0-A30A-EC0E54A90A2D" name="财务总监审核" flowable:assignee="${assignee}" flowable:formFieldValidation="true">
* <userTask id="sid-7FBBC2DF-119D-4E45-8B53-4247FCCE0B51" name="老板审核" flowable:assignee="${assignee}" flowable:formFieldValidation="true">
*/
public final static String TASK_KEY_ZJ = "sid-75D626F6-AAA4-48C0-A30A-EC0E54A90A2D";//财务总监审核节点
public final static String TASK_KEY_BOSS = "sid-7FBBC2DF-119D-4E45-8B53-4247FCCE0B51";//老板审核节点
}
Flowable RuntimeService是Flowable工作流引擎中的一个重要服务,用于运行流程实例和执行任务。以下是Flowable RuntimeService的常用方法:
- startProcessInstanceById(String processDefinitionId):通过流程定义ID启动新的流程实例。
- startProcessInstanceByKey(String processDefinitionKey, Map<String, Object> variables):通过流程定义键启动新的流程实例,并可以指定流程变量。
- deleteProcessInstance(String processInstanceId):删除指定的流程实例。
- findTasksByProcessInstanceId(String processInstanceId):根据流程实例ID查询任务。
- findTasksByExecutionId(String executionId):根据执行ID查询任务。
- completeTask(Task task, Map<String, Object> variables):完成任务,并可以指定任务变量。
- delegateTask(Task task, String candidateUser):将任务委托给指定用户。
- setVariable(String executionId, String variableName, Object value):设置执行ID对应的流程变量的值。
- getVariable(String executionId, String variableName):获取执行ID对应的流程变量的值。
通过这些方法,可以方便地运行流程实例、查询任务、完成任务和操作流程变量等。
/**
* 启动一个发票报销流程实例,设置该流程属于当前登录用户所在的企业,并设置一个业务key
* 以该业务key为主键id,在报销申请记录表中生成一条数据,
* 继续生成报销记录详情,该表中的invoiceRecordsId ==生成的业务key
* 其它相关业务操作...
*/
@Override
@Transactional
public R startInvoiceProcess(InvoiceInfoDto invoiceInfoDto) {
//获取当前登录用户
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
String entId = loginUser.getUser().getEntId();
//要报销的发票集合
List<TInvoiceInfoEntity> invoiceInfoEntityList = invoiceInfoDto.getInvoiceInfoEntityList();
//流程变量
Map<String, Object> variables = new HashMap<>();
//设置该流程属于哪个企业
variables.put(Constants.ASSIGNEE, entId);
//生成业务key
String businessKey = UUID.randomUUID().toString().replaceAll("-", "");
//正常付款(对公和对个人)
if (invoiceInfoDto.getPaymentType() == Constants.PAYMENT_TYPE) {
if (ObjectUtils.isEmpty(invoiceInfoEntityList)) {
return R.error("未选择要申请报销的发票");
}
if (invoiceInfoEntityList.size() > 1) {
//当有一个以上的发票一起报销时,判断供应商编号是否相同
boolean b = invoiceInfoEntityList.stream().allMatch(item -> item.getSupplierNo() .equals(item.getSupplierNo()) );
if (!b) {
log.error("选择的发票所属的供应商不同!");
throw new RRException("对公打款请选择同一供应商的发票!");
}
}
try {
//这里是以流程key来启动流程实例的,也就是前面创建流程模型时的模型key
//第一个参数就是流程key或者说模型key
//第二个参数可以定义一个业务key,方便后面查询流程信息的使用
//第三个参数是该流程中的流程变量,包括 节点的所有人,是否通过审核的条件(当前节点只需设置流程属于哪个企业(entId))
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(Constants.PROCESS_INVOICE_KEY, businessKey, variables);
log.info("启动发票流程,所属企业: {},流程id: {}", entId, processInstance.getId());
//新增一条报销申请记录
InvoiceRecordsEntity invoiceRecordsEntity = new InvoiceRecordsEntity();
//这里把启动流程时的业务key当报销申请记录的主键id
invoiceRecordsEntity.setId(businessKey);
//把流程实例id也保存起来
invoiceRecordsEntity.setProcessinstanceId(processInstance.getId());
//后面都是业务逻辑,就不展示了
return R.ok().put("processinstanceId", processInstance.getId());
} catch (Exception e) {
log.error("发票流程启动失败: {}", e.getMessage());
}
//预付款
if (invoiceInfoDto.getPaymentType() == Constants.PREPAY_TYPE) {
//略
}
return R.error("发票流程启动失败");
}
查询任务
前端页面需要再用户登录成功后,展示当前登录用户的待办任务,这里要用到flowable的taskService
一些方法
//判断当前登录用户是否有指定权限
private boolean hasPermission(String permissionStr) {
boolean hasPermission = false;
//角色id
String roleId = getRoleId();
//角色权限
List<SysRolePermissionEntity> rolePermissionList = sysRolePermissionService.list(new LambdaQueryWrapper<SysRolePermissionEntity>()
.eq(SysRolePermissionEntity::getRoleId, roleId));
//角色权限id集合
List<String> permissionIdList = rolePermissionList.stream().map(item -> {
return item.getPermisId();
}).collect(Collectors.toList());
//是否有指定权限
List<SysPermissionEntity> permissionList = sysPermissionService.listByIds(permissionIdList);
for (SysPermissionEntity permission : permissionList) {
if (permission.getPermissionCode().equals(permissionStr)) {
hasPermission = true;
break;
}
}
return hasPermission;
}
//根据流程实例id查询businessKey (业务key)
private String getBusinessKey(String processInstanceId) {
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
if (processInstance != null) {
return processInstance.getBusinessKey();
} else {
return null;
}
}
根据当前登录用户所拥有的权限,返回相应的待办任务
Flowable的TaskService是Flowable工作流引擎中的一个重要服务,用于管理任务和执行任务。通过TaskService,可以查询、创建、删除任务,以及与任务相关的的一些操作,如完成任务、分配任务、获取任务变量等。
以下是Flowable TaskService的一些基本操作:
- 查询任务:可以使用TaskQuery对象查询任务,可以按照任务ID、名称、负责人等条件进行查询。
- 创建任务:使用CreateTaskBuilder对象可以创建新任务,需要指定任务名称、描述、负责人等属性。
- 完成任务:使用CompleteTaskBuilder对象可以完成任务,需要指定任务ID和完成状态。
- 分配任务:使用AssignTaskBuilder对象可以分配任务,需要指定任务ID和负责人。
- 获取任务变量:使用GetVariableLambda表达式可以获取任务的变量,需要指定任务ID和变量名称。
- 设置任务变量:使用SetVariableLambda表达式可以设置任务的变量,需要指定任务ID、变量名称和变量值。
通过Flowable TaskService,可以方便地管理任务和执行任务相关操作,提高工作流引擎的效率和灵活性。
/**
* 查询登录用户的所有待处理任务
*/
@Override
public R selectTask() {
//获取当前登录用户所属企业id
String entId = getEntId();
//如果用户有提交报销申请权限(财务),返回所有待录入的发票
List<TaskDto> data = new ArrayList<>();
if (hasPermission(Constants.PERMISSION_SUBMIT_INVOICE)) {
log.info("查询财务待办任务");
LambdaQueryWrapper<TInvoiceInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(TInvoiceInfoEntity::getRegisterStatus, 0).eq(TInvoiceInfoEntity::getEntId, entId);
List<TInvoiceInfoEntity> entities = invoiceInfoService.list(queryWrapper);
if (ObjectUtils.isEmpty(entities)) {
log.info("财务没有待办任务");
} else {
data = entities.stream().map(item -> {
TaskDto taskDto = new TaskDto();
String invoiceType;
switch (item.getInvoiceType()) {
case 1:
invoiceType = "专票";
break;
case 2:
invoiceType = "普票";
break;
case 3:
invoiceType = "定额票";
break;
default:
invoiceType = "专票";
break;
}
taskDto.setTaskName("发票录入");
taskDto.setTaskType(1);
taskDto.setInvoiceNo(item.getInvoiceNo());
taskDto.setInvoiceId(item.getId());
taskDto.setTitle(invoiceType + ":" + item.getProjectName());
taskDto.setCreateTime(item.getCreateTime());
return taskDto;
}).collect(Collectors.toList());
}
}
//当前登录用户是财务总监权限
if (hasPermission(Constants.PERMISSION_RECHECK_INVOICE)) {
log.info("查询总监待办任务");
//Constants.TASK_KEY_ZJ 就是上面常量中的"sid-75D626F6-AAA4-48C0-A30A-EC0E54A90A2D"
//taskAssignee(entId)根据流程创建时设置的企业id,和taskDefinitionKey(Constants.TASK_KEY_ZJ) 来查询该企业下,财务总监审核节点的所有未提交任务
List<Task> taskList = taskService.createTaskQuery().taskAssignee(entId).taskDefinitionKey(Constants.TASK_KEY_ZJ).list();
if (ObjectUtils.isEmpty(taskList)) {
log.info("总监没有待办任务");
} else {
for (Task task : taskList) {
TaskDto taskDto = new TaskDto();
taskDto.setTaskType(2);
taskDto.setTaskName(task.getName());
String businessKey = getBusinessKey(task.getProcessInstanceId());
taskDto.setInvoiceRecordsId(businessKey);
InvoiceRecordsEntity invoiceRecords = this.getById(businessKey);
taskDto.setTitle(invoiceRecords.getRecordsName());
taskDto.setSubmitAmount(invoiceRecords.getSubmitAmount());
taskDto.setCreateTime(invoiceRecords.getCreateTime());
data.add(taskDto);
}
}
}
//当前登录用户是老板权限
if (hasPermission(Constants.PERMISSION_BOSS_INVOICE)) {
log.info("查询boss待办任务");
List<Task> taskList = taskService.createTaskQuery().taskAssignee(entId).taskDefinitionKey(Constants.TASK_KEY_BOSS).list();
if (ObjectUtils.isEmpty(taskList)) {
log.info("boss没有待办任务");
} else {
for (Task task : taskList) {
TaskDto taskDto = new TaskDto();
taskDto.setTaskType(3);
taskDto.setTaskName(task.getName());
String businessKey = getBusinessKey(task.getProcessInstanceId());
taskDto.setInvoiceRecordsId(businessKey);
InvoiceRecordsEntity invoiceRecords = this.getById(businessKey);
taskDto.setTitle(invoiceRecords.getRecordsName());
taskDto.setSubmitAmount(invoiceRecords.getSubmitAmount());
taskDto.setCreateTime(invoiceRecords.getCreateTime());
data.add(taskDto);
}
}
}
Map<String, Object> map = new HashMap<>();
map.put("invoiceData", data);
return R.ok().put("data", map);
}
流程提交
要提交流程,首先要确定流程的当前任务是什么,这里就用到了,启动流程时设置的业务key
/**
* 根据业务key和流程id 查询任务id
*/
private String getTaskIdBybusinessKey(String businessKey, String processInstanceId) {
//查询指定流程的当前任务
Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).processInstanceBusinessKey(businessKey).singleResult();
// 需要处理的任务ID
if (!ObjectUtils.isEmpty(task)) {
return task.getId();
} else {
return null;
}
}
流程提交使用 taskService.complete(taskId, variables) 方法,需要知道任务id,variables是流程变量
这里我把业务代码都删除了,只保留 一些工作流相关的供参考
因为这个流程设计的是只要是这个企业下有指定权限的用户都可以提交流程,所以
还需要判断是否有权限提交流程
如果你的流程是指定某个用户来审核,需要判断当前登录用户是不是你指定的用户
/**
* 流程提交
*/
@Override
@Transactional
public R commit(InvoiceRecordsEntity invoiceRecords) {
//获取当前登录用户
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
//流程id(在启动流程时,我们保存了流程实例id)
String processInstanceId = invoiceRecords.getProcessinstanceId();
List<TInvoiceInfoEntity> invoiceInfoEntityList = this.baseMapper.getInvoiceInfoByRecordsId(invoiceRecords.getId());
//获取流程实例
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
if (processInstance == null) {
log.error("流程不存在或已挂起");
return R.error("流程不存在或已挂起");
}
//任务id(这里也可以用invoiceRecords的id,因为启动流程时设置的businessKey就是报销记录的id)
String taskId = getTaskIdBybusinessKey(processInstance.getBusinessKey(), processInstanceId);
//流程流转数据
Map<String, Object> variables = new HashMap<>();
//流程步骤状态
Integer stepStatus = null;
//删除标记
boolean delFlag = false;
//总监审核提交
if (invoiceRecords.getStepStatus().equals(Constants.STEP_STATUS_2)) {
log.info("当前节点是总监审核");
if (invoiceRecords.getPassNum() == 0) {
//总监审核通过需要把步骤设置为老板审核
stepStatus = Constants.STEP_STATUS_3;
//把审核结果加入流程变量
variables.put(Constants.PROCESS_INVOICE_RESULT, Constants.PROCESS_INVOICE_OK);
log.info("总监审核通过");
} else {
//审核不通过
//修改状态为审核不通过
stepStatus = Constants.STEP_STATUS_4;
//删除标记
delFlag = true;
variables.put(Constants.PROCESS_INVOICE_RESULT, Constants.PROCESS_INVOICE_FAIL);
log.info("总监驳回");
}
}
//老板审核审核提交
if (invoiceRecords.getStepStatus().equals(Constants.STEP_STATUS_3)) {
log.info("当前节点是老板审核");
if (invoiceRecords.getPassNum() == 0) {
//老板审核通过,修改状态为待打款
stepStatus = Constants.STEP_STATUS_5;
variables.put(Constants.PROCESS_INVOICE_RESULT, Constants.PROCESS_INVOICE_OK);
log.info("老板审核通过");
} else {
stepStatus = Constants.STEP_STATUS_4;
delFlag = true;
variables.put(Constants.PROCESS_INVOICE_RESULT, Constants.PROCESS_INVOICE_FAIL);
log.info("老板驳回");
}
}
try {
taskService.complete(taskId, variables);
invoiceRecords.setStepStatus(stepStatus);
//更新报销记录
this.updateById(invoiceRecords);
return R.ok("流程提交成功").put("processInstanceId", processInstanceId);
} catch (Exception e) {
log.error("流程提交失败,流程id: {}", processInstanceId);
return R.error("流程提交失败");
}
}
监听器的使用
执行监听器
需求是: 发票报销审核通过或者拒绝需要发送短信通知
这里我就考虑使用了监听器
主要是用了创建流程模型时设置的执行监听器,当流程走到结束节点时就会触发,在根据流程变量发送 审核通过或不通过的短信
执行监听器
使用方法:
实现flowable的ExecutionListener接口 并实现notify 方法
使用@Component(“invoiceProcessEventListener”) 把监听器交给spring管理
“invoiceProcessEventListener” 就是创建流程模型时,委托表达式中的值
然后在notify方法中写你的业务逻辑就行了
package com.bjhtzckj.server.listener;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.bjhtzckj.commom.constants.Constants;
import com.bjhtzckj.commom.entity.InvoiceRecordsEntity;
import com.bjhtzckj.commom.entity.SysUserEntity;
import com.bjhtzckj.server.service.InvoiceRecordsService;
import com.bjhtzckj.server.service.SmsControlInfoService;
import com.bjhtzckj.server.service.SysUserService;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.ExecutionListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author 马艳鹏
* 发票流程任务完成监听器
*/
@Slf4j
@Component("invoiceProcessEventListener")
public class InvoiceProcessEventListener implements ExecutionListener {
@Autowired
private SmsControlInfoService smsService;
@Autowired
private InvoiceRecordsService invoiceRecordsService;
@Autowired
private SysUserService sysUserService;
//DelegateExecution execution 可以获取流程执行的状态、变量、任务等信息,并且可以对这些信息进行修改。
@Override
public void notify(DelegateExecution execution) {
//获取审核结果
String result = (String) execution.getVariable(Constants.PROCESS_INVOICE_RESULT);
//获取流程实例id
String processInstanceId = execution.getProcessInstanceId();
//根据流程实例id查询报销申请记录
InvoiceRecordsEntity invoiceRecords = invoiceRecordsService.getOne(new LambdaQueryWrapper<InvoiceRecordsEntity>()
.eq(InvoiceRecordsEntity::getProcessinstanceId, processInstanceId));
String phoneNum=null;
String msg=null;
if (result.equals(Constants.PROCESS_INVOICE_OK)){
log.info("---------通知财务,老板审核已通过--------");
if (invoiceRecords.getCreateUser()!=null){
phoneNum = getPhoneByUserName(invoiceRecords.getCreateUser());
msg="报销申请:"+invoiceRecords.getRecordsName()+":"+invoiceRecords.getSubmitAmount()+"已通过";
System.out.println(msg);
}
try {
smsService.sendMessage(Constants.SMS_TYPE_ENT4, Constants.SMS_SIGN+msg,phoneNum);
log.info("短信发送成功");
} catch (Exception e) {
log.error("短信发送失败: {}",e.getMessage());
}
}
if (result.equals(Constants.PROCESS_INVOICE_FAIL)){
log.info("------------通知财务,审核未通过---------------");
if (invoiceRecords.getCreateUser()!=null){
phoneNum = getPhoneByUserName(invoiceRecords.getCreateUser());
msg="报销申请:"+invoiceRecords.getRecordsName()+":"+invoiceRecords.getSubmitAmount()+"未通过";
System.out.println(msg);
}
try {
smsService.sendMessage(Constants.SMS_TYPE_ENT4, Constants.SMS_SIGN+msg,phoneNum);
log.info("短信发送成功");
} catch (Exception e) {
log.error("短信发送失败: {}",e.getMessage());
}
}
}
private String getPhoneByUserName(String userName){
SysUserEntity sysUser = sysUserService.getOne(new LambdaQueryWrapper<SysUserEntity>().eq(SysUserEntity::getUserName, userName));
if (ObjectUtil.isEmpty(sysUser)){
log.error("用户不存在或手机号为空");
}
return sysUser.getPhonenumber();
}
}
任务监听器
在新建流程模型时,我也设置了任务监听器,只是暂时没用到,方便以后拓展的使用
这里只是获取了流程所属的企业id
配置方法和前面的一样,只是实现的接口换成了 flowable的TaskListener
package com.bjhtzckj.server.listener;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.delegate.TaskListener;
import org.flowable.task.service.delegate.DelegateTask;
import org.springframework.stereotype.Component;
/**
* @author 马艳鹏
* 发票报销流程 任务监听器
*/
@Slf4j
@Component("invoiceProcessTaskListener")
public class InvoiceProcessTaskListener implements TaskListener {
//DelegateTask delegateTask 可以获取流程任务相关的信息
@Override
public void notify(DelegateTask delegateTask) {
//在任务创建后 获取该任务的信息
String assignee = delegateTask.getAssignee();
log.info("任务创建,所属企业id: {}",assignee);
//TODO 通知审核人
log.info("企业:"+assignee+"有新任务待处理");
}
}