在正式讲解之前,我们先简单的了解一下什么是工作流
工作流(Workflow),就是通过计算机对业务流程自动化执行管理。它主要解决的是“使在多个参与者之间按照某种预定义的规则自动进行传递文档、信息或任务的过程,从而实现某个预期的业务目标,或者促使此目标的实现”。那么在一个系统中工作流的功能就是对系统的业务流程进行自动化管理,而工作流是建立在业务流程的基础上,所以一个软件的系统核心根本上还是系统的业务流程,工作流只是协助进行业务流程管理。即使没有工作流业务系统也可以开发运行,只不过有了工作流可以更好的管理业务流程,提高系统的可扩展性。尤其是在 OA 系统、物流服务业相关系统、银证险等金融服务业相关系统等等系统中,都会看到工作流的影子。而 Activiti 就可以帮助我们实现工作流的相关功能...
Activiti 是一个工作流引擎, Activiti 可以将业务系统中复杂的业务流程抽取出来,使用专门的建模语言 BPMN2.0 进行定义,业务流程按照预先定义的流程进行执行,实现了系统的流程由 Activiti 进行管理,减少业务系统由于流程变更进行系统升级改造的工作量,从而提高系统的健壮性,同时也减少了系统开发维护成本。
Activiti 官网传送门:www.activiti.org/
看完上面的内容,相信各位小伙伴对工作流和 Activiti 都有了一个初步的了解,接下来就开始今天的重点,看看如何在 SpringBoot 项目中整合 Activiti7。
要实现工作流,我们首先就要绘制一个流程图,我这里使用的是 IDEA,需要先装一个 Activiti 插件(actiBPM),插件安装成功后,需要重启 IDEA。
IDEA重启完成后,我们在项目下找到 resources 文件夹,在 resources 下新建一个名为 processes 的文件夹,在 processes 文件夹下新建 .bpmn 文件,开始绘制我们的流程图(这里就绘制一个简单的请假流程图)
这里有四点是需要我们注意的:
注意事项一: 有些小伙伴编辑流程图节点时,会出现中文乱码的情况,此时我们需要将编码统一设置为 UTF-8。
修改完 IDEA 的配置后,我们再打开 IDEA 的安装路径,找到以下两个文件,并在文件最后一行新增:-Dfile.encoding=UTF-8
一般来说,经过以上两个步骤的修改,中文乱码的问题就可以被解决了。如果发现中文乱码的问题没有解决,我们再去修改一下 Custom VM Options,在配置的末尾增加一句:-Dfile.encoding=UTF-8
注意事项二: 我们新建的 .bpmn 文件,一定要放在 resources/processes 路径下,只有.bpmn 文件在该路径下,启动项目时才会自动把我们的流程图信息插入到数据库中去。
注意事项三: 有些小伙伴在编辑流程图的时候,可能会遇到不显示左侧的属性编辑框的情况。那么我们从 IDEA 左上角 File→选择setting→找到Color Scheme 菜单,将主题颜色改为 IntelliJ Light,修改之后我们重启一下IDEA,再打开 .bpmn 文件,就会发现已经默认在使用 BPMN Editor 了,选择其中的节点就能看到属性参数。这次我们再把主题颜色改成自己喜欢的颜色,BPMN Editor 依然会正常显示了。(这种操作就很迷,也不知道为啥会这样,但是这么做却很有效)
注意事项四: 我们在编辑节点信息时,输入的 Assigne 属性可能会出现保存不成功的情况,也就是在我们输入属性后,关闭 .bpmn 文件再打开,可能会发现刚刚输入的 Assigne 的属性值消失了。其实属性信息已经保存成功了,我们将 .bpmn 改成 .xml 就可以看到属性信息,但是为什么会出现这样的情况还是一个未解之谜
到这里,我们的流程图就已经绘制成功了,接下来就开始给 SpringBoot 项目整合 Activiti7。首先我们还是需要添加 Activiti 的 Maven 依赖
org.activiti
activiti-spring-boot-starter
7.0.0.Beta2
mybatis
org.mybatis
org.activiti
activiti-image-generator
5.22.0
org.activiti
activiti-bpmn-layout
6.0.0.RC1
然后我们编辑一下 .yml 配置文件,需要在 spring 节点下新增关于 Activiti7 的配置
spring:
activiti:
#1.flase:默认值。activiti在启动时,对比数据库表中保存的版本,如果没有表或者版本不匹配,将抛出异常
#2.true: activiti会对数据库中所有表进行更新操作。如果表不存在,则自动创建
#3.create_drop: 在activiti启动时创建表,在关闭时删除表(必须手动关闭引擎,才能删除表)
#4.drop-create: 在activiti启动时删除原来的旧表,然后在创建新表(不需要手动关闭引擎)
database-schema-update: true
# 检测历史信息表是否存在,activiti7默认不生成历史信息表,开启历史表
db-history-used: true
# 历史记录存储等级
history-level: full
check-process-definitions: true
接下来我们再修改一下启动文件,将 Activiti7 中自带的 Security 安全框架排除掉(因为我这里使用的是 Shiro 安全框架,Security 就没什么用处了)。
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, SecurityAutoConfiguration.class,
SecurityAutoConfiguration.class,
ManagementWebSecurityAutoConfiguration.class})
public class OAApplication
{
public static void main(String[] args)
{
SpringApplication.run(OAApplication.class, args);
System.out.println("启动成功~");
}
}
注意: 上面排除 Security 安全框架的操作对于 Activiti 7.1.0.M6 这个版本是没用的,因为这个版本的代码强引用了 SpringSecurity 里的内容,比如在 Activiti 的 SpringBoot 配置类中,强引用 UserDetailsService,没有这个就会报错,所以我们还需要把版本降到7.1.0.M4及以下。
接下来我们启动一下项目,如果数据库中多出了 Activiti 的25张表,那就代表我们成功的将 Activiti7 和 SpringBoot项目整合到一起了(每张表的含义如下图所示)。
这时候我们打开 act_ge_bytearray 表,就会发现里面多了一条记录,也就说明我们一开始绘制的请假流程图被加入到了数据库中
注意: 如果启动项目时出现 Unknown column ‘VERSION‘ in ‘field list‘ 错误,那么大概率是因为你使用了 Activiti 7.1.0.M5,针对这个问题我们有两种解决办法:第一种是在 act_re_deployment 表中加入 VERSION 和 PROJECT_RELEASE_VERSION_ 这两个字段就好了 ;第二种办法是降低 Activiti 的版本。暂时还不清楚为什么会出现这个问题,这个应该是一个官方的BUG。
接下来我们就写几个方法,模拟一下请假流程
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
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.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* @program: zy-oa
* @description: ActivitiController
* @create: 2022-05-10 09:30
**/
@Controller
@RequestMapping("/activiti")
public class ActivitiController {
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
/**
* 启动请假流程
**/
@GetMapping("/start")
public void start() {
String instanceKey = "myProcess_1";
System.out.println("开启请假流程...");
Map map = new HashMap();
//在holiday.bpmn中,填写请假单的任务办理人为动态传入的userId,此处模拟一个id
map.put("userId", "10001");
ProcessInstance instance = runtimeService.startProcessInstanceByKey(instanceKey, map);
System.out.println("启动流程实例成功: "+instance);
System.out.println("流程实例ID: "+instance.getId());
System.out.println("流程定义ID: "+instance.getProcessDefinitionId());
}
/**
* 员工申请
**/
@GetMapping("/employeeApply")
public void employeeApply() {
String instanceId = "2379b55f-d007-11ec-b669-380025172ff4"; // 任务ID
String leaveDays = "18"; // 请假天数
String leaveReason = "回家结婚"; // 请假原因
Task task = taskService.createTaskQuery().processInstanceId(instanceId).singleResult();
if (task == null) {
System.out.println("任务ID: "+instanceId+" 查询到任务为空!");
}
Map map = new HashMap();
map.put("days", leaveDays);
map.put("date", new Date());
map.put("reason", leaveReason);
taskService.complete(task.getId(), map);
System.out.println("执行【员工申请】环节,流程推动到【上级审核】环节");
}
/**
* 查看请假信息
**/
@GetMapping("/showTaskInfo")
public void showTaskInfo (){
String instanceId = "2379b55f-d007-11ec-b669-380025172ff4"; // 任务ID
Task task = taskService.createTaskQuery().processInstanceId(instanceId).singleResult();
String days = (String) taskService.getVariable(task.getId(), "days");
Date date = (Date) taskService.getVariable(task.getId(), "date");
String reason = (String) taskService.getVariable(task.getId(), "reason");
String userId = (String) taskService.getVariable(task.getId(), "userId");
System.out.println("请假天数: " + days);
System.out.println("请假理由: " + reason);
System.out.println("请假人id: " + userId);
System.out.println("请假日期: " + date.toString());
}
/**
* 领导审批
**/
@GetMapping("/departmentAudit")
public void departmentAudit() {
String instanceId = "2379b55f-d007-11ec-b669-380025172ff4"; // 任务ID
String departmentalOpinion = "恭喜恭喜,早生贵子";
Task task = taskService.createTaskQuery().processInstanceId(instanceId).singleResult();
Map map = new HashMap();
map.put("departmentalOpinion", departmentalOpinion);
taskService.complete(task.getId(), map);
System.out.println("添加审批意见,请假流程结束");
}
}
请假流程启动后,会生成一个流程实例ID(任务ID),上图中的 2379b55f-d007-11ec-b669-380025172ff4 就是对应的流程实例ID(任务ID)。后面的员工申请、查看流程信息、领导审批都是需要基于这个流程实例ID(任务ID)实现的。
员工申请
查看流程信息
领导审批
以上就是 SpringBoot 项目整合 Activiti7 实现工作流的完整步骤,在整合过程中有很多需要注意的小细节,各位小伙伴在实现过程中一定要仔细哦~