WikiWiki
首页
Java开发
Java面试
Linux手册
  • AI相关
  • Python Flask
  • Pytorch
  • youlo8
SEO
uniapp小程序
Vue前端
work
数据库
软件设计师
入门指南
首页
Java开发
Java面试
Linux手册
  • AI相关
  • Python Flask
  • Pytorch
  • youlo8
SEO
uniapp小程序
Vue前端
work
数据库
软件设计师
入门指南
  • API工具笔记
  • Java基础
  • SpringAI开发问答系统
  • 前后端分离架构
  • 工作流Activity7
  • 微服务架构

第1章 清晰的学习目标,让学习更轻松

image-20230524214553892

1.什么是工作流?

无纸化的办公方案!

2.实现工作流的方案:

需要根据实际业务需要进行调整!
可视化的流程设计

安装插件:2-6 BPMN插件 IDEA官网搜索:actibpm

https://git.imooc.com/coding-454/activiti7_workflow.git

第2章 开发前准备:环境搭建篇【选修】

image-20230524230605498

第3章 项目从git下载与打包部署

3.1主要特性

  • 完整的流程部署、创建实例、任务流转
  • 使用Acticiti7新版特性
  • 融合BPMN-JS作为流程绘制工具
  • 整合SpringSecurity安全框架

3.2启动项目流程

 测试账号:bajie
 密码:1
 测试账号:wukong
 密码:1
  • 点击IDEA右上方绿色箭头Run项目(或按Shift+F10运行项目)
  • 打开浏览器输入地址localhost:8080/layuimini/page/login-1.html

第4章 精讲最新版Activiti7核心组件

4-1 Activiti7介绍

4-1.1 Activiti7介绍

image-20230524231146780

4-1.2 Activiti7新特性介绍

image-20230524231536039

api的封装

image-20230524231634597

4-1.工作流常见的业务场景介绍

image-20230524231727436

image-20230524231744221

image-20230524231758881

4-2 BPMN2.0标准

4-2.1 BPMN2.0简介

image-20230524231931170

4-2.2BPMN2.0标准概述

image-20230524232112594

image-20230524232219641

image-20230524232242625

4-2.3 BPMN2.0常用工具解释

image-20230524232749772

image-20230524232805993

image-20230524232821565

4-3 Springboot与Activit7整合

image-20230524232910845

  1. 创建SpringBoot的版本:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    
  2. 初始添加的依赖

    <dependencies>
            <!--Activiti7-->
            <dependency>
                <groupId>org.activiti</groupId>
                <artifactId>activiti-spring-boot-starter</artifactId>
                <version>7.1.0.M4</version>
            </dependency>
            <dependency>
                <groupId>org.activiti.dependencies</groupId>
                <artifactId>activiti-dependencies</artifactId>
                <version>7.1.0.M4</version>
                <type>pom</type>
            </dependency>
            <!------------->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.1.3</version>
            </dependency>
            <dependency>
                <groupId>com.mysql</groupId>
                <artifactId>mysql-connector-j</artifactId>
                <version>8.0.31</version>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
  3. 添加yml初始配置:注意,数据库链接必须和此配置文件格式一致

    server:
      port: 8080
      servlet:
        context-path: /
    spring:
      datasource:
        username: root
        password: yanpeng2580
        url: jdbc:mysql://localhost:3306/mact?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC&nullCatalogMeansCurrent=true
        driver-class-name: com.mysql.cj.jdbc.Driver
      activiti:
        history-level: full
        db-history-used: true
        check-process-definitions: true
      # Activiti7历史表创建
      # history-level: full: 这个配置项指定了历史记录级别。在Activiti7中,历史可以被记录在多个级别上,
      # 包括none、activity、audit和full。full表示记录所有历史信息,包括任务、流程实例、变量等。
      # db-history-used: true: 这个配置项指定是否将历史记录存储在数据库中。如果设置为true,则历史记录将被保存在数据库中,
      # 否则将只保存在内存中。
      # check-process-definitions: true: 这个配置项指定是否在启动时自动检查并部署流程定义。如果设置为
      # true,则Activiti会在启动时扫描classpath下的所有.bpmn20.xml和.bpmn文件,并将它们部署到流程引擎中。
      # 这样就不需要手动部署流程定义了。
    
  4. 数据库创建 -创建一个Activiti7使用的数据库 创建好项目后启动项目,没有报错并检查数据库中是否多表

  5. 执行缺失的SQl:解决Activiti7的遗留Bug

    SET FOREIGN_KEY_CHECKS=0;
    
    -- ----------------------------
    -- 创建用户表
    -- ----------------------------
    DROP TABLE IF EXISTS `user`;
    CREATE TABLE `user` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `name` varchar(32) DEFAULT NULL COMMENT '姓名',
      `address` varchar(64) DEFAULT NULL COMMENT '联系地址',
      `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '账号',
      `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '密码',
      `roles` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '角色',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
    
    -- ----------------------------
    -- 填充用户表
    -- ----------------------------
    INSERT INTO `user` VALUES ('1', 'admincn', 'beijing', 'admin', '$2a$10$gw46pmsOVYO.smHYQ2jH.OoXoe.lGP8OStDkHNs/E74GqZDL5K7ki', 'ROLE_ACTIVITI_ADMIN');
    INSERT INTO `user` VALUES ('2', 'bajiecn', 'shanghang', 'bajie', '$2a$10$gw46pmsOVYO.smHYQ2jH.OoXoe.lGP8OStDkHNs/E74GqZDL5K7ki', 'ROLE_ACTIVITI_USER,GROUP_activitiTeam,g_bajiewukong');
    INSERT INTO `user` VALUES ('3', 'wukongcn', 'beijing', 'wukong', '$2a$10$gw46pmsOVYO.smHYQ2jH.OoXoe.lGP8OStDkHNs/E74GqZDL5K7ki', 'ROLE_ACTIVITI_USER,GROUP_activitiTeam');
    INSERT INTO `user` VALUES ('4', 'salaboycn', 'beijing', 'salaboy', '$2a$10$gw46pmsOVYO.smHYQ2jH.OoXoe.lGP8OStDkHNs/E74GqZDL5K7ki', 'ROLE_ACTIVITI_USER,GROUP_activitiTeam');
    
    -- ----------------------------
    -- 修复Activiti7的M4版本缺失字段Bug
    -- ----------------------------
    alter table ACT_RE_DEPLOYMENT add column PROJECT_RELEASE_VERSION_ varchar(255) DEFAULT NULL;
    alter table ACT_RE_DEPLOYMENT add column VERSION_ varchar(255) DEFAULT NULL;
    
    -- ----------------------------
    -- 动态表单数据存储
    -- ----------------------------
    DROP TABLE IF EXISTS `formdata`;
    CREATE TABLE `formdata` (
      `PROC_DEF_ID_` varchar(64) DEFAULT NULL,
      `PROC_INST_ID_` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
      `FORM_KEY_` varchar(255) DEFAULT NULL,
      `Control_ID_` varchar(100) DEFAULT NULL,
      `Control_VALUE_` varchar(2000) DEFAULT NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    

4-4 流程部署Deployment

image-20230525191534080

image-20230525191621626

  1. 新建BPMN

    Activiti插件actiBPM在新版的idea 2020中已经不支持,这里找到一款替代的Activiti BPMN visualizer。
    
  2. 部署创建好的工作流实例**【通过BPMN文件部署流程 / 通过ZIP文件部署流程 / 查询所有的部署】**

    package cbsfs.cn.activity;
    import org.activiti.engine.RepositoryService;
    import org.activiti.engine.repository.Deployment;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import java.io.InputStream;
    import java.util.List;
    import java.util.zip.ZipInputStream;
    @SpringBootTest
    public class Part1_Deployment {
        @Autowired
        private RepositoryService repositoryService;
        // 通过BPMN文件部署流程
        @Test
        public void initDeploymentBPMN(){
            String filename="BPMN/Part1_Deployment.bpmn"; // 定义BPMN文件名
            String pngname="BPMN/Part1_Deployment.png"; // 定义BPMN对应的图片文件名
    
            // 创建一个部署对象,将BPMN文件和图片文件都添加到部署对象中
            Deployment deployment=repositoryService.createDeployment()
                    .addClasspathResource(filename)
                    .addClasspathResource(pngname)//图片
                    .name("流程部署测试候选人task") // 设置部署名称
                    .deploy(); // 执行部署操作
    
            System.out.println(deployment.getName()); // 输出部署名称
        }
        // 通过ZIP文件部署流程
        @Test
        public void initDeploymentZIP() {
            InputStream fileInputStream = this.getClass()
                    .getClassLoader()
                    .getResourceAsStream("BPMN/Part1_DeploymentV2.zip"); // 获取ZIP文件输入流
            ZipInputStream zip=new ZipInputStream(fileInputStream); // 将输入流转换为ZIP输入流
    
            // 创建一个部署对象,将ZIP文件添加到部署对象中
            Deployment deployment=repositoryService.createDeployment()
                    .addZipInputStream(zip)
                    .name("流程部署测试zip") // 设置部署名称
                    .deploy(); // 执行部署操作
    
            System.out.println(deployment.getName()); // 输出部署名称
        }
        //查询流程部署
        @Test
        public void getDeployments() {
            // 查询所有的部署
            List<Deployment> list = repositoryService.createDeploymentQuery().list();
            for(Deployment dep : list){
                // 输出部署的ID
                System.out.println("Id:" + dep.getId());
                // 输出部署的名称
                System.out.println("Name:" + dep.getName());
                // 输出部署的时间
                System.out.println("DeploymentTime:" + dep.getDeploymentTime());
                // 输出部署的键
                System.out.println("Key:" + dep.getKey());
            }
        }
    
    }
    
    

    ①通过BPMN文件部署流程,可以同时部署流程与流程图片(版本7不需要使用) 部署后首先数据保存在这个表中ACT_RE_DEPLOYMENT 部署信息表

    image-20230525213952363

    然后再将流程文件上传到二进制表中ACT_GE_BYTEARRAY流程定义二进制表

    image-20230525214310605

    image-20230525214401646

4-5 流程定义ProcessDefinition

image-20230525231523198

image-20230527145956553

①,流程部署后,也会在流程定义表里添加信息,act_re_procdef表为流程定义表

【查询流程定义 / 删除流程定义】

package cbsfs.cn.activity;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.ProcessDefinition;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class Part2_ProcessDefinition {
    @Autowired
    private RepositoryService repositoryService;
    // 声明并自动装配RepositoryService
    // 查询流程定义
    @Test
    public void getDefinitions() {
        // 调用RepositoryService的方法来查询所有的流程定义
        List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().list();
        for (ProcessDefinition pd : list) {
            System.out.println("------流程定义--------");
            // 输出流程定义的名称、关键字、资源名称、部署ID和版本号
            System.out.println("Name:" + pd.getName());
            System.out.println("Key:" + pd.getKey());
            System.out.println("ResourceName:" + pd.getResourceName());
            System.out.println("DeploymentId:" + pd.getDeploymentId());
            System.out.println("Version:" + pd.getVersion());
        }
    }
    // 删除流程定义
    @Test
    public void delDefinition() {
        // 要删除的流程定义的部署ID
        String DeploymentId = "2577080d-faf6-11ed-8c18-22038c0a7b1d";
        // 通过RepositoryService删除指定流程定义。第二个参数表示是否级联删除相关的流程实例、历史等数据。
        repositoryService.deleteDeployment(DeploymentId, true);//true删除所有历史记录,false保留历史(实际项目中使用)
        // 打印删除成功信息
        System.out.println("删除流程定义成功");
    }
    //删除所有的流程定义
    @Test
    public void delDefinitionAll() {
        // 获取所有流程定义的列表
        List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().list();
        // 遍历每一个流程定义
        for (ProcessDefinition pd : list) {
            // 删除包含该流程定义以及相关运行数据(例如流程实例和任务实例)的部署
            repositoryService.deleteDeployment(pd.getDeploymentId(), true);//true删除所有历史记录,false保留历史(实际项目中使用)
            // 打印消息,指示已删除该流程定义
            System.out.println("删除" + pd.getName());
        }
    }
}

4-6 流程实例ProcessInstance

image-20230527152601433

image-20230527161130995

image-20230527161822160

每一次启动执行两次节点

第二个环境执行的

image-20230527162054613

流程实例相关操作:【初始化流程实例 / 获取流程实例 / 挂起-激活 / 删除】

package cbsfs.cn.activity;

import org.activiti.engine.RuntimeService;
import org.activiti.engine.runtime.ProcessInstance;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class Part3_ProcessInstance {

    //流程实例相关的接口
    @Autowired
    private RuntimeService runtimeService;

    //初始化流程实例
    @Test
    public void initProcessInstance() {
        //1、获取页面表单填报的内容,请假时间,请假事由,String fromData
        //2、fromData 写入业务表,返回业务表主键ID==businessKey
        //3、把业务数据与Activiti7流程数据关联
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myProcess_Part1", "bKey001");
        System.out.println("流程实例ID:" + processInstance.getProcessDefinitionId());
    }
    //获取流程实例列表
    @Test
    public void getProcessInstances() {
        List<ProcessInstance> processInstances = runtimeService.createProcessInstanceQuery().list();
        for (ProcessInstance processInstance : processInstances) {
            System.out.println("--------流程实例------");
            System.out.println("ProcessInstanceId:" + processInstance.getProcessInstanceId());//流程实例id
            System.out.println("ProcessDefinitionId:" + processInstance.getProcessDefinitionId());//流程定义id{key+版本号+UUID}
            System.out.println("isEnded:" + processInstance.isEnded());//流程是否执行完成
            System.out.println("isSuspended:" + processInstance.isSuspended());//流程实例是否被挂起
        }
    }
    //暂停与激活流程实例
    @Test
    public void activitieProcessInstance() {
        //流程实例挂起suspendProcessInstanceById
//        runtimeService.suspendProcessInstanceById("04b381e5-fc66-11ed-b5ab-22038c0a7b1d");
//        System.out.println("挂起流程实例");
        runtimeService.activateProcessInstanceById("04b381e5-fc66-11ed-b5ab-22038c0a7b1d");
        System.out.println("激活流程实例");
    }
    //删除流程实例
    @Test
    public void delProcessInstance() {
        runtimeService.deleteProcessInstance("04b381e5-fc66-11ed-b5ab-22038c0a7b1d", "删除实例,暂时不用");
        System.out.println("删除流程实例");
    }

}

4-7 任务处理Task(上)

image-20230527165105007

image-20230527165133925

总结:

①首先流程部署,把BPMN部署上

②查询流程定义的Key

③根据流程定义的key初始化流程实例

④任务查询:查询所有任务【管理员用的】

⑤查询指定用户的的代办任务

4-8 任务处理Task(下)

执行任务时会在这个表里执行数据

image-20230527181709201

image-20230527181807625

运行时流程在这个数据表中 当所有任务执行完成后,流程实例表,任务表将会清空 ,任务记录进入历史表中

image-20230527182651746

候选人流程任务

代码:【查询所有任务 / 查询我的代办任务 / 执行任务 / 拾取任务 / 归还与交办任务】

package cbsfs.cn.activity;

import org.activiti.engine.TaskService;
import org.activiti.engine.task.Task;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class Part4_Task {

    @Autowired
    private TaskService taskService;
    //任务查询:查询所有任务【管理员用的】
    @Test
    public void getTasks(){
        List<Task> list = taskService.createTaskQuery().list();
        for (Task tk:list){
            System.out.println("ID:"+tk.getId());//任务id
            System.out.println("Name:"+tk.getName());//名称
            System.out.println("执行人:"+tk.getAssignee());//执行人
        }
    }
    //查询我的代办任务
    @Test
    public void getTasksByAssignee(){
        List<Task> list = taskService.createTaskQuery().taskAssignee("wukong").list();
        for (Task tk:list){
            System.out.println("ID:"+tk.getId());//任务id
            System.out.println("Name:"+tk.getName());//名称
            System.out.println("执行人:"+tk.getAssignee());//执行人
        }
    }
    //执行任务

    /**
     * 当所有任务执行完成后,流程实例表,任务表将会清空
     */
    @Test
    public void completeTask(){
        taskService.complete("72830474-fc77-11ed-aa74-22038c0a7b1d");
        System.out.println("完成任务");
    }
    //拾取任务
    @Test
    public void claimTask(){
        Task task=taskService.createTaskQuery().taskId("c13e95b0-fc79-11ed-a256-22038c0a7b1d").singleResult();
        taskService.claim("c13e95b0-fc79-11ed-a256-22038c0a7b1d","bajie");
    }
    //归还与交办任务
    @Test
    public void setA_Task(){
        Task task=taskService.createTaskQuery().taskId("c13e95b0-fc79-11ed-a256-22038c0a7b1d").singleResult();
        taskService.setAssignee("c13e95b0-fc79-11ed-a256-22038c0a7b1d","bajie");//归还候选任务
        taskService.setAssignee("c13e95b0-fc79-11ed-a256-22038c0a7b1d","wukong");//交办候选任务
    }
}

4-9 历史任务HistoryService

image-20230527185012810

image-20230527185839601

代码:【根据用户名查询历史记录 / 根据流程实例ID查询历史 】

package cbsfs.cn.activity;

import org.activiti.engine.HistoryService;
import org.activiti.engine.history.HistoricTaskInstance;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class Part5_HistoricTaskInstance {
    @Autowired
    private HistoryService historyService;

    //根据用户名查询历史记录
    @Test
    public void HistoricTaskInstanceByUser(){
        List<HistoricTaskInstance> list = historyService
                .createHistoricTaskInstanceQuery()
                .orderByHistoricTaskInstanceEndTime().asc()// 排序 asc 正序
                .taskAssignee("wukong")
                .list();
        for(HistoricTaskInstance hi : list){
            System.out.println("Id:"+ hi.getId());//任务id
            System.out.println("ProcessInstanceId:"+ hi.getProcessInstanceId());//流程实例id
            System.out.println("Name:"+ hi.getName());//流程名称
            System.out.println("Time:"+ hi.getTime());
        }
    }


    //根据流程实例ID查询历史
    @Test
    public void HistoricTaskInstanceByPiID(){
        List<HistoricTaskInstance> list = historyService
                .createHistoricTaskInstanceQuery()
                .orderByHistoricTaskInstanceEndTime().asc()
                .processInstanceId("fb2345a6-fc71-11ed-a1cb-22038c0a7b1d")
                .list();
        for(HistoricTaskInstance hi : list){
            System.out.println("Id:"+ hi.getId());
            System.out.println("ProcessInstanceId:"+ hi.getProcessInstanceId());
            System.out.println("Name:"+ hi.getName());
        }
    }
}

4-10 UEL表达式(上)

UEL表达式的作用主要是赋值

image-20230527202130353

image-20230527202246068

image-20230527202532466

image-20230527202637825

image-20230527202702023

使用UEL表达式,变量名必须要小写

代码:

  1. 启动流程实例带参数,指定执行人
  2. 完成任务带参数,指定流程变量测试
  3. 启动流程实例带参数,使用实体类
  4. 任务完成环节带参数,指定多个候选人
  5. 直接指定流程变量
  6. 局部变量
package cbsfs.cn.activity;


import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.runtime.ProcessInstance;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.HashMap;
import java.util.Map;

@SpringBootTest
public class Part6_UEL {


    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private TaskService taskService;

    //启动流程实例带参数,指定执行人
    @Test
    public void initProcessInstanceWithArgs() {
        //流程变量 ${ZhiXingRen}
        //Key:ZhiXingRen Value: wukong
        Map<String, Object> variables = new HashMap<String, Object>();
        variables.put("ZhiXingRen", "wukong");
        //variables.put("ZhiXingRen2", "aaa");
        //variables.put("ZhiXingRen3", "wukbbbong");
        ProcessInstance processInstance = runtimeService
                .startProcessInstanceByKey(
                        "myProcess_UEL_V1"
                        , "bKey002"
                        , variables);
        System.out.println("流程实例ID:" + processInstance.getProcessDefinitionId());

    }

    //完成任务带参数,指定流程变量测试
    @Test
    public void completeTaskWithArgs() {
        Map<String, Object> variables = new HashMap<String, Object>();
        variables.put("pay", "200");
        taskService.complete("8fc2298b-fc90-11ed-9e5a-22038c0a7b1d",variables);
        System.out.println("完成任务");
    }

    //启动流程实例带参数,使用实体类
    @Test
    public void initProcessInstanceWithClassArgs() {
        UEL_POJO uel_pojo = new UEL_POJO();
        uel_pojo.setZhixingren("bajie");

        //流程变量
        Map<String, Object> variables = new HashMap<String, Object>();
        variables.put("uelpojo", uel_pojo);

        ProcessInstance processInstance = runtimeService
                .startProcessInstanceByKey(
                        "myProcess_uelv3"
                        , "bKey002"
                        , variables);
        System.out.println("流程实例ID:" + processInstance.getProcessDefinitionId());

    }

    //任务完成环节带参数,指定多个候选人
    @Test
    public void initProcessInstanceWithCandiDateArgs() {
        Map<String, Object> variables = new HashMap<String, Object>();
        variables.put("houxuanren", "wukong,tangseng");
        taskService.complete("7c791c8d-fc92-11ed-b14f-22038c0a7b1d",variables);
        System.out.println("完成任务");
    }

    //直接指定流程变量
    @Test
    public void otherArgs() {
        runtimeService.setVariable("4f6c9e23-d3ae-11ea-82ba-dcfb4875e032","pay","101");
//        runtimeService.setVariables(); 设置多个参数
//        taskService.setVariable(); 通过任务节点id设置变量
//        taskService.setVariables();通过任务节点id设置多个变量

    }

    //局部变量
    @Test
    public void otherLocalArgs() {
        runtimeService.setVariableLocal("4f6c9e23-d3ae-11ea-82ba-dcfb4875e032","pay","101");
//        runtimeService.setVariablesLocal();设置多个参数
//        taskService.setVariableLocal();通过任务节点id设置变量
//        taskService.setVariablesLocal();通过任务节点id设置多个变量
    }

}

4-13 流程网关-并行

image-20230527214700442

在BPMN中,网关(Gateway)是用于控制流程转移的元素之一。

**并行网关(Parallel Gateway)**是一种可以使流程同时执行多个分支的网关。

**排他网关(Exclusive Gateway)**是一种只允许流程走向一个分支的网关。该网关会根据指定的条件来决定流程走向哪条分支。

**包容网关(Inclusive Gateway)**是一种可以让流程同时走向多个分支的网关。该网关会根据指定的条件来决定流程走向哪些分支。

**事件网关(Event-based Gateway)**是一种基于事件触发的网关。当满足预设条件时,该网关会启动一个或多个分支来处理相应的任务,并将这些分支与其他未启动的分支区分开来,从而保证流程正常运行。

image-20230527220156577

Activiti是一个流程引擎框架,用于管理和执行业务流程。下面是几个高级应用的概念:

  1. 边界事件(Boundary Event):在流程中与用户任务或子流程相关联的事件。当满足一定条件时,边界事件可以触发一个操作,例如发送通知或自动完成任务。
  2. 中间事件(Intermediate Event):在流程执行期间触发的事件,与其他流程元素(如任务或网关)相连。可以使用中间事件来响应外部系统发出的信号或者标记流程的进度。
  3. 子流程(Subprocess):在主流程中包含的独立的、可重复使用的流程组件。子流程可以嵌套在其他子流程或主流程中,并且可以使用不同的流程定义进行实现。子流程通常用于处理具有复杂或可重复性的任务。

并行网关

image-20230527220635322

image-20230527222748350

代码:

package cbsfs.cn.activity;

import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.runtime.ProcessInstance;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.HashMap;
import java.util.Map;
@SpringBootTest
public class Part7_Gateway {
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private TaskService taskService;
//启动流程实例
    @Test
    public void initProcessInstance() {
        // 通过流程定义的key启动一个名为"myProcess_Inclusive"的流程实例
        ProcessInstance processInstance = runtimeService
                .startProcessInstanceByKey(
                        "myProcess_Parallel");
        // 打印流程实例ID
        System.out.println("流程实例ID:" + processInstance.getProcessDefinitionId());
    }
    @Test
    public void completeTask() {
        // 创建一个Map对象,用于设置任务变量
        Map<String, Object> variables = new HashMap<String, Object>();

        /*
         * 完成两个任务,任务ID分别为"398a746e-d3ce-11ea-8bb4-dcfb4875e032"和
         * "398a7470-d3ce-11ea-8bb4-dcfb4875e032"。这里假设这两个任务是在同一个
         * 流程实例中。
         */
        taskService.complete("1d861ffc-fc91-11ed-89a2-22038c0a7b1d");
        taskService.complete("2691c1d4-fc90-11ed-8338-22038c0a7b1d");
        // 打印完成任务的提示信息
        System.out.println("完成任务");
    }
}

4-14 流程网关-排他

**排他网关(Exclusive Gateway)**是一种只允许流程走向一个分支的网关。该网关会根据指定的条件来决定流程走向哪条分支。

image-20230527223816118

image-20230527232115603

4-15 流程网关-包容

**包容网关(Inclusive Gateway)**是一种可以让流程同时走向多个分支的网关。该网关会根据指定的条件来决定流程走向哪些分支。

image-20230527232738655

第5章 Activiti 7 新特性尝鲜

使用Activiti 7 新特性,必须要添加Spring security安全框架

5-1 API新特性ProcessRuntime

image-20230530212235346

ProcessRuntime:流程定义流程实例相关接口

Page:可以返回流程定义和流程实例

官网:为了与ProcessRuntime API交互,当前登录的用户必须具有“ACTIVITI USER”角色。

为了方便测试,添加两个类:

/*
 * Copyright 2010-2020 Alfresco Software, Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cbsfs.cn.activity.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;

import static java.util.Arrays.asList;

@Configuration
public class DemoApplicationConfiguration {

    private Logger logger = LoggerFactory.getLogger(DemoApplicationConfiguration.class);

    @Bean
    public UserDetailsService myUserDetailsService() {

        InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();

        String[][] usersGroupsAndRoles = {//GROUP_activitiTeam 用户组  //ROLE_ACTIVITI_USER角色
                {"bob", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
                {"bajie", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
                {"wukong", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
                {"other", "password", "ROLE_ACTIVITI_USER", "GROUP_otherTeam"},
                {"system", "password", "ROLE_ACTIVITI_USER"},
                {"admin", "password", "ROLE_ACTIVITI_ADMIN"},
        };

        for (String[] user : usersGroupsAndRoles) {
            List<String> authoritiesStrings = asList(Arrays.copyOfRange(user, 2, user.length));
            logger.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();
    }

}

/*
 * Copyright 2010-2020 Alfresco Software, Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cbsfs.cn.activity.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;

import java.util.Collection;

@Component
public class SecurityUtil {

    private Logger logger = LoggerFactory.getLogger(SecurityUtil.class);

    @Autowired
    private UserDetailsService userDetailsService;

    public void logInAs(String username) {

        UserDetails user = userDetailsService.loadUserByUsername(username);
        if (user == null) {
            throw new IllegalStateException("User " + username + " doesn't exist, please provide a valid user");
        }
        logger.info("> Logged in as: " + username);
        SecurityContextHolder.setContext(new SecurityContextImpl(new Authentication() {
            @Override
            public Collection<? extends GrantedAuthority> getAuthorities() {
                return user.getAuthorities();
            }

            @Override
            public Object getCredentials() {
                return user.getPassword();
            }

            @Override
            public Object getDetails() {
                return user;
            }

            @Override
            public Object getPrincipal() {
                return user;
            }

            @Override
            public boolean isAuthenticated() {
                return true;
            }

            @Override
            public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {

            }

            @Override
            public String getName() {
                return user.getUsername();
            }
        }));
        org.activiti.engine.impl.identity.Authentication.setAuthenticatedUserId(username);
    }
}

通过这个SecurityUtil登录辅助类必须使用logInAs方法进行登录,会使用Security框架自带的一个loadUserByUsername的方法查询当前用户在不在Spring Security中,如果在的话会返回一个UserDetails的实体。通过实体类获得这个用户的信息。

在DemoApplicationConfiguration中可以查看到测试所需要的用户信息:

 String[][] usersGroupsAndRoles = {//GROUP_activitiTeam 用户组  //ROLE_ACTIVITI_USER角色
                {"bob", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
                {"bajie", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
                {"wukong", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
                {"other", "password", "ROLE_ACTIVITI_USER", "GROUP_otherTeam"},
                {"system", "password", "ROLE_ACTIVITI_USER"},
                {"admin", "password", "ROLE_ACTIVITI_ADMIN"},
        };

代码:【获取流程实例 / 启动流程实例 / 删除流程实例 / 挂起流程实例 / 激活流程实例 /激活流程实例 / 获取流程实例参数】

package cbsfs.cn.activity;

import cbsfs.cn.activity.config.SecurityUtil;
import org.activiti.api.model.shared.model.VariableInstance;
import org.activiti.api.process.model.ProcessInstance;
import org.activiti.api.process.model.builders.ProcessPayloadBuilder;
import org.activiti.api.process.runtime.ProcessRuntime;
import org.activiti.api.runtime.shared.query.Page;
import org.activiti.api.runtime.shared.query.Pageable;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class Part8_ProcessRuntime {

    @Autowired
    private ProcessRuntime processRuntime;

    @Autowired
    private SecurityUtil securityUtil;//模拟登录工具类

    //获取流程实例
    @Test
    public void getProcessInstance() {
        securityUtil.logInAs("bajie"); // 模拟用户登录,这里登录的是名为 "bajie" 的用户
        Page<ProcessInstance> processInstancePage = processRuntime
                .processInstances(Pageable.of(0,100)); // 分页查询流程实例,每页显示 100 条记录
        System.out.println("流程实例数量:"+processInstancePage.getTotalItems()); // 打印流程实例总数
        List<ProcessInstance> list = processInstancePage.getContent(); // 获取当前页的流程实例列表
        for(ProcessInstance pi : list){ // 遍历流程实例列表
            System.out.println("-----------------------");
            System.out.println("getId:" + pi.getId()); // 流程实例 ID
            System.out.println("getName:" + pi.getName()); // 流程实例名称
            System.out.println("getStartDate:" + pi.getStartDate()); // 流程实例启动时间
            System.out.println("getStatus:" + pi.getStatus()); // 流程实例状态
            System.out.println("getProcessDefinitionId:" + pi.getProcessDefinitionId()); // 流程定义 ID
            System.out.println("getProcessDefinitionKey:" + pi.getProcessDefinitionKey()); // 流程定义标识符
        }
    }

    // 启动流程实例
    @Test
    public void startProcessInstance() {
        securityUtil.logInAs("bajie"); // 模拟用户登录,这里登录的是名为 "bajie" 的用户
        ProcessInstance processInstance = processRuntime.start(ProcessPayloadBuilder
                .start()
                .withProcessDefinitionKey("myProcess_ProcessRuntime") // 流程定义标识符
                .withName("第二个流程实例名称") // 流程实例名称
                //.withVariable("","") // 流程变量
                .withBusinessKey("自定义bKey") // 业务键
                .build()
        );
    }


    // 删除流程实例
    @Test
    public void delProcessInstance() {
        securityUtil.logInAs("bajie"); // 模拟用户登录,这里登录的是名为 "bajie" 的用户
        ProcessInstance processInstance = processRuntime.delete(ProcessPayloadBuilder
                .delete()
                .withProcessInstanceId("c80a5483-fefb-11ed-83e0-22038c0a7b1d") // 要删除的流程实例的 ID
                .build()
        );
    }


    // 挂起流程实例
    @Test
    public void suspendProcessInstance() {
        securityUtil.logInAs("bajie"); // 模拟用户登录,这里登录的是名为 "bajie" 的用户
        ProcessInstance processInstance = processRuntime.suspend(ProcessPayloadBuilder
                .suspend()
                .withProcessInstanceId("c8006a6e-fefb-11ed-83e0-22038c0a7b1d") // 要挂起的流程实例的 ID
                .build()
        );
    }


    // 激活流程实例
    @Test
    public void resumeProcessInstance() {
        securityUtil.logInAs("bajie"); // 模拟用户登录,这里登录的是名为 "bajie" 的用户
        ProcessInstance processInstance = processRuntime.resume(ProcessPayloadBuilder
                .resume()
                .withProcessInstanceId("c8006a6e-fefb-11ed-83e0-22038c0a7b1d") // 要激活的流程实例的 ID
                .build()
        );
    }


    // 获取流程实例参数
    @Test
    public void getVariables() {
        securityUtil.logInAs("bajie"); // 模拟用户登录,这里登录的是名为 "bajie" 的用户
        List<VariableInstance> list = processRuntime.variables(ProcessPayloadBuilder
                .variables()
                .withProcessInstanceId("c8006a6e-fefb-11ed-83e0-22038c0a7b1d") // 要获取参数的流程实例的 ID
                .build()
        );
        for (VariableInstance vi : list) { // 遍历参数列表
            System.out.println("-------------------");
            System.out.println("getName:" + vi.getName()); // 参数名称
            System.out.println("getValue:" + vi.getValue()); // 参数值
            System.out.println("getTaskId:" + vi.getTaskId()); // 参数关联的任务 ID(如果有)
            System.out.println("getProcessInstanceId:" + vi.getProcessInstanceId()); // 参数关联的流程实例 ID
        }
    }

}

5-2 API新特性TaskRuntime

image-20230531202413206

代码:【获取任务列表 / 完成任务】

package cbsfs.cn.activity;

import cbsfs.cn.activity.config.SecurityUtil;
import org.activiti.api.runtime.shared.query.Page;
import org.activiti.api.runtime.shared.query.Pageable;
import org.activiti.api.task.model.Task;
import org.activiti.api.task.model.builders.TaskPayloadBuilder;
import org.activiti.api.task.runtime.TaskRuntime;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class Part9_TaskRuntime {
    @Autowired
    private SecurityUtil securityUtil;

    @Autowired
    private TaskRuntime taskRuntime;

    //获取任务列表
    @Test
    public void getTasks() {
        //模拟用户登录
        securityUtil.logInAs("bajie");
        //获取任务分页列表,每页100条任务记录
        Page<Task> tasks = taskRuntime.tasks(Pageable.of(0,100));
        //获取当前页的任务列表
        List<Task> list=tasks.getContent();
        for(Task tk : list){
            System.out.println("-------------------");
            System.out.println("getId:"+ tk.getId()); //打印任务ID
            System.out.println("getName:"+ tk.getName()); //打印任务名称
            System.out.println("getStatus:"+ tk.getStatus()); //打印任务状态
            System.out.println("getCreatedDate:"+ tk.getCreatedDate()); //打印任务创建时间
            if(tk.getAssignee() == null){
                //如果候选人为当前用户,则任务需要从候选池中领取
                System.out.println("Assignee:待拾取任务"+tk.toString());
            }else{
                //否则打印任务的负责人
                System.out.println("Assignee:"+ tk.getAssignee());
            }
        }
    }

    //完成任务
    @Test
    public void completeTask() {
        //模拟用户登录
        securityUtil.logInAs("bajie");
        //根据任务ID获取任务实例
        Task task = taskRuntime.task("c80a5483-fefb-11ed-83e0-22038c0a7b1d");
        if(task.getAssignee() == null){
        //如果任务没有被认领,则将任务分配给当前用户
            taskRuntime.claim(TaskPayloadBuilder.claim()
                    .withTaskId(task.getId())
                    .build());
        }
        //执行任务
        taskRuntime.complete(TaskPayloadBuilder
                .complete()
                .withTaskId(task.getId())
                .build());
        System.out.println("任务执行完成");
    }
}

5-3.SpringSecurity用户登录

image-20230531204513672

image-20230531204709658

image-20230531204835548

image-20230531204918620

Activity 依赖自动引用了Spring Security 不需要额外的引用依赖,在整合好Activity的项目直接启动就好。

1.如何改变登录使用数据库中查询出来的账号和密码?

① 添加用户信息实体类,实现了Spring Security的UserDetails接口

package cbsfs.cn.activity.pojo;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Collectors;

/**
 * 用户信息实体类,实现了Spring Security的UserDetails接口
 */
@Component
public class UserInfoBean implements UserDetails {
    // 用户ID
    private Long id;
    // 用户姓名
    private String name;
    // 用户地址
    private String address;
    // 用户密码
    private String password;
    // 用户名
    private String username;
    // 用户角色,多个角色用逗号隔开
    private String roles;

    /**
     * 获取用户地址
     *
     * @return 用户地址
     */
    public String getAddress() {
        return address;
    }

    /**
     * 获取用户权限列表,包括角色和权限等
     *
     * @return 用户权限列表
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return Arrays.stream(roles.split(",")).map(
                s -> new SimpleGrantedAuthority(s)).collect(Collectors.toList());
    }

    /**
     * 获取用户密码
     *
     * @return 用户密码
     */
    @Override
    public String getPassword() {
        return password;
    }

    /**
     * 获取用户名称
     *
     * @return 用户名称
     */
    @Override
    public String getUsername() {
        return username;
    }

    /**
     * 判断账户是否未过期
     *
     * @return true表示账户未过期,false表示账户已经过期
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * 判断账户是否未锁定
     *
     * @return true表示账户未锁定,false表示账户已经被锁定
     */
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * 判断用户凭证是否未过期
     *
     * @return true表示用户凭证未过期,false表示用户凭证已经过期
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    
    /**
     * 判断用户是否可用
     *
     * @return true表示用户可用,false表示用户不可用
     */
    @Override
    public boolean isEnabled() {
        return true;
    }
}

②添加向数据库中查询用户信息的Mapper

package cbsfs.cn.activity.mapper;

import cbsfs.cn.activity.pojo.UserInfoBean;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;

/**
* @auther admin闫
* @Descriptopn
* @date 2023/5/31 21:53
*/
@Mapper
@Component
public interface UserInfoBeanMapper {
    /**
     * 根据用户名查询用户信息
     * @return 用户信息
     */
    @Select("select * from user where username = #{username}")
    UserInfoBean selectByUsername( @Param("username") String username);

}

③添加登录类,MyUserDetailsService 实现 接口

package cbsfs.cn.activity.security;

import cbsfs.cn.activity.mapper.UserInfoBeanMapper;
import cbsfs.cn.activity.pojo.UserInfoBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
/**
 * @auther admin闫
 * @Descriptopn security登录类
 * @date 2023/5/31 21:15
 */
@Component
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    UserInfoBeanMapper userInfoBeanMapper;

    /**
     * 获取账号密码方法
     * @param username 账号
     * @return  账号,密码,权限
     * @throws UsernameNotFoundException 用户名错误异常
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 使用Spring Security提供的密码编码器生成"111"的哈希值密码
//        String password = passwordEncoder().encode("111");
        // 创建一个新的User对象,包含给定的用户名、哈希密码以及"ROLE_ACTIVITI_USER"权限
//        return new User(username
//                , password
//                , AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_ACTIVITI_USER"));
        //读取数据库用户
        UserInfoBean userInfoBean=userInfoBeanMapper.selectByUsername(username);
        if (username==null){
            throw new UsernameNotFoundException("数据库中没有用户");
        }
        return userInfoBean;

    }
    /**
     * 重写密码加密
     * @return Bean对象
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

}

5-4 SpringSecurity配置

image-20230603150525775

①添加配置类【ActivitiSecurityConfig】

package cbsfs.cn.activity.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
 * @auther admin闫
 * @Descriptopn Security 配置类
 * @date 2023/6/3 15:06
 */
@Configuration
public class ActivitiSecurityConfig extends WebSecurityConfigurerAdapter {


    @Autowired
    private LoginSuccessHandler loginSuccessHandler;
    /**
     * 方法是 Spring Security 提供的一种基于 Java 代码配置安全性的方式。它允许我们在其中定义我们应用程序的安全性规则。
     * 在该方法中,参数 http 是一个 HttpSecurity 对象,用于配置 web 安全性。
     * 通过该对象,我们可以配置登录、权限、注销、CSRF 防护等方面的安全控制。
     * @param http HttpSecurity对象
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin() // 开启表单登录,如果没有登录会跳转到默认的/login路径
                .loginPage("/login") // 定义自定义的登录页面路径
                .successHandler(loginSuccessHandler) // 登录成功后执行的方法
                .and().authorizeRequests() // 配置权限
                .anyRequest().permitAll() // 所有请求都允许通过
                .and().logout().permitAll() // 允许注销登录
                .and().csrf().disable() // 禁用 CSRF 防护功能
                .headers().frameOptions().disable(); // 禁用 X-Frame-Options,支持在 iframe 中显示网页内容
    }
}

添加登录成功的方法【LoginSuccessHandler】

package cbsfs.cn.activity.security;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @auther admin闫
 * @Descriptopn 登录成功的处理类
 * @date 2023/6/3 15:28
 */
@Component("loginSuccessHandler")
public class LoginSuccessHandler implements AuthenticationSuccessHandler {

    /**
     * 重写接口中的方法,处理认证成功后的操作
     * 表单的请求方法
     * @param request 当前请求
     * @param response 当前响应
     * @param chain 当前过滤器链
     * @param authentication 认证对象
     * @throws IOException 发生 I/O 异常时抛出
     * @throws ServletException 发生 Servlet 异常时抛出
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
//        // 调用父类中的同名方法,继续执行过滤器链中的下一个过滤器
//        AuthenticationSuccessHandler.super.onAuthenticationSuccess(request, response, chain, authentication);
    }

    /**
     * 重写接口中的方法,处理认证成功后的操作
     * Ajax响应请求的方法
     * @param httpServletRequest 当前请求
     * @param httpServletResponse 当前响应
     * @param authentication 认证对象
     * @throws IOException 发生 I/O 异常时抛出
     * @throws ServletException 发生 Servlet 异常时抛出
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.getWriter().write("登录成功!LoginSuccessHandler"+authentication.getName());
    }
}

使用postman工具进行测试

地址:http://localhost:8080/login 发送POST请求

参数: username = bajie , password = 1

image-20230603162140227

5-5 SpringSecurity登录响应

登录后的失败处理

添加登录失败的方法【LoginFailureHandler】

package cbsfs.cn.activity.security;

import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @auther admin闫
 * @Descriptopn 登录失败的处理方法
 * @date 2023/6/3 15:54
 */
@Component("loginFailureHandler") // 声明为 Spring Bean,可在其他组件依赖注入
public class LoginFailureHandler implements AuthenticationFailureHandler {


    /**
     * 登录认证失败后的处理方法
     *
     * @param httpServletRequest HTTP 请求
     * @param httpServletResponse HTTP 响应
     * @param e 认证异常信息
     * @throws IOException 异常信息
     * @throws ServletException 异常信息
     */
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest,
                                        HttpServletResponse httpServletResponse,
                                        AuthenticationException e) throws IOException, ServletException {
        httpServletResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); // 设置响应状态码为 500(服务器内部错误)
        httpServletResponse.setContentType("application/json;charset=UTF-8"); // 设置响应内容的类型为 JSON 格式
        httpServletResponse.getWriter().write("登录失败!原因是" + e.getMessage()); // 返回错误消息给客户端
    }
}

添加未登录的方法【ActivitiSecurityController】

package cbsfs.cn.activity.security;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * ActivitiSecurityController类:处理Activiti的安全性控制相关请求
 */
public class ActivitiSecurityController {

    /**
     * requireAuthentication方法:处理/login请求,并返回未认证状态码和提示信息
     * @param request HTTP请求对象
     * @param response HTTP响应对象
     * @return 返回提示信息字符串
     */
    @RequestMapping("/login")
    @ResponseStatus(code = HttpStatus.UNAUTHORIZED)
    public String requireAuthentication(HttpServletRequest request, HttpServletResponse response){
        return new String("需要登录,请使用login.html或发起POST请求登录");
    }
}

修改登录security的配置文件【ActivitiSecurityConfig】

package cbsfs.cn.activity.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
 * @auther admin闫
 * @Descriptopn Security 配置类
 * @date 2023/6/3 15:06
 */
@Configuration
public class ActivitiSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private LoginSuccessHandler loginSuccessHandler;
    @Autowired
    private LoginFailureHandler loginFailureHandler;  // 新增

    /**
     * 方法是 Spring Security 提供的一种基于 Java 代码配置安全性的方式。它允许我们在其中定义我们应用程序的安全性规则。
     * 在该方法中,参数 http 是一个 HttpSecurity 对象,用于配置 web 安全性。
     * 通过该对象,我们可以配置登录、权限、注销、CSRF 防护等方面的安全控制。
     * @param http HttpSecurity对象
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin() // 开启表单登录,如果没有登录会跳转到默认的/login路径
                .loginPage("/login") // 定义自定义的登录页面路径
                .loginProcessingUrl("/login")//登录验证之后对未登录的用户进行处理	// 新增
                .successHandler(loginSuccessHandler) // 登录成功后执行的方法     
                .failureHandler(loginFailureHandler) //登录失败后执行的方法		// 新增
                .and().authorizeRequests() // 配置权限
                .anyRequest().permitAll() // 所有请求都允许通过
                .and().logout().permitAll() // 允许注销登录
                .and().csrf().disable() // 禁用 CSRF 防护功能
                .headers().frameOptions().disable(); // 禁用 X-Frame-Options,支持在 iframe 中显示网页内容
    }
}

使用postMan对登录失败进行测试

地址:http://localhost:8080/login

参数:username=bajie&password=11

image-20230603162916986

使用postMan对未登录进行测试

地址:http://localhost:8080/loginxx

参数:无

未体现

5-6 BPMN-JS整合

BPMN官网:https://bpmn.io/toolkit/bpmn-js/

image-20230603170701612

image-20230603170821453

注意找到githup的仓库7.2.1版本的例子:https://github.com/bpmn-io/bpmn-js-examples/releases/tag/v7.2.1

image-20230603191349008

下载下来找到:

image-20230603191601572

复制到目录

image-20230603191746276

进入这个目录:D:\MD笔记\工作流\Activity7_my\src\main\resources\resources\bpmnjs 这里是你的路径

运行npm 命令:

npm install --legacy-peer-deps					这里相对于js的版本过老,如果要使用中文汉化,需要运行这个命令才能安装
npm run dev									   这里使用这个命令楚辞运行

运行成功后,是英文版的,这里给这个例子汉化

将BPMN初始化包复制到

image-20230603192617868

打开resources/resources/bpmnjs/app/index.js

注释以下内容

import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda';
import camundaModdleDescriptor from 'camunda-bpmn-moddle/resources/camunda.json';

注释以下内容

var bpmnModeler = new BpmnModeler({
 container: canvas,
 propertiesPanel: {
   parent: '#js-properties-panel'
 },
 additionalModules: [
   propertiesPanelModule,
   propertiesProviderModule
 ],
 moddleExtensions: {
   camunda: camundaModdleDescriptor
 }
});

添加以下内容: 注意添加代码的位置,否则会加载不出来

//这段导入代码,加在导入的代码下面
import propertiesProviderModule from '../resources/properties-panel/provider/activiti';
import activitiModdleDescriptor from '../resources/activiti.json';
import customTranslate from '../resources/customTranslate/customTranslate';
import customControlsModule from '../resources/customControls';

//这段初始化代码,加载注释的初始化代码下面
// 添加翻译组件
var customTranslateModule = {
   translate: ['value', customTranslate]
};


var bpmnModeler = new BpmnModeler({
   container: canvas,
   propertiesPanel: {
       parent: '#js-properties-panel'
   },
   additionalModules: [
       propertiesPanelModule,
       propertiesProviderModule,
       customControlsModule,
       customTranslateModule
   ],
   moddleExtensions: {
       activiti:activitiModdleDescriptor
   }
});

使用命令行工具打开resources/resources/bpmnjs/并执行命令

npm install

npm run dev

增加BPMNJS可执行选框默认勾选,打开resources/newDiagram.bpmn

属性修改为isExecutable="true

<bpmn2:process id="Process_1" isExecutable="true">

第6章 项目:可视化UML工作流引擎web系统:需求分析与设计

image-20230603195128651

image-20230603195146760

6-1 页面功能设计

① 产品原形设计

image-20230603235841305

6-2 数据库设计

image-20230604000536578

image-20230604000933005

已经创建用户表,需要再创建用户角色中间关系表,角色表,表单业务数据

表单业务数据SQL

drop table if exists formdata;

/*==============================================================*/
/* Table: formdata                                              */
/*==============================================================*/
create table formdata
(
   PROC_DEF_ID_         varchar(64),
   PROC_INST_ID_        varchar(64),
   FORM_KEY_            varchar(255),
   Control_ID_          varchar(100),
   Control_VALUE_       varchar(2000)
);

image-20230604003750985

静态表和动态业务表单

6-3 流程定义接口设计

image-20230604003903761

接口文档 - 流程定义processDefinition

本接口文档详情如下:

上传BPMN流媒体./uploadStream

请求

  • 方法: POST
  • URL: http://localhost:8080/processDefinition/./uploadStreamAndDeployment
  • Header: 无
  • Body:
参数名描述
processsFileBPMN 文件

响应

  • 状态码: 无
  • Body: 无

部署BPMN字符./addDeploymentByString

请求

  • 方法: POST
  • URL: http://localhost:8080/processDefinition/addDeploymentByString?stringBPMN=&deploymentName
  • Header: 无
  • Query 参数:
参数名描述
stringBPMNxml 数据
deploymentName流程定义名称

响应

  • 状态码: 无
  • Body: 无

获取流程定义列表./getDefinitions

请求

  • 方法: GET
  • URL: http://localhost:8080/processDefinition/getDefinitions
  • Header: 无

响应

  • 状态码: 无
  • Body: 无

获取流程定义 XML./getDefinitionXML

请求

  • 方法: GET
  • URL: http://localhost:8080/processDefinition/getDefinitionXML?deploymentID&resourceName
  • Header: 无
  • Query 参数:
参数名描述
deploymentID流程 ID
resourceNameBPMN 文件名称

响应

  • 状态码: 无
  • Body: 无

获取流程部署列表./getDeployments

请求

  • 方法: GET
  • URL: http://localhost:8080/processDefinition/getDeployments
  • Header: 无

响应

  • 状态码: 无
  • Body: 无

删除流程部署(同时删除流程定义)./delDefinition

请求

  • 方法: GET
  • URL: http://localhost:8080/processDefinition/delDefinition?pdID
  • Header: 无
  • Query 参数:
参数名描述
pdID流程定义 ID

响应

  • 状态码: 无
  • Body: 无

接口文档 - 流程实例processInstance

本接口文档详情如下:

获取流程实例列表./getInstance

请求

  • 方法: GET
  • URL: http://localhost:8080/processInstance/getInstance
  • Header: 无

响应

  • 状态码: 无
  • Body: 无

启动流程实例./startProcess

请求

  • 方法: POST
  • URL: http://localhost:8080/processInstance/getInstance/startProcess?processDefinitionKey&instanceName&instanceVariable
  • Header: 无
  • Query 参数:
参数名描述
processDefinitionKey流程定义 Key
instanceName流程实例名称
instanceVariable流程实例参数

响应

  • 状态码: 无
  • Body: 无

删除流程实例./deleteInstance

请求

  • 方法: POST
  • URL: http://localhost:8080/processInstance/deleteInstance?instanceID
  • Header: 无
  • Query 参数:
参数名描述
instanceID流程实例 ID

响应

  • 状态码: 无
  • Body: 无

挂起流程实例./suspendInstance

请求

  • 方法: POST
  • URL: http://localhost:8080/processInstance/suspendInstance?instanceID
  • Header: 无
  • Query 参数:
参数名描述
instanceID流程实例 ID

响应

  • 状态码: 无
  • Body: 无

激活流程实例./resumeInstance

请求

  • 方法: POST
  • URL: http://localhost:8080/processInstance/resumeInstance?instanceID
  • Header: 无
  • Query 参数:
参数名描述
instanceID流程实例 ID

响应

  • 状态码: 无
  • Body: 无

获取流程参数./variables

请求

  • 方法: GET
  • URL: http://localhost:8080/processInstance/variables?instanceID
  • Header: 无
  • Query 参数:
参数名描述
instanceID流程实例 ID

响应

  • 状态码: 无
  • Body: 无

接口文档 - 任务task

我的代办任务./getTask

请求方式

GET

请求URL

http://localhost:8080/task/getTasks

请求参数

无

返回示例

无

完成代办任务./completeTask

请求方式

POST

请求URL

http://localhost:8080/task/completeTask?taskID

请求参数

参数名必选类型说明
taskID是string任务ID

返回示例

无

任务表单渲染./fromDataShow

请求方式

GET

请求URL

http://localhost:8080/task/fromDataShow?taskID

请求参数

参数名必选类型说明
taskID是string任务ID

返回示例

无

任务表单保存./formDataSave

请求方式

GET

请求URL

http://localhost:8080/task/fromDataShow?taskID&fromDataString

请求参数

参数名必选类型说明
taskID是string任务ID
fromDataString是string页面所有空间和值组成的字符串

返回示例

无

接口文档-历史activitiHistory

用户历史任务./getInstancesByUserName

请求

  • 方法:GET
  • URL:http://localhost:8080/activitiHistory/getInstancesByUserName
  • 参数:
    • userName:用户名

响应

无响应信息。

实例历史任务./getInstancesByPiID

请求

  • 方法:GET
  • URL:http://localhost:8080/activitiHistory/getInstancesByPiID
  • 参数:
    • instancesID:流程实例ID

响应

无响应信息。

高亮渲染流程实例历史./getHighLine

请求

  • 方法:GET
  • URL:http://localhost:8080/activitiHistory/getHighLine
  • 参数:
    • instancesID:流程实例ID

响应

无响应信息。

第7章 项目:可视化UML工作流引擎web系统:后端接口设计与实现

7-1 返回值与配置工具类

image-20230604151155637

创建工具类AjaxResponse

package cbsfs.cn.activity.util;

import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * @auther admin闫
 * @Descriptopn Ajax返回值工具类
 * @date 2023/6/4 15:29
 */

@Data // Lombok 注解,自动生成 getter 和 setter 方法
public class AjaxResponse {
    private Integer status; // 响应状态码
    private String msg; // 响应消息
    private Object obj; // 响应数据


    // 构造方法
    private AjaxResponse(Integer status, String msg, Object obj) {
        this.status = status;
        this.msg = msg;
        this.obj = obj;
    }

    // 静态工厂方法,用于创建 AjaxResponse 对象并初始化属性值
    public  static AjaxResponse AjaxData(Integer status, String msg, Object obj){
        return new AjaxResponse(status,msg,obj);
    }
}

创建工具类GlobalConfig

package cbsfs.cn.activity.util;

/**
 * @auther admin闫
 * @Descriptopn 枚举类 状态码
 * @date 2023/6/4 15:15
 */
public class GlobalConfig {

    /**
     * 测试开关,用于控制测试相关功能的开启和关闭
     */
    public static final Boolean test=true;

    /**
     * 响应状态码枚举类
     */
    public enum ResponseCode{
        SUCCESS(0,"成功"),
        ERROR(1,"错误");

        private final int code; // 状态码
        private final String desc; // 描述信息

        /**
         * 构造函数
         *
         * @param code 状态码
         * @param desc 描述信息
         */
        ResponseCode(int code,String desc){
            this.code=code;
            this.desc=desc;
        }

        /**
         * 获取状态码
         *
         * @return 状态码
         */
        public int getCode(){
            return code;
        }

        /**
         * 获取描述信息
         *
         * @return 描述信息
         */
        public String getDesc(){
            return desc;
        }

    }

}

7-2 登录接口

修改代码,使用工具类定义接口

【LoginFailureHandler】

package cbsfs.cn.activity.security;

import cbsfs.cn.activity.util.AjaxResponse;
import cbsfs.cn.activity.util.GlobalConfig;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @auther admin闫
 * @Descriptopn 登录失败的处理方法
 * @date 2023/6/3 15:54
 */
@Component("loginFailureHandler") // 声明为 Spring Bean,可在其他组件依赖注入
public class LoginFailureHandler implements AuthenticationFailureHandler {
    @Autowired
    private ObjectMapper objectMapper;


    /**
     * 登录认证失败后的处理方法
     *
     * @param httpServletRequest HTTP 请求
     * @param httpServletResponse HTTP 响应
     * @param e 认证异常信息
     * @throws IOException 异常信息
     * @throws ServletException 异常信息
     */
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest,
                                        HttpServletResponse httpServletResponse,
                                        AuthenticationException e) throws IOException, ServletException {
        httpServletResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); // 设置响应状态码为 500(服务器内部错误)
        httpServletResponse.setContentType("application/json;charset=UTF-8"); // 设置响应内容的类型为 JSON 格式
        httpServletResponse.getWriter().write(
                objectMapper.writeValueAsString(
                        AjaxResponse.AjaxData(GlobalConfig.ResponseCode.ERROR.getCode(), GlobalConfig.ResponseCode.ERROR.getDesc(), e.getMessage())
                )
        ); // 返回错误消息给客户端
    }
}

【LoginSuccessHandler】

package cbsfs.cn.activity.security;

import cbsfs.cn.activity.util.AjaxResponse;
import cbsfs.cn.activity.util.GlobalConfig;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @auther admin闫
 * @Descriptopn 登录成功的处理类
 * @date 2023/6/3 15:28
 */
@Component("loginSuccessHandler")
public class LoginSuccessHandler implements AuthenticationSuccessHandler {

    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 重写接口中的方法,处理认证成功后的操作
     * 表单的请求方法
     * @param request 当前请求
     * @param response 当前响应
     * @param chain 当前过滤器链
     * @param authentication 认证对象
     * @throws IOException 发生 I/O 异常时抛出
     * @throws ServletException 发生 Servlet 异常时抛出
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
//        // 调用父类中的同名方法,继续执行过滤器链中的下一个过滤器
//        AuthenticationSuccessHandler.super.onAuthenticationSuccess(request, response, chain, authentication);
    }

    /**
     * 重写接口中的方法,处理认证成功后的操作
     * Ajax响应请求的方法
     * @param httpServletRequest 当前请求
     * @param httpServletResponse 当前响应
     * @param authentication 认证对象
     * @throws IOException 发生 I/O 异常时抛出
     * @throws ServletException 发生 Servlet 异常时抛出
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=UTF-8");
//        httpServletResponse.getWriter().write("登录成功!LoginSuccessHandler"+authentication.getName());
        httpServletResponse.getWriter().write(
                objectMapper.writeValueAsString(
                        AjaxResponse.AjaxData(GlobalConfig.ResponseCode.SUCCESS.getCode(),GlobalConfig.ResponseCode.SUCCESS.getDesc(), authentication.getName())
                )
                );

    }
}

7-3 流程定义接口

【ProcessDefinitionController】

package cbsfs.cn.activity.controller;

import cbsfs.cn.activity.util.AjaxResponse;
import cbsfs.cn.activity.util.GlobalConfig;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.activiti.api.process.runtime.ProcessRuntime;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import java.util.zip.ZipInputStream;

/**
 * @auther admin闫
 * @Descriptopn 流程定义接口
 * @date 2023/6/4 15:59
 */
@RestController
@RequestMapping("/processDefinition")
@Api("流程定义接口") // API类注释
@Slf4j
public class ProcessDefinitionController {

    @Autowired
    private RepositoryService repositoryService; // 自动注入RepositoryService对象

    /**
     * 添加流程定义 通过上传BPMN
     *
     * @param multipartFile BPMN文件(zip/bpmn)
     * @param deploymentName 流程定义名称
     * @return AjaxResponse
     */
    @ApiOperation("添加流程定义 通过上传BPMN")
    @PostMapping(value = "/uploadStreamAndDeployment")
    public AjaxResponse uploadStreamAndDeployment(@RequestParam("processFile") MultipartFile multipartFile,
                                                  @RequestParam("deploymentName") String deploymentName) {
        try {
            // 获取文件上传名
            String fileName=multipartFile.getOriginalFilename();
            // 获取文件扩展名
            String extension= FilenameUtils.getExtension(fileName);
            // 获取文件字节流
            InputStream fileInputStream= multipartFile.getInputStream();
            Deployment deployment=null;
            if (extension.equals("zip")){
                ZipInputStream zipInputStream=new ZipInputStream(fileInputStream);
                deployment =repositoryService.createDeployment()
                        .addZipInputStream(zipInputStream)
                        .name(deploymentName)
                        .deploy(); // 部署流程定义
            }else {
                deployment=repositoryService.createDeployment()
                        .addInputStream(fileName,fileInputStream)
                        .name(deploymentName)
                        .deploy(); // 部署流程定义
            }
            log.info("上传流程文件:"+fileName);
            return AjaxResponse.AjaxData(
                    GlobalConfig.ResponseCode.SUCCESS.getCode(), GlobalConfig.ResponseCode.SUCCESS.getDesc()
                    , deployment.getId()+";"+fileName); // 返回部署ID以及文件名

        } catch (Exception e) {
            return AjaxResponse.AjaxData(
                    GlobalConfig.ResponseCode.ERROR.getCode(), GlobalConfig.ResponseCode.ERROR.getDesc(), e.toString());
        }
    }

    /**
     * 添加流程定义 通过上传BPMN
     *
     * @param stringBPMN XMLBPMN
     * @param deploymentName 流程定义名称
     * @return AjaxResponse
     */
    @ApiOperation("添加流程定义 通过在线提交BPMN的xml")
    @PostMapping(value = "/addDeploymentByString")
    public AjaxResponse addDeploymentByString(@RequestParam("stringBPMN") String stringBPMN,
                                              @RequestParam("deploymentName") String deploymentName) {
        try {
            Deployment deployment=repositoryService.createDeployment()
                    .addString("CreateWithBPMNJS.bpmn",stringBPMN)
                    .name(deploymentName)
                    .deploy(); // 部署流程定义

            log.info("在线部署流程:"+deploymentName);
            return AjaxResponse.AjaxData(
                    GlobalConfig.ResponseCode.SUCCESS.getCode(), GlobalConfig.ResponseCode.SUCCESS.getDesc()
                    , deployment.getId()+";"+deploymentName); // 返回部署ID以及流程定义名称

        } catch (Exception e) {
            return AjaxResponse.AjaxData(
                    GlobalConfig.ResponseCode.ERROR.getCode(), GlobalConfig.ResponseCode.ERROR.getDesc(), e.toString());
        }
    }

    /**
     * 获取流程定义列表
     *
     * @return 流程定义列表
     */
    @ApiOperation("获取流程定义列表")
    @GetMapping(value = "/getDefinitions")
    public AjaxResponse getDefinitions() {
        try {
            List<HashMap<String,Object>> listMap=new ArrayList<>();
            // 调用RepositoryService的方法来查询所有的流程定义
            List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().list();
            for (ProcessDefinition processDefinition : list) {
                HashMap<String,Object> hashMap=new HashMap<>();
                hashMap.put("Name",processDefinition.getName()); // 流程定义名称
                hashMap.put("Key",processDefinition.getKey()); // 流程定义键
                hashMap.put("ResourceName",processDefinition.getResourceName()); // 资源名称
                hashMap.put("DeploymentId",processDefinition.getDeploymentId()); // 部署ID
                hashMap.put("Version",processDefinition.getVersion()); // 版本号
                listMap.add(hashMap);
                log.info("查询流程定义:"+processDefinition.getName());
            }
            return AjaxResponse.AjaxData(
                    GlobalConfig.ResponseCode.SUCCESS.getCode(), GlobalConfig.ResponseCode.SUCCESS.getDesc(), listMap.toString()); // 返回流程定义列表

        } catch (Exception e) {
            return AjaxResponse.AjaxData(
                    GlobalConfig.ResponseCode.ERROR.getCode(), GlobalConfig.ResponseCode.ERROR.getDesc(), e.toString());
        }
    }

    /**
     * 获取流程定义XML
     *
     * @param response 响应信息
     * @param deploymentID 部署ID
     * @param resourceName BPMN 上传的文件名
     * @return void
     */
    @ApiOperation("获取流程定义XML")
    @GetMapping(value = "/getDefinitionXML")
    public void getDefinitionXML(HttpServletResponse response ,@RequestParam("deploymentID") String deploymentID,
                                 @RequestParam("resourceName") String resourceName) {
        try {
            InputStream inputStream=repositoryService.getResourceAsStream(deploymentID,resourceName); // 获取流程定义XML
            int count = inputStream.available();
            byte[] bytes=new byte[count];
            response.setContentType("text/xml");
            OutputStream outputStream=response.getOutputStream();
            while (inputStream.read(bytes)!=-1){
                outputStream.write(bytes);
            }
            inputStream.close();
        } catch (Exception e) {
            e.toString();
        }
    }

    /**
     * 获取流程部署列表
     *
     * @return 流程部署列表
     */
    @ApiOperation("获取流程部署列表")
    @GetMapping(value = "/getDeployments")
    public AjaxResponse getDeployments() {
        try {
            List<HashMap<String,Object>> listMap=new ArrayList<>();
            // 调用RepositoryService的方法来查询所有的流程定义
            List<Deployment> list = repositoryService.createDeploymentQuery().list();

            for (Deployment deployment : list) {
                HashMap<String,Object> hashMap=new HashMap<>();
                hashMap.put("ID",deployment.getId()); // 部署ID
                hashMap.put("Name",deployment.getName()); // 部署名称
                hashMap.put("DeploymentTime",deployment.getDeploymentTime()); // 部署时间
                listMap.add(hashMap);
                log.info("获取流程部署列表:"+deployment.getName());
            }
            return AjaxResponse.AjaxData(
                    GlobalConfig.ResponseCode.SUCCESS.getCode(), GlobalConfig.ResponseCode.SUCCESS.getDesc(), listMap.toString()); // 返回流程部署列表

        } catch (Exception e) {
            return AjaxResponse.AjaxData(
                    GlobalConfig.ResponseCode.ERROR.getCode(), GlobalConfig.ResponseCode.ERROR.getDesc(), e.toString());
        }
    }

    /**
     * 删除流程定义
     *
     * @param pdID 流程定义ID
     * @return AjaxResponse
     */
    @ApiOperation("删除流程定义")
    @PostMapping(value = "/delDefinition")
    public AjaxResponse delDefinition(@RequestParam("pdID") String pdID) {
        try {
            repositoryService.deleteDeployment(pdID,true); // 删除流程定义
            log.info("删除流程定义:"+pdID);
            return AjaxResponse.AjaxData(
                    GlobalConfig.ResponseCode.SUCCESS.getCode(), GlobalConfig.ResponseCode.SUCCESS.getDesc()
                    , null);

        } catch (Exception e) {
            return AjaxResponse.AjaxData(
                    GlobalConfig.ResponseCode.ERROR.getCode(), GlobalConfig.ResponseCode.ERROR.getDesc(), e.toString());
        }
    }
}

7-4 流程实例接口

【ProcessInstanceController】

package cbsfs.cn.activity.controller;

import cbsfs.cn.activity.config.SecurityUtil;
import cbsfs.cn.activity.pojo.UserInfoBean;
import cbsfs.cn.activity.util.AjaxResponse;
import cbsfs.cn.activity.util.GlobalConfig;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.activiti.api.model.shared.model.VariableInstance;
import org.activiti.api.process.model.ProcessInstance;
import org.activiti.api.process.model.builders.ProcessPayloadBuilder;
import org.activiti.api.process.runtime.ProcessRuntime;
import org.activiti.api.runtime.shared.query.Page;
import org.activiti.api.runtime.shared.query.Pageable;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.ProcessDefinition;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * @auther admin闫
 * @Descriptopn 流程实例接口
 * @date 2023/6/6 5:23
 */
@RestController
@RequestMapping("processInstance")
@Api
@Slf4j
public class ProcessInstanceController {
    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private SecurityUtil securityUtil;

    @Autowired
    private ProcessRuntime processRuntime;



    /**
     * 查询流程实例
     * @param userInfoBean 当前登录用户信息
     * @return 流程实例
     */
    @ApiOperation("查询流程实例")
    @GetMapping(value = "/getInstances")
    public AjaxResponse getInstances(@AuthenticationPrincipal UserInfoBean userInfoBean) {
        // 定义一个空的流程实例分页对象
        Page<ProcessInstance> processInstances = null;
        try {
            // 如果是测试环境,则模拟当前登录用户为 "wukong"
            if (GlobalConfig.test) {
                securityUtil.logInAs("wukong");
            }

            // 使用processRuntime查询流程实例,并将结果封装到分页对象中
            processInstances=processRuntime.processInstances(Pageable.of(0, 50));

            // 对查询结果进行排序,按照流程实例的启动时间降序排列
            List<ProcessInstance> list = processInstances.getContent();
            list.sort((y,x)->x.getStartDate().toString().compareTo(y.getStartDate().toString()));

            // 将流程实例信息封装到HashMap中,并添加到listMap集合中
            List<HashMap<String, Object>> listMap = new ArrayList<HashMap<String, Object>>();
            for(ProcessInstance pi:list){
                HashMap<String, Object> hashMap = new HashMap<>();
                hashMap.put("id", pi.getId()); // 流程实例ID
                hashMap.put("name", pi.getName()); // 流程实例名称
                hashMap.put("status", pi.getStatus()); // 流程实例状态
                hashMap.put("processDefinitionId", pi.getProcessDefinitionId()); // 流程定义ID
                hashMap.put("processDefinitionKey", pi.getProcessDefinitionKey()); // 流程定义KEY
                hashMap.put("startDate", pi.getStartDate()); // 流程实例启动时间
                hashMap.put("processDefinitionVersion", pi.getProcessDefinitionVersion()); // 流程定义版本号

                // 因为processRuntime.processDefinition("流程部署ID")查询的结果没有部署流程与部署ID,所以用repositoryService查询
                ProcessDefinition pd = repositoryService.createProcessDefinitionQuery()
                        .processDefinitionId(pi.getProcessDefinitionId())
                        .singleResult();
                hashMap.put("resourceName", pd.getResourceName()); // 资源名称
                hashMap.put("deploymentId", pd.getDeploymentId()); // 部署ID
                listMap.add(hashMap);
                log.info("查询流程实例",pi.toString());
            }

            // 返回成功响应,并将结果封装到AjaxResponse对象中
            return AjaxResponse.AjaxData(GlobalConfig.ResponseCode.SUCCESS.getCode(),
                    GlobalConfig.ResponseCode.SUCCESS.getDesc(),listMap);
        } catch (Exception e) {
            // 如果出现异常,则返回错误响应,并将异常信息封装到AjaxResponse对象中
            return AjaxResponse.AjaxData(GlobalConfig.ResponseCode.ERROR.getCode(),
                    GlobalConfig.ResponseCode.ERROR.getDesc(), e.toString());
        }
    }

    /**
     * 启动流程实例
     * @param processDefinitionKey 流程定义KEY
     * @param instanceName 流程实例名称
     * @return 流程实例
     */
    @ApiOperation("启动流程实例")
    @GetMapping(value = "/startProcess")
    public AjaxResponse startProcess(
            @RequestParam("processDefinitionKey") String processDefinitionKey,
            @RequestParam("instanceName") String instanceName
    ) {
        try {
            // 如果是测试环境,则模拟当前登录用户为 "bajie"
            if (GlobalConfig.test){
                securityUtil.logInAs("bajie");
            }else {
                // 否则获取当前登录用户的信息,并模拟其登录
                securityUtil.logInAs(SecurityContextHolder.getContext().getAuthentication().getName());
            }

            // 使用processRuntime启动流程实例,并将结果封装到ProcessInstance对象中
            ProcessInstance processInstance = processRuntime.start(
                    ProcessPayloadBuilder
                            .start()
                            .withProcessDefinitionKey(processDefinitionKey) // 流程定义KEY
                            .withName(instanceName) // 流程实例名称
                            //.withVariable("content", instanceVariable)
                            //.withVariable("参数2", "参数2的值")
                            .withBusinessKey("自定义BusinessKey") // 自定义业务KEY
                            .build()
            );

            // 返回成功响应,并将结果封装到AjaxResponse对象中
            log.info("启动流程实例成功:"+processDefinitionKey+instanceName);
            return AjaxResponse.AjaxData(GlobalConfig.ResponseCode.SUCCESS.getCode(),
                    GlobalConfig.ResponseCode.SUCCESS.getDesc(),null);
        } catch (Exception e) {
            // 如果出现异常,则返回错误响应,并将异常信息封装到AjaxResponse对象中
            log.info("启动流程实例失败:"+processDefinitionKey+instanceName);
            return AjaxResponse.AjaxData(GlobalConfig.ResponseCode.ERROR.getCode(),
                    GlobalConfig.ResponseCode.ERROR.getDesc(), e.toString());
        }
    }

    /**
     * 挂起流程实例
     * @param instanceID 流程实例ID
     * @return AjaxResponse
     */
    @ApiOperation("挂起流程实例")
    @GetMapping(value = "/suspendInstance")
    public AjaxResponse suspendInstance(
            @RequestParam("instanceID") String instanceID
    ) {
        try {
            // 如果是测试环境,则模拟当前登录用户为 "bajie"
            if (GlobalConfig.test){
                securityUtil.logInAs("bajie");
            }else {
                // 否则获取当前登录用户的信息,并模拟其登录
                securityUtil.logInAs(SecurityContextHolder.getContext().getAuthentication().getName());
            }

            // 使用processRuntime挂起流程实例,并将结果封装到ProcessInstance对象中
            ProcessInstance processInstance = processRuntime.suspend(
                    ProcessPayloadBuilder
                            .suspend()
                            .withProcessInstanceId(instanceID) // 流程实例ID
                            .build()
            );

            // 返回成功响应,并将结果封装到AjaxResponse对象中
            log.info("挂起流程实例:"+processInstance.getName());
            return AjaxResponse.AjaxData(GlobalConfig.ResponseCode.SUCCESS.getCode(),
                    GlobalConfig.ResponseCode.SUCCESS.getDesc(),processInstance.getName());
        } catch (Exception e) {
            // 如果出现异常,则返回错误响应,并将异常信息封装到AjaxResponse对象中
            log.info("挂起流程实例:"+instanceID);
            return AjaxResponse.AjaxData(GlobalConfig.ResponseCode.ERROR.getCode(),
                    GlobalConfig.ResponseCode.ERROR.getDesc(), e.toString());
        }
    }

    /**
     * 激活/重启流程实例
     * @param instanceID 流程实例ID
     * @return AjaxResponse
     */
    @ApiOperation("激活/重启流程实例")
    @GetMapping(value = "/resumeInstance")
    public AjaxResponse resumeInstance(
            @RequestParam("instanceID") String instanceID
    ) {
        try {
            // 如果是测试环境,则模拟当前登录用户为 "bajie"
            if (GlobalConfig.test){
                securityUtil.logInAs("bajie");
            }else {
                // 否则获取当前登录用户的信息,并模拟其登录
                securityUtil.logInAs(SecurityContextHolder.getContext().getAuthentication().getName());
            }

            // 使用processRuntime激活/重启流程实例,并将结果封装到ProcessInstance对象中
            ProcessInstance processInstance = processRuntime.resume(
                    ProcessPayloadBuilder
                            .resume()
                            .withProcessInstanceId(instanceID) // 流程实例ID
                            .build()
            );

            // 返回成功响应,并将结果封装到AjaxResponse对象中
            log.info("激活/重启流程实例:"+processInstance.getName());
            return AjaxResponse.AjaxData(GlobalConfig.ResponseCode.SUCCESS.getCode(),
                    GlobalConfig.ResponseCode.SUCCESS.getDesc(),processInstance.getName());
        } catch (Exception e) {
            // 如果出现异常,则返回错误响应,并将异常信息封装到AjaxResponse对象中
            log.info("激活/重启流程实例:"+instanceID);
            return AjaxResponse.AjaxData(GlobalConfig.ResponseCode.ERROR.getCode(),
                    GlobalConfig.ResponseCode.ERROR.getDesc(), e.toString());
        }
    }

    /**
     * 删除流程实例
     * @param instanceID 流程实例ID
     * @return AjaxResponse
     */
    @ApiOperation("删除流程实例")
    @GetMapping(value = "/deleteInstance")
    public AjaxResponse deleteInstance(
            @RequestParam("instanceID") String instanceID
    ) {
        try {
            // 如果是测试环境,则模拟当前登录用户为 "bajie"
            if (GlobalConfig.test){
                securityUtil.logInAs("bajie");
            }else {
                // 否则获取当前登录用户的信息,并模拟其登录
                securityUtil.logInAs(SecurityContextHolder.getContext().getAuthentication().getName());
            }

            // 使用processRuntime删除流程实例,并将结果封装到ProcessInstance对象中
            ProcessInstance processInstance = processRuntime.delete(ProcessPayloadBuilder
                            .delete()
                            .withProcessInstanceId(instanceID) // 流程实例ID
                            //.withReason("删除原因") // 删除原因
                            .build()
            );

            // 返回成功响应,并将结果封装到AjaxResponse对象中
            log.info("删除流程实例:"+processInstance.getName());
            return AjaxResponse.AjaxData(GlobalConfig.ResponseCode.SUCCESS.getCode(),
                    GlobalConfig.ResponseCode.SUCCESS.getDesc(),processInstance.getName());
        } catch (Exception e) {
            // 如果出现异常,则返回错误响应,并将异常信息封装到AjaxResponse对象中
            log.info("删除流程实例:"+instanceID);
            return AjaxResponse.AjaxData(GlobalConfig.ResponseCode.ERROR.getCode(),
                    GlobalConfig.ResponseCode.ERROR.getDesc(), e.toString());
        }
    }

    /**
     * 查询流程参数
     * @param instanceID 流程实例ID
     * @return AjaxResponse 返回一个Ajax响应对象
     */
    @ApiOperation("查询流程参数")
    @GetMapping(value = "/variables")
    public AjaxResponse variables(
            @RequestParam("instanceID") String instanceID //通过@RequestParam注解获取请求中的instanceID参数
    ) {
        try {
            // 根据全局配置test属性选择日志用户
            if (GlobalConfig.test){
                securityUtil.logInAs("bajie"); // 模拟登陆操作,以"bajie"用户身份进行操作
            }else {
                securityUtil.logInAs(SecurityContextHolder.getContext().getAuthentication().getName()); //获取当前登录用户并以其身份进行操作
            }
            // 获取指定流程实例的所有变量
            List<VariableInstance> variables = processRuntime.variables(
                    ProcessPayloadBuilder
                            .variables() //构造获取变量操作的请求体
                            .withProcessInstanceId(instanceID) //设置要获取的流程实例ID
                            .build()
            );

            log.info("查询流程参数:"+variables); //记录日志信息
            return AjaxResponse.AjaxData(GlobalConfig.ResponseCode.SUCCESS.getCode(),
                    GlobalConfig.ResponseCode.SUCCESS.getDesc(),variables); //返回成功响应
        } catch (Exception e) { //发生异常时
            log.info("查询流程参数:"+instanceID); //记录异常信息
            return AjaxResponse.AjaxData(GlobalConfig.ResponseCode.ERROR.getCode(),
                    GlobalConfig.ResponseCode.ERROR.getDesc(), e.toString()); //返回错误响应
        }
    }

}

7-5 工作任务接口

【TaskController】

package cbsfs.cn.activity.controller;

import cbsfs.cn.activity.config.SecurityUtil;
import cbsfs.cn.activity.pojo.UserInfoBean;
import cbsfs.cn.activity.util.AjaxResponse;
import cbsfs.cn.activity.util.GlobalConfig;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.activiti.api.process.model.ProcessInstance;
import org.activiti.api.process.runtime.ProcessRuntime;
import org.activiti.api.runtime.shared.query.Page;
import org.activiti.api.runtime.shared.query.Pageable;
import org.activiti.api.task.model.Task;
import org.activiti.api.task.model.builders.TaskPayloadBuilder;
import org.activiti.api.task.runtime.TaskRuntime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;


/**
 * @auther admin闫
 * @Descriptopn 任务接口
 * @date 2023/6/6 20:14
 */
@RestController
@RequestMapping("task")
@Api
@Slf4j
public class TaskController {
    @Autowired
    private SecurityUtil securityUtil;

    @Autowired
    private TaskRuntime taskRuntime;

    @Autowired
    private ProcessRuntime processRuntime;

    /**
     * 获取我的代办任务
     * @return 代办任务
     */
    @ApiOperation("获取我的代办任务")
    @GetMapping(value = "/getTasks")
    public AjaxResponse getTasks() {

        try {
            // 判断是否为测试环境,如果是则模拟用户登录
            if (GlobalConfig.test) {
                securityUtil.logInAs("bajie");
            }
            // 分页查询代办任务列表
            Page<Task> task = taskRuntime.tasks(Pageable.of(0, 100));
            List<HashMap<String,Object>> listMap=new ArrayList<>();
            for (Task tk : task.getContent()){
                // 将查询到的代办任务封装成HashMap集合添加到List集合中
                HashMap<String,Object> hashMap=new HashMap<>();
                // 获取代办任务的基本信息并添加到HashMap中
                hashMap.put("ID",tk.getId());
                hashMap.put("name",tk.getName());
                hashMap.put("status",tk.getStatus());
                hashMap.put("createdDate",tk.getCreatedDate());

                // 查询执行人并将其添加到HashMap中
                if (tk.getAssignee()!=null){
                    hashMap.put("assignee",tk.getAssignee());
                }else {
                    hashMap.put("assignee","待拾取任务");
                }

                // 查询对应实例的名称并将其添加到HashMap中
                ProcessInstance processInstance=processRuntime.processInstance(tk.getProcessInstanceId());
                hashMap.put("instanceName",processInstance.getName());
                listMap.add(hashMap);

                log.info("获取我的代办任务"+tk.getName()+":"+tk.getAssignee());
            }
            // 返回成功响应,并包含查询到的代办任务列表
            return AjaxResponse.AjaxData(GlobalConfig.ResponseCode.SUCCESS.getCode(),
                    GlobalConfig.ResponseCode.SUCCESS.getDesc(),listMap);
        } catch (Exception e) {
            log.info("获取我的代办任务失败");
            // 如果出现异常,则返回错误响应,并将异常信息封装到AjaxResponse对象中
            return AjaxResponse.AjaxData(GlobalConfig.ResponseCode.ERROR.getCode(),
                    GlobalConfig.ResponseCode.ERROR.getDesc(), e.toString());
        }
    }

    /**
     * 完成任务
     * @param taskID 代办任务ID
     * @return AjaxResponse
     */
    @ApiOperation("完成任务")
    @GetMapping(value = "/completeTask")
    public AjaxResponse completeTask(@RequestParam("taskID") String taskID) {

        try {
            // 判断是否为测试环境,如果是则模拟用户登录
            if (GlobalConfig.test) {
                securityUtil.logInAs("bajie");
            }
            // 查询待办任务信息
            Task task=taskRuntime.task(taskID);
            // 判断待办任务是否已被认领,如果未认领则进行认领操作
            if (task.getAssignee()==null){
                taskRuntime.claim(TaskPayloadBuilder.claim().withTaskId(task.getId()).build());
            }
            // 完成任务并将完成结果添加到数据库中
            taskRuntime.complete(TaskPayloadBuilder.complete()
                    .withTaskId(task.getId())
//              .withVariable("num","2")
                    .build());

            log.info("完成任务:"+taskID);
            // 返回成功响应
            return AjaxResponse.AjaxData(GlobalConfig.ResponseCode.SUCCESS.getCode(),
                    GlobalConfig.ResponseCode.SUCCESS.getDesc(),null);
        } catch (Exception e) {
            log.info("完成任务"+taskID);
            // 如果出现异常,则返回错误响应,并将异常信息封装到AjaxResponse对象中
            return AjaxResponse.AjaxData(GlobalConfig.ResponseCode.ERROR.getCode(),
                    GlobalConfig.ResponseCode.ERROR.getDesc(), e.toString());
        }
    }

    //渲染动态表单
    //保存动态表单
}

7-6 历史查询接口

【AcitivitiHistoryController】

package cbsfs.cn.activity.controller;

import cbsfs.cn.activity.config.SecurityUtil;
import cbsfs.cn.activity.pojo.UserInfoBean;
import cbsfs.cn.activity.util.AjaxResponse;
import cbsfs.cn.activity.util.GlobalConfig;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.activiti.engine.HistoryService;
import org.activiti.engine.history.HistoricTaskInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @auther admin闫
 * @Descriptopn 历史数据接口
 * @date 2023/6/6 21:13
 */
@RestController
@RequestMapping("/acitivitiHistory")
@Api
@Slf4j
public class AcitivitiHistoryController {

    @Autowired
    private SecurityUtil securityUtil;

    @Autowired
    private HistoryService historyService;

    //用户历史任务
    /**
     * 用户历史任务
     * @param userInfoBean 当前登录用户信息
     * @return 历史任务
     */
    @ApiOperation("用户历史任务")
    @GetMapping(value = "/getInstancesByUserName")
    public AjaxResponse getInstancesByUserName(@AuthenticationPrincipal UserInfoBean userInfoBean) {
        try {
            // 根据用户名查询该用户的历史任务记录并按照结束时间降序排序
            List<HistoricTaskInstance> historicTaskInstances = historyService.createHistoricTaskInstanceQuery()
                    .orderByHistoricTaskInstanceEndTime().desc()
                    .taskAssignee(userInfoBean.getUsername())
                    .list();
            // 返回成功响应,并将结果封装到AjaxResponse对象中
            log.info("用户历史任务"+historicTaskInstances.toString());
            return AjaxResponse.AjaxData(GlobalConfig.ResponseCode.SUCCESS.getCode(),
                    GlobalConfig.ResponseCode.SUCCESS.getDesc(),historicTaskInstances);
        } catch (Exception e) {
            // 如果出现异常,则返回错误响应,并将异常信息封装到AjaxResponse对象中
            log.info("用户历史任务失败"+userInfoBean.toString());
            return AjaxResponse.AjaxData(GlobalConfig.ResponseCode.ERROR.getCode(),
                    GlobalConfig.ResponseCode.ERROR.getDesc(), e.toString());
        }
    }

    /**
     * 根据流程实例ID查询任务
     * @param piID 流程实例ID
     * @return 历史任务
     */
    @ApiOperation("用户历史任务")
    @GetMapping(value = "/getInstancesByPiID")
    public AjaxResponse getInstancesByPiID(@RequestParam("PiID") String piID) {
        try {
            // 根据流程实例ID查询历史任务记录并按照结束时间降序排序
            List<HistoricTaskInstance> historicTaskInstances = historyService.createHistoricTaskInstanceQuery()
                    .orderByHistoricTaskInstanceEndTime().desc()
                    .processInstanceId(piID)
                    .list();
            // 返回成功响应,并将结果封装到AjaxResponse对象中
            log.info("用户历史任务"+historicTaskInstances.toString());
            return AjaxResponse.AjaxData(GlobalConfig.ResponseCode.SUCCESS.getCode(),
                    GlobalConfig.ResponseCode.SUCCESS.getDesc(),historicTaskInstances.toString());
        } catch (Exception e) {
            // 如果出现异常,则返回错误响应,并将异常信息封装到AjaxResponse对象中
            log.info("用户历史任务失败"+piID);
            return AjaxResponse.AjaxData(GlobalConfig.ResponseCode.ERROR.getCode(),
                    GlobalConfig.ResponseCode.ERROR.getDesc(), e.toString());
        }
    }

    //高亮显示流程历史


}

7-10 动态表单渲染方案

image-20230607195653526

image-20230607195727354

image-20230607195748558

image-20230609223951234

7-11 动态表单渲染接口

image-20230609224141221

/**
     * 渲染动态表单
     * @param taskID
     * @return
     */
    @ApiOperation("渲染动态表单")
    @GetMapping(value = "/fromDataShow")
    public AjaxResponse fromDataShow(@RequestParam("taskID") String taskID) {

        try {
            // 判断是否为测试环境,如果是则模拟用户登录
            if (GlobalConfig.test) {
                securityUtil.logInAs("bajie");
            }
            // 查询待办任务信息
            Task task=taskRuntime.task(taskID);
            UserTask userTask=(UserTask) repositoryService.getBpmnModel(task.getProcessDefinitionId())
                    .getFlowElement(task.getFormKey());
            if (userTask == null){
                return AjaxResponse.AjaxData(GlobalConfig.ResponseCode.SUCCESS.getCode(),
                        GlobalConfig.ResponseCode.SUCCESS.getDesc(),"无表单");
            }
            List<FormProperty> formProperties = userTask.getFormProperties();
            List<HashMap<String,Object>> listMap=new ArrayList<>();
            for (FormProperty fp:formProperties){
                HashMap<String,Object> hashMap=new HashMap<>();
                String[] split = fp.getId().split("-_!");
                hashMap.put("id",split[0]);
                hashMap.put("controlType",split[1]);
                hashMap.put("controlLable",split[2]);
//                hashMap.put("controlDefvalue",split[3]);
//                hashMap.put("controlParam",split[4]);
                listMap.add(hashMap);
            }


            log.info("渲染动态表单:"+taskID);
            // 返回成功响应
            return AjaxResponse.AjaxData(GlobalConfig.ResponseCode.SUCCESS.getCode(),
                    GlobalConfig.ResponseCode.SUCCESS.getDesc(),listMap);
        } catch (Exception e) {
            log.info("渲染动态表单失败:"+taskID);
            // 如果出现异常,则返回错误响应,并将异常信息封装到AjaxResponse对象中
            return AjaxResponse.AjaxData(GlobalConfig.ResponseCode.ERROR.getCode(),
                    GlobalConfig.ResponseCode.ERROR.getDesc(), e.toString());
        }
    }

7-12 动态表单解析提交数据

image-20230610000127197

image-20230610001315747

image-20230610001524068

最近更新:: 2025/8/21 13:52
Contributors: yanpeng_
Prev
前后端分离架构
Next
微服务架构