Activiti is the most detailed series of articles on the web.
Activiti integration article
1. Integrate with Spring
1.1 Adding Dependencies
<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.bobo</groupId>
<artifactId>ActivitiDemo02Spring</artifactId>
<version>1.0 the SNAPSHOT</version>
<properties>
<slf4j.version>1.6.6</slf4j.version>
<log4j.version>1.2.12</log4j.version>
</properties>
<dependencies>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-engine</artifactId>
<version>7.0.0. Walk</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring</artifactId>
<version>7.0.0. Walk</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-bpmn-model</artifactId>
<version>7.0.0. Walk</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-bpmn-converter</artifactId>
<version>7.0.0. Walk</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-json-converter</artifactId>
<version>7.0.0. Walk</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-bpmn-layout</artifactId>
<version>7.0.0. Walk</version>
<exclusions>
<exclusion>
<groupId>com.github.jgraph</groupId>
<artifactId>jgraphx</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.activiti.cloud</groupId>
<artifactId>activiti-cloud-services-api</artifactId>
<version>7.0.0. Walk</version>
</dependency>
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.5.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.7. RELEASE</version>
</dependency>
<! -- log start -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>${slf4j.version}</version>
</dependency>
<! -- log end -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>alfresco</id>
<name>Activiti Releases</name>
<url>https://artifacts.alfresco.com/nexus/content/repositories/activiti-releases/</url>
<releases>
<enabled>true</enabled>
</releases>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>
</project>
Copy the code
1.2 Adding an Integrated Configuration File
Add a Spring configuration file where you can integrate Activiti
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<! -- Data source -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/activiti? characterEncoding=utf-8&nullCatalogMeansCurrent=true&serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<property name="maxActive" value="3"/>
<property name="maxIdle" value="1"/>
</bean>
<! Workflow Engine configuration bean -->
<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
<! -- Data source -->
<property name="dataSource" ref="dataSource"/>
<! Spring transaction Manager -->
<property name="transactionManager" ref="transactionManager"/>
<! -- Database policy flase: default value. When Activiti starts, it compares versions saved in database tables and throws an exception if there are no tables or versions do not match. True: Activiti updates all tables in the database. If the table does not exist, it is created automatically. Create_drop: Create tables when Activiti is started and drop tables when activiti is shut down (you must manually shut down the engine to drop tables). Drop-create: Drop old tables when Activiti starts and create new tables (no need to manually shut down the engine). -->
<property name="databaseSchemaUpdate" value="drop-create"/>
</bean>
<! -- Process Engine -->
<bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
<property name="processEngineConfiguration" ref="processEngineConfiguration"/>
</bean>
<! Service -->
<bean id="repositoryService" factory-bean="processEngine" factory-method="getRepositoryService"/>
<! -- Process run 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"/>
<! -- Transaction manager -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<! -- notice -- -- >
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<! -- Communication behavior -->
<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>
<! - section, <aop:config proxy-target-class="true"> <aop: Advisor advice-ref="txAdvice" pointcut="execution(*com.bobo.service.impl.. (..) )"/> </aop:config>-->
</beans>
Copy the code
Note the value of databaseSchemaUpdate:
Flase: default value. When Activiti starts, it compares versions saved in database tables and throws an exception if there are no tables or versions do not match. True: Activiti updates all tables in the database. If the table does not exist, it is created automatically. Create_drop: Create tables when Activiti is started and drop tables when activiti is shut down (you must manually shut down the engine to drop tables). Drop-create: Drop old tables when Activiti starts and create new tables (no need to manually shut down the engine).
1.3 Creating Test Class Tests
package com.bobo.test;
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 ActivitiTest {
@Autowired
private RepositoryService repositoryService;
@Test
public void test01(a){ System.out.println(repositoryService); }}Copy the code
The method execution shows that the associated table structure has been created in the database, indicating a successful integration between Activiti and Spring.
2. Integration with SpringBoot
Activiti7 has been fully integrated with SpringBoot2.x since its official release
2.1 Adding dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>7.0.0. Beta 2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
Copy the code
2.2 Modifying the Configuration File
Configure Spring's data source
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql:///activiti? characterEncoding=utf-8& nullCatalogMeansCurrent=true& serverTimezone=UTC
spring.datasource.name=root
spring.datasource.password=123456
# Activiti configuration
#1.flase: default value. When Activiti starts, it compares versions saved in database tables and throws an exception if there are no tables or versions do not match
#2.true: Activiti updates all tables in the database. If the table does not exist, it is created automatically
#3. Create_drop: Create tables when Activiti is started and drop tables when activiti is shut down (you must manually shut down the engine to drop tables)
#4. Drop-create: Drop old tables when Activiti starts and then create new tables (no need to manually shut down the engine)
spring.activiti.database-schema-update=true
Activiti7 starts database history when database history is disabled by default
spring.activiti.db-history-used=true
The configurable history levels include None, Activity, Audit, and full
# None: No historical data is saved, so this is most efficient during process execution.
# Activity: A level higher than None saves the process instance and process behavior. Other data is not saved.
#audit: All process tasks and their properties are saved in addition to the data stored at the activity level. Audit is the default value for history.
#full: The highest level of historical data saving, in addition to audit level data, will save all other process related details data, including some process parameters, etc.
spring.activiti.history-level=full
By default, verify process files in the Process folder under resouces
spring.activiti.check-process-definitions=false
Copy the code
2.3 integrated SpringSecurity
Because Activiti7 is integrated with SpringBoot, by default, the SpringSecurity framework is integrated, so we need to prepare SpringSecurity configuration information
Add a utility class for SpringSecurity
package com.bobo.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.context.annotation.ComponentScan;
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(a) {
return user.getPassword();
}
@Override
public Object getDetails(a) {
return user;
}
@Override
public Object getPrincipal(a) {
return user;
}
@Override
public boolean isAuthenticated(a) {
return true;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {}@Override
public String getName(a) {
returnuser.getUsername(); }})); org.activiti.engine.impl.identity.Authentication.setAuthenticatedUserId(username); }}Copy the code
This class can be found in the Example provided by Activiti7.
Add a SpringSecurity profile
package com.bobo.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 SpringSecurityConfiguration {
private Logger logger = LoggerFactory.getLogger(SpringSecurityConfiguration.class);
@Bean
public UserDetailsService myUserDetailsService(a) {
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
// Add the user here, and the task owner used in the later process needs to be added here
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_otherTeam"},
{"system"."password"."ROLE_ACTIVITI_USER"},
{"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;
}
@Bean
public PasswordEncoder passwordEncoder(a) {
return newBCryptPasswordEncoder(); }}Copy the code
2.4 Creating a BPMN File
Create a simple BPMN file and set the user groups for the task, CandidateGroups, CandidateGroups, and the contents of the task should be the same as the user group names specified in the configuration file for SpringSecurity. You can fill in activitTeam or otherTeam. The advantage of this is that any user in a group can pick up a task when they are unsure who is responsible for the taskProcesses can be automatically deployed in Activiti7 by creating a new directory, Processes, in the Resources directory to hold BPMN files
2.5 Unit Tests
package com.bobo;
import com.bobo.utils.SecurityUtil;
import org.activiti.api.process.model.ProcessDefinition;
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.api.task.model.Task;
import org.activiti.api.task.model.builders.ClaimTaskPayloadBuilder;
import org.activiti.api.task.model.builders.TaskPayloadBuilder;
import org.activiti.api.task.model.payloads.ClaimTaskPayload;
import org.activiti.api.task.runtime.TaskRuntime;
import org.activiti.engine.RepositoryService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class ActSpringbootApplicationTests {
@Autowired
private ProcessRuntime processRuntime;
@Autowired
private TaskRuntime taskRuntime;
@Autowired
private SecurityUtil securityUtil;
@Autowired
private RepositoryService repositoryService;
@Test
void contextLoads(a) {
System.out.println(taskRuntime);
}
/** * Query flow definition */
@Test
public void test02(a){
securityUtil.logInAs("system");
Page<ProcessDefinition> processDefinitionPage =
processRuntime.processDefinitions(Pageable.of(0.10));
System.out.println("Number of process definitions available :" + processDefinitionPage.getTotalItems());
for (ProcessDefinition processDefinition : processDefinitionPage.getContent()) {
System.out.println("Process Definition:"+ processDefinition); }}/** * Deployment process */
@Test
public void test03(a){
repositoryService.createDeployment()
.addClasspathResource("processes/my-evection.bpmn")
.addClasspathResource("processes/my-evection.png")
.name("Travel Application form")
.deploy();
}
/** * Start the process instance */
@Test
public void test04(a){
securityUtil.logInAs("system");
ProcessInstance processInstance = processRuntime.start(ProcessPayloadBuilder
.start()
.withProcessDefinitionKey("my-evection")
.build()
);
System.out.println("Process instance ID :" + processInstance.getId());
}
/** * Task query, pick and complete operation */
@Test
public void test05(a){
securityUtil.logInAs("jack");
Page<Task> tasks = taskRuntime.tasks(Pageable.of(0.10));
if(tasks ! =null && tasks.getTotalItems() > 0) {for (Task task : tasks.getContent()) {
// Pick task
taskRuntime.claim(TaskPayloadBuilder
.claim()
.withTaskId(task.getId())
.build()
);
System.out.println("Mission:" + task);
taskRuntime.complete(TaskPayloadBuilder
.complete()
.withTaskId(task.getId())
.build()
);
}
}
Page<Task> taskPage2 = taskRuntime.tasks(Pageable.of(0.10));
if(taskPage2 .getTotalItems() > 0){
System.out.println("Mission:"+ taskPage2.getContent()); }}}Copy the code
Here’s the whole Activiti7 Activiti7 Activiti7 Activiti7 Activiti7 Activiti7 Activiti7 Activiti7 Activiti7 Activiti7 Activiti7 V_V