说到审批,就一定会有流程又称工作流(Workflow)。例如 开始 -> 申请 -> 主管审批 -> 人事审批 -> 结束,就是一个简单业务审批流程,按照某种预定义的规则传递文档、信息或任务的过程,通俗的说流程就是多种业务对象在一起合作完成某件事情的步骤。而在计算机体系中为了把步骤变成计算机能理解的形式就衍生出了流程引擎!在复杂多变的业务场景下,流程引擎能保证我们业务执行的准确性,大大降低我们设计业务的成本,因此市面也出现了Osworkflow、JBPM、Activiti、flowable、Camunda等功能强大的流程引擎被各行各业的计算机应用所利用。
在没有专门的工作流引擎之前,我们之前为了实现流程控制,通常的做法就是采用状态字段的值来跟踪流程的变化情况。这样不用角色的用户,通过状态字段的取值来决定记录是否显示。
针对有权限可以查看的记录,当前用户根据自己的角色来决定审批是否合格的操作。如果合格将状态字段设置一个值,来代表合格;当然如果不合格也需要设置一个值来代表不合格的情况。
这是一种最为原始的方式。通过状态字段虽然做到了流程控制,但是当我们的流程发生变更的时候,这种方式所编写的代码也要进行调整。
那么有没有专业的方式来实现流程的管理呢?并且可以做到业务流程变化之后,我们的程序也可以不用改变,依然能自动化的控制流程?答案就是:流程引擎。
本篇文章以开源的Activiti
流程引擎来介绍如何通过计算机技术对业务流程进行自动化的管理,高效准确的完成某种作业!为什么选Activiti
?
常见开源流程引擎对比:
技术组成 | Activiti | jBPM5 |
---|---|---|
数据库持久层ORM | MyBatis3 | Hibernate3 |
持久化标准 | 无 | JPA规范 |
事务管理 | MyBatis机制/Spring事务控制 | Bitronix,基于JTA事务管理 |
数据库连接方式 | Jdbc/DataSource | Jdbc/DataSource |
支持数据库 | Oracle、SQL Server、MySQL等多数数据库 | Oracle、SQL Server、MySQL等多数数据库 |
设计模式 | Command模式、观察者模式等 | |
内部服务通讯 | Service间通过API调用 | O基于Apache Mina异步通讯 |
集成接口 | SOAP、Mule、RESTful | 消息通讯 |
支持的流程格式 | BPMN2、xPDL、jPDL等 | 目前仅只支持BPMN2 xml |
引擎核心 | PVM(流程虚拟机) | Drools |
技术前身 | jBPM3、jBPM4 | Drools Flow |
所属公司 | Alfresco | jBoss.org |
Activiti
是Java
实现了工业领域BPMN2.0
的规范标准框架,老牌、成熟稳定且目前用户众多、社区活跃、趋势较好(使用越来越多)、易于上手,基于Spring、MyBatis常用互联网技术堆栈。(原文链接:https://javaforall.cn/125263.html)
官网地址:https://www.activiti.org/
多种业务对象在一起合作完成某件事情的步骤,把步骤变成计算机能理解的形式就是流程引擎。
流程引擎系统(Process Engine System
)是一个软件系统,它完成流程的定义和管理,并按照在系统中预先定义好的流程规则进行流程实例的执行。流程引擎系统不是企业的业务系统,而是为企业的业务系统的运行提供了一个软件的支撑环境。
流程引擎总是以任务(Task
)的形式驱动人处理业务或者驱动业务系统自动完成作业。有了流程引擎之后,我们不必一直等待其他人的工作进度,直白地说,我们只需要关心系统首页的待办任务数即可,由系统提醒当前有多少待办任务需要处理。因此可以自动化实现流程的管理。
流程引擎应用广泛,在由流程驱动的各种系统中都有应用,例如OA、CRM、ERP、ECM、BI等。在企业应用中还有很多产品或平台集成流程引擎,用来处理系统运行过程中发起的业务流程。
具体应用:
关键业务流程:订单. 报价处理. 合同审核. 客户电话处理. 供应链管理等
行政管理类:出差申请. 加班申请. 请假申请. 用车申请. 各种办公用品申请. 购买申请. 日报周报等凡是原来手工流转处理的行政表单。
人事管理类:员工培训安排. 绩效考评. 职位变动处理. 员工档案信息管理等。
财务相关类:付款请求. 应收款处理. 日常报销处理. 出差报销. 预算和计划申请等。
客户服务类:客户信息管理. 客户投诉. 请求处理. 售后服务管理等。
特殊服务类:ISO系列对应流程. 质量管理对应流程. 产品数据信息管理. 贸易公司报关处理. 物流公司货物跟踪处理等各种通过表单逐步手工流转完成的任务均可应用工作流软件自动规范地实施。
Activiti
是由Alfresco软件在2010年5月17日发布的业务流程管理(BPM)框架,它是覆盖了业务流程管理、工作流、服务协作等领域的一个开源的、灵活的、易扩展的可执行流程语言框架。Activiti基于Apache许可的开源BPM平台,创始人Tom Baeyens
也是JBoss jBPM的项目架构师。
Activiti
是一个工作流引擎, Activit
可以将业务系统中复杂的业务流程抽取出来,使用专门的建模语言BPMN2.0
进行定义,业务流程按照预先定义的流程进行执行,实现了系统的流程由Activiti
进行管理,减少业务系统由于流程变更进行系统升级改造的工作量,从而提高系统的健壮性,同时也减少了系统开发维护成本。
BPM(Business Process Management
),即业务流程管理,是一种规范化的构造端到端的业务流程,以持续的提高组织业务效率。常见商业管理教育如EMBA、MBA等均将BPM包含在内。
有了BPM规范后,相应的BPM软件也就诞生,BPM就是根据企业中业务环境的变化,推进人与人之间、人与系统之间以及系统与系统之间的整合及调整的经营方法与解决方案的IT工具。通过BPM软件对企业内部及外部的业务流程的整个生命周期进行建模、自动化、管理监控和优化,使企业成本降低,利润得以大幅提升。
BPM软件在企业中应用领域广泛,凡是有业务流程的地方都可以BPM软件进行管理,比如企业人事办公管理、采购流程管理、公文审批流程管理、财务管理等。
BPMN(Business Process Model AndNotation
)业务流程模型和符号是由BPMI(BusinessProcess Management Initiative
)开发的一套标准的业务流程建模符号,使用BPMN提供的符号可以创建业务流程。
2004年5月发布了BPMN1.0规范.BPMI于2005年9月并入OMG(The Object Management Group对象管理组织)组织。OMG于2011年1月发布BPMN2.0的最终版本。
BPMN 是目前被各 BPM 厂商广泛接受的 BPM 标准。Activiti
就是使用 BPMN 2.0 进行流程建模、流程执行管理,它包括很多的建模符号,比如:
圆圈代表Event
(流程中运行过程中发生的事情),包括开始事件和结束事件。
活动用圆角矩形表示,一个流程由一个活动或多个活动组成。如上图所有元素组成一个.bpmn
文件,称为业务流程模型。Bpmn图形其实是通过xml表示业务流程,使用文本编辑器打开或导出得到就是一个xml文件。
所谓的流程引擎其实就是一堆非常完善的jar包API,业务系统访问(操作)Activiti
的接口,就可以方便的操作流程相关数据,这样就可以把工作流环境与业务系统的环境集成在一起。因此要想学习这些API,我们必须搭建Activiti7
学习环境。
Activiti
运行需要有数据库的支持,支持的数据库有:h2、mysql、oracle、postgres、mssql、db2等。因此事先需要准备好一个数据库环境。
1、使用SpringBoot
初始化向导创建一个工程(版本我们选择2.6.12),并添加如下maven
依赖(activiti也选择最新版本),暂时先引入这些依赖,后面需要什么再引入什么即可。
pom.xml
<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.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.6.12version>
<relativePath/>
parent>
<groupId>com.laizhenghuagroupId>
<artifactId>activitiartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>activityname>
<description>Demo project for Spring Bootdescription>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.activitigroupId>
<artifactId>activiti-spring-boot-starterartifactId>
<version>7.1.0.M6version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.46version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
2、配置数据源(略)
3、使用activiti-spring-boot-starter
(即Activiti7
)坐标依赖后,默认集成了SpringSecurity
安全框架,因此我们需要新增SpringSecurity
相关配置。
内存用户模拟:为了方便测试,官方在GitHub
上给出了例子,我们也完全可以采用这种方式,因为只是测试,没必要完善用户信息获取的代码(用户信息存储在数据库),新增一个UserDetailsServiceConfiguration
(名字可以随便取)配置类,例如:
/**
* @description:
* @author: laizhenghua
* @date: 2022/10/16 16:11
*/
@Configuration
public class UserDetailsServiceConfiguration {
private Logger logger = LoggerFactory.getLogger(UserDetailsServiceConfiguration.class);
@Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
String[][] usersGroupsAndRoles = {{"system", "password", "ROLE_ACTIVITY_USER"}, {"admin", "password", "ROLE_ACTIVITY_ADMIN"}};
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(s -> new SimpleGrantedAuthority(s)).collect(Collectors.toList())));
}
return inMemoryUserDetailsManager;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
权限认证:也是新增一个ActivitiUserLoginConfiguration
(名字可以随便取)配置类,封装登录后权限认证功能,例如:
/**
* @description:
* @author: laizhenghua
* @date: 2022/10/16 16:19
*/
@Component
public class ActivityUserLoginConfiguration {
private Logger logger = LoggerFactory.getLogger(ActivityUserLoginConfiguration.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);
}
}
模拟登录进行认证与授权:在主程序中模拟登录即主程序实现CommandLineRunner
接口,在所有的Spring Bean
都初始化好之后,SpringApplication.run()
之前进行登录,完成认证与授权,例如:
@SpringBootApplication
public class ActivitiApplication implements CommandLineRunner {
@Autowired
private ActivityUserLoginConfiguration activityUserLoginConfiguration;
public static void main(String[] args) {
SpringApplication.run(ActivityApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
// 模拟登录
activityUserLoginConfiguration.logInAs("system");
}
}
以上均来自GitHub
上的例子,感兴趣的小伙伴可以自行研究下。
在测试类上创建流程引擎ProcessEngine
,创建过程中会自动为我们创建表!
@SpringBootTest
class ActivityApplicationTests {
@Autowired
private SpringProcessEngineConfiguration springProcessEngineConfiguration;
@Test
public void test() {
ProcessEngine processEngine = springProcessEngineConfiguration.buildProcessEngine();
System.out.println(processEngine);
}
}
最后去数据库查看下是否帮我们生成了一些表,如果生成了所有环境已经集成好了。
为了文章的完整性我们也给出原生Spring
与Activiti7
的整合方式,其实也就是熟悉Activiti7
的相关XML
配置文件。
1、使用Maven
新建一个工程,得到一个干净的结构,例如
2、引入相关依赖,详见pom.xml
文件
pom.xml
<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.0modelVersion>
<groupId>com.laizhenghuagroupId>
<artifactId>actartifactId>
<version>1.0-SNAPSHOTversion>
<properties>
<spring.version>5.3.18spring.version>
<activiti.version>7.0.0.Beta1activiti.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>${spring.version}version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.activitigroupId>
<artifactId>activiti-engineartifactId>
<version>${activiti.version}version>
dependency>
<dependency>
<groupId>org.activitigroupId>
<artifactId>activiti-springartifactId>
<version>${activiti.version}version>
dependency>
<dependency>
<groupId>org.activitigroupId>
<artifactId>activiti-bpmn-modelartifactId>
<version>${activiti.version}version>
dependency>
<dependency>
<groupId>org.activitigroupId>
<artifactId>activiti-bpmn-converterartifactId>
<version>${activiti.version}version>
dependency>
<dependency>
<groupId>org.activitigroupId>
<artifactId>activiti-json-converterartifactId>
<version>${activiti.version}version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.46version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.4.6version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.2.8version>
dependency>
dependencies>
<build>
<finalName>actfinalName>
<resources>
<resource>
<directory>src/main/javadirectory>
<includes>
<include>**/*.propertiesinclude>
<include>**/*.xmlinclude>
includes>
<filtering>falsefiltering>
resource>
<resource>
<directory>src/main/resourcesdirectory>
<includes>
<include>**/*.propertiesinclude>
<include>**/*.xmlinclude>
includes>
<filtering>falsefiltering>
resource>
resources>
build>
project>
3、添加log4j
日志配置,我们使用log4j
日志包,可以对日志进行配置。在resources
下创建log4j.properties
log4j.properties
# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE debug info warn error fatal
log4j.rootCategory=debug, CONSOLE, LOGFILE
# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r[%15.15t] %-5p %30.30c %x - %m\n
# LOGFILE is set to be a File appender using a PatternLayout.
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
# 日志文件输出位置(自定义即可)
log4j.appender.LOGFILE.File=F:\\java\\spring-cloud\\activiti.log
log4j.appender.LOGFILE.Append=true
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r[%15.15t] %-5p %30.30c %x - %m\n
4、编写activiti
的配置文件,我们使用activiti
提供的默认方式来初始化数据表。默认方式的要求是在resources
下创建 activiti.cfg.xml
文件,注意:默认方式ProcessEngineConfiguration
实现类是org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration
并且目录和文件名不能修改,因为activiti
的源码中已经设置,到固定的目录读取固定文件名的文件。
当然也可以自定义,这里为了代码结构规范我们使用自定义的方式编写配置文件。
结构如下(在spring.xml
中导入spring-activiti.xml/spring-dao.xml
,database.properties
编写数据库相关配置)
spring-dao.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:properties/database.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="${db.driver.className}"/>
<property name="url" value="${db.url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
<property name="initialSize" value="10"/>
<property name="maxActive" value="10"/>
bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
beans>
spring-activiti.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/contex
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
<property name="dataSource" ref="dataSource"/>
<property name="transactionManager" ref="transactionManager"/>
<property name="databaseSchemaUpdate" value="true"/>
bean>
beans>
5、编写测试程序,创建ProcessEngine
,在创建ProcessEngine
时会自动创建表。代码如下:
/**
* @description:
* @author: laizhenghua
* @date: 2022/10/30 22:16
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring/spring.xml")
public class ActTest {
@Autowired
private ProcessEngineConfiguration processEngineConfiguration;
@Test
public void test() {
// 创建流程引擎
ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine();
System.out.println(processEngine);
}
}
6、我们再来看有没有自动生成Activiti
需要的表
ok,表已经自动给我们生成了,到此环境集成已经完成了。
我们发现当创建创建ProcessEngine
时,Activiti
流程引擎会给我们创建以ACT_
开头的25张表,那这些表具体有什么用呢?
其实也很好区分ACT_
后面的两个字母表示表的用途。用途也和服务的 API 对应。
ACT_RE :RE
表示 repository
。 这个前缀的表包含了流程定义和流程静态资源 (图片,规则,等等)。
ACT_RU:RU
表示 runtime
。 这些运行时的表,包含流程实例,任务,变量,异步任务,等运行中的数据。Activiti 只在流程实例执行过程中保存这些数据, 在流程结束时就会删除这些记录。 这样运行时表可以一直很小速度很快。
ACT_HI:HI
表示 history
。 这些表包含历史数据,比如历史流程实例, 变量,任务等等。
ACT_GE :GE
表示general
。 通用数据, 用于不同场景下。
详细说明:
表分类 | 表名 | 解释 |
---|---|---|
一般数据 | ||
[ACT_GE_BYTEARRAY] | 通用的流程定义和流程资源 | |
[ACT_GE_PROPERTY] | 系统相关属性 | |
流程历史记录 | ||
[ACT_HI_ACTINST] | 历史的流程实例 | |
[ACT_HI_ATTACHMENT] | 历史的流程附件 | |
[ACT_HI_COMMENT] | 历史的说明性信息 | |
[ACT_HI_DETAIL] | 历史的流程运行中的细节信息 | |
[ACT_HI_IDENTITYLINK] | 历史的流程运行过程中用户关系 | |
[ACT_HI_PROCINST] | 历史的流程实例 | |
[ACT_HI_TASKINST] | 历史的任务实例 | |
[ACT_HI_VARINST] | 历史的流程运行中的变量信息 | |
流程定义表 | ||
[ACT_RE_DEPLOYMENT] | 部署单元信息 | |
[ACT_RE_MODEL] | 模型信息 | |
[ACT_RE_PROCDEF] | 已部署的流程定义 | |
运行实例表 | ||
[ACT_RU_EVENT_SUBSCR] | 运行时事件 | |
[ACT_RU_EXECUTION] | 运行时流程执行实例 | |
[ACT_RU_IDENTITYLINK] | 运行时用户关系信息,存储任务节点与参与者的相关信息 | |
[ACT_RU_JOB] | 运行时作业 | |
[ACT_RU_TASK] | 运行时任务 | |
[ACT_RU_VARIABLE] | 运行时变量表 |
前面我们介绍数据表时,也说了表名称第二部分两个字母表示表的用途,用途也和服务的API对应,从下图就能体现:
这些不同的Service
其实就是操作不同的表,拿到ProcessEngine
后我们就能获取到这些Service
,例如:
@Test
public void test() {
// 创建流程引擎
ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
RuntimeService runtimeService = processEngine.getRuntimeService();
TaskService taskService = processEngine.getTaskService();
ManagementService managementService = processEngine.getManagementService();
}
在前面的例子,我们发现可以通过processEngineConfiguration.buildProcessEngine()
方法创建出工作流引擎ProcessEngine
。所有流程的自动化管理是从ProcessEngineConfiguration
开始,也是核心所在。
ProcessEngineConfiguration
其实是一个接口,并且activiti-spring-boot-starter
默认给我们初始化了bean
实例,我们可以直接使用,例如:
@SpringBootTest
class ActivityApplicationTests {
@Autowired
private ProcessEngineConfiguration processEngineConfiguration;
@Test
void contextLoads() {
ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine();
TaskService taskService = processEngine.getTaskService();
System.out.println(taskService);
}
}
当然我们也可以自定义(以下是两种常用实现类,当然也可以使用配置文件实现)
1、SpringProcessEngineConfiguration
,自定义的实现类bean
实例我们可以指定数据源、生成表策略、事务管理器、是否使用历史数据等,例如:
/**
* @description: ProcessEngineConfiguration activiti配置类
* @author: laizhenghua
* @date: 2022/10/16 15:04
*/
@Configuration
public class ActivitiProcessEngineConfiguration {
@Autowired
private DataSource dataSource;
@Autowired
private PlatformTransactionManager platformTransactionManager;
@Bean
public SpringProcessEngineConfiguration springProcessEngineConfiguration() {
SpringProcessEngineConfiguration processEngineConfiguration = new SpringProcessEngineConfiguration();
// 数据源
processEngineConfiguration.setDataSource(dataSource);
// 数据表生成策略
processEngineConfiguration.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
// 事务管理器
processEngineConfiguration.setTransactionManager(platformTransactionManager);
// 是否使用历史表数据
processEngineConfiguration.setDbHistoryUsed(true);
// 保存历史数据级别
processEngineConfiguration.setHistoryLevel(HistoryLevel.FULL);
return processEngineConfiguration;
}
}
测试,使用SpringProcessEngineConfiguration
创建流程引擎:
@SpringBootTest
class ActivitiApplicationTests {
@Autowired
private SpringProcessEngineConfiguration springProcessEngineConfiguration;
@Test
public void test() {
ProcessEngine processEngine = springProcessEngineConfiguration.buildProcessEngine();
System.out.println(processEngine);
}
}
2、StandaloneProcessEngineConfiguration
,这个实现类和上面那个实现类类似,配置方式都一样,只是这个可以单独运行,会自动管理事务!有兴趣可以自行研究。
3、特别说明,实际开发中不建议自定义ProcessEngineConfiguration
的实现类,除非有特殊需要,因为SpringBoot
会默认帮我们创建,各种配置更加完善,使用时我们只需注入即可,例如(自动创建源码):
@Bean
@ConditionalOnMissingBean
public SpringProcessEngineConfiguration springProcessEngineConfiguration(DataSource dataSource, PlatformTransactionManager transactionManager, SpringAsyncExecutor springAsyncExecutor, ActivitiProperties activitiProperties, ResourceFinder resourceFinder, List<ResourceFinderDescriptor> resourceFinderDescriptors, ProjectModelService projectModelService, @Autowired(required = false) List<ProcessEngineConfigurationConfigurer> processEngineConfigurationConfigurers, @Autowired(required = false) List<ProcessEngineConfigurator> processEngineConfigurators) throws IOException {
SpringProcessEngineConfiguration conf = new SpringProcessEngineConfiguration(projectModelService);
conf.setConfigurators(processEngineConfigurators);
this.configureResources(resourceFinder, resourceFinderDescriptors, conf);
conf.setDataSource(dataSource);
conf.setTransactionManager(transactionManager);
conf.setAsyncExecutor(springAsyncExecutor);
conf.setDeploymentName(activitiProperties.getDeploymentName());
conf.setDatabaseSchema(activitiProperties.getDatabaseSchema());
conf.setDatabaseSchemaUpdate(activitiProperties.getDatabaseSchemaUpdate());
conf.setDbHistoryUsed(activitiProperties.isDbHistoryUsed());
conf.setAsyncExecutorActivate(activitiProperties.isAsyncExecutorActivate());
this.addAsyncPropertyValidator(activitiProperties, conf);
conf.setMailServerHost(activitiProperties.getMailServerHost());
conf.setMailServerPort(activitiProperties.getMailServerPort());
conf.setMailServerUsername(activitiProperties.getMailServerUserName());
conf.setMailServerPassword(activitiProperties.getMailServerPassword());
conf.setMailServerDefaultFrom(activitiProperties.getMailServerDefaultFrom());
conf.setMailServerUseSSL(activitiProperties.isMailServerUseSsl());
conf.setMailServerUseTLS(activitiProperties.isMailServerUseTls());
if (this.userGroupManager != null) {
conf.setUserGroupManager(this.userGroupManager);
}
conf.setHistoryLevel(activitiProperties.getHistoryLevel());
conf.setCopyVariablesToLocalForTasks(activitiProperties.isCopyVariablesToLocalForTasks());
conf.setSerializePOJOsInVariablesToJson(activitiProperties.isSerializePOJOsInVariablesToJson());
conf.setJavaClassFieldForJackson(activitiProperties.getJavaClassFieldForJackson());
if (activitiProperties.getCustomMybatisMappers() != null) {
conf.setCustomMybatisMappers(this.getCustomMybatisMapperClasses(activitiProperties.getCustomMybatisMappers()));
}
if (activitiProperties.getCustomMybatisXMLMappers() != null) {
conf.setCustomMybatisXMLMappers(new HashSet(activitiProperties.getCustomMybatisXMLMappers()));
}
if (activitiProperties.getCustomMybatisXMLMappers() != null) {
conf.setCustomMybatisXMLMappers(new HashSet(activitiProperties.getCustomMybatisXMLMappers()));
}
if (activitiProperties.isUseStrongUuids()) {
conf.setIdGenerator(new StrongUuidGenerator());
}
if (activitiProperties.getDeploymentMode() != null) {
conf.setDeploymentMode(activitiProperties.getDeploymentMode());
}
if (processEngineConfigurationConfigurers != null) {
Iterator var11 = processEngineConfigurationConfigurers.iterator();
while(var11.hasNext()) {
ProcessEngineConfigurationConfigurer processEngineConfigurationConfigurer = (ProcessEngineConfigurationConfigurer)var11.next();
processEngineConfigurationConfigurer.configure(conf);
}
}
springAsyncExecutor.applyConfig(conf);
return conf;
}
Service
是流程引擎提供用于进行工作流部署、执行、管理的服务接口,我们使用这些接口可以就是操作服务对应的数据表。
通过ProcessEngine
创建Service
,方式如下:
@SpringBootTest
class ActivityApplicationTests {
@Autowired
private SpringProcessEngineConfiguration springProcessEngineConfiguration;
@Test
public void test() {
ProcessEngine processEngine = springProcessEngineConfiguration.buildProcessEngine();
// 获取 RepositoryService
RepositoryService repositoryService = processEngine.getRepositoryService();
}
}
service名称 | service作用 |
---|---|
RepositoryService | activiti的资源管理类 |
RuntimeService | activiti的流程运行管理类 |
TaskService | activiti的任务管理类 |
HistoryService | activiti的历史管理类 |
ManagerService | activiti的引擎管理类 |
RepositoryService
是Activiti
的资源管理类,提供了管理和控制流程发布包和流程定义的操作。使用工作流建模工具设计的业务流程图需要使用此service将流程定义文件的内容部署到计算机。
除了部署流程定义以外还可以:查询引擎中的发布包和流程定义。
暂停或激活发布包,对应全部和特定流程定义。 暂停意味着它们不能再执行任何操作了,激活是对应的反向操作。获得多种资源,像是包含在发布包里的文件, 或引擎自动生成的流程图。
获得流程定义的pojo版本, 可以用来通过Java解析流程,而不必通过xml。
Activiti
的流程运行管理类。可以从这个服务类中获取很多关于流程执行相关的信息
Activiti
的任务管理类。可以从这个类中获取任务的信息。
Activiti
的历史管理类,可以查询历史信息,执行流程时,引擎会保存很多数据(根据配置),比如流程实例启动时间,任务的参与者, 完成任务的时间,每个流程实例的执行路径,等等。 这个服务主要通过查询功能来获得这些数据。
Activiti
的引擎管理类,提供了对 Activiti 流程引擎的管理和维护功能,这些功能不在工作流驱动的应用程序中使用,主要用于 Activiti 系统的日常维护。
创建Activiti
工作流主要包含以下几步:
1、定义流程:按照BPMN的规范,使用流程定义工具,用流程符号把整个流程描述出来
2、部署流程:把画好的流程定义文件,加载到数据库中,生成表的数据
3、启动流程:使用java代码来操作数据库表中的内容
BPMN 2.0是业务流程建模符号2.0的缩写。
它由Business Process Management Initiative
这个非营利协会创建并不断发展。作为一种标识,BPMN 2.0是使用一些符号来明确业务流程设计流程图的一整套符号规范,它能增进业务建模时的沟通效率。
目前BPMN2.0是最新的版本,它用于在BPM上下文中进行布局和可视化的沟通。
接下来我们先来了解在流程设计中常见的符号。
BPMN2.0
的基本符号主要包含:
活动是工作或任务的一个通用术语。一个活动可以是一个任务,还可以是一个当前流程的子处理流程; 其次,你还可以为活动指定不同的类型。常见活动如下:
排他网关(x)
只有一条路径会被选择。流程执行到该网关时,按照输出流的顺序逐个计算,当条件的计算结果为true
时,继续执行当前网关的输出流
如果多条线路计算结果都是true
,则会执行第一个值为true
的线路。如果所有网关计算结果没有true
,则引擎会抛出异常。
排他网关需要和条件顺序流结合使用,default
属性指定默认顺序流,当所有的条件不满足时会执行默认顺序流。
并行网关 (+)
所有路径会被同时选择
拆分 —— 并行执行所有输出顺序流,为每一条顺序流创建一个并行执行线路。
合并 —— 所有从并行网关拆分并执行完成的线路均在此等候,直到所有的线路都执行完成才继续向下执行。
包容网关 (+)
可以同时执行多条线路,也可以在网关上设置条件
拆分 —— 计算每条线路上的表达式,当表达式计算结果为true时,创建一个并行线路并继续执行
合并 —— 所有从并行网关拆分并执行完成的线路均在此等候,直到所有的线路都执行完成才继续向下执行。
事件网关 (+)
专门为中间捕获事件设置的,允许设置多个输出流指向多个不同的中间捕获事件。当流程执行到事件网关后,流程处于等待状态,需要等待抛出事件才能将等待状态转换为活动状态。
流是连接两个流程节点的连线。常见的流向包含以下几种:
activity
流程在线设计地址:https://bpmn.52itstyle.vip/
也可以在IDEA
安装一个插件,如下图:
绘制流程:使用画板来绘制流程,通过从右侧把图标拖拽到左侧的画板,最终效果如下:
为流程起名字:点击画板,找到属性编辑位置。
指定任务负责人:在properties
视图指定每个任务结点的负责人,如:填写出差申请的负责人为alex。
建完模后得到一个.bpmn
文件,将上面在设计器中画好的流程部署到activiti
数据库中,就是流程的定义和部署,例如:
/**
* @description:
* @author: laizhenghua
* @date: 2022/11/5 22:30
*/
@SpringBootTest
public class ActivitiDemo {
private Logger logger = LoggerFactory.getLogger(ActivitiDemo.class);
@Autowired
private SpringProcessEngineConfiguration springProcessEngineConfiguration;
@Test
public void test() {
ProcessEngine processEngine = springProcessEngineConfiguration.buildProcessEngine();
// 1.获取RepositoryService
RepositoryService repositoryService = processEngine.getRepositoryService();
// 2.向数据库注册流程
DeploymentBuilder deploymentBuilder = repositoryService.createDeployment();
Deployment deploy = deploymentBuilder.name("请假申请").addClasspathResource("bpmn/leave.bpmn").deploy();
assert deploy != null;
// 3.获取流程定义id
logger.info(String.format("获取流程定义ID=[%s]", deploy.getId()));
// 4.获取流程定义名称
logger.info(String.format("获取流程定义名称=[%s]", deploy.getName()));
}
}
程序执行成功后,我们再来看看,写入了什么数据?
/* 流程定义部署表,每部署一次增加一条记录 */
SELECT * FROM ACT_RE_DEPLOYMENT
/* 流程定义表,部署每个新的流程定义都会在这张表中增加一条记录,如果key_相同,则是版本的迭代 */
SELECT * FROM ACT_RE_PROCDEF
/* 流程资源表,存储xml等数据 */
SELECT * FROM ACT_GE_BYTEARRAY
扩展:批量部署流程,一个系统中不可能只有一个流程,我们一个个部署流程太麻烦了。所以activiti7
也支持使用zip
文件批量部署流程。部署代码如下:
流程定义部署在activiti
后就可以通过流程引擎管理业务流程了,也就是说上边部署的请假申请流程可以使用了。
针对该流程,启动一个流程表示发起一个新的请假申请单,这就相当于Java类与Java对象的关系,类定义好后需要new创建一个对象使用,当然可以new多个对象。对于请假申请流程,张三发起一个请假申请流程需要启动一个流程实例,李四发起一个请假流程也需要启动一个流程实例。
启动流程实例代码如下:
/**
* @description:
* @author: laizhenghua
* @date: 2022/11/5 22:30
*/
@SpringBootTest
public class ActivitiDemo {
private Logger logger = LoggerFactory.getLogger(ActivitiDemo.class);
@Autowired
private ProcessEngine processEngine;
@Test
public void startProcess() {
// 1.获取 RuntimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
// 2.根据流程定义的id(保存到数据库就是key_字段)启动流程,当然也很多API启动流程,可自行研究下
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("leave");
// 3.输出关键信息
String processDefinitionId = processInstance.getProcessDefinitionId(); // 流程定义ID
logger.info(String.format("流程定义ID=[%s]", processDefinitionId));
String processInstanceId = processInstance.getId(); // 流程实例ID
logger.info(String.format("流程实例ID=[%s]", processInstanceId));
String activityId = processInstance.getActivityId(); // 当前活动ID
logger.info(String.format("当前活动ID=[%s]", activityId));
}
}
我们再来看输出了啥?
我们发现当流程定义的key_
有多个时,会选择最新版本启动流程实例
再来看启动流程实例后,到底在数据库中写入了那些数据?可以看看这几张表
/* 流程执行信息 */
SELECT * FROM ACT_RU_EXECUTION
/* 流程的参与用户信息 */
SELECT * FROM ACT_RU_IDENTITYLINK
/* 任务信息 */
SELECT * FROM ACT_RU_TASK
流程启动后,任务的负责人就可以查询自己当前需要处理的任务,查询出来的任务都是该用户的待办任务。例如:
/**
* @description:
* @author: laizhenghua
* @date: 2022/11/5 22:30
*/
@SpringBootTest
public class ActivitiDemo {
private Logger logger = LoggerFactory.getLogger(ActivitiDemo.class);
@Autowired
private ProcessEngine processEngine;
@Test
public void queryTask() {
// 1.获取 TaskService
TaskService taskService = processEngine.getTaskService();
// 2.根据流程定义的 key 和 任务的负责人 查询任务
TaskQuery query = taskService.createTaskQuery();
List<Task> taskList = query.processDefinitionKey("leave").taskAssignee("alex").list();
// 3.输出任务信息
if (!CollectionUtils.isEmpty(taskList)) {
for (Task task : taskList) {
// 流程实例ID
String processInstanceId = task.getProcessInstanceId();
logger.info(String.format("流程实例ID=[%s]", processInstanceId));
// 任务ID
String taskId = task.getId();
logger.info(String.format("任务ID=[%s]", taskId));
// 任务负责人
String assignee = task.getAssignee();
logger.info(String.format("任务负责人=[%s]", assignee));
// 环节/任务名称
String name = task.getName();
logger.info(String.format("任务名称=[%s]", name));
}
}
}
}
输出内容
当然还有其他内容可以输出,这里主要是关键信息。根据查询的条件,我们也能分析出来,到底执行了怎样的SQL!如:
SELECT
T.ID_ AS TASK_ID,
T.NAME_ AS ACTIVITY_NAME,
T.ASSIGNEE_ AS USER_NAME,
D.ID_ AS INSTANCE_ID
FROM `ACT_TEST`.ACT_RU_TASK T
INNER JOIN `ACT_TEST`.ACT_RE_PROCDEF D ON D.ID_ = T.PROC_DEF_ID_
WHERE 1 = 1
AND D.KEY_ = 'leave'
AND T.ASSIGNEE_ = 'alex'
/*
TASK_ID 任务ID
ACTIVITY_NAME 环节名称
USER_NAME 负责人
INSTANCE_ID 流程实例ID
*/
任务负责人查询待办任务,选择任务进行处理,完成任务。
@SpringBootTest
public class ActivitiDemo {
private Logger logger = LoggerFactory.getLogger(ActivitiDemo.class);
@Autowired
private ProcessEngine processEngine;
@Test
public void finishTask() {
// String taskId = "ac8ebb09-6328-11ed-94c3-080058000005";
TaskService taskService = processEngine.getTaskService();
TaskQuery query = taskService.createTaskQuery();
// 根据流程定义的key和环节负责人查询任务
Task task = query.processDefinitionKey("leave").taskAssignee("alex").singleResult();
// 完成任务
taskService.complete(task.getId());
}
}
当完成某个任务,Activiti
流程引擎到底帮我们做了那些事情,可以输出SQL进行查看。例如这里我们使用的JPA
,想要输出SQL,需要新增LOG
配置。参考文章:https://blog.csdn.net/lhjllff12345/article/details/125978328
我们再来看操作了那些东西(主要看这几张表,可自行看下):
SELECT * FROM ACT_HI_TASKINST
SELECT * FROM ACT_RU_TASK
SELECT * FROM ACT_HI_ACTINST
最后我们再来看流程全生命周期信息:
SELECT
AI.PROC_DEF_ID_ AS 流程定义ID,
AI.PROC_INST_ID_ AS 流程实例ID,
AI.TASK_ID_ AS 任务ID,
AI.ACT_NAME_ AS 环节名称,
AI.ACT_TYPE_ AS 环节类型,
AI.ASSIGNEE_ AS 环节负责人,
AI.START_TIME_ AS 开始时间,
AI.END_TIME_ AS 结束时间
FROM ACT_HI_ACTINST AI WHERE AI.PROC_INST_ID_ = '3277309c-6332-11ed-ac17-080058000005' ORDER BY AI.START_TIME_ ASC