一、版本问题
原Activiti的Tijs Rademakers团队去开发Flowable框架。
现Activiti7是 Salaboy团队开发的,内核使用的还是Activiti6,扩展了云化。Activiti5、Activiti6代码目前由 Salaboy团队代为维护,目前官宣已经暂停维护
Activiti:Activiti在目前来看有点不思进取,核心功能和内核的优化并没有太大进步,着力点全在商业版和云上面,核心只支持BPMN2协议,跟6版本没有什么区别。如果你是一个老的Activiti使用者,并且只是用BPMN2协议,可以选用Activiti(非Cloud版本)。
Flowable:Flowable不管是功能层面还是在代码层面来讲,都是这3个中最重的,当初跟Activiti分道扬镳的原因也是因为理念不一样,Flowable更注重其功能性、扩展性和性能。在上面表格中,历史异步归档和异步任务全局锁都是对性能的极大优化,特别是异步任务这一项,当年在使用Activiti的使用是一个极大的困扰,因为异步任务的吞吐反而会随着实例数的增加而加速恶化。Flowable比较臃肿,它支持了太多的东西,以致于如果想做POC或者Demo,环境搭建这一步都够呛。但是如果你本身就想做一个扩展性强的,性能高的工作流平台(SaaS\PaaS),Flowable是不二的选择。
Camunda:Camunda是这3个里面比较轻量的一个,但是它并没有去掉PVM这个性能较差的流程推动引擎,所以如果你对性能要求很高的话,PVM肯定是不能满足的(Activiti已经在6.X版本的时候放弃了PVM,Flowable亦是如此)。但是Camunda有一个好东西就是它的编辑器,它是基于http://bpmn.io的bpmn.js,cmmn.js,dmn.js来开发的,并且开源了自己的组件库,所以,如果你想做一个轻巧的、灵活的、定制性强的编辑器,工作流是嵌入式的,那么Camunda是一个好选择。
二、sb+activiti+jdk8实例
1、流程
请假申请流程
出差申请流程
2、创建库activiti
Security 所采取的权限访问控制方案:
RABC -基于角色的权限访问控制(Role-Based Access Control),导入 5 张表
/*Navicat Premium Data Transfer
Source Server : localhost
Source Server Type : MySQL
Source Server Version : 50733
Source Host : localhost:3306
Source Schema : activiti_spring Target Server Type : MySQL
Target Server Version : 50733
File Encoding : 65001 Date: 18/07/2022 15:55:30
*/SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for permission
-- ----------------------------
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`url` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`description` varchar(255) DEFAULT NULL,
`pid` bigint(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;-- ----------------------------
-- Records of permission
-- ----------------------------
BEGIN;
INSERT INTO `permission` VALUES (1, '/user/common', 'common', NULL, 0);
INSERT INTO `permission` VALUES (2, '/user/admin', 'admin', NULL, 0);
INSERT INTO `permission` VALUES (3, '/process/*', 'activiti', NULL, 0);
COMMIT;-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;-- ----------------------------
-- Records of role
-- ----------------------------
BEGIN;
INSERT INTO `role` VALUES (1, 'USER');
INSERT INTO `role` VALUES (2, 'ADMIN');
INSERT INTO `role` VALUES (3, 'ACTIVITI_USER');
COMMIT;-- ----------------------------
-- Table structure for role_permission
-- ----------------------------
DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`role_id` bigint(11) NOT NULL,
`permission_id` bigint(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;-- ----------------------------
-- Records of role_permission
-- ----------------------------
BEGIN;
INSERT INTO `role_permission` VALUES (1, 1, 1);
INSERT INTO `role_permission` VALUES (2, 2, 1);
INSERT INTO `role_permission` VALUES (3, 2, 2);
INSERT INTO `role_permission` VALUES (4, 3, 3);
COMMIT;-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;-- ----------------------------
-- Records of user
-- ----------------------------
BEGIN;
INSERT INTO `user` VALUES (1, 'user', '$2a$10$4zd/aj2BNJhuM5PIs5BupO8tiN2yikzP7JMzNaq1fXhcXUefWCOF2');
INSERT INTO `user` VALUES (2, 'admin', '$2a$10$4zd/aj2BNJhuM5PIs5BupO8tiN2yikzP7JMzNaq1fXhcXUefWCOF2');
INSERT INTO `user` VALUES (3, 'Jack', '$2a$10$4zd/aj2BNJhuM5PIs5BupO8tiN2yikzP7JMzNaq1fXhcXUefWCOF2');
INSERT INTO `user` VALUES (4, 'Marry', '$2a$10$4zd/aj2BNJhuM5PIs5BupO8tiN2yikzP7JMzNaq1fXhcXUefWCOF2');
COMMIT;-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` bigint(11) NOT NULL,
`role_id` bigint(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;-- ----------------------------
-- Records of user_role
-- ----------------------------
BEGIN;
INSERT INTO `user_role` VALUES (1, 1, 1);
INSERT INTO `user_role` VALUES (3, 2, 2);
INSERT INTO `user_role` VALUES (4, 2, 3);
INSERT INTO `user_role` VALUES (5, 3, 3);
INSERT INTO `user_role` VALUES (6, 4, 3);
COMMIT;SET FOREIGN_KEY_CHECKS = 1;
3、 实例项目activity2069
jdk8
1)、pom.xml
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.0</version>
</dependency>
<dependency><!--mysql-->
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
<version>8.0.29</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-engine</artifactId>
<version>7.1.0.M6</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring</artifactId>
<version>7.1.0.M6</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-bpmn-model</artifactId>
<version>7.1.0.M6</version>
</dependency> <dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-bpmn-converter</artifactId>
<version>7.1.0.M6</version>
</dependency> <dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-json-converter</artifactId>
<version>7.1.0.M6</version>
</dependency> <dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-bpmn-layout</artifactId>
<version>7.1.0.M6</version>
</dependency> <dependency>
<groupId>org.activiti.cloud</groupId>
<artifactId>activiti-cloud-services-api</artifactId>
<version>7.0.0.Beta1</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>7.1.0.M6</version>
</dependency>
<!--引入spring-boot-starter-security 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
2)、application.properties
server.port=2069
# 数据源名称
spring.datasource.name=test
# 数据库连接地址
spring.datasource.url=jdbc:mysql://localhost:3306/activiti?characterEncoding=UTF-8&serverTimezone=UTC&nullCatalogMeansCurrent=true
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=root
# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.jdbc.Driver #spring.activiti.check-process-definitions=false
#启用项目检测表,不存则创建
spring.activiti.database-schema-update=true
#启用历史表
spring.activiti.db-history-used=true
#历史记录级别
spring.activiti.history-level=full
#关闭springAutoDelployment
spring.activiti.database-schema=never-fail
#spring.thymeleaf.prefix=classpath:/view/
3)、config//全部放开-----免登录
import com.sc.activity2069.entity.Permission;
import com.sc.activity2069.reposity.PermissionMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigure
r;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import javax.annotation.Resource;
import java.util.List; @Configuration
@Slf4j
public class SecurityConfig { @Resource
private PermissionMapper permissionMapper; @Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry
authorizeRequests = http.csrf().disable().authorizeRequests();
// 方式二:配置来源于数据库
// 1.查询到所有的权限
List<Permission> allPermission = permissionMapper.findAllPermission();
// 2.分别添加权限规则
allPermission.forEach((p -> {
authorizeRequests.antMatchers(p.getUrl()).hasAnyAuthority(p.getName()) ;
})); authorizeRequests.antMatchers("/**").fullyAuthenticated()
.anyRequest().authenticated().and().formLogin();
return http.build();
} @Bean
WebSecurityCustomizer webSecurityCustomizer() {
return web -> {
web.ignoring().antMatchers("/css/**");
web.ignoring().antMatchers("/js/**");
web.ignoring().antMatchers("/img/**");
web.ignoring().antMatchers("/plugins/**");
web.ignoring().antMatchers("/login.html");
web.ignoring().antMatchers("/**");//全部放开-----免登录
};
} @Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
} }
4)、enity
import lombok.Data;
@Data
public class Permission {
private Integer id; private String url;
private String name;
private String description;
}
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class ResponseResult { private Integer status;
private String message;
private Object data;
public ResponseResult(Integer status,String message,Object data){
this(status,message);
this.data = data;
} public static ResponseResult getSuccessResult(Object data){
ResponseResult result = new ResponseResult(200, "成功!");
result.data = data;
return result; }
public ResponseResult(Integer status,String message){
this.status = status;
this.message = message;
}
} import lombok.Data;
@Data
public class Role {
private Integer id;
private String name;
} import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List; @Data
public class User implements UserDetails {
private Integer id;
private String username;
private String password;
private String role; // 用户所有权限
private List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(); public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
} @Override
public boolean isAccountNonExpired() {
return true;
} @Override
public boolean isAccountNonLocked() {
return true;
} @Override
public boolean isCredentialsNonExpired() {
return true;
} @Override
public boolean isEnabled() {
return true;
}
}
5)、reposity
import com.sc.activity2069.entity.Permission;
import org.apache.ibatis.annotations.Select;
import java.util.List; public interface PermissionMapper {
/**
* 查询用户的权限根据用户查询权限
*
* @return
*/
@Select("select * from permission")
List<Permission> findAllPermission();
} import com.sc.activity2069.entity.Permission;
import com.sc.activity2069.entity.User;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List; public interface UserMapper {
/**
* 根据用户名称查询
*
* @param userName
* @return
*/
@Select(" select * from user where username = #{userName}")
User findByUsername(@Param("userName") String userName); /**
* 查询用户的权限根据用户查询权限
*
* @param userName
* @return
*/
@Select(" SELECT d.*\n" +
"from user a,user_role b,role_permission c,permission d\n" +
"WHERE \n" +
"a.id = b.user_id\n" +
"and b.role_id = c.role_id\n" +
"and c.permission_id = d.id\n" +
"and \n" +
"a.username= #{userName};")
List<Permission> findPermissionByUsername(@Param("userName") String userName);
}
6)、controller
import com.sc.activity2069.entity.ResponseResult;
import lombok.extern.slf4j.Slf4j;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.repository.ProcessDefinitionQuery;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors; @Slf4j
@RestController
@RequestMapping("/process")
public class ActivitiController { @Resource
private RuntimeService runtimeService; @Resource
private RepositoryService repositoryService; @Resource
private TaskService taskService; /**
* 部署流程定义
* @param name
* @param filePath
* @return
*/
@PostMapping("deploy")
public ResponseResult deployProcess(@RequestParam(name = "name") String name,
@RequestParam(name= "filePath") String filePath){
Deployment deploy = repositoryService.createDeployment()
.addClasspathResource(filePath)
.deploy();
log.info("{} 流程定义完成部署",deploy.getName());
return ResponseResult.getSuccessResult(deploy);
} /**
* 查询流程
* @param key
* @return
*/
@GetMapping(value = {"/list/{key}","/list"})
public ResponseResult getProcessList(@PathVariable(name = "key",required = false) String key) {
ProcessDefinitionQuery definitionQuery = repositoryService.createProcessDefinitionQuery();
List<ProcessDefinition> definitionList;
if (key!=null){
definitionList = definitionQuery
.processDefinitionKey(key)
.list();
} definitionList = definitionQuery.list();
List<String> processList = new ArrayList<>();
for (ProcessDefinition processDefinition : definitionList) {
processList.add(processDefinition.getName());
}
return ResponseResult.getSuccessResult(processList);
} /**
* 启动流程定义(由流程定义-》流程实例)
* @param key
* @return
*/
@PostMapping("start/{key}")
public ResponseResult startProcess(@PathVariable(name = "key") String key){
Map<String,Object> map = new HashMap<>();
map.put("assignee0","Jack");
map.put("assignee1","Marry");
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(key,map);
ResponseResult result =ResponseResult.getSuccessResult(processInstance.getProcessDefinitionName());
log.info("流程实例的内容:{}",processInstance);
return result; }
/**
* 查看任务列表
* @return
*/
@GetMapping("/task/list")
public ResponseResult getMyTaskList(){
/* Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
//获取当前登录的用户名
String username = "";
if (principal instanceof UserDetails) {
username = ((UserDetails) principal).getUsername();
} if (principal instanceof Principal) {
username = ((Principal) principal).getName();
}*/
//获取任务
List<Task> tasks = taskService.createTaskQuery()
.taskAssignee("Jack")
.list();
//获取任务名称列表
List<String> TaskList = tasks.stream()
.map(Task::getName)
.collect(Collectors.toList()); return ResponseResult.getSuccessResult(TaskList);
} /**
* 完成任务
* @param key
* @param assigne
* @return
*/
@PostMapping("complete")
public ResponseResult doTask(@RequestParam(name = "key") String key, @RequestParam(name = "assignee")String assigne){
List<Task> tasks = taskService.createTaskQuery().processDefinitionKey(key)
.taskAssignee(assigne)
.list();
if (tasks!=null && tasks.size()>0){
for (Task task : tasks) {
log.info("任务名称:{}",task.getName());
taskService.complete(task.getId());
log.info("{},任务已完成",task.getName()); }
}
return ResponseResult.getSuccessResult(null); }
/**
* 删除部署
* @param deploymentId
* @return
*/
@PostMapping("delete/{id}")
public ResponseResult deleteDeployment(@PathVariable(name = "id") String deploymentId){
/**
* deleteDeployment() 方法的第二个参数 cascade 设置为 true,表示需要进行级联删除,从而可以删除掉未完成的任务
*/
repositoryService.deleteDeployment(deploymentId,true);
return ResponseResult.getSuccessResult(null);
}
}
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; @RestController
@RequestMapping("/user")
public class UserController { @GetMapping("/common")
public String common() {
return "hello~ common";
} @GetMapping("/admin")
public String admin() {
return "hello~ admin";
} @GetMapping("/test")
public String test() {
return "hello~ test";
} @GetMapping("/hello")
public String hello(@RequestParam("code") String code) {
return "hello~ 授权码 code 为:" + code;
}
}
7)、Activity2069Application
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; @MapperScan("com.sc.activity2069.reposity")
//@SpringBootApplication(exclude = {SecurityAutoConfiguration.class,ManagementWebSecurityAutoConfiguration.class})
@SpringBootApplication
public class Activity2069Application { public static void main(String[] args) {
SpringApplication.run(Activity2069Application.class, args);
}
}
8)、流程图
resources/processes/leaveApplication.bpmn20.xml resources/processes/myEvection.bpmn20.xml
4、 实例项目activity2069启动
项目启动之后,会发现,我们所配置的 activiti_spring 数据库中,便增加了 25 张 ACT 开头的表
5、流程启动
1)、查询流程
2)、启动 请假申请(leaveApplication)流程
查旬任务---------------
3)、请假申请审核(leaveApplication)流程
查旬任务---------------