SpringBoot+Activiti实现工作流
文档结构目录
1、创建demo项目
2、安装Activiti插件
3、配置项目数据源
4、配置项目启动项
5、记录项目启动问题
6、表结构说明
7、绘制流程图
8、配置流程图的监听类
9、配置线程共享变量类
10、流程的启动
10、任务的开始和完成
由于时间原因,此篇文章只记录了概要内容,欢迎提出疑问和建议,转载请注明出处。
1、创建demo项目
我用的IDE工具为Eclipse,直接在https://start.spring.io/上创建项目
在创建项目时,可以根据项目的需要去选择SpringBoot版本和依赖
image.png
SpringBoot版本
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
Activiti版本
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter-basic</artifactId>
<version>6.0.0</version>
</dependency>
持久层mybatis
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
API工具
<!--swagger -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.8.0</version>
</dependency>
数据库mysql
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
2、安装Activiti插件
我用的IDE工具是eclipse,插件的安装方式主要有:在线安装和离线安装,我这边推荐的是离线安装,安装步骤如下:
1)下载Activiti离线安装包
链接:https://pan.baidu.com/s/19jOGCUT37fOHQO6MAa38_Q
密码:adeh
2)打开Eclipse->Help->Install New SoftWare,点击的图中Add按钮,Name自定义,Location为activiti-designer-5.14.1.zip的文件地址,如图所示:
如果在安装过程中出现插件的部分的内容没有安装成功,可以将下载内容的jar文件下的3个jar文件拷贝到eclipse安装目录的plugins目录下,重启Eclopse即可。
3、配置项目数据源
application.yml
内容如下:
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/demo?autoReconnect=true&characterEncoding=utf8&useSSL=false
username: root
password: 123
## 该配置节点为独立的节点,有很多同学容易将这个配置放在spring的节点下,导致配置无法被识别
mybatis:
mapper-locations: classpath:mapper/*.xml #注意:一定要对应mapper映射xml文件的所在路径
type-aliases-package: com.taikang.osms.model # 注意:对应实体类的路径
4、配置项目启动项
AvtivitiServerApplication.java
内容如下:
package com.taikang.osms;
import org.activiti.spring.boot.SecurityAutoConfiguration;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
/**
* <pre>
* 标题:AvtivitiServerApplication为SpringBoot服务启动类
* 功能描述:无
* 特别说明:
解决acticiti启动报错问题:exclude = SecurityAutoConfiguration.class
* 创建者:jiangnan.he
* 创建时间:2018年8月19日 上午8:00:22
* 修改者:jiangnan.he
* 修改时间:2018年8月19日 上午8:00:22
* 修改说明:无
* </pre>
*/
@ComponentScan(basePackages = { "com.taikang.osms" })
@MapperScan(basePackages = { "com.taikang.osms.dao" })
@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
public class AvtivitiServerApplication {
public static void main(String[] args) {
SpringApplication.run(AvtivitiServerApplication.class, args);
}
}
5、记录项目整合问题
问题1:
Caused by: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
The last packet successfully received from the server was 9 milliseconds ago. The last packet sent successfully to the server was 9 milliseconds ago.
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:1.8.0_181]
at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source) ~[na:1.8.0_181]
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source) ~[na:1.8.0_181]
at java.lang.reflect.Constructor.newInstance(Unknown Source) ~[na:1.8.0_181]
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425) ~[mysql-connector-java-5.1.46.jar:5.1.46]
at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:990) ~[mysql-connector-java-5.1.46.jar:5.1.46]
at com.mysql.jdbc.ExportControlled.transformSocketToSSLSocket(ExportControlled.java:201) ~[mysql-connector-java-5.1.46.jar:5.1.46]
at com.mysql.jdbc.MysqlIO.negotiateSSLConnection(MysqlIO.java:4912) ~[mysql-connector-java-5.1.46.jar:5.1.46]
at com.mysql.jdbc.MysqlIO.proceedHandshakeWithPluggableAuthentication(MysqlIO.java:1663) ~[mysql-connector-java-5.1.46.jar:5.1.46]
at com.mysql.jdbc.MysqlIO.doHandshake(MysqlIO.java:1224) ~[mysql-connector-java-5.1.46.jar:5.1.46]
at com.mysql.jdbc.ConnectionImpl.coreConnect(ConnectionImpl.java:2190) ~[mysql-connector-java-5.1.46.jar:5.1.46]
at com.mysql.jdbc.ConnectionImpl.connectWithRetries(ConnectionImpl.java:2037) ~[mysql-connector-java-5.1.46.jar:5.1.46]
... 99 common frames omitted
解决方案:在application.yml
中数据源配置useSSL
必须等于false
,如步骤2所示。
问题2:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'documentationPluginsBootstrapper' defined in URL [jar:file:/D:/repository/io/springfox/springfox-spring-web/2.8.0/springfox-spring-web-2.8.0.jar!/springfox/documentation/spring/web/plugins/DocumentationPluginsBootstrapper.class]: Unsatisfied dependency expressed through constructor parameter 1; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'webMvcRequestHandlerProvider' defined in URL [jar:file:/D:/repository/io/springfox/springfox-spring-web/2.8.0/springfox-spring-web-2.8.0.jar!/springfox/documentation/spring/web/plugins/WebMvcRequestHandlerProvider.class]: Unsatisfied dependency expressed through constructor parameter 1; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestMappingHandlerMapping' defined in class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]: Invocation of init method failed; nested exception is java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:732) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:197) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1267) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1124) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:535) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:759) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:869) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:762) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:398) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:330) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1258) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1246) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at com.taikang.osms.AvtivitiServerApplication.main(AvtivitiServerApplication.java:27) [classes/:na]
解决方案:在application启动类上加上@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
,参照步骤3图所示
6、表结构说明
Activiti在项目启动时,会自动创建以ACT_开头的28张表,表说明如下:
* Activiti自带的28张表:
* ACT_RE_*:repository,流程定义和流程静态资源 (图片,规则,等等)
*
* ACT_RU_*:runtime,在流程运行时保存数据,流程结束时清除
* ACT_RU_VARIABLE:更新流程信息,一个工作流只有一个流程
* ACT_RU_TASK:更新任务信息,一个流程有多个任务,该表对每个任务只做更新,不保留任务轨迹历史数据
*
* ACT_HI_*: history,包含历史数据,任务轨迹等
*
* ACT_GE_*: 通用数据,如存放资源文件等
*
* ACT_ID_*: ‘ID’表示identity。 这些表包含身份信息,比如用户,组等等
为了实现业务的拓展,在此28张表的基础上,我创建了2张工作流业务拓展表demo_process_ext
和demo_task_ext
,表说明如下:
* 一个完整的工作流流程主要是包含一个流程实例和多个任务实例
* 创建2张的工作流业务扩展表
* demo_process_ext:
工作流流程扩展表,保存流程实例信息,只有一条流程记录,流程的开始、过程中和结束更新数据
* demo_task_ext:
工作流任务扩展表,保存任务实例信息,多个任务多条记录
7、绘制流程图
下面我们绘制一个基本的业务审批流程,线条交集处可配置判断条件,控制流程的走向,流程的主要过程为:流程创建->任务创建->任务完成->下一个任务创建->下一个任务完成->...->最终任务结束->流程结束
image.png
流程图自动生成的代码:
<?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:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
<process id="checkProcess" name="My process" isExecutable="true">
<extensionElements>
<activiti:executionListener event="start" class="com.taikang.osms.listener.BpmProcessListener"></activiti:executionListener>
<activiti:executionListener event="end" class="com.taikang.osms.listener.BpmProcessListener"></activiti:executionListener>
</extensionElements>
<userTask id="apply" name="申请">
<extensionElements>
<activiti:taskListener event="all" class="com.taikang.osms.listener.BpmTaskListener">
<activiti:field name="taskNameConfig">
<activiti:string><![CDATA[发起申请]]></activiti:string>
</activiti:field>
</activiti:taskListener>
</extensionElements>
</userTask>
<userTask id="check" name="审核">
<extensionElements>
<activiti:taskListener event="all" class="com.taikang.osms.listener.BpmTaskListener">
<activiti:field name="taskNameConfig">
<activiti:string><![CDATA[审批]]></activiti:string>
</activiti:field>
</activiti:taskListener>
</extensionElements>
</userTask>
<endEvent id="end" name="End"></endEvent>
<startEvent id="start" name="Start"></startEvent>
<exclusiveGateway id="exclusivegateway3" name="Exclusive Gateway"></exclusiveGateway>
<sequenceFlow id="flow8" sourceRef="start" targetRef="exclusivegateway3"></sequenceFlow>
<sequenceFlow id="flow9" sourceRef="exclusivegateway3" targetRef="apply"></sequenceFlow>
<exclusiveGateway id="exclusivegateway4" name="Exclusive Gateway"></exclusiveGateway>
<sequenceFlow id="flow11" sourceRef="check" targetRef="exclusivegateway4"></sequenceFlow>
<sequenceFlow id="flow12" name="同意" sourceRef="exclusivegateway4" targetRef="end">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[#{bpmData.get("checkOpinion")!=null&&bpmData.get("checkOpinion")=="1"}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow13" name="退回" sourceRef="exclusivegateway4" targetRef="exclusivegateway3">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[#{bpmData.get("checkOpinion")!=null&&bpmData.get("checkOpinion")=="0"}]]></conditionExpression>
</sequenceFlow>
<exclusiveGateway id="exclusivegateway5" name="Exclusive Gateway"></exclusiveGateway>
<sequenceFlow id="flow14" sourceRef="apply" targetRef="exclusivegateway5"></sequenceFlow>
<sequenceFlow id="flow15" name="上报" sourceRef="exclusivegateway4" targetRef="exclusivegateway5">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[#{bpmData.get("checkOpinion")!=null&&bpmData.get("checkOpinion")=="2"}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow16" sourceRef="exclusivegateway5" targetRef="check"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_checkProcess">
<bpmndi:BPMNPlane bpmnElement="checkProcess" id="BPMNPlane_checkProcess">
<bpmndi:BPMNShape bpmnElement="apply" id="BPMNShape_apply">
<omgdc:Bounds height="55.0" width="105.0" x="200.0" y="170.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="check" id="BPMNShape_check">
<omgdc:Bounds height="55.0" width="105.0" x="200.0" y="360.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="end" id="BPMNShape_end">
<omgdc:Bounds height="35.0" width="35.0" x="235.0" y="590.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="start" id="BPMNShape_start">
<omgdc:Bounds height="35.0" width="35.0" x="235.0" y="20.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="exclusivegateway3" id="BPMNShape_exclusivegateway3">
<omgdc:Bounds height="40.0" width="40.0" x="232.0" y="90.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="exclusivegateway4" id="BPMNShape_exclusivegateway4">
<omgdc:Bounds height="40.0" width="40.0" x="232.0" y="479.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="exclusivegateway5" id="BPMNShape_exclusivegateway5">
<omgdc:Bounds height="40.0" width="40.0" x="232.0" y="279.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="flow8" id="BPMNEdge_flow8">
<omgdi:waypoint x="252.0" y="55.0"></omgdi:waypoint>
<omgdi:waypoint x="252.0" y="90.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow9" id="BPMNEdge_flow9">
<omgdi:waypoint x="252.0" y="130.0"></omgdi:waypoint>
<omgdi:waypoint x="252.0" y="170.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow11" id="BPMNEdge_flow11">
<omgdi:waypoint x="252.0" y="415.0"></omgdi:waypoint>
<omgdi:waypoint x="252.0" y="479.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow12" id="BPMNEdge_flow12">
<omgdi:waypoint x="252.0" y="519.0"></omgdi:waypoint>
<omgdi:waypoint x="252.0" y="590.0"></omgdi:waypoint>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="14.0" width="24.0" x="269.0" y="549.0"></omgdc:Bounds>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow13" id="BPMNEdge_flow13">
<omgdi:waypoint x="272.0" y="499.0"></omgdi:waypoint>
<omgdi:waypoint x="433.0" y="499.0"></omgdi:waypoint>
<omgdi:waypoint x="433.0" y="110.0"></omgdi:waypoint>
<omgdi:waypoint x="272.0" y="110.0"></omgdi:waypoint>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="14.0" width="24.0" x="439.0" y="330.0"></omgdc:Bounds>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow14" id="BPMNEdge_flow14">
<omgdi:waypoint x="252.0" y="225.0"></omgdi:waypoint>
<omgdi:waypoint x="252.0" y="279.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow15" id="BPMNEdge_flow15">
<omgdi:waypoint x="232.0" y="499.0"></omgdi:waypoint>
<omgdi:waypoint x="137.0" y="499.0"></omgdi:waypoint>
<omgdi:waypoint x="137.0" y="299.0"></omgdi:waypoint>
<omgdi:waypoint x="232.0" y="299.0"></omgdi:waypoint>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="14.0" width="24.0" x="91.0" y="390.0"></omgdc:Bounds>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow16" id="BPMNEdge_flow16">
<omgdi:waypoint x="252.0" y="319.0"></omgdi:waypoint>
<omgdi:waypoint x="252.0" y="360.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
8、配置流程图监听类
1)配置流程ID
点击流程图空白处,我们可以设置流程的ID,代码调用时需要,图中id为checkProcess
image.png
2)配置流程的监听事件
点击流程图空白处,在Properties的Listeners中,我们可以看到流程主要有2个监听事件start和end:
start:监听流程的开始
end:监听流程的结束
点击Select class 为这两个监听事件选择监听类BpmProcessListener.java(类名自定义),如下图所示,
image.png
在创建流程和结束流程时,会自动运行如下代码:
public class BpmProcessListener implements ExecutionListener {
private static final long serialVersionUID = 1L;
@SuppressWarnings("unchecked")
@Override
public void notify(DelegateExecution execution) {
// 工作流对象
ExecutionEntity ent = (ExecutionEntity)execution;
// 数据库对象
BpmDao bpmDao = (BpmDao)SpringUtil.getBean("bpmDao");
// 业务数据(由业务代码传入)
Map<String, Object> bpmData = (Map<String, Object>)ent.getVariable("bpmData");
if ("start".equals(execution.getEventName())) {
// 保存流程扩展信息
ProcessExt processExt = new ProcessExt();
processExt.setId(BpmUtil.getUUID());// 主键
processExt.setProcessId(ent.getId());// 流程ID
processExt.setProcessName((String)bpmData.get("taskText"));// 流程名称
if (ent.getSuperExecution() != null) {
// 父流程ID:流程有父级流程ID,直接使用
processExt.setSupProcessId(ent.getSuperExecution().getProcessInstanceId());
} else {
// 否则,使用启动时指定的BusinessKey
processExt.setSupProcessId(ent.getBusinessKey());
}
processExt.setBusinessCode((String)bpmData.get("businessCode"));// 业务代码
processExt.setStatus(BpmConstant.PROCESS_STATUS_ING);// 状态:进行中
processExt.setApplyTime(new Date());// 申请时间
processExt.setApplyRoleCode((String)bpmData.get("belongRoleCode"));// 申请角色
bpmDao.saveProcessExt(processExt);
} else if ("end".equals(execution.getEventName())) {
Map<String, Object> params = new HashMap<String, Object>();
params.put("processId", execution.getId());// 流程ID
params.put("checkRoleCode", (String)bpmData.get("dealRoleCode"));// 审核角色
params.put("checkTime", new Date());// 审核时间
params.put("status", BpmConstant.PROCESS_STATUS_COMPLETE);// 状态:已完成
// 更新流程扩展信息
bpmDao.updateProcessExt(params);
}
}
}
3)配置任务的监听事件
点击工作流图中的'申请'任务,配置Listeners,任务的监听事件主要有:
create:仅监听任务的创建
assignment:仅监听任务的分配
complete:仅监听任务的完成
all;create、assignment、complete均监听
image.png
点击Select Class选择监听类BpmTaskListener.java(类名自定义)
public class BpmTaskListener implements TaskListener {
private static final long serialVersionUID = 1L;
private FixedValue taskNameConfig;// 任务节点名称
public void setTaskNameConfig(FixedValue taskNameConfig) {
this.taskNameConfig = taskNameConfig;
}
@SuppressWarnings("unchecked")
@Override
public void notify(DelegateTask execution) {
// 持久层
BpmDao bpmDao = (BpmDao)SpringUtil.getBean("bpmDao");
// 工作流对象
Map<String, Object> map = execution.getVariables();
// 业务数据(业务功能传入)
Map<String, Object> bpmData = (Map<String, Object>)map.get("bpmData");
// 任务名称
String taskName = (String)taskNameConfig.getValue(execution);
if ("create".equals(execution.getEventName())) {// 任务创建
TaskExt taskExt = new TaskExt();
taskExt.setId(BpmUtil.getUUID());// 主键
taskExt.setProcessId(execution.getProcessInstanceId());// 流程ID
taskExt.setTaskId(execution.getId());// 任务ID
taskExt.setTaskName(taskName);// 任务名称
taskExt.setTaskType((String)bpmData.get("taskType"));// 任务类型
taskExt.setTaskStatus(BpmConstant.TASK_STATUS_WAIT_ACCPT);// 任务状态:未接受
taskExt.setBusinessCode((String)bpmData.get("businessCode"));// 业务代码
taskExt.setCreateTime(new Date());// 创建时间
taskExt.setBelongRoleCode((String)bpmData.get("belongRoleCode"));// 目的角色
taskExt.setBelongPersonCode((String)bpmData.get("belongPersonCode"));// 目的人
// 保存任务扩展信息
bpmDao.saveTaskExt(taskExt);
// 更新流程扩展主信息
Map<String, Object> process = new HashMap<String, Object>();
if (bpmData.get("checkOpinion") != null && !"".equals(bpmData.get("checkOpinion"))) {
if (BpmConstant.CHECK_OPINION_REFUSE.equals(bpmData.get("checkOpinion"))) {// 退回
process.put("status", BpmConstant.PROCESS_STATUS_BACK);
} else if (BpmConstant.CHECK_OPINION_HANDUP.equals(bpmData.get("checkOpinion"))) {// 上报
process.put("status", BpmConstant.PROCESS_STATUS_HANDUP);
} else {
process.put("status", BpmConstant.PROCESS_STATUS_COMPLETE);
}
} else {
process.put("status", BpmConstant.PROCESS_STATUS_ING);
}
process.put("processId", execution.getProcessInstanceId());// 流程ID
process.put("taskId", execution.getId());// 任务ID
bpmDao.updateProcessStatus(process);
// 添加线程变量
ActivitiThreadLocal.addThreadLocalData(taskExt);
} else if ("assignment".equals(execution.getEventName())) {// 任务分派
} else if ("complete".equals(execution.getEventName())) {// 任务完成
// 更新任务扩展信息
Map<String, Object> params = new HashMap<String, Object>();
params.put("taskId", execution.getId());// 任务ID
params.put("taskText", (String)bpmData.get("taskText"));// 任务内容
params.put("dealRoleCode", (String)bpmData.get("dealRoleCode"));// 处理角色
params.put("dealPersonCode", (String)bpmData.get("dealPersonCode"));// 处理人
params.put("dealTime", new Date());// 处理时间
if (bpmData.get("checkOpinion") != null && !"".equals(bpmData.get("checkOpinion"))) {
if (BpmConstant.CHECK_OPINION_REFUSE.equals(bpmData.get("checkOpinion"))) {// 退回
params.put("taskStatus", BpmConstant.TASK_STATUS_BACK);
} else if (BpmConstant.CHECK_OPINION_HANDUP.equals(bpmData.get("checkOpinion"))) {// 上报
params.put("taskStatus", BpmConstant.TASK_STATUS_HANDUP);
} else {
params.put("taskStatus", BpmConstant.TASK_STATUS_COMPLETE);
}
} else {
params.put("taskStatus", BpmConstant.TASK_STATUS_COMPLETE);
}
bpmDao.updateTaskExt(params);
}
}
}
9、配置线程共享变量
为了在业务代码获取工作流接口内的流程和任务信息,我配置了一个ThreadLocal类,存储的对象为TaskExt任务扩展表的数据
public class ActivitiThreadLocal {
private static ThreadLocal<List<TaskExt>> activitiThreadLocalData = new ThreadLocal<List<TaskExt>>();
public static void bindData(List<TaskExt> threadLocalData) {
activitiThreadLocalData.set(threadLocalData);
}
public static void bindData() {
activitiThreadLocalData.set(new ArrayList<TaskExt>());
}
public static void unBindData() {
activitiThreadLocalData.set(null);
}
public static void clear() {
List<TaskExt> threadLocalData = getThreadLocalDataAll();
if (threadLocalData != null) {
threadLocalData.clear();
}
}
public static List<TaskExt> getThreadLocalDataAll() {
return activitiThreadLocalData.get();
}
public static void addThreadLocalData(TaskExt taskExt) {
List<TaskExt> threadLocalData = activitiThreadLocalData.get();
if (threadLocalData != null) {
// 修改流程图重新编译后actClmThreadLocalData.get()获取处理的对象是null 特需要重新绑定下变量信息
threadLocalData.add(taskExt);
} else {
threadLocalData = new ArrayList<TaskExt>();
threadLocalData.add(taskExt);
bindData(threadLocalData);
}
}
}
10、流程的启动
流程的启动调用了工作流RuntimeService
接口的startProcessInstanceByKey
方法
public List<TaskExt> startTask(String bpmId, Map<String, Object> params) {
// 流程启动清空线程变量
ActivitiThreadLocal.clear();
// 将TaskExt对象绑定到当前线程
ActivitiThreadLocal.bindData();
// 启动流程
if (params != null) {
/*
* 若流程启动关联对象不为空,将该对象作为流程启动变量
*/
runtimeService.startProcessInstanceByKey(bpmId, params);
} else {
runtimeService.startProcessInstanceByKey(bpmId);
}
// 获取线程变量
List<TaskExt> taskExtList = ActivitiThreadLocal.getThreadLocalDataAll();
return taskExtList;
}
流程启动时,会自动生成第一个任务,调用TaskService接口的complete方法,完成这个任务,工作流接口又会自动生成下一个任务。分配任务时,不会生成下一个任务。
public List<TaskExt> completeTask(String taskId, Map<String, Object> params) {
try {
// 清空线程变量
ActivitiThreadLocal.clear();
// 将TaskExt对象绑定到当前线程
ActivitiThreadLocal.bindData();
// 查询当前任务
TaskExt taskExt = bpmDao.queryTaskExtById(taskId);
String status = taskExt.getTaskStatus();
if (BpmConstant.TASK_STATUS_COMPLETE.equals(status)) {
// 判断任务状态
throw new Exception("工作流任务接口调用出错,任务已被处理!");
}
// 完成任务
taskService.complete(taskId, params);
// 获取线程变量
List<TaskExt> taskExtList = ActivitiThreadLocal.getThreadLocalDataAll();
return taskExtList;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}