美文网首页Java后台运维监控Tool
SpringBoot+Activiti实现工作流

SpringBoot+Activiti实现工作流

作者: 不给糖 | 来源:发表于2018-08-21 15: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;
    }
    

    相关文章

      网友评论

      本文标题:SpringBoot+Activiti实现工作流

      本文链接:https://www.haomeiwen.com/subject/fwmliftx.html