1、概述
Activiti7 发布正式版之后,它与 SpringBoot2.x 已经完全支持整合开发。我们可以将 Activiti7 与SpringBoot 整合开发的坐标引入到工程中,从而达到 SpringBoot 支持 Activti7 整合。
整合步骤:
1、引入Activiti7和SpringBoot相关的依赖,以及其他一些相关的坐标:比如MySql驱动,Mybatis等;
2、因为Activiti7与SpringSecurity是高耦合的,所以我们需要引入SpringSecurity安全框架的相关配置;
3、编写测试类,使用Activiti7的新增的两个主要接口:ProcessRuntime 接口和TaskRuntime 接口来进行流程的相关操作;
4、整合SpringMvc,通过浏览器访问触发流程。
2、搭建工程,代码实现
2.1 搭建maven工程,添加依赖
使用IDEA搭建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.zdw.activiti</groupId>
<artifactId>activiti7-springboot</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<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>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>7.0.0.Beta2</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.27</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.2 application.yml
在resources目录下创建application.yml配置文件,里面的数据库配置根据自己的情况来配置:
#datasource
spring:
datasource:
url: jdbc:mysql://localhost:3306/activiti?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT
username : root
password : 123
driver-class-name: com.mysql.jdbc.Driver
2.3 添加SpringSecurity配置
因为 Activiti7 与 SpringBoot 整合后,默认情况下,集成了 SpringSecurity 安全框架,这样我们就要去准备 SpringSecurity 整合进来的相关用户权限配置信息。
可以查看一下整合 SpringBoot 的依赖包,发现同时也将 SpringSecurity 的依赖包也添加进项目中了,如下:
2.3.1 添加SecurityUtil 类
创建包:com.zdw.activiti.security,然后创建SecurityUtil:这个类可以从我们下载的 Activiti7 官方提供的 Example 中找到
package com.zdw.activiti.security;
import org.springframework.beans.factory.annotation.Autowired;
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 {
@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");
}
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);
}
}
2.3.2 添加 DemoApplicationConfig类
在 Activiti7 官方下载的 Example 中找到 DemoApplicationConfig 类,它的作用是为了实现SpringSecurity 框架的用户权限的配置,这样我们就可以在系统中使用用户权限信息。本次项目中基本是在文件中定义出来的用户信息,当然也可以是数据库中查询的用户权限信息。
在包com.zdw.activiti.security下面创建DemoApplicationConfig:
/*
* Copyright 2018 Alfresco, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zdw.activiti.security;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
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
@EnableWebSecurity
public class DemoApplicationConfiguration extends WebSecurityConfigurerAdapter {
private Logger logger = LoggerFactory.getLogger(DemoApplicationConfiguration.class);
@Override
@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService());
}
@Bean
public UserDetailsService myUserDetailsService() {
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
String[][] usersGroupsAndRoles = {
{"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;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.httpBasic();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
3、SpringBoot 整合 Junit 测试
在test目录下创建包com.zdw.activiti.test,然后创建测试类:Actviti7DemoApplicationTests
package com.zdw.activiti.test;
import com.zdw.activiti.security.SecurityUtil;
import org.activiti.api.process.model.ProcessDefinition;
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.runtime.TaskRuntime;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class Actviti7DemoApplicationTests {
@Autowired
private ProcessRuntime processRuntime;
@Autowired
private TaskRuntime taskRuntime;
@Autowired
private SecurityUtil securityUtil;
//查看流程定义
@Test
public void testDefinition(){
securityUtil.logInAs("salaboy");//用户验证
Page<ProcessDefinition> processDefinitionPage = processRuntime.processDefinitions(Pageable.of(0, 10));
System.out.println(" 可 用 的 流 程 定 义 数 量 : " + processDefinitionPage.getTotalItems());
for (org.activiti.api.process.model.ProcessDefinition pd : processDefinitionPage.getContent()) {
System.out.println("流程定义:" + pd);
}
}
}
执行测试方法之前,先要创建数据库activiti。
执行测试方法,可以看出控制台打印了:可用的流程定义数量 : 0 ,这是因为我们没有部署任何的流程定义,所以是0,但是我们去查看数据库,发现数据库中创建出了17张表,如下:
之前博客中,我们的数据库都是会创建25张表的,而这里只有17张表,以act_hi开头的历史表都没有创建出来。怎么解决这个问题呢?其实这是因为我们在application.yml中缺少了activiti相关的配置,没有开启创建历史表的开关,所以,我们接下来就是要添加相关配置,如下:
#datasource
spring:
datasource:
url: jdbc:mysql://localhost:3306/activiti?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT
username : root
password : 123
driver-class-name: com.mysql.jdbc.Driver
#参考配置
activiti:
# 自动建表
database-schema: ACTIVITI
database-schema-update: true
history-level: full
db-history-used: true
database-schema-update表示启动时检查数据库表,不存在则创建
history-level表示哪种情况下使用历史表,这里配置为full表示全部记录历史,方便绘制流程图
db-history-used为true表示使用历史表,如果不配置,则工程启动后可以检查数据库,只建立了17张表,历史表没有建立,则流程图及运行节点无法展示(暂未找到可行方式)
当再次运行上面的测试方法,成功之后,查看数据库,会发现以act_hi开头的7张表也为我们创建好了,如下:
4、创建流程图并部署
在resources下面创建processes文件夹,然后创建test.bpmn文件:
注意:在activiti7的官网中,解释了在与SpringBoot整合的时候,把流程图放在resources/processes目录下,那么就会自动部署流程定义。还需要了解的是,我们这里为每个流程都指定了Candidate Groups,他的值是类:DemoApplicationConfiguration中:activitiTeam,所以我们之后处理流程需要用相关的用户:salaboy、ryandawsonuk、erdemedeiros、other
String[][] usersGroupsAndRoles = {
{"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"},
};
上面的工作就绪之后,我们再次执行测试类中的testDefinition方法,会看到控制台打印:
可用的流程定义数量 : 1
流程定义:ProcessDefinition{id='test:1:cb20897d-0b39-11ea-b002-005056c00001', name='null', key='test', description='null', version=1}
这说明流程自动部署成功啦。
5、启动流程实例并完成任务
上面的这些准备工作,已经证明我们Activiti7整合SpringBoot是成功的,所以接下来要使用Activiti7提供的新接口来启动流程实例,查询任务并完成任务。
5.1 启动流程实例
//启动流程实例
@Test
public void testStartInstance(){
securityUtil.logInAs("salaboy");//用户验证
//test是我们定义流程的时候设置的key
ProcessInstance processInstance = processRuntime.start(ProcessPayloadBuilder.start().withProcessDefinitionKey("test").build());
System.out.println("流程实例的ID是:"+processInstance.getId());
}
5.2 查询并完成任务
//查询并完成任务
@Test
public void testQueryAndCompleteTask(){
securityUtil.logInAs("ryandawsonuk");//用户验证
Page<Task> taskPage = taskRuntime.tasks(Pageable.of(0, 10));
if(taskPage.getTotalItems()>0){
for (Task task : taskPage.getContent()) {
//拾取任务,由当前通过验证的用户ryandawsonuk来拾取任务
Task task_ = taskRuntime.claim(TaskPayloadBuilder.claim().withTaskId(task.getId()).build());
System.out.println("任务id:"+task_.getId()+",任务名称:"+task_.getName());
Task task_new = taskRuntime.complete(TaskPayloadBuilder.complete().withTaskId(task.getId()).build());
}
}
//然后再次查询任务
Page<Task> taskPage2 = taskRuntime.tasks(Pageable.of(0, 10));
if(taskPage2.getTotalItems()>0){
for (Task task : taskPage2.getContent()) {
System.out.println("剩下的任务名称:"+task.getName());
}
}
}
使用TaskRuntime 接口的tasks()方法实现任务的查询。
使用TaskRuntime 接口的claim()方法实现任务拾取。
使用TaskRuntime 接口的complete()方法实现任务的完成
6、加入SpringMvc
上面我们都是SpringBoot整合Junit做的测试,现在把SpringMvc加入进来,通过浏览器来触发部署流程定义,以及查询启动流程实例和完成任务的操作。
6.1 创建启动类
package com.zdw.activiti;
import org.activiti.api.process.runtime.connector.Connector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.context.annotation.Bean;
@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
public class Actviti7DemoApplication {
private Logger logger = LoggerFactory.getLogger(Actviti7DemoApplication.class);
public static void main(String[] args) {
SpringApplication.run(Actviti7DemoApplication.class, args);
}
@Bean
public Connector testConnector() {
return integrationContext -> {
logger.info("以前叫代理,现在叫连接器被调用啦~~");
return integrationContext;
};
}
}
6.2 创建控制器类
package com.zdw.activiti.controller;
import com.zdw.activiti.security.SecurityUtil;
import org.activiti.api.process.model.ProcessDefinition;
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.runtime.TaskRuntime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Create By zdw on 2019/11/20
*/
@RestController
@RequestMapping("activiti")
public class ActivitiController {
@Autowired
private ProcessRuntime processRuntime;
@Autowired
private TaskRuntime taskRuntime;
@Autowired
private SecurityUtil securityUtil;
@RequestMapping("repository")
public String repository(){
Page<ProcessDefinition> processDefinitionPage = processRuntime.processDefinitions(Pageable.of(0, 10));
System.out.println("可用的流程定义数量 : " + processDefinitionPage.getTotalItems());
for (org.activiti.api.process.model.ProcessDefinition pd : processDefinitionPage.getContent()) {
System.out.println("流程定义:" + pd);
}
return "流程部署成功";
}
}
6.3 测试部署流程定义
在测试之前,先删除activiti数据库,然后再创建一个全新的activiti数据库。
执行启动程序,浏览器访问:http://localhost:8080/activiti/repository,会弹出一个登录窗口:
此时我们就需要用:DemoApplicationConfiguration类中的存在的用户去登录,比如:salaboy,密码是:password。
登录成功,就会访问到我们的repository方法,然后自动部署位于resources/processes目录下流程定义,控制台会打印:
可用的流程定义数量 : 1
流程定义:ProcessDefinition{id='test:1:f6aae9e0-0b40-11ea-a6d6-005056c00001', name='null', key='test', description='null', version=1}
浏览器会显示方法的返回值:流程部署成功。
其他的流程实例以及任务相关的操作就不做开发了,基本上和之前的测试类的方法是一样的。