文章列表:
1. SpringBoot + Activiti 工作流引擎(一、基本概念与环境搭建)
2.SpringBoot + Activiti 工作流引擎(二、流程&任务操作)
前言
经过第一章的操作,我们已经完成了SpringBoot 与activi的整合环境搭建,本文将着重介绍如何在整合好的项目中完成流程的部署、流程启动、任务查询、任务处理、查询流程实例进度情况等内容。
activiti服务架构图:
Service 是工作流引擎提供用于进行工作流部署、执行、管理的服务接口。activiti通过各种各样的service给我们提供操作流程的api接口。
service总览:
名称 | 功能描述 | 功能详情 |
---|---|---|
RepositoryService | 资源管理类 | 提供了管理和控制流程发布包和流程定义的操作。使用工作流建模工具设计的业务流程图需要使用此 service 将流程定义文件的内容部署到计算机。 除了部署流程定义以外还可以: 查询引擎中的发布包和流程定义。 暂停或激活发布包,对应全部和特定流程定义。 暂停意味着它们不能再执行任何操作了,激活 是对应的反向操作。 获得多种资源,像是包含在发布包里的文件, 或引擎自动生成的流程图。 获得流程定义的 pojo 版本, 可以用来通过 java 解析流程,而不必通过 xml。 |
RuntimeService | 流程运行管理类 | 可以从这个服务类中获取很多关于流程执行相关的信息 |
TaskService | 任务管理类 | TaskService是 activiti 的任务管理类。 |
ManagerService | 引擎管理类 | 提供了对 Activiti 流程引擎的管理和维护功能,这些功能不在工作流驱动的应用程序中使用,主要用于 Activiti 系统的日常维护。 |
基本概念:
- 部署 activiti
Activiti 是一个工作流引擎(其实就是一堆 jar 包 API),业务系统使用 activiti 来对系统的业务流
程进行自动化管理,为了方便业务系统访问(操作)activiti 的接口或功能,通常将 activiti 环境与业务
系统的环境集成在一起。- 流程定义
使用 activiti 流程建模工具(activity-designer)定义业务流程(.bpmn 文件) 。 .bpmn 文件就是业务流程定义文件,通过 xml 定义业务流程。 如果使用其它公司开发的工作作引擎一般都提供了可视化的建模工具(Process Designer)用于生 成流程定义文件,建模工具操作直观,一般都支持图形化拖拽方式、多窗口的用户界面、丰富的过程图形元素、过程元素拷贝、粘贴、删除等功能。- 流程定义部署
向 activiti部署业务流程定义(.bpmn 文件)。 使用 activiti 提供的 api 向 activiti 中部署.bpmn
文件(一般情况还需要一块儿部署业务流程的图 片.png)- 启动一个流程实例(ProcessInstance)
启动一个流程实例表示开始一次业务流程的运行,比如员工请假流程部署完成,如果张三要请假就可以启动一个流程实例,如果李四要请假也启动一个流程实例,两个流程的执行互相不影 响,就好比定义一个 java 类,实例化两个对象一样,部署的流程就好比 java 类,启动一个流程 实例就好比 new 一个 java 对象。- 用户查询待办任务(Task)
因为现在系统的业务流程已经交给 activiti 管理,通过 activiti 就可以查询当前流程执行到哪了,当前用户需要办理什么任务了,这些 activiti帮我们管理了,而不像上边需要我们在 sql语句中的where 条件中指定当前查询的状态值是多少。- 用户办理任务
用户查询待办任务后,就可以办理某个任务,如果这个任务办理完成还需要其它用户办理,比如采 购单创建后由部门经理审核,这个过程也是由activiti 帮我们完成了,不需要我们在代码中硬编码指 定下一个任务办理人了。- 流程结束
当任务办理完成没有下一个任务/结点了,这个流程实例就完成了。
流程定义文件(bpmn文件可以在源码中获取):
一、流程部署
部署流程定义就是要将绘制好的的图形 ---- 即流程定义(.bpmn) 部署在工作流程引擎 activiti 中,执行此操作后 activiti 会将上边代码中指定的 bpm 文件和图片文件保存在 activiti 数据库。方法如下:
/**
* 部署请假流程
*/
@GetMapping("deploy")
public ResponseEntity deployProcesses() {
HashMap<String, Object> resultMap = new HashMap<>(8);
//部署对象
Deployment deployment = repositoryService.createDeployment()
// bpmn文件
.addClasspathResource("processes/holiday.bpmn")
// 图片文件
.addClasspathResource("processes/holiday.png")
.key("holiday")
.name("请假申请流程")
.deploy();
System.out.println("流程部署id:" + deployment.getId());
System.out.println("流程部署名称:" + deployment.getName());
resultMap.put("id", deployment.getId());
resultMap.put("name", deployment.getName());
resultMap.put("key", deployment.getKey());
resultMap.put("dDeploymentTime", deployment.getDeploymentTime());
return ResponseEntity.ok(resultMap);
}
请求过程:
影响的表结构:
二、启动流程
流程定义部署在 activiti 后就可以通过工作流管理业务流程了,也就是说上边部署的请假申请流程可以使用了。
针对该流程,启动一个流程表示发起一个新的请假申请单,这就相当于 java 类与 java 对象的关系,类定义好后需要 new 创建一个对象使用,当然可以 new 多个对象。对于请假申请流程,zhangsan发起一个请假申请单需要启动一个流程实例,请假申请单发起一个请假单也需要启动一个流程实例。
/**
* 启动请假流程--传入请假流程的key
*/
@GetMapping("start")
public ResponseEntity startProcessByKey(@RequestParam String processesKey) {
HashMap<String, Object> resultMap = new HashMap<>(8);
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processesKey);
resultMap.put("id", processInstance.getId());
resultMap.put("name", processInstance.getName());
resultMap.put("deploymentId", processInstance.getDeploymentId());
resultMap.put("processDefinitionId", processInstance.getProcessDefinitionId());
resultMap.put("startUserId", processInstance.getStartUserId());
resultMap.put("processDefinitionName", processInstance.getProcessDefinitionName());
return ResponseEntity.ok(resultMap);
}
启动流程请求过程:
影响表结构:
三、待办任务查询
流程启动后,任务的负责人就可以查询自己当前需要处理的任务,查询出来的任务都是该用户的待办任务。
/**
* 根据流程key和用户名获取待办流程
*
* @param processDefinitionKey 流程key(holiday)
* @param userName 用户名(zhangsan)
*/
@GetMapping("task")
public ResponseEntity getTaskByUserName(@RequestParam String processDefinitionKey, @RequestParam String userName) {
ArrayList<Object> resultList = new ArrayList<>();
List<Task> taskList = taskService.createTaskQuery()
.processDefinitionKey(processDefinitionKey)
//只查询该任务负责人的任务
.taskAssignee(userName)
.list();
taskList.forEach(task -> {
HashMap<String, Object> map = new HashMap<>(16);
System.out.println(task);
//任务ID
map.put("id", task.getId());
//任务名称
map.put("name", task.getName());
//任务委派人
map.put("assignee", task.getAssignee());
//任务创建时间
map.put("createTime", task.getCreateTime());
//任务描述
map.put("description", task.getDescription());
//任务对应得流程实例id
map.put("processInstanceId", task.getProcessInstanceId());
//任务对应得流程定义id
map.put("processDefinitionId", task.getProcessDefinitionId());
resultList.add(map);
});
return ResponseEntity.ok(resultList);
}
待办任务列表:
四、任务完成
任务负责人查询待办任务,选择任务进行处理,完成任务。
/**
* 根据任务id完成任务
*
* @param taskId 任务id
* @return
* @throws
*/
@GetMapping("completeTask")
public ResponseEntity completeTaskById(@RequestParam String taskId) {
taskService.complete(taskId);
return ResponseEntity.ok(String.format("任务id为:%s 已经完成", taskId));
}
任务完成:
zhangsan完成任务,就会流转到lisi(部门经理审批)环节。查询lisi的待办列表,结果如下:
五、挂起或者激活流程
/**
* 挂起激活流程定义
*
* @param processDefinitionId 流程定义Id
* @return
* @throws
*/
@GetMapping("suspendOrActivateProcessDefinition")
public ResponseEntity suspendOrActivateProcessDefinition(@RequestParam String processDefinitionId) {
// 获得流程定义
ProcessDefinition processDefinition = repositoryService
.createProcessDefinitionQuery()
.processDefinitionId(processDefinitionId)
.singleResult();
//是否暂停
boolean suspend = processDefinition.isSuspended();
if (suspend) {
//如果暂停则激活,这里将流程定义下的所有流程实例全部激活
repositoryService.activateProcessDefinitionById(processDefinitionId, true, null);
System.out.println("流程定义:" + processDefinitionId + "激活");
return ResponseEntity.ok(String.format("流程定义:%s激活", processDefinitionId));
} else {
//如果激活则挂起,这里将流程定义下的所有流程实例全部挂起
repositoryService.suspendProcessDefinitionById(processDefinitionId, true, null);
System.out.println("流程定义:" + processDefinitionId + "挂起");
return ResponseEntity.ok(String.format("流程定义:%s挂起", processDefinitionId));
}
}
六、流程进度查询与图片展现
/**
* 生成流程图
*/
@RequestMapping("createProcessImg")
public void createProcessImg(@RequestParam String processInstanceId, HttpServletResponse response) throws Exception {
//获取历史流程实例
HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
//根据流程定义获取输入流
InputStream is = repositoryService.getProcessDiagram(processInstance.getProcessDefinitionId());
BufferedImage bi = ImageIO.read(is);
File file = new File(processInstanceId + "Img.png");
if (!file.exists()) {
file.createNewFile();
}
FileOutputStream fos = new FileOutputStream(file);
ImageIO.write(bi, "png", fos);
fos.close();
is.close();
System.out.println("图片生成成功");
List<Task> tasks = taskService.createTaskQuery().taskCandidateUser("userId").list();
for (Task t : tasks) {
System.out.println(t.getName());
}
}
/**
* 生成流程图
*/
@RequestMapping("viewProcessImg")
public void viewProcessImg(@RequestParam String processInstanceId, HttpServletResponse response) throws Exception {
//获取历史流程实例
try {
HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
OutputStream outputStream = response.getOutputStream();
//根据流程定义获取输入流
InputStream in = repositoryService.getProcessDiagram(processInstance.getProcessDefinitionId());
IOUtils.copy(in, outputStream);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 生成流程图(高亮)
*/
@RequestMapping("viewProcessImgHighLighted")
public void viewProcessImgHighLighted(@RequestParam String processInstanceId, HttpServletResponse response) {
try {
byte[] processImage = getProcessImage(processInstanceId);
OutputStream outputStream = response.getOutputStream();
InputStream in = new ByteArrayInputStream(processImage);
IOUtils.copy(in, outputStream);
} catch (Exception e) {
e.printStackTrace();
}
}
public byte[] getProcessImage(String processInstanceId) throws Exception {
// 获取历史流程实例
HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
if (historicProcessInstance == null) {
throw new Exception();
} else {
// 获取流程定义
ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) repositoryService
.getProcessDefinition(historicProcessInstance.getProcessDefinitionId());
// 获取流程历史中已执行节点,并按照节点在流程中执行先后顺序排序
List<HistoricActivityInstance> historicActivityInstanceList = historyService
.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId)
.orderByHistoricActivityInstanceId().desc().list();
// 已执行的节点ID集合
List<String> executedActivityIdList = new ArrayList<>();
@SuppressWarnings("unused") int index = 1;
System.out.println("获取已经执行的节点ID");
for (HistoricActivityInstance activityInstance : historicActivityInstanceList) {
executedActivityIdList.add(activityInstance.getActivityId());
index++;
}
// 获取流程图图像字符流
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
//已执行flow的集合
List<String> executedFlowIdList = getHighLightedFlows(bpmnModel, historicActivityInstanceList);
ProcessDiagramGenerator processDiagramGenerator = processEngine.getProcessEngineConfiguration().getProcessDiagramGenerator();
InputStream imageStream = processDiagramGenerator.generateDiagram(bpmnModel, "png", executedActivityIdList, executedFlowIdList, "黑体", "黑体", "黑体", null, 1.0);
byte[] buffer = new byte[imageStream.available()];
imageStream.read(buffer);
imageStream.close();
return buffer;
}
}
/**
* 获取已经流转的线
*
* @param bpmnModel
* @param historicActivityInstances
* @return
*/
private static List<String> getHighLightedFlows(BpmnModel bpmnModel, List<HistoricActivityInstance> historicActivityInstances) {
// 高亮流程已发生流转的线id集合
List<String> highLightedFlowIds = new ArrayList<>();
// 全部活动节点
List<FlowNode> historicActivityNodes = new ArrayList<>();
// 已完成的历史活动节点
List<HistoricActivityInstance> finishedActivityInstances = new ArrayList<>();
for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
FlowNode flowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstance.getActivityId(), true);
historicActivityNodes.add(flowNode);
if (historicActivityInstance.getEndTime() != null) {
finishedActivityInstances.add(historicActivityInstance);
}
}
FlowNode currentFlowNode = null;
FlowNode targetFlowNode = null;
// 遍历已完成的活动实例,从每个实例的outgoingFlows中找到已执行的
for (HistoricActivityInstance currentActivityInstance : finishedActivityInstances) {
// 获得当前活动对应的节点信息及outgoingFlows信息
currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivityInstance.getActivityId(), true);
List<SequenceFlow> sequenceFlows = currentFlowNode.getOutgoingFlows();
/**
* 遍历outgoingFlows并找到已已流转的 满足如下条件认为已已流转: 1.当前节点是并行网关或兼容网关,则通过outgoingFlows能够在历史活动中找到的全部节点均为已流转 2.当前节点是以上两种类型之外的,通过outgoingFlows查找到的时间最早的流转节点视为有效流转
*/
if ("parallelGateway".equals(currentActivityInstance.getActivityType()) || "inclusiveGateway".equals(currentActivityInstance.getActivityType())) {
// 遍历历史活动节点,找到匹配流程目标节点的
for (SequenceFlow sequenceFlow : sequenceFlows) {
targetFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(sequenceFlow.getTargetRef(), true);
if (historicActivityNodes.contains(targetFlowNode)) {
highLightedFlowIds.add(targetFlowNode.getId());
}
}
} else {
List<Map<String, Object>> tempMapList = new ArrayList<>();
for (SequenceFlow sequenceFlow : sequenceFlows) {
for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
if (historicActivityInstance.getActivityId().equals(sequenceFlow.getTargetRef())) {
Map<String, Object> map = new HashMap<>();
map.put("highLightedFlowId", sequenceFlow.getId());
map.put("highLightedFlowStartTime", historicActivityInstance.getStartTime().getTime());
tempMapList.add(map);
}
}
}
if (!CollectionUtils.isEmpty(tempMapList)) {
// 遍历匹配的集合,取得开始时间最早的一个
long earliestStamp = 0L;
String highLightedFlowId = null;
for (Map<String, Object> map : tempMapList) {
long highLightedFlowStartTime = Long.valueOf(map.get("highLightedFlowStartTime").toString());
if (earliestStamp == 0 || earliestStamp >= highLightedFlowStartTime) {
highLightedFlowId = map.get("highLightedFlowId").toString();
earliestStamp = highLightedFlowStartTime;
}
}
highLightedFlowIds.add(highLightedFlowId);
}
}
}
return highLightedFlowIds;
}