环境
activiti:7+
Java:11
本机环境:window
MySQL:5.7
gradlew:6+
Activiti和Spring整合
大体流程:
- 先创建项目,并导入依赖
- 添加
activiti-spring.xml
配置文件,这里是整合的关键 - 编写测试类
创建项目
利用Intellij IDEA
创建一个Spring
空项目,gradle版的。
导入依赖
buildscript {
ext {
activitiVersion = '7.1.0.M6'
mysqlVersion = '5.1.49'
mybatisVersion = '3.5.6'
druidVersion = '1.2.5'
junitVersion = '4.12'
aspectjVersion = '1.9.6'
springTest = '5.1.3.RELEASE'
}
}
plugins {
id 'java'
}
group 'org.sgy'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
//activiti
implementation("org.activiti:activiti-engine:$activitiVersion")
implementation("org.activiti:activiti-bpmn-model:$activitiVersion")
implementation("org.activiti:activiti-bpmn-converter:$activitiVersion")
implementation("org.activiti:activiti-bpmn-layout:$activitiVersion")
//spring
implementation("org.activiti:activiti-spring:$activitiVersion")
implementation("org.aspectj:aspectjweaver:$aspectjVersion")
//json
implementation("org.activiti:activiti-json-converter:$activitiVersion")
//mysql
implementation("mysql:mysql-connector-java:$mysqlVersion")
//mybatis
implementation("org.mybatis:mybatis:$mybatisVersion")
//数据源
implementation("com.alibaba:druid:$druidVersion")
testImplementation("junit:junit:$junitVersion")
testImplementation("org.springframework:spring-test:$springTest")
}
创建activiti与spring的配置文件
文件名随意取:activiti-spring.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<!--数据源:即连接池的配置-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/activiti?useSSL=false"/>
<property name="username" value="yutao"/>
<property name="password" value="yutaoyutao"/>
</bean>
<!--事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--aop配置-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="insert*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="get*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
<!--切面,根据具体项目修改切点配置-->
<!--<aop:config proxy-target-class="true">
<aop:advisor advice-ref="txAdvice" pointcut="execution(*com.sgy.xxx..(..))"/>
</aop:config>-->
<!--工作流引擎配置bean-->
<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
<!--数据源-->
<property name="dataSource" ref="dataSource"/>
<!--使用spring事务管理器-->
<property name="transactionManager" ref="transactionManager"/>
<!--数据库策略-->
<property name="databaseSchemaUpdate" value="drop-create"/>
</bean>
<!--流程引擎-->
<bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
<property name="processEngineConfiguration" ref="processEngineConfiguration"/>
</bean>
<!--资源服务service-->
<bean id="repositoryService" factory-bean="processEngine" factory-method="getRepositoryService"/>
<!--流程运行service-->
<bean id="runtimeService" factory-bean="processEngine" factory-method="getRuntimeService"/>
<!--任务管理器service-->
<bean id="taskService" factory-bean="processEngine" factory-method="getTaskService"/>
<!--历史管理器service-->
<bean id="historyService" factory-bean="processEngine" factory-method="getHistoryService"/>
</beans>
数据库策略的作用?
<!--数据库策略-->
<property name="databaseSchemaUpdate" value="drop-create"/>
主要是启动activiti
做表操作的策略,databaseSchemaUpdate
的取值内容:
取值 | 作用 |
---|---|
false | 默认值。activiti 在启动时,会对比数据库表中保存的版本,如果没有表或者版本不匹配,将抛出异常。(生产环境常用) |
true | activiti 会对数据库中所有表进行更新操作。如果表不存在,则自动创建。(开发时常用) |
create_drop | 在activiti 启动时创建表,在关闭时删除表(必须手动关闭引擎,才能删除表,单元测试常用) |
drop-create | 在activiti 启动时删除原来的旧表,然后在创建新表(不需要手动关闭引擎) |
编写测试代码
import org.activiti.engine.RepositoryService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:activiti-spring.xml")
public class actSpringTest {
@Autowired
private RepositoryService repositoryService;
@Test
public void testRep() {
System.out.println("你执行了吗?");
System.out.println(repositoryService);
}
}
说明:
- 注解:
@ContextConfiguration
是加载spring
容器,这样,就可以使用如下代码:
@Autowired
private RepositoryService repositoryService;
- 在执行后,因为spring容器会通过xml配置文件去加载
activiti
配置,所以也就会去初始化17张表,原本有25张表,但是activiti7
默认初始化history相关的表。
自此说明整合成功。
Activiti和SpringBoot整合
大体思路:
- 先通过
springboot
初始化器生成项目骨架 - 添加依赖
- 添加配置
- 创建
SpringSecurity
权限相关的用户、密码、权限、所属组放入内存 - 启动项目
生成项目骨架
这个可以利用springboot官网去生成或者利用Intellij IDEA
来生成。
添加依赖
buildscript {
ext {
activitiVersion = '7.1.0.M6'
jbdcVersion = '2.4.4'
mysqlVersion = '5.1.49'
}
}
plugins {
id 'org.springframework.boot' version '2.4.4'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'com.sgy'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenCentral()
}
dependencies {
//activiti
implementation("org.activiti:activiti-spring-boot-starter:$activitiVersion")
//mysql
implementation("mysql:mysql-connector-java:$mysqlVersion")
//jdbc
implementation("org.springframework.boot:spring-boot-starter-jdbc:$jbdcVersion")
//web
implementation 'org.springframework.boot:spring-boot-starter-web'
//数据源
implementation("com.alibaba:druid:$druidVersion")
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
test {
useJUnitPlatform()
}
添加配置
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/activitispringboot?useSSL=false
username: yutao
password: yutaoyutao
type: com.alibaba.druid.pool.DruidDataSource
activiti:
database-schema-update: true
#activiti7默认不生成历史信息表,开启历史表
db-history-used: true
#历史信息等级
history-level: full
原则上,目前应该整合完毕;
但是,假设现在就启动,会报错:
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2021-03-28 18:15:36.430 ERROR 15604 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of method userGroupManager in org.activiti.core.common.spring.identity.config.ActivitiSpringIdentityAutoConfiguration required a bean of type 'org.springframework.security.core.userdetails.UserDetailsService' that could not be found.
The injection point has the following annotations:
- @org.springframework.beans.factory.annotation.Autowired(required=false)
Action:
Consider defining a bean of type 'org.springframework.security.core.userdetails.UserDetailsService' in your configuration.
Process finished with exit code 1
这是因为activiti7
的源码和springsecurity进行了整合,需要获取用户权限信息,如果启动时没有就会报错;正常情况下,我们应该自定义相应配置,让程序去读用户权限表,但是这里为了测试,就直接写死,并将数据保存到内存中,这里就用官方提供的例子:
配置security相关类
我们需要告诉security到哪里获取用户信息,因为是测试代码,我们就简单点,直接硬编码写在内存里吧~ 此处,采用官方提供的代码:
官方的源码类名叫DemoApplicationConfiguration
,我重命名了,改为UserDetailsConfiguration
;
package com.sgy.activitispringbootdemo.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;
@Configuration
public class UserDetailsConfiguration {
private Logger logger = LoggerFactory.getLogger(UserDetailsConfiguration.class);
@Bean
public UserDetailsService myUserDetailsService() {
//将用户信息存入内存
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
// 构造用户信息
String[][] usersGroupsAndRoles = {
// 用户名、密码、ROLE_:是角色的前置,后面跟着的是角色名、GROUP_:用户组前置,activitiTeam是组的名称
{"salaboy", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
{"ryandawsonuk", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
{"erdemedeiros", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
{"other", "password", "ROLE_ACTIVITI_USER", "GROUP_otherTeam"},
{"admin", "password", "ROLE_ACTIVITI_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;
}
/**
* 加密
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
有上面这个类,启动程序就ok了。
不过在使用activiti
的过程中,你会发现,对它的操作,都需要先登录,即让security
知道当前是哪个用户,为了方便测试,我们可以再编写一个工具类:
package com.sgy.activitispringbootdemo.utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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
@Qualifier("myUserDetailsService")
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);
}
}
怎么使用呢? 下面给出一个简单示例:
@Autowired
private SecurityUtil securityUtil;
public void test(){
securityUtil.logInAs("yutao");
}
总结
- 整合的本质,就是要让
Spring
容器,认识并可以管理第三方框架(这里就是activiti7
框架);所以配置文件是关键。 - activiti和
springboot
整合时,需要告诉springsecurity
去哪里可以获取到用户。
未解决的问题:
- Intellij IDEA 2020+的版本,activiti7没办法画bpmn图。
参考地址:
https://www.bilibili.com/video/BV1H54y167gf?p=105&spm_id_from=pageDriver