您的当前位置:首页正文

跟铁拐李李老师学习工作流的第一天

2024-11-09 来源:个人技术集锦

Day01 流程中心概述

学习目标:

  • 能够熟练掌握如何使用流程中心系统
  • 能够说出项目的应用场景、核心架构
  • 能够说出工作流的应用场景、常见的工作流引擎
  • 能够熟练掌握Activiti的核心API和入门案例
  • 能够熟练设计流程图
  • 能够熟练完成案例代码的开发
  • 能够熟悉核心服务、表、对象等及相互关系


1. 项目概述

1.1. 项目背景

请假是日常学习、工作中非常常见的一个场景,比如在学校中,小智因为生病,需要请假一天,他给班主任打个电话,说要请一天假。班主任同意后,小智的行为不会构成无故旷课。工作中的请假也是一样。请假也会包含比较复杂的情况,比如请一天假和请十天假,需要的审批流程和审批人员可能不同。

企业中请假一般会和个人假期、工资等挂钩,因此需要对请假过程进行”留痕“处理,具体做法是:员工需要填写一个请假条,写明请假的请假人、时间、缘由等,甚至需要附加证明材料,然后找各级领导进行审批,他的直接领导、领导的领导在上边签字同意,证明不是旷工。

如下图是一个企业中的请假流程:

如果公司规模比较小,可以使用口头请假、纸质请假单,但是当公司达到一定规模时,为了考勤统计、绩效考核、请假证明等原因,需要一个软件系统,用来记录所有员工的请假,员工在使用这个系统时,大致是这样的过程或步骤:

  • 员工提交请假单
  • 领导审批
  • 必要时需要二级、三级领导审批
  • 需要通知人事部门,用于考勤和工资计算

这个过程叫做工作流,工作流简单的讲就是工作的流动,是多个人协作完成一项工作。需要注意的是,在工作流中会涉及到多个角色,每个角色完成不同的任务。

工作流的场景非常多,几乎可以说无处不在,常见工作流场景:

  • 政府企业的公文流转、项目申报、公司注册、税务减免
  • 银行贷款的申请、信用卡申请
  • 企业管理中的请假、财务报销、人员招聘、调薪、奖金发放、合同续签
  • 电商系统中的退货、售后、退款、下单
  • 物流仓储中的出库、入库、运输等

工作流的场景虽然非常多,但是开发的过程确是大同小异。我们来看一个工作流系统的整体结构:

从上图我们可以看到,一个工作流大致包含三部分:

  • 业务:不同的业务流程有不同的业务单据,比如请假单和报销单,里边的属性字段是不一样的
  • 人员:企业或组织中的人员,涉及到人员不同的部门、岗位和角色等
  • 流程:这是一个公共的部分,涉及到如何设计流程、流程如何运转、如何查看运转的历史等

今天要学习的流程审批中心,就是以请假流程的开发为例,系统讲解如何开发一个工作流审批系统,只要掌握了这个系统的开发,以后开发其他业务的审批系统都是相同的过程。

1.2. 系统架构

1.2.1 功能清单

1.2.2 核心概念

1.2.3 系统结构

1.2.4 开发重点

一个工作流系统大致包含以下模块,重点和点是审批模块的开发。幸运的是,工作流引擎正是为了解决这部分难点的。

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.61---表示不区分大小写 2---表示区分大小写
  - 5.71---表示不区分大小写 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);
    }
}
  1. 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 设计流程

  1. 流程设计器

使用activiti工作流比较重要的一步就是绘制流程图。最方便的方法就是使用Idea中的插件(actiBPM)进行绘制,叫做离线流程设计器。

安装方式如下:

1). 在线安装
Settings ------> Plugins -----> Marketable ---------> 搜索 actiBPM

安装完成之后,重新启动IDEA 。

**注意: IDEA 2019.1.4之后的版本,无法在线安装此插件 **


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入门案例

  1. 搭建项目:

    maven工程、pom文件、yml文件、创建数据库、Springboot启动类

  2. 开发过程:

    设计流程、保存模型和流程图、部署流程、启动实例、执行任务、查看历史

番外 idea绘制流程图出现中文乱码怎么办?

  • 第一步 修改idea配置
  • 第二步 修改idea bin目录下的文件
  • 第三步 xi

修改idea配置

点开后的文件添加以下内容:-Dfile.encoding=UTF-8

修改idea bin下的vmprotions文件


修改idea中的enconding

File -> settings -> Editor -> Enconding

重启

能看见如下图显示就说明成功了

Top