一、Activiti7介绍
Activiti正是目前使用最为广泛的开源工作流引擎。Activiti的官网地址是 https://
www.activiti.org 历经6.x和5.x两个大的版本。
1. Activiti工作流引擎
他可以将业务系统中复杂的业务流程抽取出来,使用专门的建模语言BPMN2.0进行定义。业务流程按照预先定义的流程执行,整个实现流程完全由activiti进行管理
获取流程引擎的两种方式:
ProcessEngineConfiguration processEngineConfiguration = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml", "processEngineConfiguration");
ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine();
默认方式
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RepositoryService processEngine = processEngine.getRepositoryService();
注: 如果与springBoard整合的话,项目启动的时候会自动部署resources/processes目录下流程资源及自动获取流程引擎
2. 建模语言BPMN
整个BPMN是用一组符号来描述业务流程中发生的各种事件的。BPMN通过在这些符号事件之间连线来描述一个完整的业务流程。
- 在线设计地址: http://www.bpmnmodeler.com/
- SpringBoot集成activitimodeler实现在线绘制流程图
- idea开发工具安装插件 Activiti BPMN visualizer(重启生效)
3. Activiti使用步骤
- 部署activiti: Activiti包含一堆Jar包,因此需要把业务系统和Activiti的环境集成 在一起进行部署。
- 定义流程: 使用Activiti的建模工具定义业务流程.bpmn文件。
- 部署流程定义: 使用Activiti提供的API把流程定义内容存储起来,在Acitivti执行过程汇总可以查询定义的内容。Activiti是通过数据库来存储业务流程的。
- 启动流程实例: 流程实例也叫ProcessInstance。启动一个流程实例表示开始一次业务流程的运作。例如员工提交请假申请后,就可以开启一个流程实例,从而 推动后续的审批等操作。
- 用户查询待办任务(task):因为现在系统的业务流程都交给了activiti管理,通过activiti就可以查询当前流程执行到哪个步骤了。当前用户需要办理哪些任务也就同样可以由activiti帮我们管理,开发人员不需要自己编写sql语句进行查询了。
- 用户办理任务:用户查询到自己的待办任务后,就可以办理某个业务,如果这个业务办理完成还需要其他用户办理,就可以由activiti帮我们把工作流程往后面的 步骤推动。
- 流程结束:当任务办理完成没有下一个任务节点后,这个流程实例就执行完成 了。
二、Activiti核心类
当拿到ProcessEngine之后,我们可以简单的看一下他的方法
这几个service就是activiti最为核心的几个服务实现类。围绕activiti的核心业务功能 大都通过这几个service来组成。
service名称 | service作用 |
---|---|
RepositoryService | activiti的资源管理类 |
RuntimeService | activiti的流程运行管理类 |
TaskService | activiti的任务管理类 |
HistoryService | activiti的历史管理类 |
ManagerService | activiti的引擎管理类 |
-
RepositoryService
是activiti的资源管理类,提供了管理和控制流程发布包和流程定义的操作。
使用工作流建模工具设计的业务流程图需要使用此service将流程定义文件的内容部署到计算机。
除了部署流程定义以外还可以:查询引擎中的发布包和流程定义。
暂停或激活发布包,对应全部和特定流程定义。
暂停意味着它们不能再执行任何操作了,激活是对应的反向操作。
获得多种资源,像是包含在发布包里的文件, 或引擎自动生成的流程图。 -
RuntimeService
Activiti的流程运行管理类。可以从这个服务类中获取很多关于流程执行相关的信息 -
TaskService
Activiti的任务管理类。可以从这个类中获取任务的信息。 -
HistoryService
Activiti的历史管理类,可以查询历史信息,执行流程时,引擎会保存很多数据(根据配置),比如流程实例启动时间,任务的 参与者, 完成任务的时间,每个流程实例的执行路径等等。 这个服务主要通过查询功能来获得这些数据。 -
ManagementService
Activiti的引擎管理类,提供了对 Activiti 流程引擎的管理和维护功能,这些功能不在工作流驱动的应用程序中使用,主要用于 Activiti 系统的日常维护
三、流程部署
如果与springBoard整合的话,项目启动的时候会自动部署resources/processes目录下流程资源及自动获取流程引擎
1、流程部署的两种方式,手动部署:
@Test
public void deployProcess(){
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("bpmn/incluProcess.bpmn20.xml") // 添加bpmn资源
//png资源命名是有规范的。Leave.[key].[png|jpg|gif|svg] 或者Leave.[png|jpg|gif|svg]
.addClasspathResource("bpmn/incluProcess.png") // 添加png资源
.name("流程部署名称")
.deploy();
// 4、输出部署信息
System.out.println("流程部署id:" + deployment.getId());
System.out.println("流程部署名称:" + deployment.getName());
}
2、手动压缩包方式部署:
@Test
public void deployProcessByZip() {
// 定义zip输入流
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("bpmn/incluProcess.zip");
ZipInputStream zipInputStream = new ZipInputStream(inputStream);
// 流程部署
Deployment deployment = repositoryService.createDeployment()
.addZipInputStream(zipInputStream)
.name("流程部署名称")
.deploy();
System.out.println("流程部署id:" + deployment.getId());
System.out.println("流程部署名称:" + deployment.getName());
}
3、根据流程部署id获取流程部署资源:
@Test
public void queryBpmnFile() throws IOException {
//流程定义key
String processDefinitionKey = "incluProcess";
//得到查询器:ProcessDefinitionQuery,设置查询条件,得到想要的流程定义
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionKey(processDefinitionKey)
.latestVersion()//最新版本
.singleResult();
//.list();
//通过流程定义信息,得到部署ID
String deploymentId = processDefinition.getDeploymentId();
//通过repositoryService的方法,实现读取图片信息和bpmn信息
//png图片的流
InputStream pngInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getDiagramResourceName());
//bpmn文件的流
InputStream bpmnInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getResourceName());
//构造OutputStream流
File file_png = new File("d:/incluProcess.png");
File file_bpmn = new File("d:/incluProcess.bpmn20.xml");
FileOutputStream bpmnOut = new FileOutputStream(file_bpmn);
FileOutputStream pngOut = new FileOutputStream(file_png);
//输入流,输出流的转换
IOUtils.copy(pngInput,pngOut);
IOUtils.copy(bpmnInput,bpmnOut);
//关闭流
pngOut.close();
bpmnOut.close();
pngInput.close();
bpmnInput.close();
}
4、根据流程部署id删除流程部署:
@Test
public void deleteDeployment() {
//流程部署id
String processDeploymentId = "f46f3734-e4bb-11ed-822f-9c5c8e74980c";
//删除流程定义,如果该流程定义已有流程实例启动则删除时出错
//repositoryService.deleteDeployment(processDeploymentId);
//设置true 级联删除流程定义,即使该流程有流程实例启动也可以删除,设置为false非级别删除方式,如果流程
repositoryService.deleteDeployment(processDeploymentId, true);
}
四、流程定义与流程实例
流程定义 ProcessDefinition 和流程实例ProcessInstance是Activiti中非常重要的两个概念。他们的关系其实类似于JAVA中类和对象的概念。
流程定义ProcessDefinition是以BPMN文件定义的一个工作流程,是一组工作规范。例如我们之前定义的请假流程。
流程实例ProcessInstance则是指一个具体的业务流程。例如某个员工发起一次请假,就会实例化一个请假的流程实例,并且每个
不同的流程实例之间是互不影响的。
1、启动流程实例:
/**
* 启动流程实例(设置流程变量的值和添加businessKey)
* 同一个流程定义key部署多个版本,默认使用最新版本
* 一个流程定义下有多个实例
*/
@Test
public void startProcess(){
//流程定义key
String processDefinitionKey = "incluProcess";
//businessKey业务关键字
// 可以根据业务场景,设计成不同的数据格式,比如关键信息逗号拼接,甚至是json都可以,唯一需要注意的是这个字段的数据库长度设计是255
String businessKey = "{\"workerId\": 1,\"departmentId\": \"123\",\"roleId\": 2,\"other\": 3}";
//创建Global流程变量(启动流程时),在线程实例中传递的流程变量。这个流程变量可以在整个流程实例中使用
Map<String, Object> map = new HashMap<>();
map.put("assignee0","tom");
map.put("assignee1","jack");
//启动流程实例,并设置流程变量的值(把map传入)&&启动流程实例时,添加Businesskey,可以用这个businessKey来关联业务
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processDefinitionKey,businessKey,map);
//输出
System.out.println("流程定义id=="+processInstance.getProcessDefinitionId());//流程定义key:版本号:流程部署ID ==> incluProcess:1:1b99a469-e425-11ed-a092-9c5c8e74980c
System.out.println("流程实例名称="+processInstance.getName());
System.out.println("流程实例id:" + processInstance.getId());//56987f81-e429-11ed-8ad9-9c5c8e74980c
System.out.println("当前活动Id:" + processInstance.getActivityId());
System.out.println("BusinessKey:" + processInstance.getBusinessKey());
}
2、流程定义
查询流程定义:
@Test
public void queryProcessDefinition(){
//流程定义key
String processDefinitionKey = "incluProcess";
ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
//查询出当前所有的流程定义
List<ProcessDefinition> definitionList = processDefinitionQuery.processDefinitionKey(processDefinitionKey)
.orderByProcessDefinitionVersion()
.desc()按照版本倒叙
.list();
//输出流程定义信息
for (ProcessDefinition processDefinition : definitionList) {
System.out.println("流程部署ID ="+processDefinition.getDeploymentId());//1b598e26-e425-11ed-a092-9c5c8e74980c
System.out.println("流程定义 id="+processDefinition.getId());//incluProcess:1:1b99a469-e425-11ed-a092-9c5c8e74980c
System.out.println("流程定义 name="+processDefinition.getName());//出差申请-包含网关
System.out.println("流程定义 key="+processDefinition.getKey());//incluProcess
System.out.println("流程定义 Version="+processDefinition.getVersion());//1
System.out.println("<==========================>");
}
}
流程定义的挂起与激活:
repositoryService.suspendProcessDefinitionById(processDefinitionId);
repositoryService.activateProcessDefinitionById(processDefinitionId);
3、流程实例
查询流程实例:
@Test
public void queryProcessInstance() {
//流程定义key
String processDefinitionKey = "incluProcess";
List<ProcessInstance> list = runtimeService
.createProcessInstanceQuery()
.processDefinitionKey(processDefinitionKey)
//.processInstanceId("9f50db66-e4a0-11ed-bd31-9c5c8e74980c")
.list();
for (ProcessInstance processInstance : list) {
System.out.println("所属流程定义id:"+ processInstance.getProcessDefinitionId());//incluProcess:1:1b99a469-e425-11ed-a092-9c5c8e74980c
System.out.println("所属流程定义 Version="+processInstance.getProcessDefinitionVersion());
System.out.println("流程实例id:"+ processInstance.getProcessInstanceId());//56987f81-e429-11ed-8ad9-9c5c8e74980c
System.out.println("是否执行完成:" + processInstance.isEnded());//false
System.out.println("是否暂停:" + processInstance.isSuspended());//false
System.out.println("当前活动标识:" + processInstance.getActivityId());//null
System.out.println("业务关键字:"+processInstance.getBusinessKey());//null
System.out.println("流程变量 Variables="+ JSON.toJSONString(processInstance.getProcessVariables()));
System.out.println("<==========================>");
}
}
流程实例的挂起与激活:
runtimeService.suspendProcessInstanceById(processInstanceId);
runtimeService.activateProcessInstanceById(processInstanceId);
4、查看历史信息:
@Test
public void findHistoryInfo(){
//获取 actinst表的查询对象
HistoricActivityInstanceQuery instanceQuery = historyService.createHistoricActivityInstanceQuery();
//查询 actinst表,条件:根据 DefinitionId 查询,查询一种流程定义的所有历史信息
instanceQuery.processDefinitionId("incluProcess:1:9fe8bf00-e4d9-11ed-8e1f-9c5c8e74980c");
//查询 actinst表,条件:根据 InstanceId 查询,查询一个流程实例的所有历史信息
//instanceQuery.processInstanceId("a65107ee-e42e-11ed-b64e-9c5c8e74980c");
//增加排序操作,orderByHistoricActivityInstanceStartTime 根据开始时间排序 asc 升序
instanceQuery.orderByHistoricActivityInstanceStartTime().asc();
//查询所有内容
List<HistoricActivityInstance> activityInstanceList = instanceQuery.list();
//输出
for (HistoricActivityInstance hi : activityInstanceList) {
System.out.println("流程定义id:"+hi.getProcessDefinitionId());
System.out.println("流程实例id:"+hi.getProcessInstanceId());
System.out.println("ActivityType:"+hi.getActivityType());
System.out.println("ActivityId:"+hi.getActivityId());
System.out.println("ActivityName:"+hi.getActivityName());
System.out.println("执行人Assignee:"+hi.getAssignee());
System.out.println("TaskId:"+hi.getTaskId());
System.out.println("<==========================>");
}
}
五、流程变量
变量的作用域可以设置为Global和Local两种。
1. Global变量
这个是流程变量的默认作用域,表示是一个完整的流程实例。 Global变量中变量
名不能重复。如果设置了相同的变量名,后面设置的值会直接覆盖前面设置的变量值
- 通过启动流程时候设置变量
ProcessInstance processInstance =runtimeService.startProcessInstanceByKey(key, map);
- 通过当前流程实例设置变量
// 通过流程实例执行实例编号设置流程变量
runtimeService.setVariable(executionId, "evection", evection);
// 一次设置多个值
runtimeService.setVariables(executionId, variables)
注: executionId必须是当前未完成的流程实例的执行ID。通常此ID设 置流程实例的ID。流程变量设计完成后,也可以通过 runtimeService.getVariable()获取流程变量
- 通过当前任务设置变量
//通过任务设置流程变量
taskService.setVariable(taskId, "evection", evection);
//一次设置多个值
taskService.setVariables(taskId, variables)
注: 任务id必须是当前待办任务id,act_ru_task中存在。如果该任务已结束,会报错。也可以通过taskService.getVariable()获取流程变量
- 任务办理是设置变量
taskService.complete(task.getId(),map);
2. Local变量
Local变量的作用域只针对一个任务或一个执行实例的范围,没有流程实例大。
Local变量由于作用在不同的任务或不同的执行实例中,所以不同变量的作用域是
互不影响的,变量名可以相同。Local变量名也可以和Global变量名相同,不会有影响
- 通过当前流程实例设置变量
// 通过流程实例执行实例编号设置流程变量
runtimeService.setVariableLocal(executionId, "evection", evection);
// 一次设置多个值
runtimeService.setVariableLocal(executionId, variables)
- 通过当前任务设置变量
// 通过任务设置流程变量
taskService.setVariableLocal(taskId, "evection", evection);
// 一次设置多个值
taskService.setVariablesLocal(taskId, variables)
3. 使用流程变量
Activiti中可以使用UEL表达式来使用这些流程变量。UEL表达式可以直接获取一
个变量的值,可以计算一个Boolean结果的表达式,还可以直接使用某些对象的属性。
例如:
设置数组大于等于3: ${num <= 3}
也可以使用对象参数: ${eveaction.num <= 3}
六、网关
1、流程符号
网关是用来控制流程流向的重要组件,通常都会要结合流程变量来使用。
介绍网关之前先来了解一下流程符号,BPMN2.0的基本符号主要包含以下几类:
- 事件 Event:
事件是驱动工作流发展的核心对象,在工作流的流程定制过程中会经常看到。
- 活动 Activity:
活动是工作或任务的一个通用术语。一个活动可以是一个任务,也可以是当前流程
的子处理流程。并且,活动会有不同的类型。例如Activiti中定义了UserTask,ScriptTask,ServiceTask,MailTask等等多种类型。这些活动是构成整个业
务流程的主体。
- 网关 gateWay:
网关是用来处理角色的,他决定了工作流的业务走向
- 流向 Flow:
流就是连接两个流程节点的连线,代表了流程之间的关联关系。
2、 排他网关ExclusiveGateway*
排他网关,用来在流程中实现决策。 当流程执行到这个网关,所有分支都会判断条 件是否为true,如果为true则执行该分支
不用排他网关也可以实现分支,如:在连线的condition条件上设置分支条件。所以为什么要用排他网关呢
因为设置condition条件存在缺点:如果条件都不满足,流程就结束了(是异常结束)。
如果从网关出去的线所有条件都不满足则系统抛出异常。
排他网关只会选择一个为true的分支执行。如果有两个分支条件都为true,排他网关会按指定规则选择一条分支去执行
3、 并行网关ParallelGateway
并行网关允许将流程分成多条分支,也可以把多条分支汇聚到一起,并行网关的功 能是基于进入和外出顺序流的:
fork分支:并行后的所有外出顺序流,为每个顺序流都创建一个并发分支。
join汇聚: 所有到达并行网关,在此等待的进入分支,
直到所有进入顺序流的分支 都到达以后, 流程就会通过汇聚网关。
与其他网关的主要区别是,并行网关不会解析条件。 即使顺序流中定义了条件,也会被忽略
4、包含网关InclusiveGateway
和排他网关一样,你可以在外出顺序流上定义条件,包含网关会解析它们。 但是主 要的区别是包含网关可以选择多于一条顺序流,这和并行网关一样。
包含网关的功能是基于进入和外出顺序流的:
分支: 所有外出顺序流的条件都会被解析,结果为true的顺序流会以并行方式继续 执行, 会为每个顺序流创建一个分支。
汇聚: 所有并行分支到达包含网关,会进入等待状态, 直到每个包含流程token的 进入顺序流的分支都到达。
这是与并行网关的最大不同。换句话说,包含网关只会 等待被选中执行了的进入顺序流。在汇聚之后,流程会穿过包含网关继续执行。
包含网关可以看做是排他网关和并行网关的结合体。
5、事件网关EventGateway
事件网关允许根据事件判断流向。网关的每个外出顺序流都要连接到一个中间捕获 事件。
当流程到达一个基于事件网关,网关会进入等待状态:会暂停执行。与此同 时,会为每个外出顺序流创建相对的事件订阅。
事件网关的外出顺序流和普通顺序流不同,这些顺序流不会真的"执行", 相反它们 让流程引擎去决定执行到事件网关的流程需要订阅哪些事件。
七、Activiti7与SpringBoot整合开发
1、 引入maven依赖
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>7.0.0.Beta2</version>
<exclusions>
<exclusion>
<artifactId>mybatis</artifactId>
<groupId>org.mybatis</groupId>
</exclusion>
</exclusions>
</dependency>
2、 创建配置文件application.yml
spring:
# activiti 配置
activiti:
#1.flase:默认值。activiti在启动时,对比数据库表中保存的版本,如果没有表或者版本不匹配,将抛出异常
#2.true: activiti会对数据库中所有表进行更新操作。如果表不存在,则自动创建
#3.create_drop: 在activiti启动时创建表,在关闭时删除表(必须手动关闭引擎,才能删除表)
#4.drop-create: 在activiti启动时删除原来的旧表,然后在创建新表(不需要手动关闭引擎)
database-schema-update: true
#检测历史表是否存在 activiti7默认没有开启数据库历史记录 启动数据库历史记录
db-history-used: true
#记录历史等级 可配置的历史级别有none, activity, audit, full
#none:不保存任何的历史数据,因此,在流程执行过程中,这是最高效的。
#activity:级别高于none,保存流程实例与流程行为,其他数据不保存。
#audit:除activity级别会保存的数据外,还会保存全部的流程任务及其属性。audit为history的默认值。
#full:保存历史数据的最高级别,除了会保存audit级别的数据外,还会保存其他全部流程相关的细节数据,包括一些流程参数等。
history-level: full
#校验流程文件,默认校验resources下的processes文件夹里的流程文件
check-process-definitions: false
3、 在resources/processes目录下创建BPMN文件
支持这两种格式:
4、 使用junit方式测试
八、附:常用流程流转代码片段
1、流程的发起、待办、任务办理、驳回、跳转、撤销
发起流程
@Override
public int insertLeaveapply(Leaveapply leaveapply)
{
int rows = leaveapplyMapper.insertLeaveapply(leaveapply);
// 发起请假流程
identityService.setAuthenticatedUserId(leaveapply.getUserId());
HashMap<String, Object> variables = new HashMap<>();
variables.put("applyuserid", leaveapply.getUserId());
variables.put("deptleader", leaveapply.getDeptleader());
runtimeService.startProcessInstanceByKey("leave", String.valueOf(leaveapply.getId()), variables);
// 自动完成第一个任务,根据流程定义key和businessKey获取任务ID
Task autoTask = taskService.createTaskQuery().processDefinitionKey("leave").processInstanceBusinessKey(String.valueOf(leaveapply.getId())).singleResult();
taskService.complete(autoTask.getId());
return rows;
}
查询任务待办列表
@PostMapping("/mylist")
@ResponseBody
public TableDataInfo mylist(TaskInfo param)
{
SysUser user = getSysUser();
String username = user.getLoginName();
//构建任务查询条件
TaskQuery condition = taskService.createTaskQuery().taskAssignee(username);
if (StringUtils.isNotEmpty(param.getTaskName())) {
condition.taskName(param.getTaskName());
}
if (StringUtils.isNotEmpty(param.getProcessName())) {
condition.processDefinitionName(param.getProcessName());
}
// 过滤掉流程挂起的待办任务
int total = condition.active().orderByTaskCreateTime().desc().list().size();
int start = (param.getPageNum()-1) * param.getPageSize();
//根据构建的任务条件分页查询任务列表
List<Task> taskList = condition.active().orderByTaskCreateTime().desc().listPage(start, param.getPageSize());
List<TaskInfo> tasks = new ArrayList<>();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
taskList.stream().forEach(a->{
//根据任务列表中的所属流程实例ID获取流程实例对象
ProcessInstance process = runtimeService.createProcessInstanceQuery().processInstanceId(a.getProcessInstanceId()).singleResult();
TaskInfo info = new TaskInfo();
info.setAssignee(a.getAssignee());//任务执行人(办理人)
info.setCreateTime(sdf.format(a.getCreateTime()));//任务创建时间
info.setTaskName(a.getName());//任务名称
info.setProcessInstanceId(a.getProcessInstanceId());//流程实例ID
info.setTaskId(a.getId());//任务ID
info.setExecutionId(a.getExecutionId());//执行实例编号ID
String formKey = formService.getTaskFormData(a.getId()).getFormKey();
info.setFormKey(formKey);//流程自定义表单Key
//从流程实例中获取流程定义名称、流程开始人、businessKey、流程启动事件
info.setProcessName(process.getProcessDefinitionName());//流程定义名称
info.setStarter(process.getStartUserId());//任务发起人
info.setBusinessKey(process.getBusinessKey());//businessKey
info.setStartTime(sdf.format(process.getStartTime()));//流程启动事件
tasks.add(info);
});
TableDataInfo rspData = new TableDataInfo();
rspData.setCode(0);
rspData.setRows(tasks);
rspData.setTotal(total);
return rspData;
}
流程审批
@GetMapping("/deptleadercheck")
public String deptleadercheck(String taskid, ModelMap mmap)
{
//根据taskId获取任务对象和流程实例
Task t = taskService.createTaskQuery().taskId(taskid).singleResult();
String processId = t.getProcessInstanceId();
ProcessInstance p = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();
if (p != null) {
//根据流程实例中的businessKey获取业务对象
Leaveapply apply = leaveapplyService.selectLeaveapplyById(Long.parseLong(p.getBusinessKey()));
mmap.put("apply", apply);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
mmap.put("startTime", sdf.format(apply.getStartTime()));
mmap.put("endTime", sdf.format(apply.getEndTime()));
mmap.put("taskid", taskid);
mmap.put("userlist", userService.selectUserList(new SysUser()));
}
return prefix + "/deptleadercheck";
}
办理任务
@RequestMapping(value = "/completeTask/{taskId}", method = RequestMethod.POST)
@ResponseBody
public AjaxResult completeTask(@PathVariable("taskId") String taskId, @RequestBody(required=false) Map<String, Object> variables) {
SysUser user = getSysUser();
String username = user.getLoginName();
//设置办理任务的执行人
taskService.setAssignee(taskId, username);
// 查出流程实例id
String processInstanceId = taskService.createTaskQuery().taskId(taskId).singleResult().getProcessInstanceId();
if (variables == null) {
taskService.complete(taskId);
} else {
// 添加审批意见
if (variables.get("comment") != null) {
//根据流程实例ID和任务ID添加审批意见
taskService.addComment(taskId, processInstanceId, (String) variables.get("comment"));
variables.remove("comment");
}
taskService.complete(taskId, variables);
}
return AjaxResult.success();
}
驳回或跳转到指定节点
@GetMapping(value = "/jump/{taskId}/{sid}")
@ResponseBody
public AjaxResult jump(@PathVariable String taskId, @PathVariable String sid) {
//根据任务id获取任务实例,通过流程定义ID获取bpmn模型
Task t = taskService.createTaskQuery().taskId(taskId).singleResult();
String processDefinitionId = runtimeService.createProcessInstanceQuery().processInstanceId(t.getProcessInstanceId()).singleResult().getProcessDefinitionId();
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
//根据执行实例编号executionId获取流程图(FlowNode)中当前活动的id(activityId);寻找流程实例当前任务的activeId
Execution execution = runtimeService.createExecutionQuery().executionId(t.getExecutionId()).singleResult();
String activityId = execution.getActivityId();
FlowNode currentNode = (FlowNode)bpmnModel.getMainProcess().getFlowElement(activityId);
FlowNode targetNode = (FlowNode)bpmnModel.getMainProcess().getFlowElement(sid);
// 创建连接线
List<SequenceFlow> newSequenceFlowList = new ArrayList<SequenceFlow>();
SequenceFlow newSequenceFlow = new SequenceFlow();
newSequenceFlow.setId("newFlow");
newSequenceFlow.setSourceFlowElement(currentNode);
newSequenceFlow.setTargetFlowElement(targetNode);
newSequenceFlowList.add(newSequenceFlow);
// 备份原有方向
List<SequenceFlow> dataflows = currentNode.getOutgoingFlows();
List<SequenceFlow> oriSequenceFlows = new ArrayList<SequenceFlow>();
oriSequenceFlows.addAll(dataflows);
// 清空原有方向
currentNode.getOutgoingFlows().clear();
// 设置新方向
currentNode.setOutgoingFlows(newSequenceFlowList);
//添加流程说明并完成当前任务
taskService.addComment(taskId, t.getProcessInstanceId(), "comment", "跳转节点");
taskService.complete(taskId);
// 恢复原有方向
currentNode.setOutgoingFlows(oriSequenceFlows);
return AjaxResult.success();
}
撤销:强制结束一个流程
@GetMapping(value = "/forceEnd/{taskId}")
@ResponseBody
public AjaxResult forceEnd(@PathVariable String taskId) {
Task t = taskService.createTaskQuery().taskId(taskId).singleResult();
String processDefinitionId = runtimeService.createProcessInstanceQuery().processInstanceId(t.getProcessInstanceId()).singleResult().getProcessDefinitionId();
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
// 寻找流程实例当前任务的activeId
Execution execution = runtimeService.createExecutionQuery().executionId(t.getExecutionId()).singleResult();
String activityId = execution.getActivityId();
FlowNode currentNode = (FlowNode)bpmnModel.getMainProcess().getFlowElement(activityId);
// 创建结束节点和连接线
EndEvent end = new EndEvent();
end.setName("强制结束");
end.setId("forceEnd");
List<SequenceFlow> newSequenceFlowList = new ArrayList<SequenceFlow>();
SequenceFlow newSequenceFlow = new SequenceFlow();
newSequenceFlow.setId("newFlow");
newSequenceFlow.setSourceFlowElement(currentNode);
newSequenceFlow.setTargetFlowElement(end);
newSequenceFlowList.add(newSequenceFlow);
// 备份原有方向
List<SequenceFlow> dataflows = currentNode.getOutgoingFlows();
List<SequenceFlow> oriSequenceFlows = new ArrayList<SequenceFlow>();
oriSequenceFlows.addAll(dataflows);
// 清空原有方向
currentNode.getOutgoingFlows().clear();
// 设置新方向并添加说明完成任务后恢复原有方向
currentNode.setOutgoingFlows(newSequenceFlowList);
// 完成当前任务
taskService.addComment(taskId, t.getProcessInstanceId(), "comment", "撤销流程");
taskService.complete(taskId);
// 恢复原有方向
currentNode.setOutgoingFlows(oriSequenceFlows);
return AjaxResult.success();
}
2、流程定义和流程实例可视化管理
查询已部署工作流列表
@RequestMapping(value = "/getprocesslists", method = RequestMethod.POST)
@ResponseBody
public TableDataInfo getlist(@RequestParam(required = false) String key, @RequestParam(required = false) String name,
@RequestParam(required = false) Boolean latest, Integer pageSize, Integer pageNum) {
//构建流程定义条件查询实例
ProcessDefinitionQuery queryCondition = repositoryService.createProcessDefinitionQuery();
if (StringUtils.isNotEmpty(key)) {
queryCondition.processDefinitionKey(key);
}
if (StringUtils.isNotEmpty(name)) {
queryCondition.processDefinitionName(name);
}
if (latest) {
queryCondition.latestVersion();
}
int total = queryCondition.list().size();
int start = (pageNum - 1) * pageSize;
//分页查询流程定义
List<ProcessDefinition> pageList = queryCondition.orderByDeploymentId().desc().listPage(start, pageSize);
List<Process> mylist = new ArrayList<Process>();
for (int i = 0; i < pageList.size(); i++) {
Process p = new Process();
p.setDeploymentId(pageList.get(i).getDeploymentId());
p.setId(pageList.get(i).getId());
p.setKey(pageList.get(i).getKey());
p.setName(pageList.get(i).getName());
p.setResourceName(pageList.get(i).getResourceName());
p.setDiagramresourceName(pageList.get(i).getDiagramResourceName());
p.setVersion(pageList.get(i).getVersion());
p.setSuspended(pageList.get(i).isSuspended());
mylist.add(p);
}
TableDataInfo rspData = new TableDataInfo();
rspData.setCode(0);
rspData.setRows(mylist);
rspData.setTotal(total);
return rspData;
}
根据流程部署ID删除部署的工作流
@RequestMapping(value = "/remove/{deploymentId}", method = RequestMethod.POST)
@ResponseBody
public AjaxResult remove(@PathVariable String deploymentId) {
//根据流程部署ID删除部署的工作流
repositoryService.deleteDeployment(deploymentId, true);
return AjaxResult.success();
}
根据流程定义ID查看工作流图片
@RequestMapping(value = "/showresource", method = RequestMethod.GET)
public void showresource(@RequestParam("pdid") String pdid,
HttpServletResponse response) throws Exception {
//根据流程定义ID查询工作流图片
BpmnModel bpmnModel = repositoryService.getBpmnModel(pdid);
//@Resource
//ProcessEngineConfiguration configuration;
ProcessDiagramGenerator diagramGenerator = configuration.getProcessDiagramGenerator();
InputStream is = diagramGenerator.generateDiagram(bpmnModel, "png", "宋体", "宋体", "宋体", configuration.getClassLoader(), 1.0);
ServletOutputStream output = response.getOutputStream();
IOUtils.copy(is, output);
}
根据流程定义ID定时唤醒一个流程定义
@RequestMapping(value = "/activateProcessDefinition", method = RequestMethod.GET)
@ResponseBody
public AjaxResult activateProcessDefinition(@RequestParam("pdid") String pdid, @RequestParam("flag") Boolean flag, @RequestParam(value="date") String date) throws Exception {
//激活一个流程定义
if (StringUtils.isNotEmpty(date)) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
repositoryService.activateProcessDefinitionById(pdid, flag, sdf.parse(date));
} else {
repositoryService.activateProcessDefinitionById(pdid, flag, null);
}
return AjaxResult.success();
}
根据流程定义ID定时挂起一个流程定义
@RequestMapping(value = "/suspendProcessDefinition", method = RequestMethod.GET)
@ResponseBody
public AjaxResult suspendProcessDefinition(@RequestParam("pdid") String pdid, @RequestParam("flag") Boolean flag,
@RequestParam(value="date") String date) throws Exception {
//定时挂起一个流程定义
if (StringUtils.isNotEmpty(date)) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
repositoryService.suspendProcessDefinitionById(pdid, flag, sdf.parse(date));
} else {
repositoryService.suspendProcessDefinitionById(pdid, flag, null);
}
return AjaxResult.success();
}
查询所有正在运行的流程实例列表
@RequestMapping(value = "/listProcess", method = RequestMethod.POST)
@ResponseBody
public TableDataInfo getlist(@RequestParam(required = false) String bussinesskey, @RequestParam(required = false) String name,
Integer pageSize, Integer pageNum) {
int start = (pageNum - 1) * pageSize;
//构建流程实例条件查询实例
ProcessInstanceQuery condition = runtimeService.createProcessInstanceQuery();
if (StringUtils.isNotEmpty(bussinesskey)) {
condition.processInstanceBusinessKey(bussinesskey);
}
if (StringUtils.isNotEmpty(name)) {
condition.processDefinitionName(name);
}
int total = condition.orderByProcessDefinitionId().desc().list().size();
//分页查询流程实例
List<ProcessInstance> processList = condition.orderByProcessDefinitionId().desc().listPage(start, pageSize);
List<FlowInfo> flows = new ArrayList<>();
processList.stream().forEach(p -> {
FlowInfo info = new FlowInfo();
info.setProcessInstanceId(p.getProcessInstanceId());
info.setBusinessKey(p.getBusinessKey());
info.setName(p.getProcessDefinitionName());
info.setStartTime(p.getStartTime());
info.setStartUserId(p.getStartUserId());
info.setSuspended(p.isSuspended());
info.setEnded(p.isEnded());
// 查看当前活动任务
List<Task> tasks = taskService.createTaskQuery().processInstanceId(p.getProcessInstanceId()).list();
if (tasks.size() > 0) {
String taskName = "";
String assignee = "";
for (Task t : tasks) {
taskName += t.getName() + ",";
assignee += t.getAssignee() + ",";
}
taskName = taskName.substring(0, taskName.length() -1);
assignee = assignee.substring(0, assignee.length() -1);
info.setCurrentTask(taskName);
info.setAssignee(assignee);
}
flows.add(info);
});
TableDataInfo rspData = new TableDataInfo();
rspData.setCode(0);
rspData.setRows(flows);
rspData.setTotal(total);
return rspData;
}
根据流程实例ID流程实例的挂起
@RequestMapping(value = "/suspend/{processInstanceId}", method = RequestMethod.GET)
@ResponseBody
public AjaxResult suspend(@PathVariable String processInstanceId) {
//根据流程实例ID挂起一个流程实例
runtimeService.suspendProcessInstanceById(processInstanceId);
return AjaxResult.success();
}
根据流程实例ID流程实例的唤醒
@RequestMapping(value = "/run/{processInstanceId}", method = RequestMethod.GET)
@ResponseBody
public AjaxResult rerun(@PathVariable String processInstanceId) {
//根据流程实例ID唤醒一个流程实例
runtimeService.activateProcessInstanceById(processInstanceId);
return AjaxResult.success();
}
根据流程实例id生成流程追踪图
public void generateFlowChart(String processInstanceId, OutputStream outputStream) {
try {
if (StringUtils.isEmpty(processInstanceId)) {
logger.error("processInstanceId is null");
return;
}
// 获取历史流程实例
HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId)
.singleResult();
// 获取流程中已经执行的节点
List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(processInstanceId)
.orderByHistoricActivityInstanceStartTime().asc().list();
// 高亮节点-高亮已经执行流程节点ID集合
List<String> highLightedActivitiIds = new ArrayList<>();
for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
highLightedActivitiIds.add(historicActivityInstance.getActivityId());
}
ProcessDiagramGenerator processDiagramGenerator = processEngineConfiguration.getProcessDiagramGenerator();
BpmnModel bpmnModel = repositoryService.getBpmnModel(historicProcessInstance.getProcessDefinitionId());
// 高亮流程线-高亮流程已发生流转的线id集合
List<String> highLightedFlowIds = getHighLightedFlows(bpmnModel, historicActivityInstances);
// 生成流程追踪图-使用默认配置获得流程图表生成器,并生成追踪图片字符流
InputStream imageStream = processDiagramGenerator
.generateDiagram(bpmnModel, "png", highLightedActivitiIds, highLightedFlowIds, "宋体", "微软雅黑", "黑体", null, 2.0);
// 输出图片内容
byte[] b = new byte[1024];
int len;
while ((len = imageStream.read(b, 0, 1024)) != -1) {
outputStream.write(b, 0, len);
}
} catch (Exception e) {
logger.error("processInstanceId" + processInstanceId + "生成流程图失败,原因:" + e.getMessage(), e);
}
}
/**
* 获取已经流转的线
*
* @param bpmnModel 流程图模板
* @param historicActivityInstances 历史实例
*/
private List<String> getHighLightedFlows(BpmnModel bpmnModel, List<HistoricActivityInstance> historicActivityInstances) {
// 高亮流程已发生流转的线id集合
List<String> highLightedFlowIds = new ArrayList<>();
// 已完成的活动节点
List<FlowNode> historicActivityNodes = new ArrayList<>();
for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
FlowNode flowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstance.getActivityId(), true);
historicActivityNodes.add(flowNode);
}
FlowNode currentFlowNode;
FlowNode targetFlowNode;
// 遍历已完成的活动实例,从每个实例的outgoingFlows中找到已执行的
for (HistoricActivityInstance currentActivityInstance : historicActivityInstances) {
// 获得当前活动对应的节点信息及outgoingFlows信息
currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivityInstance.getActivityId(), true);
List<SequenceFlow> sequenceFlows = currentFlowNode.getOutgoingFlows();
// 通过outgoingFlows能够在历史活动中找到且目标节点和当前节点执行顺序相邻
for (SequenceFlow sequenceFlow : sequenceFlows) {
targetFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(sequenceFlow.getTargetRef(), true);
if (historicActivityNodes.contains(targetFlowNode)) {
int target = historicActivityNodes.indexOf(targetFlowNode);
int current = historicActivityNodes.indexOf(currentFlowNode);
if (target - current == 1) {
//获取已经流转的线ID
highLightedFlowIds.add(sequenceFlow.getId());
}
}
}
}
return highLightedFlowIds;
}