SpringBoot Activiti6

1、Activiti6简介

对于流程审批类型的项目,用Activiti解决再合适不过了。
Activiti是一种轻量级,可嵌入的BPM引擎,而且还设计适用于可扩展的云架构。可以和SpringBoot完美结合。是一个工作流处理框架。
Activiti6的7 个服务接口和28张表:

服务接口 说明
RepositoryService 仓库服务,用于管理仓库,比如部署或删除流程定义、读取流程资源等。
IdentifyService 身份服务,管理用户、组以及它们之间的关系。
RuntimeService 运行时服务,管理所有正在运行的流程实例、任务等对象。
TaskService 任务服务,管理任务。
FormService 表单服务,管理和流程、任务相关的表单。
HistroyService 历史服务,管理历史数据。
ManagementService 引擎管理服务,比如管理引擎的配置、数据库和作业等核心对象
说明
ACTRE* RE’表示repository。 这个前缀的表包含了流程定义和流程静态资源 (图片,规则,等等)
ACTRU* RU’表示runtime。这些运行时的表,包含流程实例,任务,变量,异步任务,等运行中的数据。 Activiti只在流程实例执行过程中保存这些数据,在流程结束时就会删除这些记录。 这样运行时表可以一直很小速度很快。
ACTID* ‘ID’表示identity。 这些表包含身份信息,比如用户,组等等。
ACTHI* ‘HI’表示history。 这些表包含历史数据,比如历史流程实例, 变量,任务等等。
ACTGE* 通用数据, 用于不同场景下,如存放资源文件。

2、引入依赖

  1. <!--fastjson-->
  2. <dependency>
  3. <artifactId>fastjson</artifactId>
  4. <groupId>com.alibaba</groupId>
  5. <version>1.2.67</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.apache.commons</groupId>
  9. <artifactId>commons-lang3</artifactId>
  10. </dependency>
  11. <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
  12. <dependency>
  13. <groupId>io.springfox</groupId>
  14. <artifactId>springfox-swagger2</artifactId>
  15. <version>2.9.2</version>
  16. </dependency>
  17. <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
  18. <dependency>
  19. <groupId>io.springfox</groupId>
  20. <artifactId>springfox-swagger-ui</artifactId>
  21. <version>2.9.2</version>
  22. </dependency>
  23. <!-- https://mvnrepository.com/artifact/com.github.xiaoymin/swagger-bootstrap-ui -->
  24. <dependency>
  25. <groupId>com.github.xiaoymin</groupId>
  26. <artifactId>swagger-bootstrap-ui</artifactId>
  27. <version>1.8.5</version>
  28. </dependency>
  29. <dependency>
  30. <groupId>org.activiti</groupId>
  31. <artifactId>activiti-spring-boot-starter-basic</artifactId>
  32. <version>6.0.0</version>
  33. </dependency>
  34. <dependency>
  35. <groupId>com.alibaba</groupId>
  36. <artifactId>druid-spring-boot-starter</artifactId>
  37. <version>1.1.13</version>
  38. </dependency>
  39. <dependency>
  40. <groupId>mysql</groupId>
  41. <artifactId>mysql-connector-java</artifactId>
  42. <version>8.0.19</version>
  43. </dependency>
  44. <dependency>
  45. <groupId>org.springframework.boot</groupId>
  46. <artifactId>spring-boot-starter-web</artifactId>
  47. </dependency>
  48. <dependency>
  49. <groupId>javax.servlet</groupId>
  50. <artifactId>servlet-api</artifactId>
  51. <version>2.5</version>
  52. </dependency>

3、在SpringBoot配置文件配置数据源与Activiti

  1. mysql:
  2. url: jdbc:mysql:///test?serverTimezone=UTC
  3. username: root
  4. password: 123456
  5. driverClassName: com.mysql.cj.jdbc.Driver
  6. spring:
  7. application:
  8. name: ServiceActiviti
  9. datasource:
  10. type: com.alibaba.druid.pool.DruidDataSource
  11. url: ${mysql.url}
  12. driver-class-name: ${mysql.driverClassName}
  13. username: ${mysql.username}
  14. password: ${mysql.password}
  15. druid: # #
  16. url: ${mysql.url}
  17. username: ${mysql.username}
  18. password: ${mysql.password}
  19. driver-class-name: ${mysql.driverClassName}
  20. initial-size: 10
  21. max-active: 200
  22. min-idle: 10
  23. max-wait: 60000
  24. pool-prepared-statements: false
  25. validation-query: SELECT 1 FROM DUAL
  26. test-on-borrow: false
  27. test-on-return: false
  28. test-while-idle: true
  29. time-between-eviction-runs-millis: 60000
  30. min-evictable-idle-time-millis: 30000
  31. max-pool-prepared-statement-per-connection-size: 20
  32. aop-patterns: com.msyyt.crm.activity.*
  33. filter: # 状态监控
  34. stat:
  35. enabled: true
  36. db-type: mysql
  37. log-slow-sql: true
  38. slow-sql-millis: 2000
  39. web-stat-filter: # 监控过滤器
  40. enabled: true #是否启用 默认true
  41. exclusions:
  42. - '*.js'
  43. - '*.gif'
  44. - '*.jpg'
  45. - '*.png'
  46. - '*.css'
  47. - '*.ico'
  48. - /druid/*
  49. stat-view-servlet: # druid 监控页面
  50. enabled: true
  51. url-pattern: /druid/*
  52. reset-enable: false
  53. allow: # 白名单
  54. deny: # 黑名单
  55. login-username: admin
  56. login-password: admin
  57. # 工作流
  58. activiti:
  59. # 自动部署验证设置:
  60. # true(默认)自动部署流程
  61. # false 不自动部署,需要手动部署发布流程
  62. check-process-definitions: true
  63. # 可选值为: false,true,create-drop,drop-create
  64. # 默认为true。为true表示activiti会对数据库中的表进行更新操作,如果不存在,则进行创建。
  65. database-schema-update: true

4、配置启动类(排除互斥的安全依赖以及包扫描)

  1. package com.fcant.service_acti;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.context.annotation.ComponentScan;
  5. @SpringBootApplication(exclude={
  6. org.activiti.spring.boot.SecurityAutoConfiguration.class
  7. })
  8. @ComponentScan("com.fcant")
  9. public class ServiceActiApplication {
  10. public static void main(String[] args) {
  11. SpringApplication.run(ServiceActiApplication.class, args);
  12. }
  13. }

5、项目接口文档的配置

  1. package com.fcant.service_acti.config;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
  5. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  6. import springfox.documentation.builders.ApiInfoBuilder;
  7. import springfox.documentation.builders.PathSelectors;
  8. import springfox.documentation.builders.RequestHandlerSelectors;
  9. import springfox.documentation.service.ApiInfo;
  10. import springfox.documentation.service.Contact;
  11. import springfox.documentation.spi.DocumentationType;
  12. import springfox.documentation.spring.web.plugins.Docket;
  13. import springfox.documentation.swagger2.annotations.EnableSwagger2;
  14. /**
  15. * SwaggerConfig
  16. * <p>
  17. * encoding:UTF-8
  18. *
  19. * @author Fcant 下午 23:41 2020/8/1/0001
  20. */
  21. @Configuration
  22. @EnableSwagger2
  23. public class SwaggerConfig implements WebMvcConfigurer {
  24. @Override
  25. public void addResourceHandlers(ResourceHandlerRegistry registry) {
  26. registry.addResourceHandler("swagger-ui.html")
  27. .addResourceLocations("classpath:/META-INF/resources/");
  28. registry.addResourceHandler("/webjars/**")
  29. .addResourceLocations("classpath:/META-INF/resources/webjars/");
  30. }
  31. /**
  32. * @author Fcant 13:44 2019/12/5
  33. */
  34. @Bean
  35. public Docket petApi() {
  36. return new Docket(DocumentationType.SWAGGER_2)
  37. .apiInfo(apiInfo())
  38. .select()
  39. .apis(RequestHandlerSelectors.basePackage("com.fcant.service_acti.controller"))
  40. .paths(PathSelectors.any())
  41. .build();
  42. }
  43. /**
  44. * 该套 API 说明,包含作者、简介、版本、host、服务URL
  45. * @return
  46. */
  47. private ApiInfo apiInfo() {
  48. return new ApiInfoBuilder()
  49. .title("Activiti Service API")
  50. .contact(new Contact("fcant","null","fcscanf@outlook.com"))
  51. .version("0.1")
  52. .termsOfServiceUrl("www.yuque.com/fcant")
  53. .description("Activiti Service API")
  54. .build();
  55. }
  56. }

7、在resources下面添加xml类型的工作流文件

image.png

  1. <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  2. <definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:activiti="http://activiti.org/bpmn"
  3. xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
  4. xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI"
  5. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" expressionLanguage="http://www.w3.org/1999/XPath"
  6. id="m1572942222337" name="" targetNamespace="http://www.activiti.org/test"
  7. typeLanguage="http://www.w3.org/2001/XMLSchema">
  8. <process id="QjFlow" isClosed="false" isExecutable="true" processType="None">
  9. <startEvent id="startevent1" name="开始">
  10. <documentation id="startevent1_D_1"/>
  11. </startEvent>
  12. <userTask activiti:candidateUsers="${approveUserIds}" activiti:exclusive="true" id="usertask2" name="用户审批"/>
  13. <exclusiveGateway gatewayDirection="Unspecified" id="exclusivegateway1" name="审核网关"/>
  14. <userTask activiti:assignee="${applyUserId}" activiti:exclusive="true" id="usertask1" name="提交申请"/>
  15. <endEvent id="endevent1" name="结束"/>
  16. <sequenceFlow id="flow3" sourceRef="usertask2" targetRef="exclusivegateway1"/>
  17. <sequenceFlow id="flow6" sourceRef="startevent1" targetRef="usertask1"/>
  18. <sequenceFlow id="flow7" sourceRef="usertask1" targetRef="usertask2"/>
  19. <userTask activiti:assignee="${applyUserId}" activiti:exclusive="true" id="_2" name="重新提交"/>
  20. <exclusiveGateway gatewayDirection="Unspecified" id="_5" name="审核网关"/>
  21. <sequenceFlow id="_6" sourceRef="_2" targetRef="_5"/>
  22. <sequenceFlow id="_7" sourceRef="_5" targetRef="usertask2">
  23. <conditionExpression xsi:type="tFormalExpression"><![CDATA[${result==1}]]></conditionExpression>
  24. </sequenceFlow>
  25. <sequenceFlow id="_8" sourceRef="_5" targetRef="endevent1">
  26. <conditionExpression xsi:type="tFormalExpression"><![CDATA[${result==0}]]></conditionExpression>
  27. </sequenceFlow>
  28. <sequenceFlow id="_9" sourceRef="exclusivegateway1" targetRef="endevent1">
  29. <conditionExpression xsi:type="tFormalExpression"><![CDATA[${result==1 || result==2}]]></conditionExpression>
  30. </sequenceFlow>
  31. <sequenceFlow id="_10" sourceRef="exclusivegateway1" targetRef="_2">
  32. <conditionExpression xsi:type="tFormalExpression"><![CDATA[${result==0}]]></conditionExpression>
  33. </sequenceFlow>
  34. </process>
  35. <bpmndi:BPMNDiagram documentation="background=#3C3F41;count=1;horizontalcount=1;orientation=0;width=842.4;height=1195.2;imageableWidth=832.4;imageableHeight=1185.2;imageableX=5.0;imageableY=5.0" id="Diagram-_1" name="New Diagram">
  36. <bpmndi:BPMNPlane bpmnElement="QjFlow">
  37. <bpmndi:BPMNShape bpmnElement="startevent1" id="Shape-startevent1">
  38. <omgdc:Bounds height="32.0" width="32.0" x="75.0" y="140.0"/>
  39. <bpmndi:BPMNLabel>
  40. <omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
  41. </bpmndi:BPMNLabel>
  42. </bpmndi:BPMNShape>
  43. <bpmndi:BPMNShape bpmnElement="usertask2" id="Shape-usertask2">
  44. <omgdc:Bounds height="55.0" width="105.0" x="320.0" y="130.0"/>
  45. <bpmndi:BPMNLabel>
  46. <omgdc:Bounds height="55.0" width="105.0" x="0.0" y="0.0"/>
  47. </bpmndi:BPMNLabel>
  48. </bpmndi:BPMNShape>
  49. <bpmndi:BPMNShape bpmnElement="exclusivegateway1" id="Shape-exclusivegateway1" isMarkerVisible="false">
  50. <omgdc:Bounds height="32.0" width="32.0" x="480.0" y="140.0"/>
  51. <bpmndi:BPMNLabel>
  52. <omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
  53. </bpmndi:BPMNLabel>
  54. </bpmndi:BPMNShape>
  55. <bpmndi:BPMNShape bpmnElement="usertask1" id="Shape-usertask1">
  56. <omgdc:Bounds height="55.0" width="105.0" x="165.0" y="130.0"/>
  57. <bpmndi:BPMNLabel>
  58. <omgdc:Bounds height="55.0" width="105.0" x="0.0" y="0.0"/>
  59. </bpmndi:BPMNLabel>
  60. </bpmndi:BPMNShape>
  61. <bpmndi:BPMNShape bpmnElement="endevent1" id="Shape-endevent1">
  62. <omgdc:Bounds height="32.0" width="32.0" x="685.0" y="140.0"/>
  63. <bpmndi:BPMNLabel>
  64. <omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
  65. </bpmndi:BPMNLabel>
  66. </bpmndi:BPMNShape>
  67. <bpmndi:BPMNShape bpmnElement="_2" id="Shape-_2">
  68. <omgdc:Bounds height="55.0" width="85.0" x="455.0" y="260.0"/>
  69. <bpmndi:BPMNLabel>
  70. <omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
  71. </bpmndi:BPMNLabel>
  72. </bpmndi:BPMNShape>
  73. <bpmndi:BPMNShape bpmnElement="_5" id="Shape-_5" isMarkerVisible="false">
  74. <omgdc:Bounds height="32.0" width="32.0" x="340.0" y="365.0"/>
  75. <bpmndi:BPMNLabel>
  76. <omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
  77. </bpmndi:BPMNLabel>
  78. </bpmndi:BPMNShape>
  79. <bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3" sourceElement="usertask2" targetElement="exclusivegateway1">
  80. <omgdi:waypoint x="425.0" y="157.5"/>
  81. <omgdi:waypoint x="480.0" y="156.0"/>
  82. <bpmndi:BPMNLabel>
  83. <omgdc:Bounds height="-1.0" width="-1.0" x="-1.0" y="-1.0"/>
  84. </bpmndi:BPMNLabel>
  85. </bpmndi:BPMNEdge>
  86. <bpmndi:BPMNEdge bpmnElement="_6" id="BPMNEdge__6" sourceElement="_2" targetElement="_5">
  87. <omgdi:waypoint x="455.0" y="287.5"/>
  88. <omgdi:waypoint x="372.0" y="381.0"/>
  89. <bpmndi:BPMNLabel>
  90. <omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
  91. </bpmndi:BPMNLabel>
  92. </bpmndi:BPMNEdge>
  93. <bpmndi:BPMNEdge bpmnElement="flow6" id="BPMNEdge_flow6" sourceElement="startevent1" targetElement="usertask1">
  94. <omgdi:waypoint x="107.0" y="156.0"/>
  95. <omgdi:waypoint x="165.0" y="157.5"/>
  96. <bpmndi:BPMNLabel>
  97. <omgdc:Bounds height="-1.0" width="-1.0" x="-1.0" y="-1.0"/>
  98. </bpmndi:BPMNLabel>
  99. </bpmndi:BPMNEdge>
  100. <bpmndi:BPMNEdge bpmnElement="_7" id="BPMNEdge__7" sourceElement="_5" targetElement="usertask2">
  101. <omgdi:waypoint x="340.0" y="381.0"/>
  102. <omgdi:waypoint x="290.0" y="315.0"/>
  103. <omgdi:waypoint x="320.0" y="157.5"/>
  104. <bpmndi:BPMNLabel>
  105. <omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
  106. </bpmndi:BPMNLabel>
  107. </bpmndi:BPMNEdge>
  108. <bpmndi:BPMNEdge bpmnElement="flow7" id="BPMNEdge_flow7" sourceElement="usertask1" targetElement="usertask2">
  109. <omgdi:waypoint x="270.0" y="157.5"/>
  110. <omgdi:waypoint x="320.0" y="157.5"/>
  111. <bpmndi:BPMNLabel>
  112. <omgdc:Bounds height="-1.0" width="-1.0" x="-1.0" y="-1.0"/>
  113. </bpmndi:BPMNLabel>
  114. </bpmndi:BPMNEdge>
  115. <bpmndi:BPMNEdge bpmnElement="_8" id="BPMNEdge__8" sourceElement="_5" targetElement="endevent1">
  116. <omgdi:waypoint x="371.0" y="380.0"/>
  117. <omgdi:waypoint x="600.0" y="380.0"/>
  118. <omgdi:waypoint x="685.0" y="156.0"/>
  119. <bpmndi:BPMNLabel>
  120. <omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
  121. </bpmndi:BPMNLabel>
  122. </bpmndi:BPMNEdge>
  123. <bpmndi:BPMNEdge bpmnElement="_9" id="BPMNEdge__9" sourceElement="exclusivegateway1" targetElement="endevent1">
  124. <omgdi:waypoint x="512.0" y="156.0"/>
  125. <omgdi:waypoint x="685.0" y="156.0"/>
  126. <bpmndi:BPMNLabel>
  127. <omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
  128. </bpmndi:BPMNLabel>
  129. </bpmndi:BPMNEdge>
  130. <bpmndi:BPMNEdge bpmnElement="_10" id="BPMNEdge__10" sourceElement="exclusivegateway1" targetElement="_2">
  131. <omgdi:waypoint x="496.0" y="172.0"/>
  132. <omgdi:waypoint x="496.0" y="260.0"/>
  133. <bpmndi:BPMNLabel>
  134. <omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
  135. </bpmndi:BPMNLabel>
  136. </bpmndi:BPMNEdge>
  137. </bpmndi:BPMNPlane>
  138. </bpmndi:BPMNDiagram>
  139. </definitions>

8、WorkFlow业务逻辑以及接口设计

A.WorkFlowService.java

  1. package com.fcant.service_acti.service;
  2. import javax.servlet.http.HttpServletResponse;
  3. import java.util.List;
  4. import java.util.Map;
  5. /**
  6. * WorkFlowService
  7. * <p>
  8. * encoding:UTF-8
  9. *
  10. * @author Fcant 下午 22:21 2020/8/1/0001
  11. */
  12. public interface WorkFlowService {
  13. /**
  14. * 启动流程
  15. * @param pdKey
  16. * @param businessKey
  17. * @param variables
  18. * @return
  19. */
  20. String startWorkflow(String pdKey, String businessKey, Map<String,Object> variables);
  21. /**
  22. * 继续流程
  23. * @param taskId
  24. * @param variables
  25. */
  26. void continueWorkflow(String taskId, Map variables);
  27. /**
  28. * 委托流程
  29. * @param taskId
  30. * @param variables
  31. */
  32. void delegateWorkflow(String taskId, Map variables);
  33. /**
  34. * 结束流程(一般不使用,让流程正常结束)
  35. * @param pProcessInstanceId
  36. */
  37. void endWorkflow(String pProcessInstanceId,String deleteReason);
  38. /**
  39. * 获取当前的任务节点
  40. * @param pProcessInstanceId
  41. */
  42. String getCurrentTask(String pProcessInstanceId);
  43. /**
  44. * 查询用户待办流程实例ID集合
  45. * @param userId
  46. * @param pdKey
  47. * @return
  48. */
  49. List<String> findUserProcessIds(String userId, String pdKey, Integer pageNo, Integer pageSize);
  50. /**
  51. * 获取流程图像,已执行节点和流程线高亮显示
  52. */
  53. void getProcessImage(String pProcessInstanceId, HttpServletResponse response);
  54. }

B.WorkFlowServiceImpl.java

  1. package com.fcant.service_acti.service.impl;
  2. import com.fcant.service_acti.service.WorkFlowService;
  3. import lombok.extern.slf4j.Slf4j;
  4. import org.activiti.bpmn.model.BpmnModel;
  5. import org.activiti.bpmn.model.FlowNode;
  6. import org.activiti.bpmn.model.SequenceFlow;
  7. import org.activiti.engine.*;
  8. import org.activiti.engine.history.HistoricActivityInstance;
  9. import org.activiti.engine.history.HistoricProcessInstance;
  10. import org.activiti.engine.repository.ProcessDefinition;
  11. import org.activiti.engine.runtime.ProcessInstance;
  12. import org.activiti.engine.task.DelegationState;
  13. import org.activiti.engine.task.Task;
  14. import org.activiti.image.ProcessDiagramGenerator;
  15. import org.springframework.beans.factory.annotation.Autowired;
  16. import org.springframework.stereotype.Service;
  17. import org.springframework.transaction.annotation.Transactional;
  18. import javax.servlet.http.HttpServletResponse;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.OutputStream;
  22. import java.text.SimpleDateFormat;
  23. import java.util.ArrayList;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.stream.Collectors;
  27. /**
  28. * WorkFlowServiceImpl
  29. * <p>
  30. * encoding:UTF-8
  31. *
  32. * @author Fcant 下午 22:04 2020/8/1/0001
  33. */
  34. @Service
  35. @Slf4j
  36. @Transactional(rollbackFor = Exception.class)
  37. public class WorkFlowServiceImpl implements WorkFlowService {
  38. @Autowired
  39. private RepositoryService repositoryService;
  40. @Autowired
  41. private RuntimeService runtimeService;
  42. @Autowired
  43. private TaskService taskService;
  44. @Autowired
  45. private HistoryService historyService;
  46. @Autowired
  47. private ProcessEngine processEngine;
  48. public static final String DEAL_USER_ID_KEY = "dealUserId";
  49. public static final String DELEGATE_STATE = "PENDING";
  50. /**
  51. * 启动工作流
  52. *
  53. * @param pdKey
  54. * @param businessKey
  55. * @param variables
  56. * @return
  57. */
  58. @Override
  59. public String startWorkflow(String pdKey, String businessKey, Map<String,Object> variables) {
  60. ProcessDefinition processDef = getLatestProcDef(pdKey);
  61. if (processDef == null) {
  62. // 部署流程
  63. processEngine.getRepositoryService()
  64. .createDeployment()//创建部署对象
  65. .name(pdKey)
  66. .addClasspathResource("processes/"+pdKey+".bpmn")
  67. .deploy();
  68. processDef = getLatestProcDef(pdKey);
  69. }
  70. ProcessInstance process = runtimeService.startProcessInstanceById(processDef.getId(), businessKey, variables);
  71. return process.getId();
  72. }
  73. /**
  74. * 继续流程
  75. *
  76. * @param taskId
  77. * @param variables
  78. */
  79. @Override
  80. public void continueWorkflow(String taskId, Map variables){
  81. //根据taskId提取任务
  82. Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
  83. DelegationState delegationState = task.getDelegationState();
  84. if(delegationState != null && DELEGATE_STATE.equals(delegationState.toString())){
  85. // 委托任务,先需要被委派人处理完成任务
  86. taskService.resolveTask(taskId,variables);
  87. }else {
  88. // 当前受理人
  89. String dealUserId =variables.get(DEAL_USER_ID_KEY).toString();
  90. // 签收
  91. taskService.claim(taskId, dealUserId);
  92. }
  93. // 设置参数
  94. taskService.setVariables(taskId, variables);
  95. // 完成
  96. taskService.complete(taskId);
  97. }
  98. /**
  99. * 委托流程
  100. * @param taskId
  101. * @param variables
  102. */
  103. @Override
  104. public void delegateWorkflow(String taskId, Map variables){
  105. // 受委托人
  106. String dealUserId =variables.get(DEAL_USER_ID_KEY).toString();
  107. // 委托
  108. taskService.delegateTask(taskId, dealUserId);
  109. }
  110. /**
  111. * 结束流程
  112. * @param pProcessInstanceId
  113. */
  114. @Override
  115. public void endWorkflow(String pProcessInstanceId,String deleteReason){
  116. // 结束流程
  117. runtimeService.deleteProcessInstance(pProcessInstanceId, deleteReason);
  118. }
  119. /**
  120. * 获取当前的任务节点
  121. * @param pProcessInstanceId
  122. */
  123. @Override
  124. public String getCurrentTask(String pProcessInstanceId){
  125. Task task = taskService.createTaskQuery().processInstanceId(pProcessInstanceId).active().singleResult();
  126. return task.getId();
  127. }
  128. /**
  129. *
  130. * 根据用户id查询待办流程实例ID集合
  131. *
  132. */
  133. @Override
  134. public List<String> findUserProcessIds(String userId, String pdKey, Integer pageNo, Integer pageSize) {
  135. List<Task> resultTask;
  136. if(pageSize == 0 ){
  137. // 不分页
  138. resultTask = taskService.createTaskQuery().processDefinitionKey(pdKey)
  139. .taskCandidateOrAssigned(userId).list();
  140. }else {
  141. resultTask = taskService.createTaskQuery().processDefinitionKey(pdKey)
  142. .taskCandidateOrAssigned(userId).listPage(pageNo-1,pageSize);
  143. }
  144. //根据流程实例ID集合
  145. List<String> processInstanceIds = resultTask.stream()
  146. .map(task -> task.getProcessInstanceId())
  147. .collect(Collectors.toList());
  148. return processInstanceIds == null ? new ArrayList<>() : processInstanceIds;
  149. }
  150. /**
  151. * 获取流程图像,已执行节点和流程线高亮显示
  152. */
  153. @Override
  154. public void getProcessImage(String pProcessInstanceId, HttpServletResponse response) {
  155. log.info("[开始]-获取流程图图像");
  156. // 设置页面不缓存
  157. response.setHeader("Pragma", "No-cache");
  158. response.setHeader("Cache-Control", "no-cache");
  159. response.setDateHeader("Expires", 0);
  160. response.setContentType("image/png");
  161. InputStream imageStream = null;
  162. try (OutputStream os = response.getOutputStream()){
  163. // 获取历史流程实例
  164. HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
  165. .processInstanceId(pProcessInstanceId).singleResult();
  166. if (historicProcessInstance == null) {
  167. System.out.println("获取流程实例ID[" + pProcessInstanceId + "]对应的历史流程实例失败!");
  168. } else {
  169. // 获取流程历史中已执行节点,并按照节点在流程中执行先后顺序排序
  170. List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery()
  171. .processInstanceId(pProcessInstanceId).orderByHistoricActivityInstanceId().asc().list();
  172. // 已执行的节点ID集合
  173. List<String> executedActivityIdList = new ArrayList<String>();
  174. int index = 1;
  175. log.info("获取已经执行的节点ID");
  176. for (HistoricActivityInstance activityInstance : historicActivityInstanceList) {
  177. executedActivityIdList.add(activityInstance.getActivityId());
  178. log.info("第[" + index + "]个已执行节点=" + activityInstance.getActivityId() + " : " +activityInstance.getActivityName());
  179. index++;
  180. }
  181. // 获取流程定义
  182. BpmnModel bpmnModel = repositoryService.getBpmnModel(historicProcessInstance.getProcessDefinitionId());
  183. // 已执行的线集合
  184. List<String> flowIds = getHighLightedFlows(bpmnModel, historicActivityInstanceList);
  185. // 流程图生成器
  186. ProcessDiagramGenerator pec = processEngine.getProcessEngineConfiguration().getProcessDiagramGenerator();
  187. // 获取流程图图像字符流(png/jpg)
  188. imageStream = pec.generateDiagram(bpmnModel, "jpg", executedActivityIdList, flowIds, "宋体", "微软雅黑", "黑体", null, 2.0);
  189. int bytesRead = 0;
  190. byte[] buffer = new byte[8192];
  191. while ((bytesRead = imageStream.read(buffer, 0, 8192)) != -1) {
  192. os.write(buffer, 0, bytesRead);
  193. }
  194. }
  195. log.info("[完成]-获取流程图图像");
  196. } catch (Exception e) {
  197. log.error("【异常】-获取流程图失败!",e);
  198. }finally {
  199. if(imageStream != null){
  200. try {
  201. imageStream.close();
  202. } catch (IOException e) {
  203. log.error("关闭流异常:",e);
  204. }
  205. }
  206. }
  207. }
  208. public List<String> getHighLightedFlows(BpmnModel bpmnModel, List<HistoricActivityInstance> historicActivityInstances) {
  209. // 24小时制
  210. SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  211. // 用以保存高亮的线flowId
  212. List<String> highFlows = new ArrayList<String>();
  213. for (int i = 0; i < historicActivityInstances.size() - 1; i++) {
  214. // 对历史流程节点进行遍历
  215. // 得到节点定义的详细信息
  216. FlowNode activityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstances.get(i).getActivityId());
  217. // 用以保存后续开始时间相同的节点
  218. List<FlowNode> sameStartTimeNodes = new ArrayList<FlowNode>();
  219. FlowNode sameActivityImpl1 = null;
  220. // 第一个节点
  221. HistoricActivityInstance activityImpl_ = historicActivityInstances.get(i);
  222. HistoricActivityInstance activityImp2_;
  223. for (int k = i + 1; k <= historicActivityInstances.size() - 1; k++) {
  224. // 后续第1个节点
  225. activityImp2_ = historicActivityInstances.get(k);
  226. if (activityImpl_.getActivityType().equals("userTask") && activityImp2_.getActivityType().equals("userTask") &&
  227. df.format(activityImpl_.getStartTime()).equals(df.format(activityImp2_.getStartTime()))) {
  228. // 都是usertask,且主节点与后续节点的开始时间相同,说明不是真实的后继节点
  229. } else {
  230. //找到紧跟在后面的一个节点
  231. sameActivityImpl1 = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstances.get(k).getActivityId());
  232. break;
  233. }
  234. }
  235. // 将后面第一个节点放在时间相同节点的集合里
  236. sameStartTimeNodes.add(sameActivityImpl1);
  237. for (int j = i + 1; j < historicActivityInstances.size() - 1; j++) {
  238. // 后续第一个节点
  239. HistoricActivityInstance activityImpl1 = historicActivityInstances.get(j);
  240. // 后续第二个节点
  241. HistoricActivityInstance activityImpl2 = historicActivityInstances.get(j + 1);
  242. if (df.format(activityImpl1.getStartTime()).equals(df.format(activityImpl2.getStartTime()))) {
  243. // 如果第一个节点和第二个节点开始时间相同保存
  244. FlowNode sameActivityImpl2 = (FlowNode) bpmnModel.getMainProcess().getFlowElement(activityImpl2.getActivityId());
  245. sameStartTimeNodes.add(sameActivityImpl2);
  246. } else {// 有不相同跳出循环
  247. break;
  248. }
  249. }
  250. // 取出节点的所有出去的线
  251. List<SequenceFlow> pvmTransitions = activityImpl.getOutgoingFlows();
  252. // 对所有的线进行遍历
  253. for (SequenceFlow pvmTransition : pvmTransitions) {
  254. // 如果取出的线的目标节点存在时间相同的节点里,保存该线的id,进行高亮显示
  255. FlowNode pvmActivityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement(pvmTransition.getTargetRef());
  256. if (sameStartTimeNodes.contains(pvmActivityImpl)) {
  257. highFlows.add(pvmTransition.getId());
  258. }
  259. }
  260. }
  261. return highFlows;
  262. }
  263. /**
  264. * 获取最新版本流程
  265. *
  266. * @param modelName
  267. * @return
  268. */
  269. private ProcessDefinition getLatestProcDef(String modelName) {
  270. return repositoryService.createProcessDefinitionQuery().processDefinitionKey(modelName).
  271. latestVersion().singleResult();
  272. }
  273. }

C.ActivitiController.java

  1. package com.fcant.service_acti.controller;
  2. import com.fcant.service_acti.result.Result;
  3. import com.fcant.service_acti.service.WorkFlowService;
  4. import io.swagger.annotations.Api;
  5. import io.swagger.annotations.ApiOperation;
  6. import lombok.extern.slf4j.Slf4j;
  7. import org.apache.commons.lang.StringUtils;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.web.bind.annotation.*;
  10. import javax.servlet.http.HttpServletResponse;
  11. import java.util.Arrays;
  12. import java.util.HashMap;
  13. import java.util.Map;
  14. /**
  15. * ActivitiController
  16. * <p>
  17. * encoding:UTF-8
  18. *
  19. * @author Fcant 下午 22:16 2020/8/1/0001
  20. */
  21. @RestController
  22. @Api(tags = "activiti",value = "工作流控制器")
  23. @RequestMapping("/activiti")
  24. @Slf4j
  25. public class ActivitiController {
  26. @Autowired
  27. private WorkFlowService workFlowService;
  28. @PostMapping("/apply")
  29. @ApiOperation(value="启动请假流程")
  30. public Result startWorkflow(@RequestParam(required = false) String pdKey){
  31. Map param = new HashMap(4){{
  32. put("applyUserId","001");
  33. put("approveUserIds", Arrays.asList("001","002","003"));
  34. }};
  35. if(StringUtils.isBlank(pdKey)){
  36. pdKey="QjFlow";
  37. }
  38. // 启动流程
  39. String pdId = workFlowService.startWorkflow(pdKey, "QJ001", param);
  40. // 获取请假申请任务节点
  41. String Id = workFlowService.getCurrentTask(pdId);
  42. // 完成请假申请任务节点
  43. Map continueParam = new HashMap(2){{
  44. put("dealUserId",param.get("applyUserId"));
  45. }};
  46. workFlowService.continueWorkflow(Id,continueParam);
  47. return Result.success().reSetMsg("请假已提交");
  48. }
  49. @PostMapping("/approve")
  50. @ApiOperation(value="审批请假流程")
  51. public Result continueWorkflow(@RequestParam String pId,@RequestParam String result){
  52. Map param = new HashMap(2){{
  53. put("dealUserId","001");
  54. put("result",result);
  55. }};
  56. // 获取请假审批任务节点
  57. String Id = workFlowService.getCurrentTask(pId);
  58. // 完成请假审批任务节点
  59. workFlowService.continueWorkflow(Id,param);
  60. return Result.success().reSetMsg("审批成功");
  61. }
  62. @PostMapping("/delegate")
  63. @ApiOperation(value="委托请假流程")
  64. public Result delegateWorkflow(@RequestParam String pId,@RequestParam String userId){
  65. Map param = new HashMap(2){{
  66. put("dealUserId",userId);
  67. }};
  68. // 获取请假审批任务节点
  69. String Id = workFlowService.getCurrentTask(pId);
  70. // 完成请假审批任务节点
  71. workFlowService.delegateWorkflow(Id,param);
  72. return Result.success().reSetMsg("委托成功");
  73. }
  74. /**
  75. * 查询用户待办流程实例
  76. * @param userId
  77. * @param pdKey
  78. */
  79. @GetMapping("/user-process")
  80. @ApiOperation(value="查询用户待办流程实例")
  81. public Result findUserProcessIds(@RequestParam String userId, @RequestParam(required = false) String pdKey) {
  82. if(StringUtils.isBlank(pdKey)){
  83. pdKey="QjFlow";
  84. }
  85. // 获取流程图
  86. return Result.success().addData("workflow", workFlowService.findUserProcessIds(userId,pdKey,1,0));
  87. }
  88. /**
  89. * 读取流程资源
  90. * @param pId 流程实例id
  91. */
  92. @GetMapping("/read-resource")
  93. @ApiOperation(value="读取流程资源")
  94. public void readResource(@RequestParam String pId, HttpServletResponse response) {
  95. // 获取流程图
  96. workFlowService.getProcessImage(pId, response);
  97. }
  98. }

9、启动项目,生成28张acti表

image.png

10、访问接口进行测试

image.png