目录
5.1 API 新特性 - ProcessRuntime
5.1.1 官方文档
- 与TaskRuntime API类似,为了与ProcessRuntime API交互,当前登录的用户必须具有“ACTIVITI_user”角色。
5.1.2 设置谷歌样式(题外话)
intellij-java-google-style.xml
5.1.2 拉取 Activiti 源码
> ssh 或者 zip 都可以,反正下载就可以了
5.1.3 拷贝源码两个类
- DemoApplicationConfiguration
- SecurityUtil
com.edcode.activiti.DemoApplicationConfiguration#myUserDetailsService# usersGroupsAndRoles修改两个用户
// 1用户名、2用户密码、3用户角色、4用户组
String[][] usersGroupsAndRoles = {
{"bob", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
// 修改 bajie 和 wukong
{"bajie", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
{"wukong", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
{"other", "password", "ROLE_ACTIVITI_USER", "GROUP_otherTeam"},
{"system", "password", "ROLE_ACTIVITI_USER"},
{"admin", "password", "ROLE_ACTIVITI_ADMIN"},
};
其余默认
5.2 API 新特性 - ProcessRuntime
5.2.1 创建流程实例(单元测试类)
- Part8_ProcessRuntime
5.2.1.1 获取流程实例
@Test
public void getProcessInstance() {
// 1. 登录
securityUtil.logInAs("bajie");
// 2. 分页查询
Page<ProcessInstance> processInstancePage = processRuntime.processInstances(Pageable.of(0, 100));
System.out.println("流程实例数量:" + processInstancePage.getTotalItems());
processInstancePage.getContent().forEach(processInstance -> {
System.out.println("-----------------------");
System.out.println("getId:" + processInstance.getId());
System.out.println("getName:" + processInstance.getName());
System.out.println("getStartDate:" + processInstance.getStartDate());
System.out.println("getStatus:" + processInstance.getStatus());
System.out.println("getProcessDefinitionId:" + processInstance.getProcessDefinitionId());
System.out.println("getProcessDefinitionKey:" + processInstance.getProcessDefinitionKey());
});
}
5.2.1.2 bpmn部署
- 创建 Part8_ProcessRuntime.bpmn20.xml
- com.edcode.activiti.Part1_Deployment# initDeploymentBPMN
@Test
public void initDeploymentBPMN() {
String filename = "BPMN/Part8_ProcessRuntime.bpmn20.xml";
// String pngname="BPMN/Part1_Deployment.png";
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource(filename)
// .addClasspathResource(pngname)
.name("流程部署测试 - pRuntime")
.deploy();
System.out.println(deployment.getName());
}
5.2.1.3 启动流程实例
@Test
public void startProcessInstance() {
securityUtil.logInAs("bajie");
ProcessInstance processInstance = processRuntime.start(ProcessPayloadBuilder
.start()
.withProcessDefinitionKey("myProcess_ProcessRuntime")
.withName("第一个流程实例名称")
// .withVariable("","")
.withBusinessKey("自定义bKey").build());
}
5.2.1.4 查询我的代办任务
- com.edcode.activiti.Part4_Task# getTasksByAssignee
@Test
public void getTasksByAssignee() {
taskService.createTaskQuery()
.taskAssignee("bajie")
.list().forEach(task -> {
System.out.println("Id:" + task.getId());
System.out.println("Name:" + task.getName());
System.out.println("Assignee:" + task.getAssignee());
});
}
5.2.2 删除流程实例(单元测试类)
5.2.2.1 获取流程实例
调用 getProcessInstance() 方法,获取刚刚创建的流程实例 id
5.2.2.2 删除流程实例
@Test
public void delProcessInstance() {
securityUtil.logInAs("bajie");
ProcessInstance processInstance = processRuntime.delete(ProcessPayloadBuilder
.delete()
.withProcessInstanceId("3f28b96a-775d-11ec-ac61-5e879ca31830")
.build());
}
若这个时候再次查询,刚刚启动的流程实例,就删除不到了
5.2.3 挂起流程实例(单元测试类)
/**
* 挂起流程实例
* getStatus:RUNNING to getStatus:SUSPENDED (暂停)
*/
@Test
public void suspendProcessInstance() {
securityUtil.logInAs("bajie");
ProcessInstance processInstance = processRuntime.suspend(ProcessPayloadBuilder
.suspend()
.withProcessInstanceId("5a5eb790-7438-11ec-b645-5e879ca31830")
.build());
}
5.2.4 激活流程实例(单元测试类)
/**
* 激活流程实例
* getStatus:SUSPENDED to getStatus:RUNNING
*/
@Test
public void resumeProcessInstance() {
securityUtil.logInAs("bajie");
ProcessInstance processInstance = processRuntime.resume(ProcessPayloadBuilder
.resume()
.withProcessInstanceId("5a5eb790-7438-11ec-b645-5e879ca31830")
.build());
}
5.2.5 获取流程实例参数(单元测试类)
@Test
public void getVariables() {
securityUtil.logInAs("bajie");
processRuntime
.variables(ProcessPayloadBuilder.variables()
.withProcessInstanceId("2b2d3990-d3ca-11ea-ae96-dcfb4875e032").build())
.forEach(variableInstance -> {
System.out.println("-------------------");
System.out.println("getName:" + variableInstance.getName());
System.out.println("getValue:" + variableInstance.getValue());
System.out.println("getTaskId:" + variableInstance.getTaskId());
System.out.println("getProcessInstanceId:" + variableInstance.getProcessInstanceId());
});
}
5.2.6 流程实例 UEL变量参数(单元测试类)
/**
* 流程实例 UEL变量参数
* VariableInstance
* 版本6 是在org.activiti.engine.*
* 版本7 是在org.activiti.api.*
*/
@Test
public void getVariables() {
securityUtil.logInAs("bajie");
processRuntime
.variables(ProcessPayloadBuilder.variables()
.withProcessInstanceId("5d8dd859-7477-11ec-907c-5e879ca31830")
.build())
.forEach(variableInstance -> {
System.out.println("-------------------");
System.out.println("getName:" + variableInstance.getName());
System.out.println("getValue:" + variableInstance.getValue());
System.out.println("getTaskId:" + variableInstance.getTaskId());
System.out.println("getProcessInstanceId:" + variableInstance.getProcessInstanceId());
});
}
5.2.6.1 输出效果
-------------------
getName:pay
getValue:101
getTaskId:null
getProcessInstanceId:5d8dd859-7477-11ec-907c-5e879ca31830
id 需要是有 uel 变量参数的流程,不然返回空
5.4 API 新特性 - TaskRuntime
5.4.1 获取当前登录用户任务
@Test
public void getTasks() {
securityUtil.logInAs("wukong");
Page<Task> tasks = taskRuntime.tasks(Pageable.of(0, 100));
tasks.getContent().forEach(task -> {
System.out.println("-------------------");
System.out.println("getId:" + task.getId());
System.out.println("getName:" + task.getName());
System.out.println("getStatus:" + task.getStatus());
System.out.println("getCreatedDate:" + task.getCreatedDate());
if (task.getAssignee() == null) {
//候选人为当前登录用户,null的时候需要前端拾取
System.out.println("Assignee:待拾取任务");
} else {
System.out.println("Assignee:" + task.getAssignee());
}
});
}
5.4.2 完成任务
@Test
public void completeTask() {
securityUtil.logInAs("wukong");
Task task = taskRuntime.task("286808fc-74fc-11ec-b5e3-5e879ca31830");
if (task.getAssignee() == null) {
taskRuntime.claim(TaskPayloadBuilder.claim()
.withTaskId(task.getId())
.build());
}
taskRuntime.complete(TaskPayloadBuilder
.complete()
.withTaskId(task.getId())
.build());
System.out.println("任务执行完成");
}
5.5 Spring Security 用户登录
Activiti 是新特性是需要 Spring Security 技术栈
5.5.1 Spring Security 的主要功能
- 认证
- 授权/鉴权
5.5.2 用户的三种来源
- application.properties 配置用户
- 代码中配置内存用户
- 从数据库中加载用户
5.5.3 接着要说的知识点
- 内存登录改为数据库登录
- SpringSecurity 配置文件详解
- 登录响应处理方案
5.5.4 pom.xml
既然 Activiti 是新特性是需要 Spring Security 技术栈,那么 Activiti 本身就自带了 Spring Security ,除非有版本需要,可以排除在引入自己需要 Spring Security 版本
5.5.5 启动项目
- DemoApplicationConfiguration
- 屏蔽 @Configuration 与 @Bean
5.5.5.1 使用 Security 指定的账户密码
请求:http://localhost:8080/hello 会显示如下页面:
5.5.5.2 使用代码中配置内存用户
- DemoApplicationConfiguration
- 解除 @Configuration 与 @Bean
- 使用 usersGroupsAndRoles 指定的帐号密码登录
5.5.5.3 任意用户名+自定义密码登录
@Configuration
public class MyUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String passWord = passwordEncoder().encode("123456");
return new User(
username,
passWord,
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_ACTIVITI_USER"));
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
任意用户名,密码:123456,就可以登录。
5.6 Spring Security 用户登录
5.6.1 数据库方式查询用户
创建用户实体
@Component
public class UserInfoBean implements UserDetails {
private Long id;
public String name;
private String address;
private String username;
private String password;
private String roles;
/**
* 从数据库中取出roles字符串后,进行分解,构成一个GrantedAuthority的List返回
*
* 库里面是逗号分隔,ROLE_ACTIVITI_USER,GROUP_activitiTeam,g_bajiewukong
*
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Arrays.stream(roles.split(","))
.map(e -> new SimpleGrantedAuthority(e))
.collect(Collectors.toSet());
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
public String getAddress() {
return address;
}
}
创建用户Mapper
@Mapper
@Component
public interface UserInfoBeanMapper {
/**
* 从数据库中查询用户
*
* @param username
* @return
*/
@Select("select * from user where username = #{username}")
UserInfoBean selectByUsername(@Param("username") String username);
}
自定义 UserDetailsService
@Configuration
@RequiredArgsConstructor
public class MyUserDetailsServiceImpl implements UserDetailsService {
private Logger logger = LoggerFactory.getLogger(getClass());
private final UserInfoBeanMapper userInfoBeanMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
logger.info("登录用户:" + username);
String passWord = passwordEncoder().encode("123456");
logger.info("登录密码:" + passWord);
// return new User(
// // 没有做任何校验,密码是自定义
// username,
// passWord,
// AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_ACTIVITI_USER"));
// 读取数据库判断用户
// 如果用户是 null 抛出异常
// 返回用户信息
UserInfoBean userInfoBean = userInfoBeanMapper.selectByUsername(username);
if (userInfoBean == null) {
logger.error("数据库中没有 [{}] 用户",username);
throw new UsernameNotFoundException("数据库中没有此用户");
}
return userInfoBean;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
按照数据库 user 表里面用户和密码进行登录
5.7 Spring Security 配置
- 登录成功与登录失败的初阶段处理
5.7.1 Security 配置类
package com.edcode.activiti.security;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* @author eddie.lee
* @date 2022-01-17 20:30
* @description Security 配置类
*/
@Configuration
@RequiredArgsConstructor
public class ActivitiSecurityConfig extends WebSecurityConfigurerAdapter {
private final LoginSuccessHandler loginSuccessHandler;
private final LoginFailureHandler loginFailureHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
// 登录方法
.loginPage("/login")
.loginProcessingUrl("/login")
.successHandler(loginSuccessHandler)
.failureHandler(loginFailureHandler)
.and()
.authorizeRequests()
.anyRequest().permitAll()
.and()
.logout().permitAll()
.and()
.csrf().disable()
.headers().frameOptions().disable()
;
}
}
5.7.2 登录成功的处理
package com.edcode.activiti.security;
import com.edcode.activiti.util.AjaxResponse;
import com.edcode.activiti.util.GlobalConfig;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author eddie.lee
* @date 2022-01-17 20:35
* @description 登录成功
*/
@Component("loginSuccessHandler")
@RequiredArgsConstructor
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, Authentication authentication) throws IOException, ServletException {
}
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, Authentication authentication)
throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.getWriter().write("登录成功 loginSuccessHandler:" + authentication.getName());
}
}
5.7.3 登录失败的处理
package com.edcode.activiti.security;
import com.edcode.activiti.util.AjaxResponse;
import com.edcode.activiti.util.GlobalConfig;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author eddie.lee
* @date 2022-01-17 20:44
* @description 登录失败
*/
@Component("loginFailureHandler")
@RequiredArgsConstructor
public class LoginFailureHandler implements AuthenticationFailureHandler {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, AuthenticationException e)
throws IOException, ServletException {
logger.info("登录失败");
httpServletResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.getWriter().write("登录失败, 原因是:" + e.getMessage());
}
}
5.7.4 控制层
package com.edcode.activiti.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
/**
* @author eddie.lee
* @date 2022-01-17 20:52
* @description 外部请求
*/
@RestController
@RequiredArgsConstructor
public class ActivitiSecurityController {
private final Logger logger = LoggerFactory.getLogger(getClass());
@PostMapping("/login")
@ResponseStatus(code = HttpStatus.UNAUTHORIZED)
public String requireAuthentication(HttpServletRequest request, HttpServletResponse response) {
return new String("需要登录,使用/login.html或发起post求情");
}
}
5.7.5 Postman
5.9 BPMN-JS 使用
5.9.1 Activiti7与BPMN-JS整合
5.9.1.1 安装Node.js
- 下载地址https://nodejs.org/en/download/
5.9.1.2 BPMN-JS地址
- 下载源码https://github.com/bpmn-io/bpmn-js-examples/
5.9.1.3 在resources文件夹下再创建一个resources文件夹,
实际路径为resources/resources/
5.9.1.4 从github克隆bpmn-js-examples
bpmn-js-examples 项目下 properties-panel 复制到你当前的Springboot工程里,把名称 properties-panel 修改为 bpmnjs
5.9.1.5 解压 bpmnjs_init.zip 汉化包资料拖到bpmnjs
5.9.1.6 屏蔽部分引入
src/main/resources/resources/bpmnjs/app/index.js
// import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda';
// import camundaModdleDescriptor from 'camunda-bpmn-moddle/resources/camunda.json';
// var bpmnModeler = new BpmnModeler({
// container: canvas,
// propertiesPanel: {
// parent: '#js-properties-panel'
// },
// additionalModules: [
// propertiesPanelModule,
// propertiesProviderModule
// ],
// moddleExtensions: {
// camunda: camundaModdleDescriptor
// }
// });
5.9.1.7 加入汉化包配置引用
import propertiesProviderModule from '../resources/properties-panel/provider/activiti';
import activitiModdleDescriptor from '../resources/activiti.json';
import customTranslate from '../resources/customTranslate/customTranslate';
import customControlsModule from '../resources/customControls';
// 添加翻译组件
var customTranslateModule = {
translate: ['value', customTranslate]
};
var bpmnModeler = new BpmnModeler({
container: canvas,
propertiesPanel: {
parent: '#js-properties-panel'
},
additionalModules: [
propertiesPanelModule,
propertiesProviderModule,
customControlsModule,
customTranslateModule
],
moddleExtensions: {
activiti:activitiModdleDescriptor
}
});
增加BPMNJS可执行选框默认勾选,打开resources/newDiagram.bpmn
<bpmn2:process id="Process_1" isExecutable="true">
5.9.1.8 BPMN-JS 前端项目启动
cmd 或者 idea 工具都可以
切换地址:{PATH}\src\main\resources\res
ources\bpmnjs
安装需要依赖
npm install
运行 bpmn-js 项目
npm run dev
就会弹出:http://localhost:9013/
5.9.2 整合时候出现问题
-
properties-panel 没有 app 文件夹
答:版本问题,在 8.1.0 之后就没有了 app 文件夹 -
打开 WEB 页面之后提示:“Import Error Details bo.get is not a function”
答:版本过高,推荐版本是在 Master v7.2.1 - v7.3.1 (汉化包兼容到这)