2. 工作流介绍
2.1 概念介绍
- 工作流(Workflow),就是通过计算机对业务流程自动化执行管理。它主要解决的是 “使在多个参与者之间按照某种预定义的规则自动进行传递文档、信息或任务的过程,从而实现某个预期的业务目标,或者促使此目标的实现”。
- 工作流引擎,是指workflow作为应用系统的一部分,并为之提供对各应用系统有决定作用的根据角色、分工和条件的不同决定信息传递路由、内容等级等核心解决方案。工作流引擎包括流程的节点管理、流向管理、流程样例管理等重要功能。
2.2 适用场景
1.关键业务流程:订单、报价处理、采购处理、合同审核、客户电话处理、供应链管理等
2.行政管理类:出差申请、加班申请、请假申请、用车申请、各种办公用品申请、购买申请、日报周报等凡是原来手工流转处理的行政表单。
3.人事管理类:员工培训安排、绩效考评、职位变动处理、员工档案信息管理等。
4.财务相关类:付款请求、应收款处理、日常报销处理、出差报销、预算和计划申请等。
5.客户服务类:客户信息管理、客户投诉、请求处理、售后服务管理等。
2.3 技术实现
1). 自行实现
在没有专门的工作流引擎之前,我们之前为了实现流程控制,通常的做法就是采用状态字段的值来跟踪流程的变化情况。这样不用角色的用户,通过状态字段的取值来决定记录是否显示。
2). 第三方提供的工作流引擎
JBPM
Activiti
3. Activiti介绍
3.1 Activiti介绍
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-18TZL6gA-1650199519758)(img/image-20210301000158273.png)]
Activiti是一个开源的工作流引擎,它实现了BPMN 2.0规范,可以发布设计好的流程定义,并通过API进行流程调度。Activiti 作为一个遵从 Apache 许可的工作流和业务流程管理开源平台,其核心是基于 Java 的超快速、超稳定的 BPMN2.0 流程引擎,强调流程服务的可嵌入性和可扩展性,同时更加强调面向业务人员。Activiti 流程引擎重点关注在系统开发的易用性和轻量性上。每一项BPM 业务功能 Activiti 流程引擎都以服务的形式提供给开发人员。通过使用这些服务,开发人员能够构建出功能丰富、轻便且高效的 BPM 应用程序。
版本: Activiti 7.1.0.M6
官网: https://www.activiti.org/
3.2 BPM
BPM(Business Process Management),即业务流程管理,是一种以规范化的构造端到端的卓越业务流程为中心,以持续的提高组织业务绩效为目的系统化方法。常见商业管理教育如 EMBA、MBA等均将 BPM 包含在内。
3.3 BPMN
BPMN(Business Process Model And Notation)- 业务流程模型和符号, 是由 BPMI(Business Process Management Initiative)开发的一套标准的业务流程建模符号,使用BPMN 提供的符号可以创建业务流程。
2004年5月发布了BPMN1.0规范。BPMI于2005年9月并入OMG(The Object Management Group 对象管理组织)组织。OMG于2011年1月发布BPMN2.0的最终版本。
BPMN是目前被各BPM厂商广泛接受的BPM标准。Activiti就是使用BPMN 2.0进行流程建模、流程执行管理,它包括很多的建模符号,比如:Event 用一个圆圈表示,它是流程中运行过程中发生的事情。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fCL7bRYT-1650199519758)(img/image-20210301001145476.png)]
4. Activiti入门
4.1 准备工作
4.1.1 案例介绍
请假的业务,是我们最为熟悉的一套业务了,主要包括请假当事人,以及他的直属领导,或更高层的领导来逐个进行审批,最后完成一个请假的流程。每个公司的请假流程细节上可能存在差异,但总体流程都差不多。下面我们一起来看一个简版的请假流程,如下:
- 当事人发起请假申请
- 直属领导根据实际情况进行审核,如果没有问题就通过,流程继续向后执行。如果有问题就可以不通过,流程就终止。
4.1.2 环境搭建
1). pom.xml
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.3.4.RELEASE</version>
</parent>
<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.projectlombok</groupId>
<artifactId>lombok</artifactId>
</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>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.69</version>
</dependency>
<!-- activiti-engine -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>7.1.0.M6</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-bpmn-layout</artifactId>
<version>7.1.0.M6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
2). application.yml
spring:
datasource:
url: jdbc:mysql://192.168.200.128:3306/activiti_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Hongkong
username : root
password : 123456
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
# 是否开启自动部署
deployment-mode: never-fail
logging:
level:
org.activiti.engine.impl.persistence.entity: trace
3). 引导类
@SpringBootApplication
public class ActivitiDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ActivitiDemoApplication.class, args);
}
}
4). 引入配置类
因为Activiti7与SpringBoot整合后,默认情况下,集成了SpringSecurity安全框架,这样我们就要去准备SpringSecurity整合进来的相关用户权限配置信息。
SpringBoot的依赖包已经将SpringSecurity的依赖包也添加进项目中。
本次项目中基本是在文件中定义出来的用户信息,当然也可以是数据库中查询的用户权限信息。后面处理流程时用到的任务负责人,需要添加在这里。
package com.itheima.activitidemo.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Configuration
@Slf4j
public class UserConfiguration {
@Bean
public UserDetailsService myUserDetailsService() {
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
String[][] usersGroupsAndRoles = {
{"itcast", "$2a$10$yRkoWX3dbPF8TX4KhB9kC.C4XiOJ4ThD/YQoersDtZjzJUYp4Rs7a", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
{"itheima", "$2a$10$yRkoWX3dbPF8TX4KhB9kC.C4XiOJ4ThD/YQoersDtZjzJUYp4Rs7a", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
{"nineclock","$2a$10$yRkoWX3dbPF8TX4KhB9kC.C4XiOJ4ThD/YQoersDtZjzJUYp4Rs7a", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
{"heima", "$2a$10$yRkoWX3dbPF8TX4KhB9kC.C4XiOJ4ThD/YQoersDtZjzJUYp4Rs7a", "ROLE_ACTIVITI_USER", "GROUP_otherTeam"},
{"admin", "$2a$10$yRkoWX3dbPF8TX4KhB9kC.C4XiOJ4ThD/YQoersDtZjzJUYp4Rs7a", "ROLE_ACTIVITI_ADMIN"},
};
for (String[] user : usersGroupsAndRoles) {
List<String> authoritiesStrings = Arrays.asList(Arrays.copyOfRange(user, 2, user.length));
log.info("> Registering new user: " + user[0] + " with the following Authorities[" + authoritiesStrings + "]");
inMemoryUserDetailsManager.createUser(new User(user[0], passwordEncoder().encode(user[1]),
authoritiesStrings.stream().map(s -> new SimpleGrantedAuthority(s)).collect(Collectors.toList())));
}
return inMemoryUserDetailsManager;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
5). 创建数据库
create database activiti_demo default charset utf8mb4;
环境准备好了之后,就可以直接启动引导类,这时数据库表就会自动创建(注意:连接的数据库需要自己创建,数据库不会自动创建),Activiti7中自带的数据库表共25张。下面我们就来介绍一下这些表。
4.2 数据库表
看到程序运行之后自动创建的表,我们发现Activiti 的表都以 ACT_ 开头。
第二部分是表示表的用途的两个字母标识。 用途也和服务的 API 对应。
表结构的类型:
类型 | 含义 |
ACT_GE | GE 表示 general, 通用数据, 用于不同场景下 |
ACT_HI | HI 表示 history, 这些表包含历史数据,比如历史流程实例, 变量,任务等等。 |
ACT_RE | RE 表示 repository, 这个前缀的表包含了流程定义和流程静态资源 (图片,规则,等等)。 |
ACT_RU | RU 表示 runtime, 这些运行时的表,包含流程实例,任务,变量,异步任务,等运行中的数据。 Activiti 只在流程实例执行过程中保存这些数据, 在流程结束时就会删除这些记录。 这样运行时表可以一直很小速度很快。 |
表结构的说明:
表分类 | 表名 | 解释 |
一般数据 | ||
ACT_GE_BYTEARRAY | 通用的流程定义和流程资源 | |
ACT_GE_PROPERTY | 系统相关属性 | |
流程历史记录 | ||
ACT_HI_ACTINST | 历史的流程实例 | |
ACT_HI_ATTACHMENT | 历史的流程附件 | |
ACT_HI_COMMENT | 历史的说明性信息 | |
ACT_HI_DETAIL | 历史的流程运行中的细节信息 | |
ACT_HI_IDENTITYLINK | 历史的流程运行过程中用户关系 | |
ACT_HI_PROCINST | 历史的流程实例 | |
ACT_HI_TASKINST | 历史的任务实例 | |
ACT_HI_VARINST | 历史的流程运行中的变量信息 | |
流程定义表 | ||
ACT_RE_DEPLOYMENT | 部署单元信息 | |
ACT_RE_MODEL | 模型信息 | |
ACT_RE_PROCDEF | 已部署的流程定义 | |
运行实例表 | ||
ACT_RU_EVENT_SUBSCR | 运行时事件 | |
ACT_RU_EXECUTION | 运行时流程执行实例 | |
ACT_RU_IDENTITYLINK | 运行时用户关系信息,存储任务节点与参与者的相关信息 | |
ACT_RU_JOB | 运行时作业 | |
ACT_RU_TASK | 运行时任务 | |
ACT_RU_VARIABLE | 运行时变量表 |
4.3 流程设计器
我们使用activiti工作流,比较重要的一步就是通过 BPMN2.0 流程设计器绘制流程图。而我们使用Idea开发,最方便的就是使用Idea中的插件进行绘制。安装方式如下:
1). 在线安装
Settings ------> Plugins -----> Marketable ---------> 搜索 actiBPM
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wgkWMYyW-1650199519759)(img/image-20210301094718937.png)]
2). 离线安装
部分版本的IDEA在Martketplace中搜索不到 actiBPM , 这个时候就选择离线安装 ;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zaCQnzdy-1650199519759)(img/image-20210301094317182.png)]
安装完成之后,重新启动IDEA 。
4.4 流程设计
鼠标右键,选择 “BpmnFile”,创建一个流程图,然后就可以开启绘制。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hDjfP0OL-1650199519760)(img/image-20210301094942908.png)]
打开流程设计器,拖动BPMN2.0中的组件,开始绘制。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pbYaTMFf-1650199519764)(img/image-20210301095856943.png)]
请假流程中,我们将流程的任务执行者,任务执行者可以是单个用户(审批人itheima…)(Assignee),候选用户组(Candidate Users),候选组(Candidate Groups)。本次操作我们打算单个用户设置流程的执行者。
流程图里的流程需要用自带的api部署,光画出来表里是没有的
4.5 API介绍
Service是工作流引擎提供用于进行工作流部署、执行、管理的服务接口,我们使用这些接口可以就是操作服务对应的数据表。
Service总览:
service名称 | service作用 |
RepositoryService | activiti资源管理类,提供了管理和控制流程发布包和流程定义的操作, 业务流程图需使用此service将流程定义文件的内容部署。(保存工作流到数据库里的api) |
RuntimeService | activiti的流程运行管理类,可以从这个服务类中获取很多关于流程执行相关的信息。(工作流运行api) |
TaskService | activiti的任务管理类, 可以从这个类中获取任务的信息。(查询任务api) |
HistoryService | activiti的历史管理类,可以查询历史信息,执行流程时,引擎会保存很多数据,这个服务主要通过查询功能来获得这些数据。(查询历史数据api) |
ManagementService | activiti的引擎管理类,提供了流程引擎的管理和维护功能, 主要用于 Activiti 系统的日常维护。 |
4.6 流程部署
通常情况下,一些流程是提前规定好的(比如我们的请假流程),此时我们可以提前在我们的流程设计器中将流程先设计出来,再将已设计好的流程定义文件部署到 activti流程引擎中。
@SpringBootTest
@RunWith(SpringRunner.class)
public class ActivitiDemoTest3 {
@Autowired
private RepositoryService repositoryService;
@Test
public void testDeployment(){
Deployment deployment = repositoryService.createDeployment()
.name("请求审批")//部署流程的名字
.addClasspathResource("demo1.bpmn") //(流程文件名)
.deploy(); //执行部署
}
}
查询流程部署数据:
1). ACT_RE_DEPLOYMENT
2). ACT_RE_PROCDEF
3). ACT_GE_BYTEARRAY
4.7 启动流程
发起申请,指用户发起某一审批流程。首先我们先获取服务对象,然后根据我们定义表当中的key/ID来得到流程实例。发起流程之后,流程就会往下走一步,进入到请假申请。
代码如下:
@Autowired
private RuntimeService runtimeService;
@Test
public void testStart(){
//根据id启动任务
runtimeService.startProcessInstanceById("myProcess_1:1:2504");
//根据key 启动任务
//runtimeService.startProcessInstanceByKey("myProcess_1");
}
查询任务数据:
ACT_RU_TASK :
4.8 完成任务
同样完成任务我们首先就要得到我们用来完成任务的服务类,我们是根据运行时任务表当中的id来完成的。
1). 完成第一步任务
当我们完成当前任务(请假申请),流程就会进入到领导审批。
具体代码如下:
@Autowired
private TaskService taskService;
@Test
public void testComplete(){
taskService.complete("5006"); //任务id
}
查询任务数据(ACT_RU_TASK):
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gfg6kzKr-1650199519765)(img/image-20210301103208466.png)]
2). 完成第二步任务
当完成当前任务(领导审批)之后,该流程就结束了,流程结束,数据库表 ACT_RU_TASK 中的数据就会被删除。
@Test
public void testComplete(){
taskService.complete("7503");//任务id
}
4.9 查询任务
我们可以通过API, 查询当前指定用户, 待处理的任务 ;
/**
* 查询指定用户任务
*/
@Test
public void rejectTask(){
List<Task> taskList = taskService.createTaskQuery()
.processDefinitionId("myProcess_1:2:10004")//流程定义的id
.taskAssignee("itheima").list();//任务审批人
for (Task task : taskList) {
System.out.println(task);
}
}
4.10 删除流程
@Test
public void testDeleteTask(){
//根据流程 key查询流程
ProcessDefinition definition = repositoryService
.createProcessDefinitionQuery()
.processDefinitionKey("myProcess_1").singleResult();
//根据 id删除 流程
repositoryService.deleteDeployment(definition.getDeploymentId());
}
4.11 小结
- 环境准备
- 绘制流程
- 流程部署
- 流程启动
- 任务完成
- 删除任务
5. 审批管理
5.1 需求概述
5.1.1 需求说明
1). 审批流程列表查询
点击 “审批管理”,需要查询当前企业下的审批流程列表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PlekvFa5-1650199519766)(img/image-20210301105501154.png)]
2). 新增审批流程
点击 “创建新审批”,可以在当前企业下创建一个新的 审批流程,审批流程的新增包含以下三块:审批设置、表单设计、流程设计。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tWKQR7m6-1650199519766)(img/image-20210301105743110.png)]
5.1.2 表结构
在实际项目开发中,审批中心模块往往是通过 Activiti 结合实际业务完成开发。所以在设计审批中心数据库表时,除了刚才介绍过得Activiti中的25张数据库表之外,还会包含对应的审批业务数据库表。
序号 | 数据库表 | 介绍 |
1 | approve_definition | 流程定义表 |
2 | approve_definition_template | 流程定义模板表 |
3 | approve_inst | 流程实例表 |
4 | approve_inst_no_ctrl | 简版流程实例表 |
5 | approve_inst_node | 流程实例节点表 |
6 | approve_inst_record | 历史记录表 |
5.2 准备工作
在 nineclock-model 模块中创建新的包结构 approve。并引入实体类 与 DTO。
该部分实体类及DTO封装对象,后面我们用到的时候,再来解析说明。
5.3 环境搭建
创建审批微服务 nineclock-approve,并创建对应的包结构。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NaoB9ncW-1650199519767)(img/image-20210301110724982.png)]
1). pom.xml
<dependencies>
<!-- nacos注册中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!-- API接口 -->
<dependency>
<groupId>com.nineclock</groupId>
<artifactId>nineclock-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- activiti 起步依赖 -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<artifactId>mybatis</artifactId>
<groupId>org.mybatis</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-bpmn-layout</artifactId>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-image-generator</artifactId>
</dependency>
<!-- feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--mongodb-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<!-- rocketMQ -->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
</dependency>
<dependency>
<artifactId>commons-io</artifactId>
<groupId>commons-io</groupId>
<version>2.6</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
2). application.yml
server:
port: 8086
spring:
application:
name: approve-service
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.200.128:3306/nineclock_approve?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: root
password: 123456
data:
mongodb:
host: 192.168.200.128
port: 27017
database: nineclock
cloud:
nacos:
discovery:
server-addr: 192.168.200.128:8848
main:
allow-bean-definition-overriding: true
#activiti 配置
activiti:
database-schema-update: true
db-history-used: true
history-level: full
check-process-definitions: false
use-strong-uuids: false
deployment-mode: never-fail
jackson:
serialization: {WRITE_DATES_AS_TIMESTAMPS: true}
mybatis-plus:
type-aliases-package: com.nineclock.approve.pojo
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: uuid
logging:
level:
com.nineclock: debug
org.activiti.engine.impl.persistence.entity: trace
nineclock:
auth:
signKey: C7B644C6F24526A820DE800DED2608ED
rocketmq:
name-server: 192.168.200.128:9876
producer:
group: nineclock-group
3). 引导类
@SpringBootApplication
@EnableDiscoveryClient
@MapperScan(basePackages = "com.nineclock.approve.mapper")
@ServletComponentScan(basePackages ="com.nineclock.common.filter")
public class NcApproveApplication {
public static void main(String[] args) {
SpringApplication.run(NcApproveApplication.class, args);
}
}
4). 配置类
参照 nineclock-attendance 微服务,引入对应的配置类
- ImportConfig : 基础服务配置类,包含 全局异常处理、Swagger、短信发送、OSS存储的配置类
- MybatisPlusConfig : MybatisPlus的配置类(分页插件)
- NcFeignInterceptor : Feign远程调用拦截器(Authorization头信息)
- ResourceServerConfig : 资源服务的配置类
- TokenConfig : JWT令牌配置类
也可以直接从提供的资料中拷贝。
5). Mapper接口引入
public interface ApproveDefinitionMapper extends BaseMapper<ApproveDefinition> {
}
也可以直接从提供的资料中拷贝。
6). 网关配置
#审批微服务
- id: approve-service
uri: lb://approve-service
predicates:
- Path=/approve/**
filters:
- StripPrefix=1
7). 获取随机数
在创建审批定义时需要获取一个随机的序列,提前提供一个获取序列的接口
@Api(value = "审批流程管理", tags = "审批中心")
public interface ApproveDefinitionControllerApi {
@ApiOperation(value = "流程定义: 获取序列")
public Result<String> seq();
}
@RestController
@RequestMapping("/approve")
public class ApproveDefinitionController implements ApproveDefinitionControllerApi {
@Override
@GetMapping("/seq")
public Result<String> seq() {
return Result.success(UUIDUtils.getUUID());
}
}
5.4 审批流程列表
5.4.1 思路分析
1). 涉及表结构
CREATE TABLE `approve_definition` (
`id` varchar(32) NOT NULL COMMENT 'id ',
`company_id` varchar(32) DEFAULT NULL COMMENT '企业ID',
`group_type` varchar(32) DEFAULT NULL COMMENT '审批类型(1:出勤休假, 2:财务, 3:人事, 4:行政, 5:其他)',
`name` varchar(128) DEFAULT NULL COMMENT '审批名称',
`seq` int(11) DEFAULT NULL COMMENT '序号 ',
`icon` varchar(128) DEFAULT NULL COMMENT '图标 ',
`description` varchar(1024) DEFAULT NULL COMMENT '审批说明',
`opinion_prompt` varchar(128) DEFAULT NULL COMMENT '审批意见填写提示',
`opinion_required` varchar(32) DEFAULT NULL COMMENT '审批意见是否必填 (1: 必填, 0: 选填)',
`allow_user_json` varchar(1024) DEFAULT NULL COMMENT '谁可以发起这个审批 (json格式数据)',
`form_json` text COMMENT '表单 (json格式数据)',
`flow_json` text COMMENT '流程 (json格式数据)',
`table_name` varchar(40) DEFAULT NULL COMMENT '提交表单表名',
`columns` text COMMENT '提交表单字段名',
`is_valid` varchar(32) DEFAULT NULL COMMENT '有效标记(1: 有效 , 0: 无效)',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '最后更新时间',
`data_from` varchar(3) DEFAULT NULL COMMENT '数据来源',
`template_id` varchar(32) DEFAULT NULL COMMENT '模板ID',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='审批定义表 ';
2). 数据交互实体
/**
* 审批流程返回数据对象
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class ApproveGroupDefinitionDto implements Serializable {
private String groupType; //分类 -- 考勤相关的所有流程
private List<ApproveDefinition> definitionList; 审批定义 -- 所有考勤相关的审批流程
}
5.4.2 接口文档
@Api(value = "审批流程管理")
public interface ApproveDefinitionControllerApi {
@ApiOperation(value = "查询流程定义列表")
public Result<List<ApproveGroupDefinitionDto>> queryApproveGroupDefinition();
}
5.4.3 代码实现
1). ApproveDefinitionController
@RestController
@RequestMapping("/approve")
public class ApproveDefinitionController implements ApproveDefinitionControllerApi {
@Autowired
private ApproveDefinitionService approveDefinitionService;
@Override
@GetMapping("/approveGroupDefinition")
public Result<List<ApproveGroupDefinitionDto>> queryApproveGroupDefinition(){
List<ApproveGroupDefinitionDto> definitionQueryOutList = approveDefinitionService.queryApproveGroupDefinition();
return Result.success(definitionQueryOutList);
}
}
2). ApproveDefinitionService
public interface ApproveDefinitionService {
/**
* 查询审批定义列表
* @return
*/
public List<ApproveGroupDefinitionDto> queryApproveGroupDefinition();
}
3). ApproveDefinitionServiceImpl
@Transactional
@Service
public class ApproveDefinitionServiceImpl implements ApproveDefinitionService {
@Autowired
private ApproveDefinitionMapper approveDefinitionMapper;
@Override
public List<ApproveGroupDefinitionDto> queryApproveGroupDefinition() {
List<ApproveGroupDefinitionDto> definitionDtoList = new ArrayList<ApproveGroupDefinitionDto>();
//1. 查询当前企业关联的流程定义
LambdaQueryWrapper<ApproveDefinition> definitionQueryWrapper = new LambdaQueryWrapper<ApproveDefinition>();
definitionQueryWrapper.eq(ApproveDefinition::getCompanyId, UserHolder.get().getCompanyId());
definitionQueryWrapper.eq(ApproveDefinition::getIsValid, "1");
//查询所有的审批流程对象
List<ApproveDefinition> approveDefinitions = approveDefinitionMapper.selectList(definitionQueryWrapper);
//2. 组装数据
if(CollectionUtil.isNotEmpty(approveDefinitions)){
//按照group_type进行分类
Map<String, List<ApproveDefinition>> map = approveDefinitions.stream().collect(Collectors.groupingBy(ApproveDefinition::getGroupType));
for (String groupType : map.keySet()) {
ApproveGroupDefinitionDto approveGroupDefinitionDto = new ApproveGroupDefinitionDto();
approveGroupDefinitionDto.setGroupType(groupType);
approveGroupDefinitionDto.setDefinitionList(map.get(groupType));
definitionDtoList.add(approveGroupDefinitionDto);
}
}
return definitionDtoList;
}
}
5.4.4 测试
启动微服务,登录系统后台,工作台 ------> 审批 ------> 审批管理 ; 查看审批流程列表是否可以正常展示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NAkrX42a-1650199519768)(img/image-20210301115014750.png)]
注意:
需要注意数据库中现有的数据,是否符合条件,是否是当前企业ID关联的审批流程。
5.5 新增审批流程
5.5.1 思路分析
点击 创建新审批 ,我们需要在页面添加三部分的信息: 审批设置基本信息、表单设计、流程设计,再点击 “发布” 按钮时,需要将三个部分的数据封装起来,提交到服务端。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2cmz6ABR-1650199519768)(img/image-20210301115548098.png)]
数据封装实体类:
/**
* 保存审批定义
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class ApproveDefinitionSaveDto implements Serializable {
private ApproveDefinitionBaseDataDto baseData; //基础信息
private List<ApproveDefinitionTableDataDto> tableData; //表单信息
private String flowData; //流程信息
}
5.5.2 接口定义
@ApiOperation(value = "流程定义: 新增/修改")
public Result saveApproveDefinition(ApproveDefinitionSaveDto approveDefinitionSaveDto);
5.5.3 代码实现
1). ApproveDefinitionController
@Override
@PostMapping("/approveDefinition")
public Result saveApproveDefinition(@RequestBody ApproveDefinitionSaveDto approveDefinitionSaveDto){
approveDefinitionService.saveApproveDefinition(approveDefinitionSaveDto);
return Result.success();
}
2). ApproveDefinitionService
/**
* 保存或修改审批流程定义
* 如果传递的参数包含ID : 更新
* 如果传递的参数不包含ID: 保存
* @param approveDefinitionDto
*/
public void saveApproveDefinition(ApproveDefinitionSaveDto approveDefinitionSaveDto);
3). ApproveDefinitionServiceImpl
@Override
public void saveApproveDefinition(ApproveDefinitionSaveDto approveDefinitionDto) {
//判定是否存在ID
ApproveDefinitionBaseDataDto baseData = approveDefinitionDto.getBaseData();
if(baseData == null){
throw new NcException(ResponseEnum.INVALID_PARAM_ERROR);
}
ApproveDefinition definition = null;
if(StrUtil.isEmpty(baseData.getId())){ //新增
//组装流程定义基础信息
definition = BeanHelper.copyProperties(baseData, ApproveDefinition.class, "allowUserJson");
//组装流程定义允许操作用户信息
List<AllowUserObjDto> allowUserJson = baseData.getAllowUserJson();
definition.setAllowUserJson(JsonUtils.toString(allowUserJson));
//组装流程定义的表单信息
List<ApproveDefinitionTableDataDto> tableData = approveDefinitionDto.getTableData();
definition.setFormJson(JsonUtils.toString(tableData));
//对当前流程配置一个统一的名字 tablename ---> 后续拓展使用
definition.setTableName("flow_"+ UUIDUtils.getUUID());
//需要将所有的表单信息,记录下来并且拓展参数,fieldKey
List<ColumnObjDto> columns = expandFieldKey(approveDefinitionDto);
definition.setColumns(JsonUtils.toString(columns));
//组装流程定义的流程信息 ---> 后续拓展使用
JSONArray jsonArray = JSONUtil.parseArray(approveDefinitionDto.getFlowData());
expandParamWithNodeKey(jsonArray);
definition.setFlowJson(JSONUtil.toJsonStr(jsonArray));
//组装流程定义的其他信息
definition.setDataFrom("1");
definition.setIsValid("1");// 1表示此流程有效
definition.setCompanyId(UserHolder.get().getCompanyId()+"");
//保存
approveDefinitionMapper.insert(definition);
}else{ //更新
}
//TODO 审批流程部署 ----> 需要将页面审批流程中涉及的流程图, 部署到Activiti引擎中 ;
}
private void expandParamWithNodeKey(JSONArray jsonArray) {
for(int i = 0; i < jsonArray.size(); i++){
JSONObject jsonNode = (JSONObject) jsonArray.get(i);
String nodeKey = jsonNode.getStr("nodeKey");
if(StrUtil.isEmpty(nodeKey)){
jsonNode.put("nodeKey", UUIDUtils.getUUID());
}
//处理网关节点的内部节点 nodeKey
String type = jsonNode.getStr("type");
if("condition".equals(type)){
JSONArray batchArray = (JSONArray) jsonNode.get("node");
if(batchArray!=null && batchArray.size()>0){
for (int j = 0; j < batchArray.size(); j++) {
JSONArray innerArray = (JSONArray) batchArray.get(j);
if(innerArray!=null && innerArray.size()>0){
for (int k = 0; k < innerArray.size(); k++) {
JSONObject node = (JSONObject) innerArray.get(k);
if(!node.containsKey("nodeKey")){
node.put("nodeKey", UUIDUtils.getUUID());
}
}
}
}
}
}
}
}
/**
* 扩充参数 : fieldKey
* @param approveDefinitionDto
* @return
*/
private List<ColumnObjDto> expandFieldKey(ApproveDefinitionSaveDto approveDefinitionDto) {
List<ColumnObjDto> columns = new ArrayList<>();
for (ApproveDefinitionTableDataDto tableData : approveDefinitionDto.getTableData()) {
if(StrUtil.isEmpty(tableData.getFieldKey())){
tableData.setFieldKey(UUIDUtils.getUUID());
}
columns.add(new ColumnObjDto(tableData.getFieldKey(), tableData.getLab(), tableData.getTitle(), "1"));
}
return columns;
}