工作流定义:
业务过程的部分或整体在计算机应用环境下的自动化。
它主要解决的是“使在多个参与者之间按照某种预定义的规则传递文档、信息或任务的过程自动进行,从而实现某个预期的业务目标,或者促使此目标的实现”。
Activiti是一个针对企业用户、开发人员、系统管理员的轻量级工作流业务管理平台,其核心是使用Java开发的快速、稳定的BPMN2.0流程引擎。
生命周期:
工作流生命周期分为5步:
1. 定义:流程定义,
2. 发布:打包后在系统平台中发布,
3. 执行:流程引擎按照定义的流程处理路线执行业务
4. 监控:收集任务结果,根据不同结果进行处理。
5. 优化:优化流程
特点:
数据持久化:使用mybatis
Service接口:service方式调用
流程设计器-插件Activiti-Modeler
原生支持spring
分离运行与历史数据
SpringBoot集成:
使用activiti-spring-boot-starter-basic:
pom文件引入依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!-- mysql 驱动依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Druid DataSource -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- activiti -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter-basic</artifactId>
<version>${activiti.version}</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-json-converter</artifactId>
<version>${activiti.version}</version>
<exclusions>
<exclusion>
<groupId>org.activiti</groupId>
<artifactId>activiti-bpmn-model</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-codec</artifactId>
<version>${apache.xmlgraphics.version}</version>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-css</artifactId>
<version>${apache.xmlgraphics.version}</version>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-svg-dom</artifactId>
<version>${apache.xmlgraphics.version}</version>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-svggen</artifactId>
<version>${apache.xmlgraphics.version}</version>
</dependency>
</dependencies>
此pom文件同时集成了Activiti-Modeler,如出现无法启动情况springframework相关问题,原因为版本不一致导致:
Exception in thread "main" java.lang.IllegalArgumentException: Cannot instantiate interface org.springframework.context.ApplicationListener : org.springframework.boot.logging.ClasspathLoggingApplicationListener
解决方式:
添加parent统一依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.8.RELEASE</version>
<relativePath/>
</parent>
配置yml文件:
数据库连接配置:
activiti基础配置:
# activiti default configuration
#建表规则 :true: activiti会对数据库中所有表进行更新操作。如果表不存在,则自动创建。
# flase: 默认值。activiti在启动时,会对比数据库表中保存的版本,如果没有表或者版本不匹配,将抛出异常。
# create_drop: 在activiti启动时创建表,在关闭时删除表(必须手动关闭引擎,才能删除表)。
# drop-create: 在activiti启动时删除原来的旧表,然后在创建新表(不需要手动关闭引擎)
spring.activiti.database-schema-update = true
#校验流程文件,默认校验resources下的processes文件夹里的流程文件
spring.activiti.check-process-definitions = true
#自定义流程文件位置
spring.activiti.process-definition-location-prefix = classpath:/process/
# process-definition-location-suffixes:
# - **.bpmn
# - **.bpmn20.xml
#记录历史等级 可配置的历史级别有none, acitivity, audit, all
spring.activiti.history-level = full
使用:
activiti-spring-boot-starter可直接通过注入service方式进行调用。
核心API:
ProcessEngine(流程引擎):在Activiti中最核心的类,其他的类都是由他而来。
调用ProcessEngines的getDefaultProceeEngine方法时会自动加载classpath下名为activiti.cfg.xml文件。
RepositoryService | 管理流程定义 |
RuntimeService | 执行管理,包括启动、推进、删除流程实例等操作 |
TaskService | 任务管理 |
HistoryService | 历史管理(执行完的数据的管理) |
IdentityService | 组织机构管理 |
service使用方式心得:
service都是通过调用Query对象再拼接条件进行查询,第一次使用建议先熟悉常用表(流程定义表,任务表,历史表…)query对象的一般为具体数据库名称,需要查哪个表的数据就进行哪个查询对象的创建。
ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery().orderByDeploymentId().desc();
此方法实际调用sql为:
SELECT DISTINCT RES.* FROM ACT_RE_PROCDEF RES ORDER BY RES.DEPLOYMENT_ID_ DESC LIMIT 15 OFFSET 0
完整请假流程代码(附带动态回退指定节点):
package com.activiti6.controller;
import com.activiti6.controller.editor.TestCommand;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.UserTask;
import org.activiti.engine.*;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.impl.util.ProcessDefinitionUtil;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @ClassName TestController
* @Description 测试
* @Author Dy
* @Date 2020/8/14 16:54
* @Version 1.0
**/
@Controller
public class TestController {
@Autowired
RepositoryService repositoryService;
@Autowired
RuntimeService runtimeservice;
@Autowired
TaskService taskservice;
@Autowired
ManagementService managementService;
@Autowired
HistoryService historyService;
//1、部署流程资源【第一种方式:classpath】
@GetMapping("/deploy1")
public void deploy1() {
Deployment deployment = repositoryService//获取流程定义和部署对象相关的Service
.createDeployment()//创建部署对象
.name("请假测试回退")//声明流程的名称
.addClasspathResource("processes/test.bpmn")//加载资源文件,一次只能加载一个文件
.deploy();//完成部署
System.out.println("部署ID:" + deployment.getId());//1
System.out.println("部署时间:" + deployment.getDeploymentTime());
}
//创建流程实例 请假
/*
start 部署任务
*/
@RequestMapping(value = "/start", method = RequestMethod.POST)
public void start() {
//创建流程实例
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("applyuserid", "123");
ProcessInstance starttest = runtimeservice.startProcessInstanceByKey("test", "user1", variables);
System.out.println("流程部署id:" + starttest.getDeploymentId());
System.out.println("流程实例id:" + starttest.getId());
System.out.println("活动id:" + starttest.getActivityId());
}
@RequestMapping(value = "/complete/{actId}", method = RequestMethod.GET)
public void complete(@PathVariable String actId) {
Task task = taskservice.createTaskQuery().processInstanceId(actId).singleResult();
System.out.println("任务id:" + task.getId());
taskservice.claim(task.getId(), "group2");
HashMap<String, Object> map = new HashMap<>();
map.put("reason","true");
taskservice.complete(task.getId(),map);
}
/**
* 获取流程线信息
*
* @param procdefId 流程id
*/
@GetMapping(value = "/procdef/info")
public void prepare(@RequestParam("procdefId") String procdefId) {
HashMap<Object, Object> map = new HashMap<>();
// 获取bomn对象
BpmnModel bpmnModel = repositoryService.getBpmnModel(procdefId);
//获取Process
Process process = bpmnModel.getProcesses().get(0);
//获取所有FlowElement
Collection<FlowElement> flowElements = process.getFlowElements();
for (FlowElement flowElement : flowElements) {
//如果是任务节点
if (flowElement instanceof UserTask) {
//获取连线信息
UserTask userTask = (UserTask) flowElement;//节点转换
userTask.getCandidateGroups();
// List<SequenceFlow> incomingFlows = userTask.getIncomingFlows();
// for (SequenceFlow incomingFlow : incomingFlows) {
// System.out.println(incomingFlow.getId()+"=="+ incomingFlow.getName());
// }
}
}
}
/**
* 自定义回退
*
* @param actId
*/
/*
activiti 6去除pvm包,所有流程定义相关内容通过bpmnModel获取
*/
@GetMapping(value = "/rollback")
public void rollBack(@RequestParam("actId") String actId, @RequestParam("key") String key) {
String taskId = "";
String tagNode = "";
//获取当前任务id
Task task = taskservice.createTaskQuery().processInstanceId(actId).singleResult();
System.out.println("任务id:" + task.getId());
taskId = task.getId();
//获取目标
BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
Collection<FlowElement> flowElements = bpmnModel.getProcesses().get(0).getFlowElements();
for (FlowElement flowElement : flowElements) {
if (flowElement instanceof UserTask) {
//获取用户任务
UserTask userTask = (UserTask) flowElement;//节点转换
if (userTask.getName().equals(key)) {
tagNode = userTask.getId();
break;
}
}
}
//跳转
managementService.executeCommand(new TestCommand(taskId, tagNode));
}
/**
* 历史活动查询
*/
@GetMapping("/his")
public void history(@RequestParam("actId") String actId){
List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery().
processInstanceId(actId).
finished()
.activityType("userTask")
.orderByActivityId()
.list();
for (HistoricActivityInstance hai : list) {
System.out.println("活动ID:"+hai.getId());
System.out.println("流程实例ID:"+hai.getProcessInstanceId());
System.out.println("活动名称:"+hai.getActivityName());
System.out.println("办理人:"+hai.getAssignee());
System.out.println("开始时间:"+hai.getStartTime());
System.out.println("结束时间:"+hai.getEndTime());
System.out.println("=================================");
}
}
}
package com.activiti6.controller.editor;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.Process;
import org.activiti.engine.ActivitiEngineAgenda;
import org.activiti.engine.impl.interceptor.Command;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.persistence.entity.ExecutionEntityManager;
import org.activiti.engine.impl.persistence.entity.TaskEntity;
import org.activiti.engine.impl.persistence.entity.TaskEntityManager;
import org.activiti.engine.impl.util.ProcessDefinitionUtil;
/**
* @ClassName TestService
* @Description TODO
* @Author Dy
* @Date 2020/8/17 11:37
* @Version 1.0
**/
public class TestCommand implements Command<Object> {
/**
* 当前节点
*/
private String taskId;
/**
* 目标节点
*/
private String targetNodeId;
public TestCommand(String currentTaskId, String targetNodeId) {
this.taskId = currentTaskId;
this.targetNodeId = targetNodeId;
}
public String getTaskId() {
return taskId;
}
public void setTaskId(String taskId) {
this.taskId = taskId;
}
public String getTargetNodeId() {
return targetNodeId;
}
public void setTargetNodeId(String targetNodeId) {
this.targetNodeId = targetNodeId;
}
@Override
public Object execute(CommandContext commandContext) {
ExecutionEntityManager executionEntityManager = commandContext.getExecutionEntityManager();
TaskEntityManager taskEntityManager = commandContext.getTaskEntityManager();
TaskEntity taskEntity = taskEntityManager.findById(this.taskId);
ExecutionEntity executionEntity = executionEntityManager.findById(taskEntity.getExecutionId());
Process process = ProcessDefinitionUtil.getProcess(executionEntity.getProcessDefinitionId());
taskEntityManager.deleteTask(taskEntity, "移动节点", true, true);
FlowElement targetFlowElement = process.getFlowElement(targetNodeId);
executionEntity.setCurrentFlowElement(targetFlowElement);
ActivitiEngineAgenda agenda = commandContext.getAgenda();
agenda.planContinueProcessInCompensation(executionEntity);
return null;
}
}
流程图:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI"
xmlns:activiti="http://activiti.org/bpmn"
xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:tns="http://www.activiti.org/test"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
expressionLanguage="http://www.w3.org/1999/XPath"
id="m1597395350401"
name=""
targetNamespace="http://www.activiti.org/test"
typeLanguage="http://www.w3.org/2001/XMLSchema">
<process xmlns="" id="test" isClosed="false" isExecutable="true" name="请假测试"
processType="None">
<startEvent id="_2" name="请假"/>
<userTask activiti:candidateGroups="组长,经理" activiti:exclusive="true" id="_3" name="项目经理">
<documentation id="_3_D_1">修改</documentation>
</userTask>
<endEvent id="_4" name="结束"/>
<sequenceFlow id="_5" sourceRef="_2" targetRef="_3"/>
<userTask activiti:candidateGroups="总监组" activiti:exclusive="true" id="_6" name="总监审批"/>
<exclusiveGateway gatewayDirection="Unspecified" id="_7" name="ExclusiveGateway"/>
<sequenceFlow id="_8" sourceRef="_3" targetRef="_7"/>
<endEvent id="_9" name="EndEvent"/>
<sequenceFlow id="_10" name="拒绝" sourceRef="_7" targetRef="_9">
<conditionExpression xsi:type="tFormalExpression">${reason=='false'}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="_11" name="同意" sourceRef="_7" targetRef="_6">
<conditionExpression xsi:type="tFormalExpression">${reason=='true'}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="_12" sourceRef="_6" targetRef="_4"/>
</process>
<bpmndi:BPMNDiagram xmlns=""
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">
<bpmndi:BPMNPlane bpmnElement="test">
<bpmndi:BPMNShape bpmnElement="_2" id="Shape-_2">
<omgdc:Bounds height="32.0" width="32.0" x="105.0" y="280.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_3" id="Shape-_3">
<omgdc:Bounds height="55.0" width="85.0" x="235.0" y="275.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_4" id="Shape-_4">
<omgdc:Bounds height="32.0" width="32.0" x="895.0" y="280.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_6" id="Shape-_6">
<omgdc:Bounds height="55.0" width="85.0" x="555.0" y="275.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_7" id="Shape-_7" isMarkerVisible="false">
<omgdc:Bounds height="32.0" width="32.0" x="395.0" y="285.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_9" id="Shape-_9">
<omgdc:Bounds height="32.0" width="32.0" x="385.0" y="435.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="_12" id="BPMNEdge__12" sourceElement="_6" targetElement="_4">
<omgdi:waypoint x="640.0" y="302.5"/>
<omgdi:waypoint x="895.0" y="296.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_5" id="BPMNEdge__5" sourceElement="_2" targetElement="_3">
<omgdi:waypoint x="137.0" y="296.0"/>
<omgdi:waypoint x="235.0" y="302.5"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_8" id="BPMNEdge__8" sourceElement="_3" targetElement="_7">
<omgdi:waypoint x="320.0" y="302.5"/>
<omgdi:waypoint x="395.0" y="301.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_11" id="BPMNEdge__11" sourceElement="_7" targetElement="_6">
<omgdi:waypoint x="427.0" y="301.0"/>
<omgdi:waypoint x="555.0" y="302.5"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_10" id="BPMNEdge__10" sourceElement="_7" targetElement="_9">
<omgdi:waypoint x="406.0" y="312.0"/>
<omgdi:waypoint x="406.0" y="435.80131584642936"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>