语言确实有其局限性,但我相信:一件值得做的事情即使做的不怎么样也是值得的!
概念
1.流程审批以前的实现方式
在没有专门的工作流引擎之前,为了实现流程控制,通常的做法就是采用状态字段的值来跟踪流程的变化情况。通过状态字段的取值来决定记录是否显示。
缺点:耦合性太高
通过状态字段虽然做到了流程控制,但是当我们的流程发生变更的时候,这种方式所编写的代码也要进行调整。
2.activiti工作流引擎
官方网站:https://www.activiti.org/
业务系统和流程系统剥离
Activiti是一个工作流引擎,activiti可以将业务系统中复杂的业务流程抽取出来。
使用专门的建模语言BPMN2.O进行定义,业务流程按照预先定义的流程进行执行,实现了系统的流程由activiti进行管理,减少业务系统由于流程变更进行系统升级改造的工作量,从而提高系统的健壮性,同时也减少了系统开发维护成本。
3.BPM和BPMN
BPM(Business Process Management)即业务流程管理,是一种规范化的构造端到端的业务流程
BPMN(Business Process Model AndNotation)-业务流程模型和符号是由BPMl(BusinessProcess
Management Initiative)开发的一套标准的业务流程建模符号,使用BPMN提供的符号可以创建业务流程。
Bpmn图形其实是通过XML表示业务流程,流程图其实就是一个XML,由流程设计器读取这XML,显示为图形化界面
activiti的使用
1.activiti部署
Activiti是一个工作流引擎(其实就是一堆jar包API),业务系统访问activiti的接口,就可以方便的操作流程相关数据,这样就可以把工作流环境与业务系统的环境集成在一起
2.流程定义器
使用activiti流程建模工具(activity-designer)定义业务流程(.bpmn文件)。.bpmn文件就是业务流程定义文件,通过xml定义业务流程。
3.流程存储数据库
activiti部署业务流程定义(.bpmn文件)时:
需要使用activiti提供的api把流程定义内容存储在数据库中,在activiti执行过程中可以查询定义的内容
4.启动流程实例
流程实例也叫:ProcessInstance
启动一个流程实例表示开始一次业务流程的运行
注意:多个流程实例之间互相不影响
5.查询和办理任务
系统的业务流程交给activiti管理,通过activiti就可以查询当前流程执行到哪了,当前用户需要办理什么任务了,这些activiti帮我们管理了,而不需要开发人员自己编写sq语句查询。
用户办理任务:用户查询待办任务后,就可以办理某个任务
6.流程结束
当任务办理完成没有下一个任务结点了,这个流程实例就完成了。
activiti整合SpringBoot环境搭建
1.下载activitiBPM插件
actiBPM - IntelliJ IDEs Plugin | Marketplace
点击下载
打开idea的插件
在resource/processes创建bpmn文件
定义流程,按照BPMN的规范,使用流程定义工具,用流程符号把整个流程描述出来
2.POM文件引入maven依赖
pom.xml文件全部内容如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xym</groupId>
<artifactId>activiti-project</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>7.0.0.Beta2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.application.yml配置文件
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql:///actspringboot?useUnicode=true&characterEconding=utf8&serverTimezone=GMT
username: root
password: 123456
activiti:
#true:表不存在会自动创建
database-schema-update: true
#开启历史表
db-history-used: true
#保存历史数据的最高级别(会保存流程相关的细节数据)
history-level: full
4.SecurityUtil 和 DemoApplicationConfig
/**
* @Description:快速实现SpringSecurity安全框架的配置
* @Author: xiaoyumao
* @Date: 2022/11/25 16:21
*/
public class SecurityUtil {
private Logger logger = LoggerFactory.getLogger(SecurityUtil.class);
@Autowired
@Qualifier("myUserDetailsService")
private UserDetailsService userDetailsService;
public void logInAs(String username) {
UserDetails user = userDetailsService.loadUserByUsername(username);
if (user == null) {
throw new RuntimeException("用户" + username + "不存在");
}
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 b) throws IllegalArgumentException {
}
@Override
public String getName() {
return user.getUsername();
}
}));
org.activiti.engine.impl.identity.Authentication.setAuthenticatedUserId(username);
}
}
/**
* @Description: 实现SpringSecurity框架的用户权限的配置
* @Author: xiaoyumao
* @Date: 2022/11/25 16:39
*/
@Slf4j
@Configuration
public class DemoApplicationConfig {
private Logger logger = LoggerFactory.getLogger(DemoApplicationConfig.class);
@Bean
public UserDetailsService myUserDetailsService() {
//把用户存储在内存中
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
//这里添加用户,后面处理流程用到的任务负责人在这里添加
String[][] usersGroupsAndRoles = {
{"jack", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
{"rose", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
{"tom", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
{"other", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
{"system", "password", "ROLE_ACTIVITI_USER"},
{"admin", "password", "ROLE_ACTIVITI_USER"},
};
for (String[] user : usersGroupsAndRoles) {
//把用户的角色和组拿出来
List<String> authoritiesStrings = Arrays.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(str -> new SimpleGrantedAuthority(str)).collect(Collectors.toList())));
}
return inMemoryUserDetailsManager;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
5.测试类-部署/查询流程
activiti7可以自动部署流程,前提是在resources目录下创建一个新的目录processes,用来放置bpmn文件
部署流程,把画好的流程定义文件,加载到数据库中,生成表的数据
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class Demo {
@Autowired
private ProcessRuntime processRuntime;
@Autowired
private TaskRuntime taskRuntime;
@Autowired
private SecurityUtil securityUtil;
/**
* 查看流程定义内容
*/
@Test
public void findProcess(){
//说明哪个用户正在执行这个方法
securityUtil.logInAs("jack");
//流程定义的分页对象
Page<ProcessDefinition> definitionPage = processRuntime.processDefinitions(Pageable.of(0,10));
log.info("流程定义总数:{}",definitionPage.getTotalItems());
for (ProcessDefinition processDefinition : definitionPage.getContent()) {
System.out.println("=====================================================");
log.info("流程定义内容:{}",processDefinition);
System.out.println("=====================================================");
}
}
}
在数据库中,就可以看的创建的表
6.测试类-启动流程
启动流程,使用java代码来操作数据库表中的内容
/**
* 启动流程
*/
@Test
public void startProcess(){
//设置登录用户
securityUtil.logInAs("system");
ProcessInstance processInstance = processRuntime
.start(ProcessPayloadBuilder.start().withProcessDefinitionKey("myProcess_1").build());
log.info("流程实例内容:{}",processInstance);
}
控制台打印
7.测试类-执行任务
活动Activity 活动是工作或任务的一个通用术语。一个活动可以是一个任务,还可以是一个当前流程的子处理流程;其次,你还可以为活动指定不同的类型。
/**
* 执行任务
*/
@Test
public void doTask() {
//设置登录用户 如果other用户属于别的组,查不到任务
securityUtil.logInAs("rose");
//查询任务
Page<Task> taskPage = taskRuntime.tasks(Pageable.of(0, 10));
if (taskPage != null && taskPage.getTotalItems() > 0) {
for (Task task : taskPage.getContent()) {
//拾取任务
taskRuntime.claim(TaskPayloadBuilder.claim().withTaskId(task.getId()).build());
log.info("任务内容:{}", task);
//完成任务
taskRuntime.complete(TaskPayloadBuilder.complete().withTaskId(task.getId()).build());
}
}
}
activiti的25张表
1.表前缀含义
ACT_RE:“RE”代表“Repository”(仓库),这些表中保存一些‘静态’信息,如流程定义和流程资源(如图片、规则等);
ACT RU:'RU'表示runtime。这些运行时的表,包含流程实例,任务,变量,异步任务,等运行中的数据
activiti只在流程实例执行过程中保存这些数据,在流程结束时就会别除这些记录。这样运行时表可以一直很小速度很快。
ACT_HI* : “HI”代表“History”(历史),这些表中保存的都是历史数据,比如执行过的流程实例、变量、任务,等等。Activit默认提供了4种历史级别:
Ø none: 不保存任何历史记录,可以提高系统性能;
Ø activity:保存所有的流程实例、任务、活动信息;
Ø audit:也是Activiti的默认级别,保存所有的流程实例、任务、活动、表单属性;
Ø full:最完整的历史记录,除了包含audit级别的信息之外还能保存详细,例如:流程变量。
对于几种级别根据对功能的要求选择,如果需要日后跟踪详细可以开启full。
ACT_GE:“GE”代表“General”(通用),用在各种情况下;
2.activiti数据表清单
此外还有两张表:ACT_EVT_LOG和ACT_PROCDEF_INFO没有按照规则来,两者分别属于HI和RE。
3.获取service操作表(原生的)
activiti的入门
1.流程变量
流程变量就是activiti在管理工作流时根据管理需要而设置的变量。在连线上使用UEL表达式,决定流程走向
流程运转有时需要靠流程变量,比如:在出差申请流程流转时如果出差天数大于3天则由经理审核,否则侧由人事直接审核,出差天数就可以设置为流程变量,在流程流转时使用。
流向Flow
流是连接两个流程节点的连线。常见的流向包含以下几种:
2.组任务
a、查询组任务
指定候选人,查询该候选人当前的待办任务。注意候选人不能立即办理任务。
b、拾取(claim)任务
该组任务的所有候选人都能拾取。
将候选人的组任务,变成个人任务。原来候选人就变成了该任务的负责人。
归还
如果拾取后不想办理该任务?
需要将已经拾取的个人任务归还到组里边,将个人任务变成了组任务。====
转办
c、查询个人任务
查询方式同个人任务部分,根据assignee查询用户负责的个人任务。
d、办理个人任务
3.网关
网关用来处理决策,有几种常用网关需要了解:
为什么用网关
如果不用网关,传过来的流程变量不符合任何一个条件,会马上结束当前的任务(这样不好)
如果用的是排他网关,就不是结束任务,而是报错。(这样好)
排他网关
排他网关只会选择一个为true的分支执行。如果有两个分支条件都为true,排他网关会选择id值较小的一条分支去执行。
并行网关
并行网关允许将流程分成多条分支,也可以把多条分支汇聚到一起,并行网关的功能是基于进入和外出顺序流的:
fork分支:
并行后的所有外出顺序流,为每个顺序流都创建一个并发分支。
join汇聚:
所有到达并行网关,在此等待的进入分支,直到所有进入顺序流的分支都到达以后,流程就会通过汇聚网关。与其他网关的主要区别是,并行网关不会解析条件。即使顺序流中定义了条件,也会被忽略
包含网关
包含网关的功能是基于进入和外出顺序流的:
分支:
所有外出顺序流的条件都会被解析,结果为true的顺序流会以并行方式继续执行,会为每个顺序流创建一个分支。
汇聚:
所有并行分支到达包含网关,会进入等待状态,直到每个包含流程token的进入顺序流的分支都到达。这是与并行网关的最大不同。换句话说,包含网关只会等待被选中执行了的进入顺序流。在汇聚之后,流程会穿过包含网
关继续执行。