Flowable7.x学习笔记(十八)拾取我的待办

前言

        本文从解读源码到实现功能,完整的学习Flowable的【TaskService】-【claim】方法实现的任务拾取功能。

一、概述

        当调用 TaskService.claim(taskId, userId) 时,Flowable 会先加载并校验任务实体,再判断该任务是否已被认领;若未被认领,则将 assignee 字段设为传入的 userId(null 则表示“解绑”),并在身份链接(IdentityLink)表中做相应的增删操作;随后触发任务分配事件、写入历史记录(如开启历史配置时),最后将更新结果刷新到数据库。所有这些操作最终都委托给了 TaskServiceImpl 中的 ClaimTaskCmd 来完成。

二、方法签名与定位

        claim(String taskId, String userId) 方法在 org.flowable.engine.TaskService 接口中定义,由 TaskServiceImpl 实现,实现层直接调用命令执行器(commandExecutor.execute(new ClaimTaskCmd(taskId, userId))),ClaimTaskCmd 是执行认领逻辑的核心。

        ClaimTaskCmd 继承自 NeedsActiveTaskCmd,用于确保任务处于激活状态后再执行认领逻辑。

public class ClaimTaskCmd extends NeedsActiveTaskCmd {
    protected String userId;
    public ClaimTaskCmd(String taskId, String userId) {
        super(taskId);
        this.userId = userId;
    }
}

        构造器保存了要认领的 taskId 和 userId,并由父类处理挂起状态检查,当传入的 userId 非空时,表示要将任务分配给某位用户。

① 设置认领时间与状态

        使用引擎的 Clock 获取当前时间,调用 task.setClaimTime(...) 和 task.setClaimedBy(userId) 设置认领元数据。将任务状态更新为 Task.CLAIMED。

② 冲突检查

        若该任务已被分配给某人(task.getAssignee()!=null),且与当前 userId 不同,则抛出 FlowableTaskAlreadyClaimedException,避免多用户并发认领。

        若已分配给同一用户,则仅记录一次 recordTaskInfoChange(...),保持历史一致性。

③ 首次分配

        当任务尚未有 assignee 时,调用 TaskHelper.changeTaskAssignee(task, userId) 为任务设置新认领者。

        如果配置了 UserTaskStateInterceptor,会触发其 handleClaim(...) 回调,用于外部扩展。

④ 写入身份链接历史

        最后,无论是首次分配还是重复分配,都通过 HistoryManager.createUserIdentityLinkComment(...) 将 ASSIGNEE 类型的身份链接(认领记录)写入历史审计表。

三、加载与校验任务

① 加载任务实体

        命令中首先通过 TaskEntityManager.findById(taskId) 从 ACT_RU_TASK 表中获取 TaskEntity。

② 任务存在性检查

        若返回 null,抛出 FlowableObjectNotFoundException,提示“无此任务”。

③ 已认领冲突检查

        若 task.getAssignee() 不为 null 且与传入的 userId 不同,则抛出冲突异常(FlowableConflictException),禁止不同用户重复认领.

四、赋值 assignee 与解绑

① 设为认领

        当 userId 非空时,调用 TaskEntity.setAssignee(userId) 将任务归属新认领者。

② 设为解绑

        当 userId==null 时,等价于 unclaim 操作,相当于将 assignee 设回 null。

五、身份链接(IdentityLink)处理

① 历史遗留机制

        Flowable 中“候选人”与“认领人”都存储在 ACT_RU_IDENTITYLINK 表,但 ASSIGNEE 类型在表中常伴随特殊处理(空 TASK_ID 或 PROC_INSTANCE_ID 字段)。

② 若是认领

        在命令里会先 删除 原有与该任务相关的 ASSIGNEE 类型 IdentityLink,再 新增 一条新的 IdentityLinkType.ASSIGNEE,以确保数据一致。

③ 若是解绑

        则只执行删除操作,不新增。

六、事件分发与历史记录

① 事件分发

        完成认领后,Flowable 会通过 EventDispatcher 发布 ENTITY_LINK_CREATED(或删除时 ENTITY_LINK_DELETED)以及 TASK_ASSIGNED 等事件,供监听器或审计插件消费。

② 历史记录

        若引擎配置 historyLevel ≥ AUDIT,会在 ACT_HI_TASKINST 表中记录任务认领时间、认领者等信息。

七、持久化更新

        最后,命令通过 TaskEntityManager.update(taskEntity) 将变更刷回 ACT_RU_TASK 表,并同步提交事务。若配置了异步历史,历史记录写入也可能延迟到异步作业中完成,进一步提升性能。

八、完成后端接口

① 定义请求参数

        这里我们只需要传入任务ID即可,用户ID我们从框架的session中获取,一般的脚手架都是这样的,请结合自己的脚手架处理。

package com.ceair.entity.request;import lombok.Data;import java.io.Serial;
import java.io.Serializable;/*** @author wangbaohai* @ClassName PickupMyTaskReq* @description: 拾取我的任务请求参数* @date 2025年05月03日* @version: 1.0.0*/
@Data
public class PickupMyTaskReq implements Serializable {@Serialprivate static final long serialVersionUID = 1L;// 任务编号private String taskId;}

② 定义服务接口

/*** 领取我的任务接口* 此方法允许用户领取属于自己的任务,根据提供的条件和参数** @param pickupMyTaskReq 领取任务的请求对象,包含领取任务所需的信息和条件* @return 返回一个Boolean值,表示任务领取是否成功true表示成功,false表示失败*/
Boolean pickupMyTask(PickupMyTaskReq pickupMyTaskReq);

③ 实现服务接口

/*** 拾取我的待办任务* <p>* 此方法允许当前登录用户拾取一个待办任务通过提供任务ID和当前用户信息,* 系统将该任务分配给当前用户** @param pickupMyTaskReq 包含任务ID的请求对象如果请求对象或任务ID为空,*                        将抛出IllegalArgumentException异常* @return 任务拾取成功返回true,否则抛出异常* @throws BusinessException        如果用户未登录或任务拾取过程中发生业务异常* @throws IllegalArgumentException 如果输入参数不合法,如任务ID为空*/
@Override
public Boolean pickupMyTask(PickupMyTaskReq pickupMyTaskReq) {try {// 参数判空if (pickupMyTaskReq == null || StringUtils.isBlank(pickupMyTaskReq.getTaskId())) {log.error("任务拾取失败:非法的任务ID");throw new IllegalArgumentException("任务拾取失败:非法的任务ID");}// 获取当前登录用户信息UserInfo userInfo = userInfoUtils.getUserInfoFromAuthentication();if (userInfo == null) {log.error("拾取我的待办任务失败,原因:用户未登录");throw new BusinessException("拾取我的待办任务失败,原因:用户未登录");}// 通过 taskService 拾取任务taskService.claim(pickupMyTaskReq.getTaskId(), String.valueOf(userInfo.getId()));return true;} catch (IllegalArgumentException e) {log.error("任务拾取失败,原因:参数错误", e);throw new BusinessException("任务拾取失败,原因:参数错误", e);} catch (BusinessException e) {log.error("任务拾取失败,原因:业务异常", e);throw new BusinessException("任务拾取失败,原因:业务异常", e);} catch (Exception e) {log.error("任务拾取失败,原因:未知异常", e);throw new BusinessException("任务拾取失败,原因:未知异常", e);}
}

④ 定义功能接口

/*** 任务拾取。* <p>* 权限: /api/v1/myTask/pickupMyTask* 参数: pickupMyTaskReq - 包含任务拾取相关信息的请求对象* 返回: Result<Boolean> 表示任务拾取是否成功* <p>* 异常处理:* - 业务层异常  返回任务拾取失败信息* - 其他未知异常  系统异常提示*/
@PreAuthorize("hasAnyAuthority('/api/v1/myTask/pickupMyTask')")
@Parameter(name = "pickupMyTaskReq", description = "任务拾取请求对象", required = true)
@Operation(summary = "任务拾取")
@PostMapping("/pickupMyTask")
public Result<Boolean> pickupMyTask(@RequestBody PickupMyTaskReq pickupMyTaskReq) {try {// 调用业务层方法,将任务分配给当前登录用户return Result.success(mayTaskService.pickupMyTask(pickupMyTaskReq));} catch (Exception e) {log.error("任务拾取失败,原因:{}", e.getMessage());return Result.error("任务拾取失败,原因:" + e.getMessage());}
}

九、完善前端功能按钮

① 定义前端参数类型

// 拾取任务请求参数

export interface PickupMyTaskReq {

  taskId: string // 任务编号,对应 Java 中的 String taskId

}

② 封装请求接口

/**

 * 拾取任务

 */

export function pickupMyTask(data: PickupMyTaskReq) {

  return request.post<any>({

    url: '/pm-process/api/v1/myTask/pickupMyTask',

    data,

  })

}

③ 优化界面按钮

        这里把拾取按钮加上权限自定义指令,以及点击事件功能

<el-button v-if="scope.row.status === 1" v-hasButton="`btn.myTask.pickupMyTask`" type="primary" @click="onPickup(scope.row)">
  拾取
</el-button>

④ 完成按钮功能

/*** 异步函数用于处理任务拾取操作* @param row 任务对象,包含任务ID等信息*/
async function onPickup(row: TaskVO) {try {// 获取当前任务ID并设置参数const param: PickupMyTaskReq = {taskId: row.taskId,}// 调用后端接口进行拾取操作const result: any = await pickupMyTask(param)// 如果接口调用成功且返回的状态码为200,则显示成功提示信息if (result.success && result.code === 200) {ElMessage({message: '拾取成功',type: 'success',})// 重新加载数据handerPageData()}else {ElMessage({message: `拾取失败: ${result.message}`,type: 'error',})}}catch (error) {// 捕获异常并提取错误信息let errorMessage = '未知错误'if (error instanceof Error) {errorMessage = error.message}// 显示操作失败的错误提示信息ElMessage({message: `拾取失败: ${errorMessage || '未知错误'}`,type: 'error',})}
}

十、添加权限

创建按钮

分配权限

十一、验证功能

定义一个流程并且发布,第一个节点我们作为候选人,让我们可以拾取

启动流程

查看待办

拾取待办

可以看到我们拾取任务成功后,就可以办理或者归还任务了。

后记

        下一篇文章来梳理归还任务的梳理以及具体的实现方法,本文的完整代码仓库地址请查看专栏第一篇文章的说明。

本文的后端分支是 process-10

本文的前端分支是 process-12

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/79325.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SQL经典实例

第1章 检索记录 1.1 检索所有行和列 知识点&#xff1a;使用SELECT *快速检索表中所有列&#xff1b;显式列出列名&#xff08;如SELECT col1, col2&#xff09;提高可读性和可控性&#xff0c;尤其在编程场景中更清晰。 1.2 筛选行 知识点&#xff1a;通过WHERE子句过滤符合条…

HTTPcookie与session实现

1.HTTP Cookie 定义 HTTP Cookie &#xff08;也称为 Web Cookie 、浏览器 Cookie 或简称 Cookie &#xff09;是服务器发送到 用户浏览器并保存在浏览器上的一小块数据&#xff0c;它会在浏览器之后向同一服务器再次发 起请求时被携带并发送到服务器上。通常&#xff0…

【算法基础】冒泡排序算法 - JAVA

一、算法基础 1.1 什么是冒泡排序 冒泡排序是一种简单直观的比较排序算法。它重复地走访待排序的数列&#xff0c;依次比较相邻两个元素&#xff0c;如果顺序错误就交换它们&#xff0c;直到没有元素需要交换为止。 1.2 基本思想 比较相邻元素&#xff1a;从头开始&#xf…

0902Redux_状态管理-react-仿低代码平台项目

文章目录 1 Redux 概述1.1 核心概念1.2 基本组成1.3 工作流程1.4 中间件&#xff08;Middleware&#xff09;1.5 适用场景1.6 优缺点1.7 Redux Toolkit&#xff08;现代推荐&#xff09;1.8 与其他工具的对比1.9 总结 2 todoList 待办事项案例3 Redux开发者工具3.1 核心功能3.2…

《ATPL地面培训教材13:飞行原理》——第6章:阻力

翻译&#xff1a;Leweslyh&#xff1b;工具&#xff1a;Cursor & Claude 3.7&#xff1b;过程稿 第6章&#xff1a;阻力 目录 引言寄生阻力诱导阻力减少诱导阻力的方法升力对寄生阻力的影响飞机总阻力飞机总重量对总阻力的影响高度对总阻力的影响构型对总阻力的影响速度稳…

C++总结01-类型相关

一、数据存储 1.程序数据段 • 静态&#xff08;全局&#xff09;数据区&#xff1a;全局变量、静态变量 • 堆内存&#xff1a;程序员手动分配、手动释放 • 栈内存&#xff1a;编译器自动分配、自动释放 • 常量区&#xff1a;编译时大小、值确定不可修改 2.程序代码段 •…

【Hot 100】94. 二叉树的中序遍历

目录 引言二叉树的中序遍历我的解题代码优化更清晰的表述建议&#xff1a; &#x1f64b;‍♂️ 作者&#xff1a;海码007&#x1f4dc; 专栏&#xff1a;算法专栏&#x1f4a5; 标题&#xff1a;【Hot 100】94. 二叉树的中序遍历❣️ 寄语&#xff1a;书到用时方恨少&#xff…

大语言模型(LLMs)微调技术总结

文章目录 全面总结当前大语言模型&#xff08;LLM&#xff09;微调技术1. 引言2. 为什么需要微调&#xff1f;3. 微调技术分类概览4. 各种微调技术详细介绍4.1 基础微调方法4.1.1 有监督微调&#xff08;Supervised Fine-Tuning, SFT&#xff09;4.1.2 全参数微调&#xff08;F…

解决Maven项目中报错“java不支持版本6即更高的版本 7”

错误背景 当Maven项目编译或运行时出现错误提示 Java不支持版本6即更高的版本7&#xff0c;通常是由于项目配置的JDK版本与当前环境或编译器设置不一致导致的。例如&#xff1a; 项目配置的Java版本为6或7&#xff0c;但实际使用的是JDK 17。Maven或IDE的编译器未正确指定目标…

C++笔记-多态(包含虚函数,纯虚函数和虚函数表等)

1.多态的概念 多态(polymorphism)的概念:通俗来说&#xff0c;就是多种形态。多态分为编译时多态(静态多态)和运行时多态(动态多态)&#xff0c;这里我们重点讲运行时多态&#xff0c;编译时多态(静态多态)和运行时多态(动态多态)。编译时多态(静态多态)主要就是我们前面讲的函…

【Unity】MVP框架的使用例子

在提到MVP之前&#xff0c;可以先看看这篇MVC的帖子&#xff1a; 【Unity】MVC的简单分享以及一个在UI中使用的例子 MVC的不足之处&#xff1a; 在MVC的使用中&#xff0c;会发现View层直接调用了Model层的引用&#xff0c;即这两个层之间存在着一定的耦合性&#xff0c;而MV…

前端js学算法-实践

1、两数之和 const twoSum (nums, target) > {const obj {}for (let m 0; m < nums.length; m) {const cur nums[m]const diff target - curif(obj.hasOwnProperty(diff)){ // 查询对象中是否存在目标值-当前值键值对console.log([obj[diff], m]) // 存在则直接获取…

《MATLAB实战训练营:从入门到工业级应用》趣味入门篇-用声音合成玩音乐:MATLAB电子琴制作(超级趣味实践版)

《MATLAB实战训练营&#xff1a;从入门到工业级应用》趣味入门篇-用声音合成玩音乐&#xff1a;MATLAB电子琴制作&#xff08;超级趣味实践版&#xff09; 开篇&#xff1a;当MATLAB遇见音乐 - 一场数字与艺术的浪漫邂逅 想象一下&#xff0c;你正坐在一台古老的钢琴前&#x…

实战探讨:为什么 Redis Zset 选择跳表?

在了解了跳表的原理和实现后&#xff0c;一个常见的问题&#xff08;尤其是在面试中&#xff09;随之而来&#xff1a;为什么像 Redis 的有序集合 (Zset) 这样的高性能组件会选择使用跳表&#xff0c;而不是大家熟知的平衡树&#xff08;如红黑树&#xff09;呢&#xff1f; 对…

数据结构-线性结构(链表、栈、队列)实现

公共头文件common.h #define TRUE 1 #define FALSE 0// 定义节点数据类型 #define DATA_TYPE int单链表C语言实现 SingleList.h #pragma once#include "common.h"typedef struct Node {DATA_TYPE data;struct Node *next; } Node;Node *initList();void headInser…

高中数学联赛模拟试题精选学数学系列第3套几何题

△ A B C \triangle ABC △ABC 的内切圆 ⊙ I \odot I ⊙I 分别与边 B C BC BC, C A CA CA, A B AB AB 相切于点 D D D, E E E, F F F, D D ′ DD DD′ 为 ⊙ I \odot I ⊙I 的直径, 过圆心 I I I 作直线 A D ′ AD AD′ 的垂线 l l l, 直线 l l l 分别与 D E DE…

使用 ossutil 上传文件到阿里云 OSS

在处理文件存储和传输时&#xff0c;阿里云的对象存储服务&#xff08;OSS&#xff09;是一个非常方便的选择。特别是在需要批量上传文件或通过命令行工具进行文件管理时&#xff0c;ossutil提供了强大的功能。本文将详细说明如何使用 ossutil 上传文件到阿里云 OSS&#xff0c…

DeepSeek与MySQL:开启数据智能新时代

目录 一、引言&#xff1a;技术融合的力量二、DeepSeek 与 MySQL&#xff1a;技术基石2.1 DeepSeek 技术探秘2.2 MySQL 数据库深度解析 三、DeepSeek 与 MySQL 集成&#xff1a;从理论到实践3.1 集成原理剖析3.2 集成步骤详解 四、应用案例&#xff1a;实战中的价值体现4.1 电商…

WebAPI项目从Newtonsoft.Json迁移到System.Text.Json踩坑备忘

1.控制器层方法返回类型不能为元组 控制器层方法返回类型为元组时&#xff0c;序列化结果为空。 因为元组没有属性只有field&#xff0c;除非使用IncludeFields参数专门指定&#xff0c;否则使用System.Text.Json进行序列化时不会序列化field var options new JsonSerializ…

202553-sql

目录 一、196. 删除重复的电子邮箱 - 力扣&#xff08;LeetCode&#xff09; 二、602. 好友申请 II &#xff1a;谁有最多的好友 - 力扣&#xff08;LeetCode&#xff09; 三、176. 第二高的薪水 - 力扣&#xff08;LeetCode&#xff09; 一、196. 删除重复的电子邮箱 - 力扣…