第11天-审批中心

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_GEGE 表示 general, 通用数据, 用于不同场景下
ACT_HIHI 表示 history, 这些表包含历史数据,比如历史流程实例, 变量,任务等等。
ACT_RERE 表示 repository, 这个前缀的表包含了流程定义和流程静态资源 (图片,规则,等等)。
ACT_RURU 表示 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作用
RepositoryServiceactiviti资源管理类,提供了管理和控制流程发布包和流程定义的操作, 业务流程图需使用此service将流程定义文件的内容部署。(保存工作流到数据库里的api)
RuntimeServiceactiviti的流程运行管理类,可以从这个服务类中获取很多关于流程执行相关的信息。(工作流运行api)
TaskServiceactiviti的任务管理类, 可以从这个类中获取任务的信息。(查询任务api)
HistoryServiceactiviti的历史管理类,可以查询历史信息,执行流程时,引擎会保存很多数据,这个服务主要通过查询功能来获得这些数据。(查询历史数据api)
ManagementServiceactiviti的引擎管理类,提供了流程引擎的管理和维护功能, 主要用于 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

image-20210301102100324

2). ACT_RE_PROCDEF

image-20210301102139462

3). ACT_GE_BYTEARRAY

image-20210301102227764

4.7 启动流程

发起申请,指用户发起某一审批流程。首先我们先获取服务对象,然后根据我们定义表当中的key/ID来得到流程实例。发起流程之后,流程就会往下走一步,进入到请假申请。

image-20210301103615056

代码如下:

@Autowired
private RuntimeService runtimeService;

@Test
public void testStart(){
  //根据id启动任务
	runtimeService.startProcessInstanceById("myProcess_1:1:2504");
  //根据key 启动任务
    //runtimeService.startProcessInstanceByKey("myProcess_1");
}

查询任务数据:

ACT_RU_TASK :

image-20210301102853365

4.8 完成任务

同样完成任务我们首先就要得到我们用来完成任务的服务类,我们是根据运行时任务表当中的id来完成的。

1). 完成第一步任务

当我们完成当前任务(请假申请),流程就会进入到领导审批。

image-20210301103729726

具体代码如下:

@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 中的数据就会被删除。

image-20210301104027065
@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张数据库表之外,还会包含对应的审批业务数据库表

序号数据库表介绍
1approve_definition流程定义表
2approve_definition_template流程定义模板表
3approve_inst流程实例表
4approve_inst_no_ctrl简版流程实例表
5approve_inst_node流程实例节点表
6approve_inst_record历史记录表

5.2 准备工作

在 nineclock-model 模块中创建新的包结构 approve。并引入实体类 与 DTO。

image-20210301111105397

该部分实体类及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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值