Day01 流程中心概述
学习目标:
- 能够熟练掌握如何使用流程中心系统
- 能够说出项目的应用场景、核心架构
- 能够说出工作流的应用场景、常见的工作流引擎
- 能够熟练掌握Activiti的核心API和入门案例
- 能够熟练设计流程图
- 能够熟练完成案例代码的开发
- 能够熟悉核心服务、表、对象等及相互关系
1. 项目概述
1.1. 项目背景
请假是日常学习、工作中非常常见的一个场景,比如在学校中,小智因为生病,需要请假一天,他给班主任打个电话,说要请一天假。班主任同意后,小智的行为不会构成无故旷课。工作中的请假也是一样。请假也会包含比较复杂的情况,比如请一天假和请十天假,需要的审批流程和审批人员可能不同。企业中请假一般会和个人假期、工资等挂钩,因此需要对请假过程进行”留痕“处理,具体做法是:员工需要填写一个请假条,写明请假的请假人、时间、缘由等,甚至需要附加证明材料,然后找各级领导进行审批,他的直接领导、领导的领导在上边签字同意,证明不是旷工。
如下图是一个企业中的请假流程:
如果公司规模比较小,可以使用口头请假、纸质请假单,但是当公司达到一定规模时,为了考勤统计、绩效考核、请假证明等原因,需要一个软件系统,用来记录所有员工的请假,员工在使用这个系统时,大致是这样的过程或步骤:
- 员工提交请假单
- 领导审批
- 必要时需要二级、三级领导审批
- 需要通知人事部门,用于考勤和工资计算
这个过程叫做工作流,工作流简单的讲就是工作的流动,是多个人协作完成一项工作。需要注意的是,在工作流中会涉及到多个角色,每个角色完成不同的任务。
工作流的场景非常多,几乎可以说无处不在,常见工作流场景:
- 政府企业的公文流转、项目申报、公司注册、税务减免
- 银行贷款的申请、信用卡申请
- 企业管理中的请假、财务报销、人员招聘、调薪、奖金发放、合同续签
- 电商系统中的退货、售后、退款、下单
- 物流仓储中的出库、入库、运输等
- …
工作流的场景虽然非常多,但是开发的过程确是大同小异。我们来看一个工作流系统的整体结构:
从上图我们可以看到,一个工作流大致包含三部分:
- 业务:不同的业务流程有不同的业务单据,比如请假单和报销单,里边的属性字段是不一样的
- 人员:企业或组织中的人员,涉及到人员不同的部门、岗位和角色等
- 流程:这是一个公共的部分,涉及到如何设计流程、流程如何运转、如何查看运转的历史等
今天要学习的流程审批中心,就是以请假流程的开发为例,系统讲解如何开发一个工作流审批系统,只要掌握了这个系统的开发,以后开发其他业务的审批系统都是相同的过程。
1.2. 系统架构
1.2.1 功能清单
1.2.2 核心概念
3.工作流引擎:来开发工作流转的框架,是可重用的软件代码。
1.2.3 系统结构
进一步细化:
1.2.4 开发重点
一个工作流系统大致包含以下模块,重点和点是审批模块的开发。幸运的是,工作流引擎正是为了解决这部分难点的。
如下图所示,有了工作流引擎,我们的开发重点是:业务表单和用户操作。
流程审批中心基于最新的Activiti7工作流引擎开发,下面我们首先要学习一下Activiti7的相关知识,通过对入门案例的编写,深刻理解工作流中的一些概念、掌握工作流的开发步骤和方法。
2. Activiti入门案例
2.1 Activiti基础
2.1.1 核心概念
如果要学习一门技术,首先要掌握这门技术相关的一些概念。
下图以生活中的请假为例,对比了工作流和生活中请假过程的一些概念。
2.1.2 核心API
Activiti作为一个框架,提供了一些API,以便于我们开发使用。
Service是Activiti引擎提供用于进行工作流部署、执行、管理的服务接口,我们使用这些接口可以就是操作服务对应的数据表。
2.1.3 表结构
2.2 案例准备
2.2.1 案例介绍
请假的业务,是现实生活中非常常见的一个业务,主要包括当事人提交请假,他的直属领导,或更高层的领导来逐个进行审批,最后完成一个请假的过程。每个公司的请假流程细节上可能存在差异,但总体流程都差不多。下面我们一起来看一个简版的请假流程,如下:
- 当事人发起请假申请
- 领导审批,通过或者拒绝
实现步骤:
2.2.3 创建工程
- 创建数据库
- 创建maven工程:itcast-workflow
- 创建resources目录
- 导入pom.xml文件内容
- 导入application.yml文件内容
- 启动类
- UserDetailService实现类
- 测试类
- 数据库脚本
create database itcast_workflow default charset utf8mb4;
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.5.0</version>
</parent>
<groupId>com.itheima</groupId>
<artifactId>itcast-workflow</artifactId>
<version>0.0.1</version>
<name>${project.artifactId}</name>
<description>itcast-workflow</description>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>7.1.0.M6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.application.yml
server:
port: 8090
spring:
datasource:
url: jdbc:mysql://localhost:3306/itcast_workflow?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username : root
password : root
driver-class-name: com.mysql.jdbc.Driver
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
#默认采用UUID作为主键, 设置为false, 将采用整型主键
use-strong-uuids: false
#关闭springAutoDeployment自动部署流程定义
deployment-mode: never-fail
logging:
level:
org.activiti.engine.impl.persistence.entity: debug
注意事项:
Mysql 设置区分大小写:
- 修改配置文件my.ini(linux是my.cnf文件),在[mysqld] 后面添加:lower_case_table_names=2
- 注意:
- 5.6: 1---表示不区分大小写 2---表示区分大小写
- 5.7: 1---表示不区分大小写 0---表示区分大小写
4.启动类
package com.itheima.activiti;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
- UserDetailServiceImpl
Activiti默认集成SpringSecurity安全框架,需要创建UserDetailsService的一个实现类
package com.itheima.activiti.auth;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
return null;
}
}
环境准备好了之后,就可以直接运行启动类,下图中的表会自动被创建(注意:连接的数据库需要自己创建,数据库不会自动创建),Activiti7中自带的数据库表共25张。
数据库表
看到程序运行之后自动创建的表,我们发现Activiti 的表都以 ACT_ 开头。
第二部分是表示表的用途的两个字母标识。 用途也和服务的 API 对应。
表结构说明:
6.测试类
package com.itheima.activiti.test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class ActivitiTest {
}
在测试类中开发案例代码。
2.2.4 设计流程
- 流程设计器
使用activiti工作流比较重要的一步就是绘制流程图。最方便的方法就是使用Idea中的插件(actiBPM)进行绘制,叫做离线流程设计器。
与离线流程设计器对应的是在线流程设计,我们将在第二天的课程中介绍
顾名思义,在线流程设计器的作用是通过web线上直接设计流程,在线上环境下,可以直接保存和部署。
安装方式如下:
1). 在线安装
Settings ------> Plugins -----> Marketable ---------> 搜索 actiBPM
安装完成之后,重新启动IDEA 。
**注意: IDEA 2019.1.4之后的版本,无法在线安装此插件 **
https://plugins.jetbrains.com/plugin/7429-actibpm/versions
2). 离线安装
部分版本的IDEA在Martketplace中搜索不到 actiBPM , 这个时候就选择离线安装 ;
2. 设计请假流程
鼠标右键,选择 “BpmnFile”,创建一个流程图,然后就可以开启绘制。
打开流程设计器,拖动BPMN中的组件,开始绘制。
2.3 案例代码
/**
* 1、保存模型
* 2、保存流程图
* 3、部署模型
* 4、查询流程定义
* 5、启动流程
* 6、执行任务
* 7、查询历史
*/
2.3.1 保存模型
Activit的模型包含两部分,Model和流程图(文件),本案例保存模型
@Autowired
RepositoryService repositoryService;
/**
* 保存模型对象
*/
@Test
public void saveModel(){
//创建activiti模型对象
Model model = repositoryService.newModel();
model.setKey("myKey");
model.setName("测试保存模型");
//保存流程模型
repositoryService.saveModel(model);
}
流程模型保存在ACT_RE_MODEL表中。
2.3.2 保存流程图
Activit的模型包含两部分,Model和流程图(文件),本案例保存流程图
/**
* 保存流程图
*/
@Test
public void testSaveBpmn() throws IOException {
File file = new File("src/main/resources/holiday.bpmn");
byte[] bytes = FileUtils.readFileToByteArray(file);
repositoryService.addModelEditorSource("15001", bytes);
}
流程图保存在ACT_GE_BYTEARRAY表中:
2.3.3 部署模型
通常情况下,一些流程是提前规定好的(比如我们的请假流程),此时我们可以提前在我们的流程设计器中将流程先设计出来,再将已设计好的流程定义文件部署到 activti流程引擎中。
/**
* 部署流程
*/
@Test
public void testDeployment(){
repositoryService.createDeployment()
.key("mykey")
.name("简易请假流程")
.addClasspathResource("holiday.bpmn")
.deploy();
}
查询流程部署数据:
1). ACT_RE_PROCDEF:已部署的流程定义
- 流程部署后,将会创建流程定义(ProcessDefination),
- 相同key的流程部署后,将会创建一个流程的不同的流程定义,即该流程的不同版本。
- 2). ACT_RE_DEPLOYMENT:部署单元信息
3). ACT_GE_BYTEARRAY:通用的流程定义和流程资源
2.3.4 查询流程定义
对于一个流程图,只要key相同,每部署一次将会生成一个流程定义。即一个流程可以有多个流程定义。
/**
* 查询流程定义
*/
@Test
public void testProcessDef(){
List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery()
.processDefinitionKey("myProcess_1")
.list();
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i).getId());
}
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionKey("myProcess_1")
.latestVersion().singleResult();
System.out.println(processDefinition.getId());
}
2.3.5 启动实例
一个流程部署后,生成流程定义,我们可以根据流程定义,发起流程。
- 发起流程时,依据的是流程定义里的key,这个key就是流程图中的id:myProcess_1。
- 发起流程是运行时服务对象创建了一个流程实例。
- 发起流程后,流程会往下走一步,进入到请假申请。
代码如下:
/**
* 启动实例
*/
@Autowired
private RuntimeService runtimeService;
@Test
public void testStart(){
runtimeService.startProcessInstanceByKey("myProcess_1");
}
1)ACT_RU_EXECUTION:运行时流程执行实例
2)ACT_RU_TASK:运行时任务
3)ACT_HI_PROCINST: 历史的流程实例
4)ACT_HI_ACTINST:历史的活动实例
5)ACT_HI_TASKINST:历史的任务实例
2.3.6 完成任务
同样完成任务我们首先就要得到我们用来完成任务的服务类,我们是根据运行时任务表当中的id来完成的。
1). 完成第一步任务
当我们完成当前任务(请假申请),流程就会进入到领导审批。
具体代码如下, task id是ACT_RU_TASK的主键:
/**
* 执行任务
*/
@Autowired
private TaskService taskService;
@Test
public void testComplete(){
taskService.complete("7505");
}
执行完成后:
同样完成领导审批任务(ACT_RU_TASK):
2). 完成第二步任务
当完成当前任务(领导审批)之后,该流程就结束了,流程结束,数据库表 ACT_RU_TASK 中的数据就会被删除。
@Test
public void testComplete(){
taskService.complete("10002");
}
2.3.7 查询历史
我们可以通过API, 查询当前指定用户, 待处理的任务 ;
/**
* 查询流程历史
*
*/
@Autowired
HistoryService historyService;
@Test
public void testHistory(){
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionKey("myProcess_1")
.latestVersion()
.singleResult();
List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery()
.processDefinitionId(processDefinition.getId()).list();
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i).getActivityName());
}
}
Activiti历史信息很丰富,经常用的是以下几个:
- 活动
- 实例
- 任务
- 变量
3. 今日总结
3.1. 基本概念
- 工作流、工作流引擎、常见工作流引擎
- 流程图、节点、活动、模型、部署、流程定义、流程实例、流程任务
- 流程图的结构
3.2. Activiti核心API和表
4个核心API,25张表
3.3 Activiti入门案例
-
搭建项目:
maven工程、pom文件、yml文件、创建数据库、Springboot启动类
-
开发过程:
设计流程、保存模型和流程图、部署流程、启动实例、执行任务、查看历史
番外 idea绘制流程图出现中文乱码怎么办?
- 第一步 修改idea配置
- 第二步 修改idea bin目录下的文件
- 第三步 xi
修改idea配置
点开后的文件添加以下内容:-Dfile.encoding=UTF-8
修改idea bin下的vmprotions文件
修改idea中的enconding
File -> settings -> Editor -> Enconding
重启
能看见如下图显示就说明成功了