【Aactiviti7 从入门到放弃】(二)Activiti7工作流新特性

目录

5.1 API 新特性 - ProcessRuntime

5.1.1 官方文档

Gitbook Address

  • 与TaskRuntime API类似,为了与ProcessRuntime API交互,当前登录的用户必须具有“ACTIVITI_user”角色。

5.1.2 设置谷歌样式(题外话)

intellij-java-google-style.xml
在这里插入图片描述

5.1.2 拉取 Activiti 源码

Activiti Github

在这里插入图片描述> 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 整合时候出现问题

  1. properties-panel 没有 app 文件夹
    答:版本问题,在 8.1.0 之后就没有了 app 文件夹

  2. 打开 WEB 页面之后提示:“Import Error Details bo.get is not a function”
    答:版本过高,推荐版本是在 Master v7.2.1 - v7.3.1 (汉化包兼容到这)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

eddie_k2

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值