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

1.什么是工作流?
无纸化的办公方案!
2.实现工作流的方案:
需要根据实际业务需要进行调整!
可视化的流程设计
安装插件:2-6 BPMN插件 IDEA官网搜索:actibpm
https://git.imooc.com/coding-454/activiti7_workflow.git
第2章 开发前准备:环境搭建篇【选修】

第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介绍

4-1.2 Activiti7新特性介绍

api的封装

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



4-2 BPMN2.0标准
4-2.1 BPMN2.0简介

4-2.2BPMN2.0标准概述



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



4-3 Springboot与Activit7整合

创建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>初始添加的依赖
<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>添加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文件,并将它们部署到流程引擎中。 # 这样就不需要手动部署流程定义了。数据库创建 -创建一个Activiti7使用的数据库 创建好项目后启动项目,没有报错并检查数据库中是否多表
执行缺失的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


新建BPMN
Activiti插件actiBPM在新版的idea 2020中已经不支持,这里找到一款替代的Activiti BPMN visualizer。部署创建好的工作流实例**【通过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 部署信息表

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


4-5 流程定义ProcessDefinition


①,流程部署后,也会在流程定义表里添加信息,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



每一次启动执行两次节点
第二个环境执行的

流程实例相关操作:【初始化流程实例 / 获取流程实例 / 挂起-激活 / 删除】
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(上)


总结:
①首先流程部署,把BPMN部署上
②查询流程定义的Key
③根据流程定义的key初始化流程实例
④任务查询:查询所有任务【管理员用的】
⑤查询指定用户的的代办任务
4-8 任务处理Task(下)
执行任务时会在这个表里执行数据


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

候选人流程任务
代码:【查询所有任务 / 查询我的代办任务 / 执行任务 / 拾取任务 / 归还与交办任务】
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


代码:【根据用户名查询历史记录 / 根据流程实例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表达式的作用主要是赋值





使用UEL表达式,变量名必须要小写
代码:
- 启动流程实例带参数,指定执行人
- 完成任务带参数,指定流程变量测试
- 启动流程实例带参数,使用实体类
- 任务完成环节带参数,指定多个候选人
- 直接指定流程变量
- 局部变量
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 流程网关-并行

在BPMN中,网关(Gateway)是用于控制流程转移的元素之一。
**并行网关(Parallel Gateway)**是一种可以使流程同时执行多个分支的网关。
**排他网关(Exclusive Gateway)**是一种只允许流程走向一个分支的网关。该网关会根据指定的条件来决定流程走向哪条分支。
**包容网关(Inclusive Gateway)**是一种可以让流程同时走向多个分支的网关。该网关会根据指定的条件来决定流程走向哪些分支。
**事件网关(Event-based Gateway)**是一种基于事件触发的网关。当满足预设条件时,该网关会启动一个或多个分支来处理相应的任务,并将这些分支与其他未启动的分支区分开来,从而保证流程正常运行。

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


代码:
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)**是一种只允许流程走向一个分支的网关。该网关会根据指定的条件来决定流程走向哪条分支。


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

第5章 Activiti 7 新特性尝鲜
使用Activiti 7 新特性,必须要添加Spring security安全框架
5-1 API新特性ProcessRuntime

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

代码:【获取任务列表 / 完成任务】
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用户登录




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配置

①添加配置类【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

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

使用postMan对未登录进行测试
地址:http://localhost:8080/loginxx
参数:无
未体现
5-6 BPMN-JS整合
BPMN官网:https://bpmn.io/toolkit/bpmn-js/


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

下载下来找到:

复制到目录

进入这个目录:D:\MD笔记\工作流\Activity7_my\src\main\resources\resources\bpmnjs 这里是你的路径
运行npm 命令:
npm install --legacy-peer-deps 这里相对于js的版本过老,如果要使用中文汉化,需要运行这个命令才能安装
npm run dev 这里使用这个命令楚辞运行
运行成功后,是英文版的,这里给这个例子汉化
将BPMN初始化包复制到

打开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系统:需求分析与设计


6-1 页面功能设计
① 产品原形设计

6-2 数据库设计


已经创建用户表,需要再创建用户角色中间关系表,角色表,表单业务数据
表单业务数据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)
);

静态表和动态业务表单
6-3 流程定义接口设计

接口文档 - 流程定义processDefinition
本接口文档详情如下:
上传BPMN流媒体./uploadStream
请求
- 方法: POST
- URL: http://localhost:8080/processDefinition/./uploadStreamAndDeployment
- Header: 无
- Body:
| 参数名 | 描述 |
|---|---|
| processsFile | BPMN 文件 |
响应
- 状态码: 无
- Body: 无
部署BPMN字符./addDeploymentByString
请求
- 方法: POST
- URL: http://localhost:8080/processDefinition/addDeploymentByString?stringBPMN=&deploymentName
- Header: 无
- Query 参数:
| 参数名 | 描述 |
|---|---|
| stringBPMN | xml 数据 |
| 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 |
| resourceName | BPMN 文件名称 |
响应
- 状态码: 无
- 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 返回值与配置工具类

创建工具类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 动态表单渲染方案




7-11 动态表单渲染接口

/**
* 渲染动态表单
* @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 动态表单解析提交数据



