盘点Flow:Activiti7Task入门

这是我参与更文挑战的第10天,活动详情查看: 更文挑战

首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> 😜😜😜
文章合集 : 🎁 juejin.cn/post/694164…
Github : 👉 github.com/black-ant

一 . 前言

此篇文档将开启 Activiti 的系列文档 , 所以这篇文章的内容主要以流程使用为主 , 为了更清晰 , 我们由 Task 往外层分析 >>>

后续再来看看 Process 实例的创建以及配置的处理

二 . 整体使用

2.1 Maven 依赖

<dependency>
    <groupId>org.activiti</groupId>
    <artifactId>activiti-spring-boot-starter</artifactId>
    <version>7.0.0.Beta2</version>
</dependency>

<!-- 由于需要使用数据库 , 所以需要加入如下 DAO 框架 -->

<!-- DAO -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- PS : 该包主要是为了构建一个 DataSource-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

复制代码

2.2 application.yml

PS : 无需创建数据库 , Activiti 中会默认创建表 , 创建流程我们以后来看 >>>

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/activiti007?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT&nullCatalogMeansCurrent=true
    username : root
    password : 123456
    driver-class-name: com.mysql.jdbc.Driver
  activiti:
    database-schema-update: true
server:
  port: 8086

复制代码

默认会建如下表 :

image.png

2.3 前置准备

Activiti 默认是需要和用户绑定的 , 此处需要进行必要的配置 :

在缓存中添加2个用户

@Configuration
public class SecurityConfiguration {

    private Logger logger = LoggerFactory.getLogger(SecurityConfiguration.class);

    @Bean
    public UserDetailsService myUserDetailsService() {

        InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
        logger.info("> Registering new user: " + "root" + " with the following Authorities[ 'ACTIVE' , 'ADMIN' ]");

        // 构建 Group 组信息
        List<SimpleGrantedAuthority> groupList = new ArrayList<>();
        // 注意 , 该权限是必须的
        groupList.add(new SimpleGrantedAuthority("ROLE_ACTIVITI_USER"));
        groupList.add(new SimpleGrantedAuthority("ADMIN"));

        // 准备2个用户 : Root , Admin
        inMemoryUserDetailsManager.createUser(new User("root", passwordEncoder().encode("123456"), groupList));
        inMemoryUserDetailsManager.createUser(new User("admin", passwordEncoder().encode("123456"), groupList));

        return inMemoryUserDetailsManager;
    }


    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

复制代码

模拟登录工具类

@Component
public class SecurityUtil {

    private Logger logger = LoggerFactory.getLogger(SecurityUtil.class);

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    public void logInAs(String username) {

        UserDetails user = userDetailsService.loadUserByUsername(username);

        logger.info("> 用户安全配置 (1) : 简单校验用户是否存在 [{}]", username);
        if (user == null) {
            throw new IllegalStateException("User " + username + " doesn't exist, please provide a valid user");
        }

        logger.info("------> 用户安全配置 (2) , Security 中模拟登录对象 :{} <-------", username);
        SecurityContextHolder.setContext(new SecurityContextImpl(new UsernamePasswordAuthenticationToken(user.getUsername(), "123456")));

        logger.info("------> 用户安全配置 (3) , Activiti 中设置对象 :{} <-------", username);
        org.activiti.engine.impl.identity.Authentication.setAuthenticatedUserId(username);
    }
}

复制代码

2.4 一个简单的 flow 流程

@RestController
public class StartController {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private ProcessRuntime processRuntime; //实现流程定义相关操作

    @Autowired
    private TaskRuntime taskRuntime; //实现任务相关操作

    @Autowired
    private SecurityUtil securityUtil;//SpringSecurity相关的工具类

    @RequestMapping("/test")
    public String test() {
        logger.info("------> [成功进入 StartController] <-------");
        return "Success !";
    }

    @GetMapping("/info")
    public String getInfd() {
        Page<ProcessDefinition> processDefinitionPage = processRuntime
                .processDefinitions(Pageable.of(0, 10));
        logger.info("------> 可用的流程定义数量:[{}] <-------", processDefinitionPage.getTotalItems());
        for (ProcessDefinition pd : processDefinitionPage.getContent()) {
            logger.info("------> 流程定义:[{}] <-------", pd);
        }

        return "success";
    }

    @GetMapping("/startFlow")
    public String startFlow() {
        securityUtil.logInAs("root");
        ProcessInstance pi = processRuntime.start(ProcessPayloadBuilder
                .start()
                // processers 中定义的 .bpm 文件
                .withProcessDefinitionKey("SimpleProcess")
                .build());//启动流程实例

        logger.info("------>流程实例ID: + [{}] <-------", pi.getId());
        return "开启流程";
    }

    @GetMapping("/selectFlow")
    public String selectFlow() {
        securityUtil.logInAs("root");
        Page<Task> taskPage = taskRuntime.tasks(Pageable.of(0, 10));
        if (taskPage.getTotalItems() > 0) {
            taskPage.getContent().forEach(item -> {
                logger.info("------> 剩余任务 :[{}] <-------", JSONObject.toJSONString(item));
            });
        } else {
            logger.info("------> 任务全部执行完成 <-------", taskPage.getContent());
        }


        return "查询 Flow : " + taskPage.getTotalItems();
    }

    @GetMapping("/doFlow")
    public String doFlowBusiness() {

        logger.info("------> [进入 doFlowBusiness 处理流程]  <-------");
        securityUtil.logInAs("root");
        Page<Task> taskPage = taskRuntime.tasks(Pageable.of(0, 10));

        logger.info("------> Task 启动完成 <-------");

        if (taskPage.getTotalItems() > 0) {
            for (Task task : taskPage.getContent()) {

                logger.info("------> 循环处理任务 [{}] <-------", task.getName());

                //拾取任务
                taskRuntime.claim(TaskPayloadBuilder.claim().withTaskId(task.getId()).build());

                //执行任务
                taskRuntime.complete(TaskPayloadBuilder.complete().withTaskId(task.getId()).build());
            }
        }

        logger.info("------> 查询任务的结果 <-------");

        Page<Task> taskPage2 = taskRuntime.tasks(Pageable.of(0, 10));
        if (taskPage2.getTotalItems() > 0) {
            logger.info("------> 剩余任务 :[{}] <-------", taskPage2.getContent());
        } else {
            logger.info("------> 任务全部执行完成 <-------", taskPage2.getContent());
        }

        return "Success : Do Flow Business 处理完成";
    }
    
    @GetMapping("deleteFlow")
    public String deleteFlow() {
        // PS : 此处如果是多个用户 , 需要切换用户
        // securityUtil.logInAs("admin");
        Page<Task> temTaskList = taskRuntime.tasks((Pageable.of(0, 10)));
        temTaskList.getContent().forEach(item -> {
            try {
                logger.info("------> Step 4 item : 删除Task :{} <-------", item.getId());
                taskRuntime.delete(TaskPayloadBuilder.delete().withTaskId(item.getId()).build());
            } catch (Exception e) {
                logger.error("E----> error :{} -- content :{}", e.getClass(), e.getMessage());
            }

        });
        return "success";
    }


}

复制代码

三 . Task 核心流程

@Component
public class ActivitiTaskRuntimeService implements ApplicationRunner {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    private static String taskId = "MyTask001";

    @Autowired
    private TaskRuntime taskRuntime;
    @Autowired
    private SecurityUtil securityUtil;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        logger.info("------> [开启一个完整的 Activiti 流程] , 首先模拟登录一个用户 <-------");

        // PS : Activiti 默认依赖 Spring Security
        securityUtil.logInAs("root");

        deleteTask();

        // 创建流程
        createTask();

        // 查询对象
        getTaskInfo();
        selectTaskInfo();

        selectTaskInfoByUserId("admin");
        selectTaskInfoByUserId("root");

        // 执行流程
        doTask();

        // 再次查询流程
        getTaskInfo();
        selectTaskInfo();
    }


    public void getTaskInfo() {
        try {
            // Step 2 : 查询单个 Task 信息
            Task temTask = taskRuntime.task(taskId);
            logger.info("------> Step 2  查询 ID : {} - 对应的 Task :{} <-------", taskId, JSONObject.toJSONString(temTask));
        } catch (NotFoundException e) {
            logger.error("E----> 当前 Task 已经处理完成 , 未查询到 error :{} -- content :{}", e.getClass(), e.getMessage());
        }
    }

    /**
     * 查询当前的 Task 案例
     */
    public void selectTaskInfo() {
        // Step 2 : 查询已知的所有的 Task 信息
        Pageable pageable = Pageable.of(0, 10);
        Page<Task> temTaskList = taskRuntime.tasks(pageable);
        temTaskList.getContent().forEach(item -> {
            logger.info("------> Step 2-1  查询系列数量 - [{}] - 对应的 Task :{}  <-------", temTaskList.getTotalItems(), JSONObject.toJSONString(item));
        });

    }

    /**
     * 对应委托人查询自己的任务
     */
    public void selectTaskInfoByUserId(String assignee) {
        // Step 2 : 查询已知的所有的 Task 信息
        Pageable pageable = Pageable.of(0, 10);
        Page<Task> temTaskList = taskRuntime.tasks(pageable, TaskPayloadBuilder.tasks().withAssignee(assignee).build());
        temTaskList.getContent().forEach(item -> {
            logger.info("------> Step 2-2  查询 assignee :{} 系列数量 - [{}] - 对应的 Task :{}  <-------", assignee, temTaskList.getTotalItems(), JSONObject.toJSONString(item));
        });

    }


    /**
     * 创建一个 Task 任务
     */
    public void createTask() {
        logger.info("------> Step 1 : 创建一个 Task 开始 <-------");
        CreateTaskPayload taskPayloadBuilder = TaskPayloadBuilder.create()
                .withName("First Team Task")
                .withDescription("This is something really important")
                // 设置当前 Task 的用户组
                .withGroup("ADMIN")
                .withPriority(10)
                .build();
        Task temTask = taskRuntime.create(taskPayloadBuilder);

        logger.info("------> Step 1 创建第二个 Task , 注意 , 此处设置了 Assignee  <-------");
        CreateTaskPayload taskPayloadBuilderTo = TaskPayloadBuilder.create()
                .withName("Second Team Task")
                .withDescription("This is something really important hava Assignee")
                // 设置当前 Task 的用户组
                .withGroup("ADMIN")
                .withAssignee("admin")
                .withPriority(10)
                .build();
        taskRuntime.create(taskPayloadBuilderTo);


        this.taskId = temTask.getId();
    }

    /**
     * 执行一个 Task
     */
    public void doTask() {

        logger.info("------> Step 3-1 : 声明一个 Task 开始 claimed <-------");
        taskRuntime.claim(TaskPayloadBuilder.claim().withTaskId(taskId).build());
        logger.info("------> Step 3-3 : 完成一个 Task 开始 complete <-------");
        taskRuntime.complete(TaskPayloadBuilder.complete().withTaskId(taskId).build());
    }

    /**
     * 删除 Task : PS : 注意 , 删除也需要权限
     */
    public void deleteTask() {
        logger.info("------> Step 4 : 删除所有的Task <-------");

        Pageable pageable = Pageable.of(0, 10);
        Page<Task> temTaskList = taskRuntime.tasks(pageable);
        temTaskList.getContent().forEach(item -> {
            try {
                logger.info("------> Step 4 item : 删除Task :{} <-------", item.getId());
                taskRuntime.delete(TaskPayloadBuilder.delete().withTaskId(item.getId()).build());
            } catch (Exception e) {
                logger.error("E----> error :{} -- content :{}", e.getClass(), e.getMessage());
            }

        });

        securityUtil.logInAs("admin");
        Pageable pageableAdmin = Pageable.of(0, 10);
        Page<Task> temTaskListAdmin = taskRuntime.tasks(pageable);
        temTaskListAdmin.getContent().forEach(item -> {
            try {
                logger.info("------> Step 4 item : 删除Task :{} <-------", item.getId());
                taskRuntime.delete(TaskPayloadBuilder.delete().withTaskId(item.getId()).build());
            } catch (Exception e) {
                logger.error("E----> error :{} -- content :{}", e.getClass(), e.getMessage());
            }
        });

        securityUtil.logInAs("root");


    }
}


复制代码

3.1 TaskRuntime 模块

接口一览

C- TaskRuntime
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);
  ...
}

复制代码

方法作用

  • Task task(String taskId) : 通过id获取任务
  • Page tasks(Pageable pageable); 获取当前认证用户所有的 Task
  • Page tasks(Pageable pageable,GetTasksPayload getTasksPayload) : 获取在Payload中应用筛选器的所有任务
  • Task create(CreateTaskPayload createTaskPayload) : 创建 Task
  • Task claim(ClaimTaskPayload claimTaskPayload) : 声明一个 Task
    • 如果没有经过验证的用户抛出IllegalStateException
    • 如果当前认证用户不是一个候选用户抛出IllegalStateException
    • 当前方法不支持模拟,它将始终接受当前已验证的用户
    • 在声明之后,任务应该处于分配状态
  • Task release(ReleaseTaskPayload releaseTaskPayload) : 发布一个先前声明的任务
  • Task complete(CompleteTaskPayload completeTaskPayload) : 在有效负载中设置变量来完成选定的任务
    • 此方法还检查任务是否在完成之前分配给当前已验证的用户
    • 该方法返回一个浅层Task对象,其中包含验证任务已完成所需的基本信息
  • Task update(UpdateTaskPayload updateTaskPayload) : 更新任务详细信息
  • Task delete(DeleteTaskPayload deleteTaskPayload) : 删除任务

3.2 Task 对象

Task 是流程中的核心流转对象 , 来看一下该对象的参数有哪些 :

public interface Task extends TaskInfo {

  /**
   * 创建新任务时优先级的默认值
   */
  int DEFAULT_PRIORITY = 50;

  /** 任务的名称或标题. */
  void setName(String name);
  
  /** 为任务设置可选的本地化名称. */
  void setLocalizedName(String name);

  /** 修改任务描述 */
  void setDescription(String description);
  
  /** 为任务设置可选的本地化描述. */
  void setLocalizedDescription(String description);

  /** 设定任务的重要性/紧迫性*/
  void setPriority(int priority);

  /**
   * 负责此任务的人员的userId.
   */
  void setOwner(String owner);

  /**
   * 被委派此任务的人的userId
   */
  void setAssignee(String assignee);

  /** 此任务的当前委派状态. */
  DelegationState getDelegationState();

  /** 此任务的当前委派状态。 */
  void setDelegationState(DelegationState delegationState);

  /**更改任务的到期日期 */
  void setDueDate(Date dueDate);

  /**
   * 更改任务的类别。这是一个可选的字段,允许将任务标记为属于某个类别。
   */
  void setCategory(String category);

  /** 父任务 ID */
  void setParentTaskId(String parentTaskId);

  /** 修改任务的tenantId */
  void setTenantId(String tenantId);

  /** 更改任务的表单键 */
  void setFormKey(String formKey);

  /** 指示此任务是否挂起. */
  boolean isSuspended();

}

复制代码

3.3 TaskRuntimeImpl 模块

TaskRuntimeImpl 是 TaskRuntime 的实现类 , Task 处理中 ,通过该对象实现业务的具体操作 , 我们以删除操作为例 , 看一下整体的流程 :

Step 1 : Delete 的触发

在业务中 ,通过调用 方法触发对 Task 的删除操作

    public void deleteTask() {
        logger.info("------> Step 4 : 删除所有的Task <-------");
        // 设置当前用户
        securityUtil.logInAs("admin");
        Pageable pageableAdmin = Pageable.of(0, 10);
        Page<Task> temTaskListAdmin = taskRuntime.tasks(pageable);
        temTaskListAdmin.getContent().forEach(item -> {
            taskRuntime.delete(TaskPayloadBuilder.delete().withTaskId(item.getId()).build());
        });

    }

复制代码

PS : 这里我们做了一个特殊操作 ->securityUtil.logInAs("admin");

这是因为 Task 的操作是由权限划分的 ,对应的人员只能操作自己的 Task

Step 2 : 调用 TaskRuntimeImpl # delete 方法

 public Task delete(DeleteTaskPayload deleteTaskPayload) {
        // 获取一个 Task
        Task task;
        try {
            task = task(deleteTaskPayload.getTaskId());
        } catch (IllegalStateException ex) {
            throw new IllegalStateException("T....");
        }
        
        // 获取当前认证的 Userid
        String authenticatedUserId = securityManager.getAuthenticatedUserId();
        // 验证您是否试图删除您是受让人或所有者的任务
        if ((task.getAssignee() == null 
                || task.getAssignee().isEmpty() 
                || !task.getAssignee().equals(authenticatedUserId))
           && (task.getOwner() == null 
               || task.getOwner().isEmpty() 
               || !task.getOwner().equals(authenticatedUserId))) {
            throw new IllegalStateException(".....");
        }
        
        // 这里可以理解为通过原本的数据构建了一个同样的 Task 
        TaskImpl deletedTaskData = new TaskImpl(task.getId(),
                                                task.getName(),
                                                Task.TaskStatus.DELETED);
        
        // 设置 Reason 原因
        if (!deleteTaskPayload.hasReason()) {
            deleteTaskPayload.setReason("Cancelled by " + authenticatedUserId);
        }
        
        // 执行 Service 删除 -> PS:0001
        taskService.deleteTask(deleteTaskPayload.getTaskId(),
                               deleteTaskPayload.getReason(),
                               true);
        return deletedTaskData;
    }


//  PS:0001 此处调用 taskService 进行处理 -> TaskServiceImpl
public void deleteTask(String taskId, String deleteReason, boolean cancel) {
    commandExecutor.execute(new DeleteTaskCmd(taskId, deleteReason, false, cancel));
}

复制代码

其中有2个需要注意的地方 :

  • C- DeleteTaskCmd : 该对象是一个命令对象 , 猜测这里是使用命令模式进行处理
  • C- commandExecutor 对象

来看一下对象是干嘛的 :

// commandExecutor 是一个接口 , 其中有三个方法
C- commandExecutor : 
	M- CommandConfig getDefaultConfig() : 获取默认的CommandConfig,如果没有提供就使用
	M- <T> T execute(CommandConfig config, Command<T> command) : 使用指定的CommandConfig执行命令
	M-  <T> T execute(Command<T> command) : 使用默认的 CommandConfig执行命令
        
C- CommandExecutorImpl
	I- commandExecutor    
        
// 我们来看一下 , 执行了什么      
public <T> T execute(CommandConfig config, Command<T> command) {
    // 这里的 first 是 LogInterceptor
    return first.execute(config, command);
}


复制代码

PS : 到了 first.execute(config, command); 这一步 ,实际上就开始调用拦截器链了

Step 3 : 拦截器链的调用

拦截链的生成后面展示 , 这里只说一说执行了什么

// 从前面我们分析 Delete 流程的时候就可以看到 , 其走了一个拦截链  ,如下图所示 : 

- CommandContextInterceptor
- CommandInvoker
- DebugCommandInvoker
- CommandInvoker
- JtaRetryInterceptor
- JtaTransactionInterceptor
- LoggingCommandInvoker
- SpringTransactionInterceptor
- TotalExecutionTimeCommandInterceptor
- TransactionContextInterceptor
- RetryInterceptor

复制代码

avtiviti-AbstractCommandInterceptor.png

这些拦截链并不是全部会走 , 主要走的如下几个 :

  • Step 1 : SpringTransactionInterceptor : 控制事务
  • Step 2 : CommandContextInterceptor : 准备容器
  • Step 3 : TransactionContextInterceptor : 构建 TransactionContext
  • Step 4 : CommandInvoker 中准备 DbSqlSession , 通过 executeOperation # runnable.run() 执行处理线程
  • Step 5 : NeedsActiveTaskCmd 执行 TaskCmd
  • Step 6 : CompleteTaskCmd 发起 execute 执行 (PS : 操作类型在 Command 中 ,典型的命令模式)
  • Step End : TaskEntityManagerImpl 中执行具体的 DB 操作

总结

这一篇基本上把主流程大概介绍完了 , 后面来深入一下以下操作 :

  • SpringConfiguration 操作
  • 整体 Process 流程
  • 定制操作处理