Springboot整合工作流引擎Activiti(一)

导读:本篇文章讲解 Springboot整合工作流引擎Activiti(一),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

环境:Springboot2.2.11.RELEASE + Activiti7.1.0.M6 + MySQL

环境说明:

不要通过如下方式引包

<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.activiti.dependencies</groupId>
				<artifactId>activiti-dependencies</artifactId>
				<version>7.1.0.M6</version>
				<scope>import</scope>
				<type>pom</type>
			</dependency>
		</dependencies>
</dependencyManagement>

如果通过上面的方式引入会有各种问题。

正确方式:

<dependencies>
		<dependency>
			<groupId>org.activiti.dependencies</groupId>
			<artifactId>activiti-dependencies</artifactId>
			<version>7.1.0.M6</version>
			<type>pom</type>
		</dependency>
		<dependency>
			<groupId>org.activiti</groupId>
			<artifactId>activiti-spring-boot-starter</artifactId>
			<version>7.1.0.M6</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.1.4</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
</dependencies>

不知为何activit7中要吧这security强关联。。。

以上是pom.xml中所要引入的依赖。


所有的表:

Springboot整合工作流引擎Activiti(一)

 

ACT_RE_*: ‘RE’表示repository。这个前缀的表包含了流程定义和流程 静态资源(图片、规则等等)

ACT_RU_*: ‘RU’表示runtime。这些运行时的表,包含流程实例,认为,变量,异步任务等运行中的数据。Activiti只在流程实例执行过程中保持这些数据,在流程结束时就会删除这些记录。这样运行时表可以一直很小速度很快。

ACT_HI_*: ‘HI’表示history。这些表包含历史数据,比如历史流程实例,遍历,任务等等。

ACT_GE_*: ‘GE’表示general。通用数据,用于不同场景。

数据表分类

通用数据(act_ge_*)

Springboot整合工作流引擎Activiti(一)

 

流程定义(act_re_*)

Springboot整合工作流引擎Activiti(一)

来源网络

运行实例(act_ru_*)

Springboot整合工作流引擎Activiti(一)

来源网络

历史流程(act_hi_*)

Springboot整合工作流引擎Activiti(一)

来源网络

其他

Springboot整合工作流引擎Activiti(一)

来源网络


核心类

ProcessEngine

    流程引擎的抽象,可以通过此类获取需要的所有服务。

    通过ProcessEngine获取,Activiti将不同生命周期的服务封装在不同Service中,包括定义、部署、运行。通过服务类可获取相关生命周期中的服务信息。

TaskService

    流程运行过程中,每个任务节点的相关操作接口,如complete,delete,delegate等。
RepositoryService

    流程定义和部署相关的存储服务。
RuntimeService

    流程运行时相关的服务,如根据流程好启动流程实例startProcessInstanceByKey。
HistoryService

    历史记录相关服务接口。


关于eclipse中安装插件就不说了,我是把插件下载下来安装的,在线安装不上。

设计请假流程

在src/main/resources下新建processes文件夹,springboot下默认的流程文件定义路径前缀及文件后缀如下:

Springboot整合工作流引擎Activiti(一)

 

这里可以在application.yml配置文件中更改。

设计一个请假的流程holiday.bpmn

Springboot整合工作流引擎Activiti(一)

 

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns: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.pack.org">
  <process id="holiday" name="holiday" isExecutable="true">
    <startEvent id="startevent1" name="Start"></startEvent>
    <endEvent id="endevent1" name="End"></endEvent>
    <userTask id="usertask1" name="部门经理审批" activiti:assignee="${mgr}"></userTask>
    <userTask id="usertask2" name="总经理审批" activiti:assignee="${top}"></userTask>
    <sequenceFlow id="flow2" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
    <sequenceFlow id="flow3" sourceRef="usertask2" targetRef="endevent1"></sequenceFlow>
    <userTask id="usertask3" name="填写审批单" activiti:assignee="${assignee}"></userTask>
    <sequenceFlow id="flow4" sourceRef="startevent1" targetRef="usertask3"></sequenceFlow>
    <sequenceFlow id="flow5" sourceRef="usertask3" targetRef="usertask1"></sequenceFlow>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_holiday">
    <bpmndi:BPMNPlane bpmnElement="holiday" id="BPMNPlane_holiday">
      <bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
        <omgdc:Bounds height="35.0" width="35.0" x="505.0" y="60.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
        <omgdc:Bounds height="35.0" width="35.0" x="505.0" y="550.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
        <omgdc:Bounds height="55.0" width="105.0" x="470.0" y="290.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="usertask2" id="BPMNShape_usertask2">
        <omgdc:Bounds height="55.0" width="105.0" x="470.0" y="420.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="usertask3" id="BPMNShape_usertask3">
        <omgdc:Bounds height="55.0" width="105.0" x="470.0" y="170.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
        <omgdi:waypoint x="522.0" y="345.0"></omgdi:waypoint>
        <omgdi:waypoint x="522.0" y="420.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
        <omgdi:waypoint x="522.0" y="475.0"></omgdi:waypoint>
        <omgdi:waypoint x="522.0" y="550.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
        <omgdi:waypoint x="522.0" y="95.0"></omgdi:waypoint>
        <omgdi:waypoint x="522.0" y="170.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5">
        <omgdi:waypoint x="522.0" y="225.0"></omgdi:waypoint>
        <omgdi:waypoint x="522.0" y="290.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

上面的每一个节点(任务)都动态的指派了用户执行。

填写审批单:${assignee};

部门经理审批: ${mgr};

总经理审批:${top};

每一个节点执行完成时都需要指明下一个节点的执行人。

配置文件

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.pack.domain
server:
  port: 8080
spring:
  activiti:
    check-process-definitions: true
    db-history-used: true
    history-level: full
    database-schema-update: true
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/activiti?serverTimezone=GMT%2B8
    username: root
    password: xxxxxx
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      minimumIdle: 10
      maximumPoolSize: 200
      autoCommit: true
      idleTimeout: 30000
      poolName: MasterDatabookHikariCP
      maxLifetime: 1800000
      connectionTimeout: 30000
      connectionTestQuery: SELECT 1

spring.activiti.db-history-used:表示是用历史表,如果不设置为true那么只会生成17张表,只有设置为true后才会生成25张表。如果不生成历史表那么,流程图及运行节点无法展示。

spring.activiti.history-level:对于历史数据,保存到何种粒度,Activiti提供了history-level属性对其进行配置。history-level属性有点像log4j的日志输出级别,该属性有以下四个值:

  • none:不保存任何的历史数据,因此,在流程执行过程中,这是最高效的。
  • activity:级别高于none,保存流程实例与流程行为,其他数据不保存。
  • audit:除activity级别会保存的数据外,还会保存全部的流程任务及其属性。audit为history的默认值。
  • full:保存历史数据的最高级别,除了会保存audit级别的数据外,还会保存其他全部流程相关的细节数据,包括一些流程参数等。

spring.activiti.check-process-definitions:如果不设置为true,那么流程定义必须手动进行部署。


sprint security配置,放行任何请求:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			.csrf().disable()
			.authorizeRequests()
			.antMatchers("/**")
			.permitAll() ;
			
	}
	
}

服务工具类HolidayService

@Service
public class HolidayService {

	private static final Logger logger = LoggerFactory.getLogger(HolidayService.class);

	@Resource
	private ProcessEngine processEngine;
	@Resource
	private RepositoryService repositoryService ;
	@Resource
	private RuntimeService runtimeService ;
	@Resource
	private TaskService taskService ;

	/**
	 * <p>
	 * 流程定义的部署 activiti表有哪些? 
	 * act_re_deployment 流程定义部署表,记录流程部署信息 
	 * act_re_procdef 流程定义表,记录流程定义信息 
	 * act_ge_bytearray 资源表(bpmn文件及png文件)
	 * </p>
	 * <p>时间:2021年1月22日-下午3:33:00</p>
	 * @author xg
	 * @return void
	 */
	public void createDeploy() {
		Deployment deployment = repositoryService.createDeployment()
				.addClasspathResource("processes/holiday.bpmn")
				.addClasspathResource("processes/holiday.png")
				.name("请假申请单流程")
				.key("holiday")
				.category("InnerP")
				.deploy();
		logger.info("流程部署id: {}", deployment.getId());
		logger.info("流程部署名称: {}", deployment.getName());
	}
  // 注意这里这个方法是当我们没有开启自动部署流程定义时,就需要手动部署。
	/**
	 * <p>
	 * 	流程定义查询
	 * </p>
	 * <p>时间:2021年1月22日-下午3:45:02</p>
	 * @author xg
	 * @param processDefinition
	 * @return void
	 */
	public List<ProcessDefinition> queryProcessDefinitionByKey(String processDefinition) {
		// 查询流程定义
		ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
		List<ProcessDefinition> list = processDefinitionQuery.processDefinitionKey(processDefinition).list();
		list.forEach(pd -> {
			logger.info("------------------------------------------------");
			logger.info("流程部署id:{}", pd.getDeploymentId());
			logger.info("流程定义id:{}", pd.getId());
			logger.info("流程定义名称:{}", pd.getName());
			logger.info("流程定义key:{}", pd.getKey());
			logger.info("流程定义版本:{}", pd.getVersion());
			logger.info("------------------------------------------------");
		});
		return list ;
	}

	/**
	 * <p>
	 * 	删除流程
	 * </p>
	 * <p>时间:2021年1月22日-下午4:21:40</p>
	 * @author xg
	 * @return void
	 */
	public void deleteDeployment(String deploymentId) {
		// 设置true 级联删除流程定义,即使该流程有流程实例启动也可以删除,设置为false非级别删除方式,如果流程
		repositoryService.deleteDeployment(deploymentId, true);
	}

	/**
	 * <p>
	 * 	启动流程实例(比如用户根据定义好的流程发起一个流程的实例(这里的请假流程申请))
	 * <p>时间:2021年1月22日-下午4:54:56</p>
	 * @author xg
	 * @return void
	 */
	public void startProcessInstanceById(String processDefinitionId) {
		ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinitionId) ;
		logger.info("流程定义ID: {}", processInstance.getProcessDefinitionId());
		logger.info("流程实例ID: {}", processInstance.getId());
	}
	
	/**
	 * <p>
	 * 	启动流程实例,指定业务Key(方便关联业务数据)(比如用户根据定义好的流程发起一个流程的实例(这里的请假流程申请))
	 * 	Businesskey(业务标识)
		启动流程实例时,指定的businesskey,就会在act_ru_execution #流程实例的执行表中存储businesskey。
		Businesskey:业务标识,通常为业务表的主键,业务标识和流程实例一一对应。业务标识来源于业务系统。存储业务标识就是根据业务标识来关联查询业务系统的数据。
		比如:请假流程启动一个流程实例,就可以将请假单的id作为业务标识存储到activiti中,
		将来查询activiti的流程实例信息就可以获取请假单的id从而关联查询业务系统数据库得到请假单信息。
	 * <p>时间:2021年1月22日-下午4:54:56</p>
	 * @author xg
	 * @return void
	 */
	public void startProcessInstanceToBussinessKey(String processDefinitionId, String bussinessKey) {
		ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinitionId, bussinessKey);
		logger.info("流程定义ID: {}", processInstance.getProcessDefinitionId());
		logger.info("流程实例ID: {}", processInstance.getId());
		logger.info("BussinessKey: {}", processInstance.getBusinessKey()) ;
	}
	
	/**
	 *  <p>
	 *  	设置assignee的取值,用户可以在界面上设置流程的执行人
	 *  </p>
	 *  <p>时间:2021年1月22日-下午8:30:39</p>
	 * @author xg
	 * @param processDefinitionId 
	 * @return void
	 */
	public void startProcessInstanceAssignVariables(String processDefinitionId, Map<String, Object> variables) {
		ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinitionId, variables);
		logger.info("流程定义ID: {}", processInstance.getProcessDefinitionId());
		logger.info("流程实例ID: {}", processInstance.getId());
		logger.info("BussinessKey: {}", processInstance.getBusinessKey()) ;
	}
	
	/**
	 *  <p>
	 *  	查询指派关联的用户任务
	 *  </p>
	 *  <p>时间:2021年1月23日-上午11:39:56</p>
	 * @author xg
	 * @param assignee 关联用户
	 * @return List<Task>
	 */
	public List<Task> queryTasks(String assignee) {
		TaskQuery query = taskService.createTaskQuery() ;
		return query.taskAssignee(assignee).orderByTaskCreateTime().asc().list() ;
	}
	
	public void executionTask(Map<String, Object> variables, String instanceId) {
		Task task = taskService.createTaskQuery().processInstanceId(instanceId).singleResult() ;
		if (task == null) {
			logger.error("任务【{}】不存在", instanceId) ;
			throw new RuntimeException("任务【" + instanceId + "】不存在") ;
		}
		taskService.complete(task.getId(), variables) ;
	}

}

HolidayController接口

@RestController
@RequestMapping("/holidays")
public class HolidayController {
	
	@Resource
	private HolidayService holidayService ;
	
	/**
	 *  <p>查询制定key的流程审批</p>
	 *  <p>时间:2021年1月23日-上午11:17:02</p>
	 * @author xg
	 * @param key ProcessDefinitionKey
	 * @return R
	 */
	@GetMapping("")
	public R lists(String key) {
		return R.success(holidayService.queryProcessDefinitionByKey(key)) ;
	}
	
	/**
	 *  <p>创建请假流程审批(私有)</p>
	 *  <p>时间:2021年1月23日-上午10:31:47</p>
	 * @author xg
	 * @return R
	 */
	@GetMapping("/_deploy")
	public R createDeploy() {
		holidayService.createDeploy();
		return R.success() ;
	}
	
	/**
	 *  <p>启动请假审批流程</p>
	 *  <p>时间:2021年1月23日-上午10:32:55</p>
	 * @author xg
	 * @param userId
	 * @param processDefinitionId 流程定义Id
	 * @return R
	 */
	@GetMapping("/start")
	public R startProcess(String userId, String processDefinitionId) {
		Map<String, Object> variables = new HashMap<>() ;
		variables.put("assignee", userId) ;
		holidayService.startProcessInstanceAssignVariables(processDefinitionId, variables) ;
		return R.success() ;
	}
	
	/**
	 *  <p>
	 *  	查询指派给我的任务
	 *  </p>
	 *  <p>时间:2021年1月23日-上午11:41:21</p>
	 * @author xg
	 * @param userId 用户Id
	 * @return R
	 */
	@GetMapping("/tasks")
	public R myTasks(String userId) {
		List<Task> list = holidayService.queryTasks(userId) ;
    // 注意这里需要我们自己组装下数据,不然会报错。
		List<Map<String, Object>> result = list.stream().map(task -> {
			Map<String, Object> res = new HashMap<String, Object>() ;
			res.put("id", task.getId()) ;
			res.put("assignee", task.getAssignee()) ;
			res.put("createTime", task.getCreateTime()) ;
			res.put("bussinessKey", task.getBusinessKey()) ;
			res.put("category", task.getCategory()) ;
			res.put("dueDate", task.getDueDate()) ; // 到期日期
			res.put("desc", task.getDescription()) ;
			res.put("name", task.getName()) ;
			res.put("owner", task.getOwner()) ;
			res.put("instanceId", task.getProcessInstanceId()) ;
			res.put("variables", task.getProcessVariables()) ;
			return res ;
		}).collect(Collectors.toList()) ;
		return R.success(result) ;
	}
	
	/**
	 *  <p>
	 *  	填写审批单
	 *  </p>
	 *  <p>时间:2021年1月23日-上午11:57:30</p>
	 * @author xg
	 * @param Map取值如下
	 * @param days 请假天数
	 * @param explain 审批单说明
	 * @param instanceId 流程实例ID
	 * @param assignee 指定下一个流程执行人
	 * @return R
	 */
	@GetMapping("/apply")
	public R fillApply(@RequestParam Map<String, Object> variables) {
		String instanceId = (String) variables.remove("instanceId") ;
		if (StringUtils.isEmpty(instanceId)) {
			return R.failure("未知任务") ;
		}
		holidayService.executionTask(variables, instanceId);
		return R.success() ; 
	}
	
}

测试:

1、启动服务

Springboot整合工作流引擎Activiti(一)

 

这里放在processes中的流程定义文件已经被自动部署上了。查看表act_re_procdef

Springboot整合工作流引擎Activiti(一)

 

  1. 查询制定key的流程审批
    接口:/holidays

Springboot整合工作流引擎Activiti(一)

 

  1. 启动请假审批流程
    接口:/holidays/start

Springboot整合工作流引擎Activiti(一)

 

参数:
processDefinitionId:流程定义中的ID。
userId:要处理用户的id。

查看表信息:

Springboot整合工作流引擎Activiti(一)

 

这时候就为用户id为:10000的生成了一个要处理的任务,填写审批单。

  1. 查询指派给我需要处理的任务
    接口:/holidays/tasks

Springboot整合工作流引擎Activiti(一)

 

  1. 填写审批单
    接口:/holidays/apply

Springboot整合工作流引擎Activiti(一)

 

参数:
mgr:指定下一个节点处理人。
explain:请假原因。
days:请假天数。

这里根据自己的业务需要去设置。

再次调用查询接口userId=10002

Springboot整合工作流引擎Activiti(一)

 

流程已经到了部门经理。

再次调用/holidays/apply接口。

Springboot整合工作流引擎Activiti(一)

 

参数:
top:指明总经理节点需要处理的userId。

Springboot整合工作流引擎Activiti(一)

 

再次调用/holidays/apply接口。(总经理处理)

Springboot整合工作流引擎Activiti(一)

 

查询对应的任务表信息,已经没有数据了。

Springboot整合工作流引擎Activiti(一)

 

查询表:act_hi_actinst

Springboot整合工作流引擎Activiti(一)

 

到此一个流程就走完了。下篇 查看流程图。

完毕!!!

给个关注+转发谢谢

SpringBoot项目中异步调用接口方式知多少?

 

SpringBoot项目中异步调用接口方式知多少?

Springboot整合工作流引擎Activiti(一)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/80056.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!