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

news2024/11/16 5:44:21

一、先看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位的ID
        return ((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 {

	@Override
	public 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{

	@Override
	public 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.运行测试

四、总结

经过测试二种方案都是可行。推荐使用方案一,可以修改比较容易。但是要接受雪花算法的缺点。 后期还可以优化id生成算法。

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

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

相关文章

饿了么冰杯外卖爆涨350%,“冰+X”激发酒饮即时零售夏季增长加速

近日&#xff0c;饿了么联合尼尔森IQ共同发布的《2024夏季即时零售冰品酒饮消费洞察报告》显示&#xff0c;大暑前后冰杯外卖量同比去年增长350%&#xff0c;冰杯搭配啤酒等酒水饮料的外卖量也同比增长约300%。 报告综合多渠道零售数据和案例分析&#xff0c;剖析了冰品酒饮在…

基于51单片机的车窗控制系统proteus仿真

地址&#xff1a; https://pan.baidu.com/s/1w5qrAvn1cUK7ZX2GJvWBQw 提取码&#xff1a;1234 仿真图&#xff1a; 芯片/模块的特点&#xff1a; AT89C52/AT89C51简介&#xff1a; AT89C52/AT89C51是一款经典的8位单片机&#xff0c;是意法半导体&#xff08;STMicroelectro…

DedeCMS-V5.7.82-UTF8织梦

先进行安装 然后进入首页和管理员后台 内容要在后台进行操作 首页 后台 1.通过文件管理器上传WebShell 访问目标靶场其思路为 dedecms 后台可以直接上传任意文件&#xff0c;可以通过文件管理器上传php文件获取webshel 登陆到后台点击【核心】【文件式管理器】【文件上传】将…

基于深度学习的面部表情分类识别系统

&#xff1a;温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长 QQ 名片 :) 1. 项目简介 面部表情识别是计算机视觉领域的一个重要研究方向&#xff0c; 它在人机交互、心理健康评估、安全监控等领域具有广泛的应用。近年来&#xff0c;随着深度学习技术的快速发展&#xf…

C++篇:入门(2)

引用 引用的概念以及定义&#xff1a; 在C中&#xff0c;引用&#xff08;Reference&#xff09;是一个非常重要的概念又可以称之为取别名&#xff0c;它允许我们创建一个已存在对象的别名。引用提供了一种机制&#xff0c;通过它可以直接访问另一个变量、对象或函数的值&#…

Nginx进阶-常见配置(一)

一、nginx Proxy 反向代理 1、代理原理 反向代理产生的背景&#xff1a; 在计算机世界里&#xff0c;由于单个服务器的处理客户端&#xff08;用户&#xff09;请求能力有一个极限&#xff0c;当用户的接入请求蜂拥而入时&#xff0c;会造成服务器忙不过来的局面&#xff0c…

【实现100个unity特效之12】Unity中的冲击波 ——如何使用ShaderGraph制作一个冲击波着色器

最终效果 文章目录 最终效果新增LitShaderGraph圆环扭曲效果优化冲击波效果屏幕全屏冲击波圆形冲击波最终连线图代码控制补充源码完结 新增LitShaderGraph 圆环扭曲效果 让我们从一个UV节点开始 创建一个Vector2变量RingSpawnPosition表示冲击波生成位置,在X和Y上将其默认值…

springboot实现前后端调用axios异步请求(后端单体服务器static部分)

目的&#xff1a;让页面调用controller&#xff0c;将数据加载到页面中&#xff08;只不过这个前端页面我们直接就是放到了static里面了&#xff09;。 第一步&#xff1a;导入文件 所需要的文件见本文最后“文件获取”&#xff1a; &#xff08;1&#xff09;文件如下&…

汇昌联信拼多多运营怎么样?

汇昌联信拼多多运营怎么样?在探讨汇昌联信在拼多多平台的运营情况时&#xff0c;首先需要明确的回答是&#xff1a;汇昌联信在拼多多的运营表现是积极的&#xff0c;并取得了一定的成效。接下来&#xff0c;我们将从五个不同的角度深入分析其运营策略及效果。 一、产品多样性与…

Centos7挂载数据盘

查看当前服务器有哪些磁盘 fdisk -l 2.格式化 mke2fs -t ext4 /dev/vdc 3.挂载数据盘 mkdir /sdxinfang mount /dev/vdc /sdxinfang/ 为了避免每次开机都要重新挂载&#xff0c;直接设置系统挂载信息&#xff0c;这样开机会自动挂载 vim /etc/fstab 在文件末尾增加以下内容&…

Axure八大优质Web端系统框架模版

在当今数字化转型的浪潮中&#xff0c;Axure作为一款强大的原型设计工具&#xff0c;以其快速、直观和易用的特点&#xff0c;成为了众多设计师和产品经理的首选。本文将详细介绍六套基于Axure制作的智慧系统原型框架模版&#xff0c;包括智慧园区、智慧社区、智慧乡村、智慧驾…

4个好用的 CSS 伪类 :not()、:has()、 :is()、:where()

文章目录 &#xff08;1&#xff09;:not()&#xff08;2&#xff09;:has()&#xff08;3&#xff09;:is()&#xff08;4&#xff09;:where()&#xff08;5&#xff09;:where()与:is() 的区别 &#xff08;1&#xff09;:not() :not 伪类&#xff1a;用于选择不满足给定条…

微信小程序开发费用一览表,不同开发方式的费用对比

微信小程序作为当前移动互联网领域的重要入口之一&#xff0c;其开发费用因开发方式、功能需求、设计复杂度及开发团队的不同而有所差异。本文将详细梳理微信小程序开发的几种主要方式&#xff0c;并对比各方式的费用情况&#xff0c;以便企业和个人在选择时能够有更清晰的了解…

PHP 打印 V 和倒 V 图案的程序(Program to print V and inverted-V pattern)

倒 V 型模式&#xff1a;给定 n 的值&#xff0c;打印倒 V 型模式。示例&#xff1a; 输入&#xff1a;n 5 输出 &#xff1a; E D D C C B B A A 输入&#xff1a;n 7 输出 &#xff1a; G F F E E D D C C B B A…

pycharm中安装、使用扩展工具,以QT Designer为例

pycharm中安装、使用扩展工具&#xff0c;以QT Designer为例 第一步&#xff0c;下载QT Designer安装包。找到QT Designer.exe所在位置&#xff0c;复制路径 第二步&#xff0c;打开Pycharm&#xff0c;选择Setting&#xff0c;找到扩展工具&#xff08;External Tools&#xf…

git回退未commit、回退已commit、回退已push、合并某一次commit到另一个分支

文章目录 1、git回退未commit2、git回退已commit3、git回退已push的代码3.1 直接丢弃某一次的push3.2 撤销push后&#xff0c;不丢弃改动&#xff0c;重新修改后要再次push 4、合并某一次commit到另一个分支 整理几个工作上遇到的git问题。 1、git回退未commit git回退未comm…

【C++】STL-哈希表封装unorder_set和unordered_map

目录 1、实现哈希表的泛型 2、unordered_set和unordered_map的插入 3、迭代器 3.1 operator 3.2 const迭代器 4、find 5、unordered_map的operator[] 6、对于无法取模的类型 7、介绍unordered_set的几个函数 7.1 bucket_count 7.2 bucket_size 7.3 bucket 7.4 rese…

Gcc/G++编译C/C++文件(主要以C++语言为主,C语言就做阐述 用法一样 就是将G++换成GCC)

首先&#xff0c;我们在Linux中创建一个helloc.cc文件(C文件) vim helloc.cc 直接用g裸编译 g helloc.cc 生成的a.out就是二进制可执行文件 如果要产生 自定义可执行文件 就需要下面的编译步骤 繁琐操作 g -c helloc.cc 会生成目标文件 g -o hello helloc.o 此时hell…

仿SOUL社交友附近人婚恋约仿陌陌APP系统源码

专门为单身男女打造的恋爱交友社区&#xff0c;就是一个由千千万万单身男女组建的大家庭。他们来自全国各地&#xff0c;或许有着不同的人生经历&#xff0c;却有着共同的对恋爱交友的渴望。他们可以通过文字、语音、视频聊天的方式&#xff0c;和镜头前的彼此诉说自己工作中发…

95页PPT丨IBM-IT应用规划

一、IBM针对IT应用规划项目核心内容IBM在IT应用规划项目中的核心内容&#xff0c;旨在帮助企业实现数字化转型&#xff0c;优化IT资源配置&#xff0c;并确保IT战略与业务目标的一致性。以下是IBM IT应用规划项目的详细核心内容&#xff1a; 资料下载方式&#xff0c;请看每张…