目录
2.2 下载 Activiti-Core 的 demo 示例
3.1 SpringBoot 整合 Activiti7 的 配置
3.2.2 添加 DemoApplicationConfig 类
3.3 使用 SpringBoot 整合 Junit 方式测试新特性
四,Activiti7+ SpringBoot+SpringMVC 开发
一,Activiti7 基本介绍
Alfresco 软件在 2010 年 5 月 17 日宣布 Activiti 业务流程管理(BPM)开源项目的正式启动,其 首席架构师由业务流程管理 BPM 的专家 Tom Baeyens 担任,Tom Baeyens 就是原来 jbpm 的架构师, 而 jbpm 是一个非常有名的工作流引擎,当然 activiti 也是一个工作流引擎。 Activiti 是一个工作流引擎, activiti 可以将业务系统中复杂的业务流程抽取出来,使用专门的建模语言(BPMN2.0)进行定义,业务系统按照预先定义的流程进行执行,实现了业务系统的业务 流程由 activiti 进行管理,减少业务系统由于流程变更进行系统升级改造的工作量,从而提高系统的 健壮性,同时也减少了系统开发维护成本。
官方网站:https://www.activiti.org/
版本有:5.x,6.x,7.x
二,下载Activit 官网demo 学习
Activiti7 分为core,cloud两类,我们主要学习core 的内容。
2.1 进入 Activiti-Core 学习向导页面
2.2 下载 Activiti-Core 的 demo 示例
GitHub - AlfrescoArchive/activiti-examples: This repository contains Activiti 7.x Examples
2.3 Activiti7 面临的挑战
Tom Baeyens 离职,并带着原班 Activiti 开发者又开发了一版新的工作流引擎,叫 flowable。这对于 Activiti 阵营而言是一个不少的挑战。我们现在所学的 Activiti7 是另一个架构师在原有的 Activiti6 基 础上再进行一层封装,所以 API 有着很大的改变。
2.4 Activiti7 的组成部分
从这个部分我们可以看到 Activiti 现在已经分成两个不同的部分,Activiti Core 作为 Activiti 的核心部分,本次我们重点解决。另一部分 Activiti Cloud 主要是利用云服务来实现分布式业务流程开发。
2.5 Activiti 新的 API 介绍
Activiti7 为了简化对工作流的操作,特别在原有 API 的基础上再次进行封闭,这样我们原来所学习 的 Activiti 基本 API 就被封闭起来了。具体要学习的包括: ProcessRuntime 接口 TaskRuntime 接口
2.5.1 TaskRuntime
下面这个链接是 新API的教材地址
Getting Started - Activiti Core - Activiti & Activiti Cloud Developers Guide
public interface TaskRuntime {
TaskRuntimeConfiguration configuration();
Task task(String taskId);
Page tasks(Pageable pageable);
Page tasks(Pageable pageable, GetTasksPayload payload);
Task create(CreateTaskPayload payload);
Task claim(ClaimTaskPayload payload);
Task release(ReleaseTaskPayload payload);
Task complete(CompleteTaskPayload payload);
Task update(UpdateTaskPayload payload);
Task delete(DeleteTaskPayload payload);
...
}
创建任务
taskRuntime.create(
TaskPayloadBuilder.create()
.withName("First Team Task")
.withDescription("This is something really important")
.withGroup("activitiTeam")
.withPriority(10)
.build());
2.5.2 ProcessRuntime
public interface ProcessRuntime {
ProcessRuntimeConfiguration configuration();
ProcessDefinition processDefinition(String processDefinitionId);
Page processDefinitions(Pageable pageable);
Page processDefinitions(Pageable pageable,
GetProcessDefinitionsPayload payload);
ProcessInstance start(StartProcessPayload payload);
Page processInstances(Pageable pageable);
Page processInstances(Pageable pageable,
GetProcessInstancesPayload payload);
ProcessInstance processInstance(String processInstanceId);
ProcessInstance suspend(SuspendProcessPayload payload);
ProcessInstance resume(ResumeProcessPayload payload);
ProcessInstance delete(DeleteProcessPayload payload);
void signal(SignalPayload payload);
...
}
获取这两个类,直接@Autowried 就好
查询流程定义
Page processDefinitionPage = processRuntime
.processDefinitions(Pageable.of(0, 10));
logger.info("> Available Process definitions: " +
processDefinitionPage.getTotalItems());
for (ProcessDefinition pd : processDefinitionPage.getContent()) {
logger.info("\t > Process definition: " + pd);
}
1,因为activiti7 和springSecurity 进行了强耦合,所以在进行activiti 增删改查的时候都要做权限验证
2,流程定义需要放在 /src/main/resources/processes/
三,Activiti7 与 SpringBoot 整合开发
Activiti7 发布正式版之后,它与 SpringBoot2.x 已经完全支持整合开发。我们可以将 Activiti7 与 SpringBoot 整合开发的坐标引入到工程中,从而达到 SpringBoot 支持 Activti7 整合。
SpringBoot 整合 Activti7 的具体步骤如下:
1.添加 SpringBoot 整合 Activti7 的坐标
2.添加 SpringSecurity 安全框架的整合配置信息
3.使用 Activti7 新支持的类来实现工作流开发 ProcessRuntime 接口 TaskRuntime 接口
4.使用新的 API 实现工作流开发,主要包括
流程定义查询
启动流程实例
任务的查询
任务的完成
3.1 SpringBoot 整合 Activiti7 的 配置
3.1.1 引入依赖
<?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>org.example</groupId>
<artifactId>springBoot_Activiti7New</artifactId>
<version>1.0-SNAPSHOT</version>
<!--activiti7与SpringBoot整合的相关依赖-->
<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>
<!-- https://mvnrepository.com/artifact/org.activiti/activiti-spring-boot-starter -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>7.1.0.M6</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>8.0.21</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.1.2 配置文件
spring:
datasource:
url: jdbc:mysql://localhost:3306/activiti?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT
username : root
password : root
driver-class-name: com.mysql.jdbc.Driver
activiti:
database-schema-update: true
history-level: full
db-history-used: true
Activiti7与SpringBoot整合时,默认生成的activiti数据库中只有17张表,无另外8张历史表。
Activiti默认关闭了历史表的使用。
在连接数据库的application.yml配置文件中,开启即可,配置文件内容如下。
3.2 添加SpringSecurity 安全框架整合配置
因为 Activiti7 与 SpringBoot 整合后,默认情况下,集成了 SpringSecurity 安全框架,这样我们就要去 准备 SpringSecurity 整合进来的相关用户权限配置信息。
可以查看一下整合 SpringBoot 的依赖包,发现同时也将 SpringSecurity 的依赖包也添加进项目中了,
3.2.1 添加 SecurityUtil 类
添加 SecurityUtil 类。 为了能够快速实现 SpringSecurity 安全框架的配置,所添加的一个组件。
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);
}
}
3.2.2 添加 DemoApplicationConfig 类
在 Activiti7 官方下载的 Example 中找到 DemoApplicationConfig 类,它的作用是为了实现 SpringSecurity 框架的用户权限的配置,这样我们就可以在系统中使用用户权限信息。本次项目中基 本是在文件中定义出来的用户信息,当然也可以是数据库中查询的用户权限信息。
/*
* 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.
*/
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.2.3 启动类
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;
};
}
}
3.3 使用 SpringBoot 整合 Junit 方式测试新特性
3.4 查看流程定义信息
@RunWith(SpringRunner.class)
@SpringBootTest
public class ActivitiDemoApplicationTests {
@Autowired
private ProcessRuntime processRuntime;
@Autowired
private TaskRuntime taskRuntime;
@Autowired
private SecurityUtil securityUtil;
/**
* 查看流程定义
*/
@Test
public void contextLoads(){
securityUtil.logInAs("system");
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);
}
}
}
通过 ProcessRuntime 的方法,我们可以查看已经部署的流程定义信息。通过加入 Pageable.of()方法可 以实现分页查询效果。
3.5 启动流程实例
/**
* 启动流程实例
*/
@Test
public void testStartProcess() {
securityUtil.logInAs("system");
ProcessInstance pi = processRuntime.start(ProcessPayloadBuilder.start().withProcessDefinitionKey("myProcess").build());
System.out.println("流程实例 ID:" + pi.getId());
}
现在要启动流程实例,我们可以使用 ProcessRuntime 的 start()方法就可以实现流程实例的启动
3.6 查询并完成任务
/**
* 查询任务,并完成自己的任务
*/
@Test
public void testTask() {
securityUtil.logInAs("ryandawsonuk");
Page<Task> taskPage=taskRuntime.tasks(Pageable.of(0,10));
if (taskPage.getTotalItems()>0){
for (Task task:taskPage.getContent()){
taskRuntime.claim(TaskPayloadBuilder.claim().withTaskId(task.getId()).build());
System.out.println("任务:"+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("任务:"+taskPage2.getContent());
}
}
使用 TaskRuntime 接口的 tasks()方法实现任务的查询。
使用 TaskRuntime 接口的 claim()方法实现任务拾取。
使用 TaskRuntime 接口的 complete()方法实现任务的完成
四,Activiti7+ SpringBoot+SpringMVC 开发
4.1添加 Controller 类
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.model.Task;
import org.activiti.api.task.model.builders.TaskPayloadBuilder;
import org.activiti.api.task.runtime.TaskRuntime;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.logging.Logger;
@RestController
public class TestController {
private Logger logger = (Logger) LoggerFactory.getLogger(TestController.class);
@Autowired
private ProcessRuntime processRuntime;
@Autowired
private TaskRuntime taskRuntime;
@Autowired
private SecurityUtil securityUtil;
@RequestMapping(value = "/hello")
public void hello() {
//首先,取出项目中的最多 10 个流程定义
Page<ProcessDefinition> processDefinitionPage =
processRuntime.processDefinitions(Pageable.of(0, 10));
if (processDefinitionPage.getTotalItems() > 0) {
//然后,对取出的流程进行启动
for (ProcessDefinition definition : processDefinitionPage.getContent()) {
logger.info("流程定义信息:" + definition);
processRuntime.start(ProcessPayloadBuilder.start().withProcessDefinitionId(definition.getId()).build());
}
}
//完成 流程启动后, 由于当前项目中 只有 other.bpmn 一个流程,且该流程在设计时,已分配给activitiTeam 组
//因此我们登录一个activitiTeam组成员,该账号信息会被设置到security上下文中,activiti会对其信息进行读取
//获取当前用户任务,最多 10 个
Page<Task> taskPage = taskRuntime.tasks(Pageable.of(0, 10));
//由于目前只有一个流程,两个任务,我们尝试一下完成一个,看看会发生什么变化
if (taskPage.getTotalItems() > 0) {
for (Task task : taskPage.getContent()) {
logger.info("任务信息:" + task);
//注意,完成任务前必须先声明
taskRuntime.claim(TaskPayloadBuilder.claim().withTaskId(task.getId()).build());
//完成任务
taskRuntime.complete(TaskPayloadBuilder.complete().withTaskId(task.getId()).build()
);
}
}
//上一轮任务完成,再看一下,现在流程是否走到了 second?
Page<Task> taskPage2 = taskRuntime.tasks(Pageable.of(0, 10));
if (taskPage2.getTotalItems() > 0) {
logger.info("任务信息:" + taskPage2.getContent());
}
}
}