如何做网站推广在找产品营销推广吗,自己网站制作的详细教程,做家宴网站,网站开发交接自定义MyBatis-Plus分布式ID生成器#xff08;解决ID长度超过JavaScript整数安全范围问题#xff09;
版本
MyBatis-Plus 3.4.1
问题
MyBatis-Plus 默认生成的是 64bit 长整型#xff0c;而 JS 的 Number 类型精度最高只有 53bit#xff0c;如果以 Long 类型 ID 和前端…自定义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;}Overridepublic 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 {Beanpublic 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;}