工作流:我的理解-->工作流是一种简化审批业务流程开发的技术 至少我现在看到的都是这样
当然例如退货,甚至前后企业的ERP系统都感觉可以使用工作流来完成
activiti的一些缺点
-
提供的设计器只能开发者使用,而且体验不是很好
-
不支持直接面向业务人员使用
-
不支持表单
-
不支持国产功能,比如会签、跳转等功能
-
不支持流程状态图
-
与SpringClound微服务集成方式不优雅
-
使用门槛、成本比较高
如果将工作流单独抽出成一个应用
一般的审批业务大致
1.集成流程引擎
2.画流程
3.保存流程图
4.部署和运行流程图
5.开发同意和不同意审批功能
SpringBoot集成Activiti8
使用最新版本SpringBoot
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>demo</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>8.7.0</version>
<!-- <exclusions>-->
<!-- <!– 排除 mybatis(低版本的activiti-spring-boot-starter需要移除Mybatis依赖) 相关依赖 –>-->
<!-- <exclusion>-->
<!-- <groupId>org.mybatis</groupId>-->
<!-- <artifactId>mybatis</artifactId>-->
<!-- </exclusion>-->
<!-- </exclusions>-->
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.31</version>
</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>3.0.4</version>-->
<!-- </dependency>-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.9</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-jsqlparser</artifactId>
<version>3.5.9</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter-test</artifactId>
<version>3.0.4</version>
<scope>test</scope>
</dependency>
</dependencies>
<repositories>
<repository>
<id>activiti-releases</id>
<url>https://artifacts.alfresco.com/nexus/content/repositories/activiti-releases</url>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
Activity中引入了Security
package com.example.demo;
import org.activiti.api.runtime.shared.identity.UserGroupManager;
import org.activiti.core.common.spring.identity.config.ActivitiSpringIdentityAutoConfiguration;
import org.activiti.spring.boot.ProcessEngineAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import java.util.List;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
//SecurityManager在JDK17已经废除
// @Bean
// public SecurityManager securityManager() {
// return new SecurityManager() {
// @Override
// public String getAuthenticatedUserId() {
// return "miukoo-user-1";
// }
//
// @Override
// public List<String> getAuthenticatedUserGroups() throws SecurityException {
// return null;
// }
//
// @Override
// public List<String> getAuthenticatedUserRoles() throws SecurityException {
// return null;
// }
// };
// }
@Bean
public UserGroupManager userGroupManager() {
return new UserGroupManager(){
@Override
public List<String> getUserGroups(String username) {
return null;
}
@Override
public List<String> getUserRoles(String username) {
return null;
}
@Override
public List<String> getGroups() {
return null;
}
@Override
public List<String> getUsers() {
return null;
}
};
}
}
application.yml中的配置
server:
port: 8081
spring:
activiti:
database-schema-update: true # 自动创建数据库
db-history-used: true # 开启流程历史记录
history-level: full # 历史流程数据会全部记录到历史表中,支持none, activity, audit, full, 4种粒度的数据,常用的就是full
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/activiti?allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC&nullCatalogMeansCurrent=true#nullCatalogMeansCurrent=true一定要有
username: root
password: 123456
mybatis-plus:
#指定实体类
type-aliases-package: com.example.activiti.entity#指定自己的包
mapper-locations: classpath*:mappers/*.xml
configuration:
map-underscore-to-camel-case: true # 驼峰命名
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 输出sql
BPMN介绍:
BPMN:业务流程建模标记语言.就是那个乱78遭的XML
流程图最终也是转成那个.xml
示例
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:activiti="http://activiti.org/bpmn" xmlns:bioc="http://bpmn.io/schema/bpmn/biocolor/1.0" xmlns:tns="http://www.activiti.org/testm1685269119653" xmlns:xsd="http://www.w3.org/2001/XMLSchema" id="hiss_flow_1692440641378" name="" targetNamespace="http://www.activiti.org/testm1685269119653">
<process id="hiss_process_12_1692440641431" name="hehe_process" isExecutable="true">
<startEvent id="StartEvent_1" />
<userTask id="Activity_1xi315o" name="填写请假单" activiti:assignee="张三">
<incoming>Flow_109kst5</incoming>
<outgoing>Flow_0wd9dr1</outgoing>
</userTask>
<sequenceFlow id="Flow_109kst5" sourceRef="StartEvent_1" targetRef="Activity_1xi315o" />
<userTask id="Activity_0xmqcac" name="经理审批" activiti:assignee="李四">
<incoming>Flow_0wd9dr1</incoming>
<outgoing>Flow_1s2a4e5</outgoing>
</userTask>
<sequenceFlow id="Flow_0wd9dr1" sourceRef="Activity_1xi315o" targetRef="Activity_0xmqcac" />
<endEvent id="Event_1qwztvn">
<incoming>Flow_1s2a4e5</incoming>
</endEvent>
<sequenceFlow id="Flow_1s2a4e5" sourceRef="Activity_0xmqcac" targetRef="Event_1qwztvn" />
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="hiss_process_12_1692440641431">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="173" y="102" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1xi315o_di" bpmnElement="Activity_1xi315o">
<dc:Bounds x="330" y="80" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0xmqcac_di" bpmnElement="Activity_0xmqcac">
<dc:Bounds x="560" y="80" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1qwztvn_di" bpmnElement="Event_1qwztvn">
<dc:Bounds x="792" y="102" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_109kst5_di" bpmnElement="Flow_109kst5" bioc:stroke="#060606" bioc:fill="none">
<di:waypoint x="209" y="120" />
<di:waypoint x="330" y="120" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0wd9dr1_di" bpmnElement="Flow_0wd9dr1" bioc:stroke="#060606" bioc:fill="none">
<di:waypoint x="430" y="120" />
<di:waypoint x="560" y="120" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1s2a4e5_di" bpmnElement="Flow_1s2a4e5" bioc:stroke="#060606" bioc:fill="none">
<di:waypoint x="660" y="120" />
<di:waypoint x="792" y="120" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
这个好比建模
@SpringBootTest
@Slf4j
class DemoApplicationTests {
@Autowired
private RepositoryService repositoryService;
//@Test
@ParameterizedTest
@ValueSource(strings = {"D:\\test\\bpmn-01.xml"})
public void contextLoads(String file) {
Model model = repositoryService.newModel();//快速新建一个流程模型的对象
model.setName("单人审批请假流程"); // 设置名称
model.setCategory("学习"); // 设置分类
//保存到ACT_RE_MODEL
repositoryService.saveModel(model);// 保存BPMN设计好的流程文件的方法
String xml = FileUtil.readUtf8String(file);
//保存到ACT_GE_BYTEARRAY(可以看作是ACT_RE_MODEL的附件表)
repositoryService.addModelEditorSource(model.getId(),xml.getBytes());// 保存xml信息
assertNotNull(model.getId()); // 断言插入成功
//4bc2b9d5-e292-11ef-8d3a-00ff5bcf811d
System.out.println("model.getId() = " + model.getId());
}
}
ACT_RE_MODEL表
ACT_GE_BYTEARRAY表
建模之后 要发布(部署) 为什么要这么做? 万一需要修改呢 建模之后需要修改
事实上部署的那张表就好比把建模的这张表数据拷贝了一份过去
那么部署之后 每启动一次 就是一个流程实例(当然流程里的填写的表单数据不一样)
模型好比.XML文件 部署好比model的备份
部署
@Autowired
RuntimeService runtimeService;
/**
* 流程部署测试
*/
@Test
//示例 这样方式 "单人审批请假流程"不能重复
public void testDeployment(){
// 通过名称查询出之前保存的流程图
Model model = repositoryService.createModelQuery().modelName("单人审批请假流程").singleResult();
assertNotNull(model); // 断言查询成功
byte[] xml = repositoryService.getModelEditorSource(model.getId());// 获取内容
String xmlName = "222.bpmn20.xml";//bpmn20.xml结尾是一个约定
DeploymentBuilder deploymentBuilder = repositoryService.createDeployment()
.key(model.getKey())
.name(model.getName())
.category(model.getCategory())
.enableDuplicateFiltering()// 自动去重
.tenantId(model.getTenantId())
.addBytes(xmlName, xml);// 设置部署的xml名称、内容
Deployment deploy = deploymentBuilder.deploy(); // 部署
assertNotNull(deploy.getId()); // 断言插入成功
model.setDeploymentId(deploy.getId());
repositoryService.saveModel(model); // 部署的id更新回Model对象
log.info("new deployment id : {}", model.getId());
}
@Test
public void testDeployment2() {
String modelId = "99a327d6-e2b1-11ef-b8e6-00ff5bcf811d";
String xmlName = UUID.randomUUID()+".bpmn20.xml";//这个只是个名称 无所谓 把byte[]放进去
Model model = repositoryService.getModel(modelId); // 查询模型基本信息
byte[] xmlContent = repositoryService.getModelEditorSource(modelId); // 读取模型的xml内容
DeploymentBuilder deploymentBuilder = repositoryService.createDeployment()
.category(model.getCategory())
.name(model.getName())
.addBytes(xmlName, xmlContent);
//存到act_re_deployment
Deployment deployment = deploymentBuilder.deploy(); // 部署(把原模型文件复制一份)
model.setDeploymentId(deployment.getId());
repositoryService.saveModel(model); // 把部署的id回写到模型表中
}
运行 需要一个流程定义ID 所谓流程定义ID就是BPMN文件中的process id =xxxxxx
也就是act_re_procdef表中的KEY_字段
返回的ProcessInstance就是流程实例 act_hi_procinst 流程实例表
/**
* 运行测试
*/
@ParameterizedTest
@ValueSource(strings = {"hiss_process_12_1692440641431"})
public void testRun(String key){
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(key);
assertNotNull(processInstance.getId()); // 断言插入成功
System.out.println("processInstance.getId() = " + processInstance.getId());
}
运行实例
会往这些表中插入数据
ACT_HI_TASKINST 任务分配表(只记录有人参与的节点信息)
ACT_HI_PROCINST 流程定义表(就是部署之后从model复制了一份)
ACT_HI_ACTINST
ACT_HI_IDENTITYLINK
运行时用户关系表 任务与用户/用户组的关系表ACT_RU_EXECUTION
运行时流程执行实例(记录当前执行节点信息)ACT_RU_TASK
运行时任务表 任务指派给谁 里面的ASSIGNEE_字段 可定义为色(需要人参与的节点)另外一张是
act_ru_variable ;运行时变量表 这里没有变量插入
这些表通常与工作流或流程引擎(如 Activiti 或 Flowable)相关,存储了任务、流程实例、执行、身份链接等数据。
act_ru_task
上面 建模 部署 运行 都已经完成了
下面开始办理功能
大致分为三个方法来完成上面BPMN的功能
节点二就是将请假单填写好 例如请假人 请假天数 原因等等 然后经理审批同意或拒绝
@Autowired
private TaskService taskService;
/**
* 完成填写清单
* @param taskId
*/
@ParameterizedTest
@ValueSource(strings = {"4da685db-e312-11ef-9761-00ff5bcf811d"})
//说就是 act_ru_task 表的ID
public void testCompleteNode2(String taskId) {
//存储请假单的信息
//流程变量:给流程传递的一些表单数据值
Map<String,Object> variables = new HashMap<>();
variables.put("apply","张三");
variables.put("days","好几年");
variables.put("reason","因公 visited");
variables.put("startDate","2022-02-21");
variables.put("endDate","2025-02-22");
//taskService.complete(taskId);//告诉Activiti当前任务已经完成,可以下一步了
taskService.complete(taskId, variables);
}
运行上面的会有个报错 没关系 Security L78Z的东西
在 act_ru_variable中
而在act_hi_varinst表中也会记录一份
原因是act_ru_variable中的记录在流程结束会清除 而act_hi_varinst做为历史记录
下面流程已经到经理审批了 那么审批暂时就同意 拒绝
/**
* 同意
* @param taskId
*/
@ParameterizedTest
//act_ru_task 表的ID因为已经变成李四 当然实际运用的时候关联查
@ValueSource(strings = {"465544b8-e31b-11ef-9061-00ff5bcf811d"})
public void testAgree(String taskId) {
// 把【同意】看做是填写的【审批表单(包括:审批结果、审批意见)】中的approvalStatus字段
Map<String, Object> variables = new HashMap<>();
variables.put("approvalStatus","同意");//都是变量 实际中定义一个变量名 来表示这个变量是表示同意的
variables.put("approvalNote","同意的备注意见:请好好玩,玩好了再上班");
//taskService.complete(taskId);//告诉Activiti当前任务已经完成,可以下一步了 那么此时已经结束了
taskService.complete(taskId,variables);//同意 但是有审批意见(变量)等
}
act_ru_task表和act_ru_variable表中的数据全部已经清空了
历史活动表中的数据
要测试不同意 因为前面流程已经结束了
需要重新运行下
/**
* 拒绝
* @param processInstanceId
*/
@ParameterizedTest
//act_ru_execution表中的流程实例的id
@ValueSource(strings = {"d3e0a207-e31f-11ef-977d-00ff5bcf811d"})
public void testReject(String processInstanceId) {
String reason="就是不同意就是不同意";
Map<String, Object> variables = new HashMap<>();
variables.put("approvalStatus", "不同意");//这个字段记录审批同意或者拒绝
variables.put("approvalNote", reason);
// 记录流程变量
runtimeService.setVariables(processInstanceId, variables);
// 添加流程变量,表示任务被拒绝
runtimeService.deleteProcessInstance(processInstanceId, reason);
}