当前位置: 代码网 > it编程>编程语言>Javascript > 如何自定义Mybatis-Plus分布式ID生成器(解决ID长度超过JavaScript整数安全范围问题)

如何自定义Mybatis-Plus分布式ID生成器(解决ID长度超过JavaScript整数安全范围问题)

2024年08月06日 Javascript 我要评论
自定义mybatis-plus分布式id生成器(解决id长度超过javascript整数安全范围问题)版本mybatis-plus 3.4.1问题mybatis-plus 默认生成的是 64bit 长

自定义mybatis-plus分布式id生成器(解决id长度超过javascript整数安全范围问题)

版本

mybatis-plus 3.4.1

问题

mybatis-plus 默认生成的是 64bit 长整型,而 js 的 number 类型精度最高只有 53bit,如果以 long 类型 id 和前端 js 进行交互,会出现精度丢失(最后两位数字变成 00) 而导致最终系统报错。

解决方案

一种方案是在响应前端时,将 id 转换成 string 类型返回,但这个方法治标不治本,因此最终通过采用截短 id 长度,以避免 id 超过 js 整数安全范围。

缩短雪花算法后空间划分(可根据实际需求调整):
1. 高位 32bit 作为秒级时间戳, 时间戳减去固定值(2024 年时间戳)
2. 5bit 作为机器标识, 最高可部署 32 台机器
3. 最后 16bit 作为自增序列, 单节点最高每秒 2^16 = 65536 个 id

代码实现

通过实现 mybatis-plus identifiergenerator 接口以自定义 id 生成器

import com.baomidou.mybatisplus.core.incrementer.identifiergenerator;
import lombok.extern.slf4j.slf4j;
import org.springframework.stereotype.component;
/**
 * 符合 javascript 整数安全范围的自定义id生成器
 *
 * @author panda
 */
@slf4j
@component
public class jssafeidgenerator implements identifiergenerator {
    /** 初始偏移时间戳 2024-01-01 */
    private static final long offset = 1704067200l;
    /** 机器id (0~15 保留 16~31作为备份机器) */
    private static final long worker_id;
    /** 机器id所占位数 (5bit, 支持最大机器数 2^5 = 32)*/
    private static final long worker_id_bits = 5l;
    /** 自增序列所占位数 (16bit, 支持最大每秒生成 2^16 = ‭65536‬) */
    private static final long sequence_id_bits = 16l;
    /** 机器id偏移位数 */
    private static final long worker_shift_bits = sequence_id_bits;
    /** 自增序列偏移位数 */
    private static final long offset_shift_bits = sequence_id_bits + worker_id_bits;
    /** 机器标识最大值 (2^5 / 2 - 1 = 15) */
    private static final long worker_id_max = ((1 << worker_id_bits) - 1) >> 1;
    /** 备份机器id开始位置 (2^5 / 2 = 16) */
    private static final long back_worker_id_begin = (1 << worker_id_bits) >> 1;
    /** 自增序列最大值 (2^16 - 1 = ‭65535) */
    private static final long sequence_max = (1 << sequence_id_bits) - 1;
    /** 发生时间回拨时容忍的最大回拨时间 (秒) */
    private static final long back_time_max = 1l;
    /** 上次生成id的时间戳 (秒) */
    private static long lasttimestamp = 0l;
    /** 当前秒内序列 (2^16)*/
    private static long sequence = 0l;
    /** 备份机器上次生成id的时间戳 (秒) */
    private static long lasttimestampbak = 0l;
    /** 备份机器当前秒内序列 (2^16)*/
    private static long sequencebak = 0l;
    static {
        // 初始化机器id 可配置文件获取
        long workerid = 1;
        if (workerid < 0 || workerid > worker_id_max) {
            throw new illegalargumentexception(string.format("worker-id [%d] 越界, 有效范围: 0 ~ %d ", workerid, worker_id_max));
        }
        worker_id = workerid;
    }
    @override
    public synchronized number nextid(object entity) {
        return nextid(systemclock.now() / 1000);
    }
    /**
     * 主机器自增序列
     * @param timestamp 当前unix时间戳
     * @return long
     */
    private static synchronized long nextid(long timestamp) {
        if (timestamp < lasttimestamp) {
            log.warn("时钟回拨, 启用备份机器id: now: [{}] last: [{}]", timestamp, lasttimestamp);
            return nextidbackup(timestamp);
        }
        if (timestamp != lasttimestamp) {
            lasttimestamp = timestamp;
            sequence = 0l;
        }
        if (0l == (++sequence & sequence_max)) {
            sequence--;
            return nextidbackup(math.max(timestamp, lasttimestampbak));
        }
        return ((timestamp - offset) << offset_shift_bits) | (worker_id << worker_shift_bits) | sequence;
    }
    /**
     * 备份机器自增序列
     * @param timestamp 当前unix时间戳
     * @return long
     */
    private static long nextidbackup(long timestamp) {
        if (timestamp < lasttimestampbak) {
            if (lasttimestampbak - (systemclock.now() / 1000) <= back_time_max) {
                timestamp = lasttimestampbak;
            } else {
                throw new runtimeexception(string.format("时钟回拨: now: [%d] last: [%d]", timestamp, lasttimestampbak));
            }
        }
        if (timestamp != lasttimestampbak) {
            lasttimestampbak = timestamp;
            sequencebak = 0l;
        }
        if (0l == (++sequencebak & sequence_max)) {
            return nextidbackup(timestamp + 1);
        }
        return ((timestamp - offset) << offset_shift_bits) | ((worker_id ^ back_worker_id_begin) << worker_shift_bits) | sequencebak;
    }
}
import java.util.concurrent.scheduledthreadpoolexecutor;
import java.util.concurrent.timeunit;
import java.util.concurrent.atomic.atomiclong;
/**
 * 缓存时间戳解决system.currenttimemillis()高并发下性能问题
 *
 * @author panda
 **/
public class systemclock {
    private final long period;
    private final atomiclong now;
    private systemclock(long period) {
        this.period = period;
        this.now = new atomiclong(system.currenttimemillis());
        scheduleclockupdating();
    }
    /**
     * 尝试下枚举单例法
     */
    private enum systemclockenum {
        system_clock;
        private systemclock systemclock;
        systemclockenum() {
            systemclock = new systemclock(1);
        }
        public systemclock getinstance() {
            return systemclock;
        }
    }
    /**
     * 获取单例对象
     * @return com.cmallshop.module.core.commons.util.sequence.systemclock
     */
    private static systemclock getinstance() {
        return systemclockenum.system_clock.getinstance();
    }
    /**
     * 获取当前毫秒时间戳
     * @return long
     */
    public static long now() {
        return getinstance().now.get();
    }
    /**
     * 起一个线程定时刷新时间戳
     */
    private void scheduleclockupdating() {
        scheduledthreadpoolexecutor scheduler = new scheduledthreadpoolexecutor(1, runnable -> {
            thread thread = new thread(runnable, "system clock");
            thread.setdaemon(true);
            return thread;
        });
        scheduler.scheduleatfixedrate(() -> now.set(system.currenttimemillis()), period, period, timeunit.milliseconds);
    }
}

springboot 项目中如何引用?

import com.baomidou.mybatisplus.core.config.globalconfig;
import lombok.requiredargsconstructor;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
@configuration
@requiredargsconstructor
public class mybatisplusconfiguration {
    @bean
    public globalconfig globalconfig() {
        globalconfig globalconfig = new globalconfig();
        globalconfig.setidentifiergenerator(new jssafeidgenerator());
        return globalconfig;
    }
}

id 映射字段添加 @tableid(type = idtype.assign_id) 注解

import com.baomidou.mybatisplus.annotation.idtype;
import com.baomidou.mybatisplus.annotation.tableid;
import lombok.allargsconstructor;
import lombok.data;
import lombok.noargsconstructor;
import java.io.serializable;
@data
@noargsconstructor
@allargsconstructor
public class base implements serializable {
    @tableid(type = idtype.assign_id)
    private long id;
}

到此这篇关于自定义mybatis-plus分布式id生成器(解决id长度超过javascript整数安全范围问题)的文章就介绍到这了,更多相关mybatis-plus分布式id生成器内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com