(四)activit5.23.0修复跟踪高亮显示BUG

一、先看bug

在 (三)springboot2.7.6集成activit5.23.0之流程跟踪高亮显示 末尾就发现高亮显示与预期不一样,比如上面的任务2前面的箭头没有高亮显示。

二、分析原因

具体分析步骤省略了,主要是ProcessInstanceHighlightsResource类中有段代码有bug。

可以看到源码本身有注释,说按开始时间排序不正确,用默认排序是正确的。

 select * from `act_hi_actinst` 

我们可以去数据库中查询活动列表,希望按活动发生的先后顺序排序。可以发现,大部分时候,按活动的开始时间排序是正确的,但是在活动系统自动完成或者说几个活动在毫秒级时间内同时完成,这个时候无法通过开始时间判断活动发生的先后顺序。所以官方源码中注释了按开始时间排序。

后面还有一个按活动id排序,这个也是不对的,活动id在流程部署时就确定了,与流程实例中的活动顺序无关。

那注释上为什么说按默认排序是正确的呢?

我的理解是当默认排序是以活动记录的插入顺序排序时,就是正确的。

但实际情况默认排序并不一定总是活动记录的插入顺序排序的。不一定总是正确。

MySQL的默认排序规则:
1.如果查询条件无索引列,默认按主键正序排序。
2.查询条件中有索引列,默认顺序为:主键 > 唯一索引 > 普通索引,如果在SQL中查询条件同时存在有多个,那么按照索引最先创建的顺序进行正序排序。
例:SELECT * FROM a WHERE a.id = ‘a’ and a.user_id = ‘a’;
如果id和user_id都是索引,id先创建,则按照id进行正序排序。

 从上面截图,我们就可以看到,默认排序并没有按插入顺序排序。主要原因是ID_是字符串类型,而不是整数类型,所以升序就是上图的结果。

综上,高亮显示BUG的原因是查询活动列表时没有按活动的先后顺序排序。

三、修复方案

找出原因后,就可以针对性的进行修复。具体上面的问题,可以有2种方案。

(方案一)利用mysql的默认排序规则。

这种方案,这则是利用activiti的ID生成器实现,默认的ID生成器实现就不具体分析了,看上面的截图大概也差不多可以推测出来。只要将ID生成器替换为严格按递增顺序生成的就可以了。

下面介绍几种的ID生成方法:

  • UUID:生成的UUID是由 8-4-4-4-12格式的数据组成,其中32个字符和4个连字符' - ',一般我们使用的时候会将连字符删除 uuid.toString().replaceAll("-","")。该算法不是递增的,不能满足要求。
  • StrongUuidGenerator:activiti自带的ID生成器。但是生成的ID不是递增的。
  • 数据库生成:ID_字段类型是字符串,所以无法使用自增字段。如果要改为整数自增字段,引擎改动太复杂,后过不可控,排除。
  • 雪花算法-Snowflake:该方法比较适配。但他也有相应的缺点:依赖系统时钟,64位字符串占空间,不适用短时间生成大量的ID。
  • 百度-UidGenerator:不是很熟悉,没用过。
  • 美团Leaf:不是很熟悉,没用过。

所以该方案,只要把ID生成器换成雪花算法就可以了。但要注意避免雪花算法生成重复ID。

1.雪花算法实现SnowflakeIdWorker.java
package xpl.util.id;/*** Twitter_Snowflake<br>* SnowFlake的结构如下(每部分用-分开):<br>* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>* 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0<br>* 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)* 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>* 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId<br>* 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br>* 加起来刚好64位,为一个Long型。<br>* SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。*/
public class SnowflakeIdWorker {private final static SnowflakeIdWorker sfiw = new SnowflakeIdWorker(0,0);// ==============================Fields===========================================/** 开始时间截 (2015-01-01) */private final long twepoch = 1420041600000L;/** 机器id所占的位数 */private final long workerIdBits = 5L;/** 数据标识id所占的位数 */private final long datacenterIdBits = 5L;/** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */private final long maxWorkerId = -1L ^ (-1L << workerIdBits);/** 支持的最大数据标识id,结果是31 */private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);/** 序列在id中占的位数 */private final long sequenceBits = 12L;/** 机器ID向左移12位 */private final long workerIdShift = sequenceBits;/** 数据标识id向左移17位(12+5) */private final long datacenterIdShift = sequenceBits + workerIdBits;/** 时间截向左移22位(5+5+12) */private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;/** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */private final long sequenceMask = -1L ^ (-1L << sequenceBits);/** 工作机器ID(0~31) */private long workerId;/** 数据中心ID(0~31) */private long datacenterId;/** 毫秒内序列(0~4095) */private long sequence = 0L;/** 上次生成ID的时间截 */private long lastTimestamp = -1L;//==============================Constructors=====================================/*** 构造函数* @param workerId 工作ID (0~31)* @param datacenterId 数据中心ID (0~31)*/public SnowflakeIdWorker(long workerId, long datacenterId) {if (workerId > maxWorkerId || workerId < 0) {throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));}if (datacenterId > maxDatacenterId || datacenterId < 0) {throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));}this.workerId = workerId;this.datacenterId = datacenterId;}// ==============================Methods==========================================/*** 获得下一个ID (该方法是线程安全的)* @return SnowflakeId*/public synchronized long getNextId() {long timestamp = timeGen();//如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常if (timestamp < lastTimestamp) {throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));}//如果是同一时间生成的,则进行毫秒内序列if (lastTimestamp == timestamp) {sequence = (sequence + 1) & sequenceMask;//毫秒内序列溢出if (sequence == 0) {//阻塞到下一个毫秒,获得新的时间戳timestamp = tilNextMillis(lastTimestamp);}}//时间戳改变,毫秒内序列重置else {sequence = 0L;}//上次生成ID的时间截lastTimestamp = timestamp;//移位并通过或运算拼到一起组成64位的IDreturn ((timestamp - twepoch) << timestampLeftShift) //| (datacenterId << datacenterIdShift) //| (workerId << workerIdShift) //| sequence;}/*** 获得下一个ID (该方法是线程安全的)* @return SnowflakeId*/public static long nextId() {return sfiw.getNextId();}/*** 阻塞到下一个毫秒,直到获得新的时间戳* @param lastTimestamp 上次生成ID的时间截* @return 当前时间戳*/protected long tilNextMillis(long lastTimestamp) {long timestamp = timeGen();while (timestamp <= lastTimestamp) {timestamp = timeGen();}return timestamp;}/*** 返回以毫秒为单位的当前时间* @return 当前时间(毫秒)*/protected long timeGen() {return System.currentTimeMillis();}//==============================Test=============================================/** 测试 */public static void main(String[] args) {SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);for (int i = 0; i < 1000; i++) {long id = idWorker.getNextId();System.out.println(Long.toBinaryString(id));System.out.println(id);}}
}
2.自定义ID生成器

SnowflakeIdWorkerGenerator.java

package org.activiti.engine.impl.ext;import org.activiti.engine.impl.cfg.IdGenerator;import xpl.util.id.SnowflakeIdWorker;public class SnowflakeIdWorkerGenerator implements IdGenerator {@Overridepublic String getNextId() {return ""+SnowflakeIdWorker.nextId();}}
3.替换activiti默认的ID生成器

这个替换研究了好一会,才找到替换的方法。

所以需要扩展引擎配置,只需要实现ProcessEngineConfigurationConfigurer接口就可以了。

于是,我们创建MyProcessEngineConfigurationConfigurer类。代码如下:

package xpl.study.activiti;import org.activiti.engine.impl.ext.SnowflakeIdWorkerGenerator;
import org.activiti.spring.SpringProcessEngineConfiguration;
import org.activiti.spring.boot.ProcessEngineConfigurationConfigurer;
import org.springframework.context.annotation.Configuration;@Configuration
public class MyProcessEngineConfigurationConfigurer implements ProcessEngineConfigurationConfigurer{@Overridepublic void configure(SpringProcessEngineConfiguration processEngineConfiguration) {processEngineConfiguration.setIdGenerator(new SnowflakeIdWorkerGenerator());}}
4.运行测试

(方案二)使用order by与实现按活动发生顺序进行排序。

1.对act_hi_actinst表新增一个排序字段,并且自增。

2.修改源码HistoricActivityInstanceQuery,新增一个接口orderBySeq。
package org.activiti.engine.history;
import org.activiti.engine.query.Query;/*** Programmatic querying for {@link HistoricActivityInstance}s.* * @author Tom Baeyens* @author Joram Barrez*/
public interface HistoricActivityInstanceQuery extends Query<HistoricActivityInstanceQuery, HistoricActivityInstance>{//...........HistoricActivityInstanceQuery orderBySeq();}
3.修改源码HistoricActivityInstanceQueryImpl,新增orderBySeq实现
public HistoricActivityInstanceQuery orderBySeq() {orderBy(HistoricActivityInstanceQueryProperty.SEQ);return this;}
4.修改源码HistoricActivityInstanceQueryProperty ,新增一个静态变量。
public static final HistoricActivityInstanceQueryProperty SEQ = new HistoricActivityInstanceQueryProperty("SEQ_");
5.修改源码ProcessInstanceHighlightsResource ,查询时拼接seq字段参与排序。

6.运行测试

方案二其实还可以更简单的实现。第一步与上面一样,但是可以省略上面2,3,4步,直接到第5步,利用createNativeHistoricActivityInstanceQuery查询列表,就可以直接跳过2,3,4步实现了。

四、总结

经过测试二种方案都是可行。个人比较倾向于第二种。第二种比较有安全感,第一种如果服务器时间回拨,可能导致ID重复,系统故障。虽然发生几率不是很大,但如果对系统稳定性要求较高的话还是存在一些风险。

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

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

相关文章

【iOS】暑假第二周——网易云APP 仿写

目录 前言首页关于UINavigationBarAppearance “我的”账号夜间模式——多界面传值遇到的问题所用到的其他知识整理NSNotificationreloadData各种键盘模式 总结 前言 有了之前仿写ZARA的基础&#xff0c;本周我们仿写了网易云APP&#xff0c;在这里对多界面传值进行了首次应用—…

算力共享中神经网络切片和算力分配策略

目录 神经网络切片 按照算力的分布进行网络层数切片;就是算力越强,运算神经网络层数越多 神经网络切片和算力占比进行映射 算力分配策略 get_current_shard 神经网络切片 按照算力的分布进行网络层数切片;就是算力越强,运算神经网络层数越多 神经网络切片和算力占比进…

【MySQL】索引——索引的引入、认识磁盘、磁盘的组成、扇区、磁盘访问、磁盘和MySQL交互、索引的概念

文章目录 MySQL1. 索引的引入2. 认识磁盘2.1 磁盘的组成2.2 扇区2.3 磁盘访问 3. 磁盘和MySQL交互4. 索引的概念4.1 索引测试4.2 Page4.3 单页和多页情况 MySQL 1. 索引的引入 海量表在进行普通查询的时候&#xff0c;效率会非常的慢&#xff0c;但是索引可以解决这个问题。 -…

PHP中关于排名和显示的问题

&#x1f3c6;本文收录于《CSDN问答解惑-专业版》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收…

RabbitMQ应用场景及特性

RabbitMQ是一款开源的消息队列中间件&#xff0c;拥有非常好用的管理控制面板&#xff0c;类似使用navicat一样&#xff0c;简便的操纵数据库。 应用场景 一、流量削峰 在一些并发量较高的场景下&#xff0c;比如秒杀活动&#xff0c;抢票等&#xff0c;同一时间访问量急剧增…

【Linux】shell命令与Linux权限的概念

目录 一、shell命令二、Linux权限的概念2.1 Linux权限的概念2.1.1 用户2.1.2 指令2.1.2.1 su指令2.1.2.2 sudo指令 2.2 Linux权限管理2.2.1 文件访问者的分类&#xff08;人&#xff09;2.2.2 文件类型和访问权限&#xff08;事物属性&#xff09;2.2.2.1 文件类型2.2.2.2 基本…

[240804] OpenTofu 1.8.0 发布,带来更友好的编码体验 | 生成式 AI 滥用现象分析

目录 OpenTofu 1.8.0 发布&#xff0c;带来更友好的编码体验生成式 AI 滥用现象分析 OpenTofu 1.8.0 发布&#xff0c;带来更友好的编码体验 OpenTofu 1.8.0 现已发布&#xff0c;主要功能包括&#xff1a; 变量和局部值的早期求值: 现在可以在模块源、后端配置和状态加密等更…

使用 1panel面板 部署 springboot 和 vue

代码仓库&#xff1a;还没弄 目录 网站介绍安装步骤1. 准备云服务器2. 准备域名&#xff08;可跳过&#xff09;3. 安装1panel面板4. 服务器开放端口5. 进入1panel面板6. 安装并启动软件&#xff08;服务器和面板开放端口&#xff09;7. 打包并上传项目7.1 打包 Java项目&#…

网页保护用户 小tips

在使用创建web开发的过程中&#xff0c;直接使用用户名url&#xff0c;容易造成用户信息的被攻击&#xff0c;例如对方直接访问 ../../.../username 的网页&#xff0c;可以窃取用户信息&#xff0c;然而把usename变成一堆乱码就安全的多 效果&#xff1a; 代码&#xff1a;…

想做抖音短视频,视频素材去哪里找啊?

各位抖音上的短视频创作者们&#xff0c;是否曾幻想过自己的作品能够在全网爆火&#xff0c;却常因为缺少那些能够让视频更加生动的素材而感到困扰&#xff1f;不用担心&#xff0c;今天我要为大家介绍几个优秀的视频素材网站&#xff0c;让你的抖音之路顺风顺水&#xff01; …

星环科技与宁夏银行“大数据联合实验室”揭牌,持续打造金融科技新范式

5月30-31日&#xff0c;2024向星力未来数据技术峰会期间&#xff0c;在峰会现场来宾共同见证下&#xff0c;星环科技与宁夏银行“大数据联合实验室”正式揭牌&#xff0c;宁夏银行股份有限公司首席信息官崔彦刚与星环科技副总裁邱磊共同为联合实验室揭牌。 星环科技与宁夏银行借…

【每日刷题】Day92

【每日刷题】Day92 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. 面试题 16.05. 阶乘尾数 - 力扣&#xff08;LeetCode&#xff09; 2. 取近似值_牛客题霸_牛客网 (n…

4. 最长公共前缀

4. 最长公共前缀 题目题目分析 题目 题目分析 首先要对字符串数组进行分析&#xff0c;字符串数组元素的最长公共前缀肯定不会超过最小元素长度&#xff0c;并如存在公共前缀则需遍历整个字符串元素&#xff0c;有点像二维数组&#xff0c;最后加上截取字符串加上判空操作就完…

测试——Selenium

内容大纲: 什么是自动化测试 什么是Selenium Selenium工作原理 Selenium环境搭建 Selenium API 目录 1. 什么是自动化测试 2. 什么是Selenium 3. Selenium工作原理 4. Selenium环境搭建(java) 5. Selenium API 5.1 定位元素 5.1.1 CSS选择器定位元素 5.1.2 XPath定位元…

Kubernets(k8s) 网络原理三:同主机内Pod相互访问

前两篇文章中我们介绍了pod怎么和宿主机通信以及pod怎么访问外网&#xff0c;这两种通信是理解pod间通信的基础。 关于pod间的相互访问&#xff0c;这里还需要细化一下。回想一下pod在k8s节点中的分布&#xff0c;两个pod可能分布在同一台宿主机上&#xff0c;也可能分布在不同…

ECMAScript 12 (ES12, ES2021) 新特性

还是大剑师兰特&#xff1a;曾是美国某知名大学计算机专业研究生&#xff0c;现为航空航海领域高级前端工程师&#xff1b;CSDN知名博主&#xff0c;GIS领域优质创作者&#xff0c;深耕openlayers、leaflet、mapbox、cesium&#xff0c;canvas&#xff0c;webgl&#xff0c;ech…

PXE实验

实验前准备 关闭VMware的dhcp 点击 编辑 点击 虚拟网络编辑器 选择 NAT模式 将dhcp取消勾选 准备两台虚拟机 一台试验机&#xff0c;&#xff08;网络环境正常并且有图形化的界面的rhel7&#xff09; 一台测试机 init 5 --------------> 开启图形化界面 如…

element-plus框架+vue3+echart——后台页面

一、图表样式 图表组件&#xff1a;echarts https://echarts.apache.org/examples/zh/index.html element-plus框架&#xff1a; https://www.cwgj.xyz/zh-CN/ 1、折线图 栅格 一共24。 12代表占一半50%&#xff0c; 当页面缩小到一定程度 占整个屏幕的100%。 id"mo…

拉刀基础知识——拉刀的种类

如前面所说&#xff1a;近期要围绕拉削和拉刀这个话题&#xff0c;分享一些相关的内容&#xff0c;从最基础的知识开始&#xff0c;为此还专门买了本旧书——《拉刀设计》入门学习。废话不多说&#xff0c;直接开始。 拉刀最早由冲头演变而来&#xff0c;用于加工方孔&#xf…

C:关于static 和 extern 关键字的介绍-学习笔记

目录 1、作用域与生命周期 1.1 作用域 1.2 生命周期 1.3 变量的作用域和生命周期之间的关系 2、static 和 extern 2.1 static 修饰局部变量&#xff1a; 2.2 static 修饰全局变量&#xff08;包含extern的作用&#xff09;&#xff1a; 2.3 static修饰函数&#xff1a…