欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

基于 Spring Boot 2.x 使用 Activiti 创建一个简易的请假流程

程序员文章站 2024-03-22 11:59:22
...




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 请假流程图

流程图:
基于 Spring Boot 2.x 使用 Activiti 创建一个简易的请假流程
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 推荐参考资料

springboot2.04与activiti 6.0集成

SpringBoot2集成Activiti6

SpringBoot activiti 系列教程

Activiti工作流实战开发

Activiti第一篇【介绍、配置开发环境、快速入门】

Activiti5 学习笔记(八)—— comment 批注

Activiti学习(四)——流程变量的设置和获取

Activiti 删除部署与流程实例

5 Github 源码

Gtihub 源码地址 : https://github.com/Flying9001/springBootDemo

个人公众号:404Code,分享半个互联网人的技术与思考,感兴趣的可以关注.
基于 Spring Boot 2.x 使用 Activiti 创建一个简易的请假流程