基于 Spring Boot 2.x 使用 Activiti 创建一个简易的请假流程
1 摘要
工作流是需要和业务结合起来,才能够发挥其作用。本文将介绍基于 Spring Boot 2、Activiti 6 创建一个简易的请假流程。
2 准备工作
(1) Activiti 入门教程(官方示例) — 2020-07-21
(2) SpringBoot 2.x 快速集成 Activiti — 2020-07-21
(3) 绘制一个 Activiti BPMN 流程图 — 2020-07-22
(4) Activiti 核心 API 在 Spring Boot 2.x 中的简易使用教程 — 2020-7-23
3 请假流程图
流程图:
bpmn 文件:
./activiti-workflow/src/main/resources/processes/student_leave_2.bpmn
<?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.activiti.org/processdef">
<process id="student_leave_2" name="student_leave_2" isExecutable="true">
<startEvent id="startEvent1" name="开始"></startEvent>
<userTask id="sid-486A7692-35E4-476A-94EF-3566C01B8F39" name="学生申请" activiti:assignee="${student}">
<extensionElements>
<modeler:initiator-can-complete xmlns:modeler="http://activiti.com/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
</extensionElements>
</userTask>
<userTask id="sid-23DCF2CD-D1E0-4B66-9853-352B2E505782" name="班主任审批" activiti:assignee="${teacher}">
<extensionElements>
<modeler:initiator-can-complete xmlns:modeler="http://activiti.com/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
</extensionElements>
</userTask>
<endEvent id="sid-52FDCD77-D54C-4340-B624-93B92D941E77" name="结束"></endEvent>
<sequenceFlow id="sid-96C3385F-899C-4149-B695-CEF25F5213CB" sourceRef="startEvent1" targetRef="sid-486A7692-35E4-476A-94EF-3566C01B8F39"></sequenceFlow>
<sequenceFlow id="sid-5602EE93-61DC-4326-80A6-C76661889900" name="提交申请" sourceRef="sid-486A7692-35E4-476A-94EF-3566C01B8F39" targetRef="sid-23DCF2CD-D1E0-4B66-9853-352B2E505782"></sequenceFlow>
<sequenceFlow id="sid-EABF7DE5-1A32-4EFF-9627-9B5AB70D5E8A" name="同意" sourceRef="sid-23DCF2CD-D1E0-4B66-9853-352B2E505782" targetRef="sid-52FDCD77-D54C-4340-B624-93B92D941E77"></sequenceFlow>
</process>
</definitions>
3 核心代码
3.1 数据库表
学生请假流程的数据库表
./doc/sql/activiti_leave_create.sql
/*==============================================================*/
/* DBMS name: MySQL 5.0 */
/* Created on: 2020/7/13 16:53:12 */
/*==============================================================*/
drop table if exists leave_info;
/*==============================================================*/
/* Table: leave_info */
/*==============================================================*/
create table leave_info
(
id varchar(40) not null comment '编号',
student_name varchar(30) comment '学生姓名',
student_id varchar(40) comment '学生编号',
leave_reason varchar(100) comment '请假原因',
leave_duration int comment '请假时长(单位:天)',
primary key (id)
)
ENGINE = INNODB DEFAULT
CHARSET = UTF8;
alter table leave_info comment '请假信息';
3.2 流程管理工具类
关于流程的部署与使用,作者的思路是这样的:对于一张流程图,在一个系统中只部署一次,后边每次都是启动这个流程的实例,这样可以避免重复部署同一个流程,在查询代理人流程任务时,也能避免冲突。流程引擎使用单例模式创建,因为创建流程引擎是一个耗时耗资源的重量级操作。
./activiti-workflow/src/main/java/com/ljq/demo/springboot/activiti/common/util/ActivitiManager.java
package com.ljq.demo.springboot.activiti.common.util;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollectionUtil;
import com.ljq.demo.springboot.activiti.dao.ActivitiDeployDao;
import com.ljq.demo.springboot.activiti.model.vo.HistoricTaskInstanceVO;
import com.ljq.demo.springboot.activiti.model.vo.TaskVO;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.TaskService;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Comment;
import org.activiti.engine.task.Task;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @Description: Activiti 流程管理
* @Author: junqiang.lu
* @Date: 2020/7/9
*/
public class ActivitiManager {
private static volatile ProcessEngine processEngine;
/**
* 启动流程
*
* @param processFile
* @param processKey
* @param deployDao
* @param businessKey
* @param variable
* @return
*/
public static ProcessInstance startProcess(String processFile, String processKey, ActivitiDeployDao deployDao,
String businessKey,Map<String, Object> variable) {
// 判断是否部署过
int count = deployDao.checkDeployed(processFile);
if (count < 1) {
deployProcess(processFile);
}
// 初始化流程引擎,并且启动流程
ProcessInstance instance = init().getRuntimeService().startProcessInstanceByKey(processKey,businessKey,variable);
return instance;
}
/**
* 查询单个流程的历史记录
*
* @param processKey
* @param businessKey
* @return
*/
public static List<HistoricTaskInstanceVO> queryHistoryTask(String processKey, String businessKey) {
List<HistoricTaskInstance> historicTaskInstanceList = init().getHistoryService()
.createHistoricTaskInstanceQuery()
.processDefinitionKey(processKey)
.processInstanceBusinessKey(businessKey)
.list();
List<HistoricTaskInstanceVO> taskInstanceVOList = new ArrayList<>();
historicTaskInstanceList.stream().forEach(taskInstance -> {
HistoricTaskInstanceVO taskInstanceVO = new HistoricTaskInstanceVO();
BeanUtil.copyProperties(taskInstance, taskInstanceVO, CopyOptions.create().ignoreError().ignoreNullValue());
List<Comment> commentList = init().getTaskService().getTaskComments(taskInstance.getId());
if (CollectionUtil.isNotEmpty(commentList)) {
taskInstanceVO.setComment(commentList.get(0).getFullMessage());
}
taskInstanceVOList.add(taskInstanceVO);
});
return taskInstanceVOList;
}
/**
* 查询流程实例
*
* @param processKey
* @param businessKey
* @return
*/
public static ProcessInstance queryProcessInstance(String processKey,String businessKey) {
ProcessInstance processInstance = init().getRuntimeService()
.createProcessInstanceQuery()
.processDefinitionKey(processKey)
.processInstanceBusinessKey(businessKey)
.singleResult();
return processInstance;
}
/**
* 查询当前代理人在当前流程中待办理任务(列表)
*
* @param processKey
* @param assignee
* @return
*/
public static List<TaskVO> queryTaskList(String processKey, String assignee) {
List<Task> taskList = init().getTaskService()
.createTaskQuery()
.processDefinitionKey(processKey)
.taskAssignee(assignee)
.list();
List<TaskVO> taskVOList = new ArrayList<>();
taskList.stream().forEach(task -> {
TaskVO taskVO = new TaskVO();
BeanUtil.copyProperties(task, taskVO);
taskVOList.add(taskVO);
});
return taskVOList;
}
/**
* 完成当前节点任务
*
* @param taskId
* @param processInstanceId
* @param comment
*/
public static void complete(String taskId, String processInstanceId, String comment) {
TaskService taskService = init().getTaskService();
taskService.addComment(taskId, processInstanceId, comment);
taskService.complete(taskId);
}
/**
* 删除流程实例(包括流程历史)
*
* @param processInstanceId
* @param deleteReason
*/
public static void deleteProcessInstance(String processInstanceId, String deleteReason) {
init().getRuntimeService().deleteProcessInstance(processInstanceId, deleteReason);
}
/**
* 初始化
*/
private static ProcessEngine init() {
if (processEngine == null) {
synchronized (ActivitiManager.class) {
if (processEngine == null) {
processEngine = ProcessEngines.getDefaultProcessEngine();
System.out.println("初始化流程引擎");
}
}
}
return processEngine;
}
/**
* 部署流程
*
* @param processFile
* @return
*/
private static Deployment deployProcess(String processFile) {
Deployment deployment = init().getRepositoryService()
.createDeployment()
.addClasspathResource(processFile)
.deploy();
return deployment;
}
}
3.3 流程的使用
流程的使用主要是在业务层(Service 层)
学生请假流程的业务层实现类:
./activiti-workflow/src/main/java/com/ljq/demo/springboot/activiti/service/impl/LeaveInfoServiceImpl.java
(1) 学生发起一个请假申请(启动流程)
/**
* 新增(单条)
*
* @param leaveInfoAddParam
* @return
* @throws Exception
*/
@Override
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = {Exception.class})
public ApiResult add(LeaveInfoAddParam leaveInfoAddParam) throws Exception {
// 请求参数获取
LeaveInfoEntity leaveInfoParam = new LeaveInfoEntity();
BeanUtil.copyProperties(leaveInfoAddParam, leaveInfoParam, CopyOptions.create()
.ignoreNullValue().ignoreError());
String id = IDGenerator.getID();
leaveInfoParam.setId(id);
// 保存
leaveInfoDao.save(leaveInfoParam);
// 指定节点办理人
Map<String, Object> variable = new HashMap<>(16);
variable.put("student", leaveInfoAddParam.getStudentId());
variable.put("teacher", leaveInfoAddParam.getTeacherId());
variable.put("teacherName", "张三丰");
ProcessInstance instance = ActivitiManager.startProcess(ActivitiConst.STUDENT_LEAVE_PROCESS_FILE,
ActivitiConst.STUDENT_LEAVE_PROCESS_KEY, deployDao, id, variable);
return ApiResult.success(id);
}
(2) 查询请假详情(请假流程历史记录)
/**
* 查询详情(单条)
*
* @param leaveInfoInfoParam
* @return
* @throws Exception
*/
@Override
public ApiResult info(LeaveInfoInfoParam leaveInfoInfoParam) throws Exception {
// 查询
LeaveInfoEntity leaveInfoDB = leaveInfoDao.queryObject(leaveInfoInfoParam.getId());
if (Objects.isNull(leaveInfoDB)) {
return ApiResult.success();
}
List<HistoricTaskInstanceVO> historicTaskInstanceVOList = ActivitiManager
.queryHistoryTask(ActivitiConst.STUDENT_LEAVE_PROCESS_KEY, leaveInfoDB.getId());
Map<String, Object> extraDataMap = new HashMap<>(16);
extraDataMap.put("history", historicTaskInstanceVOList);
return ApiResult.success(leaveInfoDB, extraDataMap);
}
(3) 查询待审批列表(根据代理人查询待执行流程任务)
/**
* 查询待审批列表
*
* @param jobListParam
* @return
* @throws Exception
*/
@Override
public ApiResult jobList(LeaveInfoJobListParam jobListParam) throws Exception {
List<TaskVO> taskVOList = ActivitiManager.queryTaskList(ActivitiConst.STUDENT_LEAVE_PROCESS_KEY, jobListParam.getUserId());
return ApiResult.success(taskVOList);
}
(4) 完成审批(完成任务节点)
根据不同的用户,完成任务的节点也不相同,对于学生而言,完成审批即为「提交请假请求」,对于老师而言,即为「审批通过」
/**
* 完成审批
*
* @param approvalParam
* @return
* @throws Exception
*/
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = {Exception.class})
@Override
public ApiResult approval(LeaveInfoApprovalParam approvalParam) throws Exception {
List<TaskVO> taskVOList = ActivitiManager.queryTaskList(ActivitiConst.STUDENT_LEAVE_PROCESS_KEY,
approvalParam.getUserId());
if (taskVOList.stream().filter(task -> Objects.equals(task.getId(), approvalParam.getTaskId()))
.findFirst().isPresent()) {
ActivitiManager.complete(approvalParam.getTaskId(), approvalParam.getProcessInstanceId(),
approvalParam.getComment());
} else {
return ApiResult.failure(ResponseCode.LEAVE_INFO_WORKFLOW_TASK_NOT_EXIST);
}
return ApiResult.success();
}
(5) 删除请假信息(删除流程实例,包含历史记录)
/**
* 删除(单条)
*
* @param leaveInfoDeleteParam
* @return
* @throws Exception
*/
@Override
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = {Exception.class})
public ApiResult delete(LeaveInfoDeleteParam leaveInfoDeleteParam) throws Exception {
LeaveInfoEntity leaveInfoDB = leaveInfoDao.queryObject(leaveInfoDeleteParam.getId());
if (Objects.isNull(leaveInfoDB) || !Objects.equals(leaveInfoDB.getStudentId(),
leaveInfoDeleteParam.getUserId())) {
return ApiResult.failure(ResponseCode.LEAVE_INFO_NOT_EXIST);
}
// 查询请假流程实例
ProcessInstance processInstance = ActivitiManager.queryProcessInstance(ActivitiConst.STUDENT_LEAVE_PROCESS_KEY,
leaveInfoDeleteParam.getId());
// 删除请假流程实例
ActivitiManager.deleteProcessInstance(processInstance.getId(), leaveInfoDeleteParam.getDeleteReason());
// 删除请假信息
leaveInfoDao.delete(leaveInfoDB);
return ApiResult.success();
}
至此,一个简单的请假流程已经实现了。更多的使用细节,可参考项目源码。
4 推荐参考资料
Activiti5 学习笔记(八)—— comment 批注
5 Github 源码
Gtihub 源码地址 : https://github.com/Flying9001/springBootDemo
个人公众号:404Code,分享半个互联网人的技术与思考,感兴趣的可以关注.