word做招聘网站,外汇平台网站开发需求说明,wordpress博客优点,网络营销推广渠道有哪些一、先看bug 在 #xff08;三#xff09;springboot2.7.6集成activit5.23.0之流程跟踪高亮显示 末尾就发现高亮显示与预期不一样#xff0c;比如上面的任务2前面的箭头没有高亮显示。
二、分析原因
具体分析步骤省略了#xff0c;主要是ProcessInstanceHighlightsResour…一、先看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(-,)。该算法不是递增的不能满足要求。StrongUuidGeneratoractiviti自带的ID生成器。但是生成的ID不是递增的。数据库生成ID_字段类型是字符串所以无法使用自增字段。如果要改为整数自增字段引擎改动太复杂后过不可控排除。雪花算法-Snowflake该方法比较适配。但他也有相应的缺点依赖系统时钟64位字符串占空间不适用短时间生成大量的ID。百度-UidGenerator不是很熟悉没用过。美团Leaf不是很熟悉没用过。
所以该方案只要把ID生成器换成雪花算法就可以了。但要注意避免雪花算法生成重复ID。
1.雪花算法实现SnowflakeIdWorker.java
package xpl.util.id;/*** Twitter_Snowflakebr* SnowFlake的结构如下(每部分用-分开):br* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 br* 1位标识由于long基本类型在Java中是带符号的最高位是符号位正数是0负数是1所以id一般是正数最高位是0br* 41位时间截(毫秒级)注意41位时间截不是存储当前时间的时间截而是存储时间截的差值当前时间截 - 开始时间截)* 得到的值这里的的开始时间截一般是我们的id生成器开始使用的时间由我们程序来指定的如下下面程序IdWorker类的startTime属性。41位的时间截可以使用69年年T (1L 41) / (1000L * 60 * 60 * 24 * 365) 69br* 10位的数据机器位可以部署在1024个节点包括5位datacenterId和5位workerIdbr* 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位(125) */private final long datacenterIdShift sequenceBits workerIdBits;/** 时间截向左移22位(5512) */private final long timestampLeftShift sequenceBits workerIdBits datacenterIdBits;/** 生成序列的掩码这里为4095 (0b1111111111110xfff4095) */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 cant be greater than %d or less than 0, maxWorkerId));}if (datacenterId maxDatacenterId || datacenterId 0) {throw new IllegalArgumentException(String.format(datacenter Id cant 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 QueryHistoricActivityInstanceQuery, 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重复系统故障。虽然发生几率不是很大但如果对系统稳定性要求较高的话还是存在一些风险。