Java后台运维监控Tool

SpringBoot+Activiti实现工作流

2018-08-21  本文已影响1647人  不给糖
文档结构目录

1、创建demo项目
2、安装Activiti插件
3、配置项目数据源
4、配置项目启动项
5、记录项目启动问题
6、表结构说明
7、绘制流程图
8、配置流程图的监听类
9、配置线程共享变量类
10、流程的启动
10、任务的开始和完成
由于时间原因,此篇文章只记录了概要内容,欢迎提出疑问和建议,转载请注明出处。

1、创建demo项目

我用的IDE工具为Eclipse,直接在https://start.spring.io/上创建项目

image.png

在创建项目时,可以根据项目的需要去选择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的文件地址,如图所示:

image.png

如果在安装过程中出现插件的部分的内容没有安装成功,可以将下载内容的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_extdemo_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;
}
上一篇下一篇

猜你喜欢

热点阅读