diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1b6d9fa30..e8ab173e6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,7 +3,7 @@
-------------------------------------------------------------------------------------------------------------
-# 5.7.3 (2021-06-26)
+# 5.7.3 (2021-06-28)
### 🐣新特性
* 【core 】 增加Convert.toSet方法(issue#I3XFG2@Gitee)
@@ -14,6 +14,7 @@
* 【core 】 增加可自定义日期格式GlobalCustomFormat
* 【jwt 】 JWT修改默认有序,并规定payload日期格式为秒数
* 【json 】 增加JSONWriter
+* 【core 】 IdUtil增加getWorkerId和getDataCenterId(issueI3Y5NI@Gitee)
### 🐞Bug修复
* 【json 】 修复XML转义字符的问题(issue#I3XH09@Gitee)
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/ObjectId.java b/hutool-core/src/main/java/cn/hutool/core/lang/ObjectId.java
index 8b4740296..e4a8c06fa 100644
--- a/hutool-core/src/main/java/cn/hutool/core/lang/ObjectId.java
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/ObjectId.java
@@ -1,16 +1,16 @@
package cn.hutool.core.lang;
-import java.lang.management.ManagementFactory;
-import java.net.NetworkInterface;
-import java.nio.ByteBuffer;
-import java.util.Enumeration;
-import java.util.concurrent.atomic.AtomicInteger;
-
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ClassLoaderUtil;
import cn.hutool.core.util.RandomUtil;
+import cn.hutool.core.util.RuntimeUtil;
import cn.hutool.core.util.StrUtil;
+import java.net.NetworkInterface;
+import java.nio.ByteBuffer;
+import java.util.Enumeration;
+import java.util.concurrent.atomic.AtomicInteger;
+
/**
* MongoDB ID生成策略实现
* ObjectId由以下几部分组成:
@@ -156,14 +156,7 @@ public class ObjectId {
// 进程ID初始化
int processId;
try {
- // 获取进程ID
- final String processName =ManagementFactory.getRuntimeMXBean().getName();
- final int atIndex = processName.indexOf('@');
- if (atIndex > 0) {
- processId = Integer.parseInt(processName.substring(0, atIndex));
- } else {
- processId = processName.hashCode();
- }
+ processId = RuntimeUtil.getPid();
} catch (Throwable t) {
processId = RandomUtil.randomInt();
}
@@ -180,4 +173,4 @@ public class ObjectId {
return processPiece;
}
// ----------------------------------------------------------------------------------------- Private method end
-}
\ No newline at end of file
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java b/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java
index 9183fe5d0..63f7897d9 100644
--- a/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java
@@ -1,6 +1,7 @@
package cn.hutool.core.lang;
import cn.hutool.core.date.SystemClock;
+import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import java.io.Serializable;
@@ -14,6 +15,7 @@ import java.util.Date;
* snowflake的结构如下(每部分用-分开):
*
*
+ * 符号位(1bit)- 时间戳相对值(41bit)- 数据中心标志(5bit)- 机器标志(5bit)- 递增序号(12bit) * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 **
@@ -23,7 +25,8 @@ import java.util.Date; *
* 并且可以通过生成的id反推出生成时间,datacenterId和workerId *
- * 参考:http://www.cnblogs.com/relucent/p/4955340.html
+ * 参考:http://www.cnblogs.com/relucent/p/4955340.html
+ * 关于长度是18还是19的问题见:https://blog.csdn.net/unifirst/article/details/80408050
*
* @author Looly
* @since 3.0.1
@@ -31,33 +34,63 @@ import java.util.Date;
public class Snowflake implements Serializable {
private static final long serialVersionUID = 1L;
- private final long twepoch;
- private final long workerIdBits = 5L;
+
+ /**
+ * 默认的起始时间,为Thu, 04 Nov 2010 01:42:54 GMT
+ */
+ public static long DEFAULT_TWEPOCH = 1288834974657L;
+ /**
+ * 默认回拨时间,2S
+ */
+ public static long DEFAULT_TIME_OFFSET = 2000L;
+
+ private static final long WORKER_ID_BITS = 5L;
// 最大支持机器节点数0~31,一共32个
@SuppressWarnings({"PointlessBitwiseExpression", "FieldCanBeLocal"})
- private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
- private final long dataCenterIdBits = 5L;
+ private static final long MAX_WORKER_ID = -1L ^ (-1L << WORKER_ID_BITS);
+ private static final long DATA_CENTER_ID_BITS = 5L;
// 最大支持数据中心节点数0~31,一共32个
@SuppressWarnings({"PointlessBitwiseExpression", "FieldCanBeLocal"})
- private final long maxDataCenterId = -1L ^ (-1L << dataCenterIdBits);
- // 序列号12位
- private final long sequenceBits = 12L;
+ private static final long MAX_DATA_CENTER_ID = -1L ^ (-1L << DATA_CENTER_ID_BITS);
+ // 序列号12位(表示只允许workId的范围为:0-4095)
+ private static final long SEQUENCE_BITS = 12L;
// 机器节点左移12位
- private final long workerIdShift = sequenceBits;
+ private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;
// 数据中心节点左移17位
- private final long dataCenterIdShift = sequenceBits + workerIdBits;
+ private static final long DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
// 时间毫秒数左移22位
- private final long timestampLeftShift = sequenceBits + workerIdBits + dataCenterIdBits;
+ private static final long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATA_CENTER_ID_BITS;
// 序列掩码,用于限定序列最大值不能超过4095
@SuppressWarnings("FieldCanBeLocal")
- private final long sequenceMask = ~(-1L << sequenceBits);// 4095
+ private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);// 4095
+ private final long twepoch;
private final long workerId;
private final long dataCenterId;
private final boolean useSystemClock;
+ // 允许的时钟回拨数
+ private final long timeOffset;
+
private long sequence = 0L;
private long lastTimestamp = -1L;
+ /**
+ * 构造,使用自动生成的工作节点ID和数据中心ID
+ */
+ public Snowflake() {
+ this(IdUtil.getWorkerId(IdUtil.getDataCenterId(MAX_DATA_CENTER_ID), MAX_WORKER_ID)
+ , IdUtil.getDataCenterId(MAX_DATA_CENTER_ID));
+ }
+
+ /**
+ * 构造
+ *
+ * @param workerId 终端ID
+ */
+ public Snowflake(long workerId) {
+ this(workerId, IdUtil.getDataCenterId(MAX_DATA_CENTER_ID));
+ }
+
/**
* 构造
*
@@ -87,21 +120,34 @@ public class Snowflake implements Serializable {
* @since 5.1.3
*/
public Snowflake(Date epochDate, long workerId, long dataCenterId, boolean isUseSystemClock) {
+ this(epochDate, workerId, dataCenterId, isUseSystemClock, DEFAULT_TIME_OFFSET);
+ }
+
+ /**
+ * @param epochDate 初始化时间起点(null表示默认起始日期),后期修改会导致id重复,如果要修改连workerId dataCenterId,慎用
+ * @param workerId 工作机器节点id
+ * @param dataCenterId 数据中心id
+ * @param isUseSystemClock 是否使用{@link SystemClock} 获取当前时间戳
+ * @param timeOffset 允许时间回拨的毫秒数
+ * @since 5.7.3
+ */
+ public Snowflake(Date epochDate, long workerId, long dataCenterId, boolean isUseSystemClock, long timeOffset) {
if (null != epochDate) {
this.twepoch = epochDate.getTime();
} else{
// Thu, 04 Nov 2010 01:42:54 GMT
- this.twepoch = 1288834974657L;
+ this.twepoch = DEFAULT_TWEPOCH;
}
- if (workerId > maxWorkerId || workerId < 0) {
- throw new IllegalArgumentException(StrUtil.format("worker Id can't be greater than {} or less than 0", maxWorkerId));
+ if (workerId > MAX_WORKER_ID || workerId < 0) {
+ throw new IllegalArgumentException(StrUtil.format("worker Id can't be greater than {} or less than 0", MAX_WORKER_ID));
}
- if (dataCenterId > maxDataCenterId || dataCenterId < 0) {
- throw new IllegalArgumentException(StrUtil.format("datacenter Id can't be greater than {} or less than 0", maxDataCenterId));
+ if (dataCenterId > MAX_DATA_CENTER_ID || dataCenterId < 0) {
+ throw new IllegalArgumentException(StrUtil.format("datacenter Id can't be greater than {} or less than 0", MAX_DATA_CENTER_ID));
}
this.workerId = workerId;
this.dataCenterId = dataCenterId;
this.useSystemClock = isUseSystemClock;
+ this.timeOffset = timeOffset;
}
/**
@@ -111,7 +157,7 @@ public class Snowflake implements Serializable {
* @return 所属机器的id
*/
public long getWorkerId(long id) {
- return id >> workerIdShift & ~(-1L << workerIdBits);
+ return id >> WORKER_ID_SHIFT & ~(-1L << WORKER_ID_BITS);
}
/**
@@ -121,7 +167,7 @@ public class Snowflake implements Serializable {
* @return 所属数据中心
*/
public long getDataCenterId(long id) {
- return id >> dataCenterIdShift & ~(-1L << dataCenterIdBits);
+ return id >> DATA_CENTER_ID_SHIFT & ~(-1L << DATA_CENTER_ID_BITS);
}
/**
@@ -131,7 +177,7 @@ public class Snowflake implements Serializable {
* @return 生成的时间
*/
public long getGenerateDateTime(long id) {
- return (id >> timestampLeftShift & ~(-1L << 41L)) + twepoch;
+ return (id >> TIMESTAMP_LEFT_SHIFT & ~(-1L << 41L)) + twepoch;
}
/**
@@ -142,8 +188,8 @@ public class Snowflake implements Serializable {
public synchronized long nextId() {
long timestamp = genTime();
if (timestamp < this.lastTimestamp) {
- if(this.lastTimestamp - timestamp < 2000){
- // 容忍2秒内的回拨,避免NTP校时造成的异常
+ if(this.lastTimestamp - timestamp < timeOffset){
+ // 容忍指定的回拨,避免NTP校时造成的异常
timestamp = lastTimestamp;
} else{
// 如果服务器时间有问题(时钟后退) 报错。
@@ -152,7 +198,7 @@ public class Snowflake implements Serializable {
}
if (timestamp == this.lastTimestamp) {
- final long sequence = (this.sequence + 1) & sequenceMask;
+ final long sequence = (this.sequence + 1) & SEQUENCE_MASK;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
@@ -163,7 +209,10 @@ public class Snowflake implements Serializable {
lastTimestamp = timestamp;
- return ((timestamp - twepoch) << timestampLeftShift) | (dataCenterId << dataCenterIdShift) | (workerId << workerIdShift) | sequence;
+ return ((timestamp - twepoch) << TIMESTAMP_LEFT_SHIFT)
+ | (dataCenterId << DATA_CENTER_ID_SHIFT)
+ | (workerId << WORKER_ID_SHIFT)
+ | sequence;
}
/**
diff --git a/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java b/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java
index cc137f926..03e876c8d 100644
--- a/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java
@@ -544,15 +544,7 @@ public class NetUtil {
return null;
}
- byte[] mac = null;
- try {
- final NetworkInterface networkInterface = NetworkInterface.getByInetAddress(inetAddress);
- if (null != networkInterface) {
- mac = networkInterface.getHardwareAddress();
- }
- } catch (SocketException e) {
- throw new UtilException(e);
- }
+ final byte[] mac = getHardwareAddress(inetAddress);
if (null != mac) {
final StringBuilder sb = new StringBuilder();
String s;
@@ -570,6 +562,39 @@ public class NetUtil {
return null;
}
+ /**
+ * 获得本机物理地址
+ *
+ * @return 本机物理地址
+ * @since 5.7.3
+ */
+ public static byte[] getLocalHardwareAddress() {
+ return getHardwareAddress(getLocalhost());
+ }
+
+ /**
+ * 获得指定地址信息中的硬件地址
+ *
+ * @param inetAddress {@link InetAddress}
+ * @return 硬件地址
+ * @since 5.7.3
+ */
+ public static byte[] getHardwareAddress(InetAddress inetAddress) {
+ if (null == inetAddress) {
+ return null;
+ }
+
+ try {
+ final NetworkInterface networkInterface = NetworkInterface.getByInetAddress(inetAddress);
+ if (null != networkInterface) {
+ return networkInterface.getHardwareAddress();
+ }
+ } catch (SocketException e) {
+ throw new UtilException(e);
+ }
+ return null;
+ }
+
/**
* 获取主机名称,一次获取会缓存名称
*
diff --git a/hutool-core/src/main/java/cn/hutool/core/util/IdUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/IdUtil.java
index ee31a7fa0..b83944e75 100644
--- a/hutool-core/src/main/java/cn/hutool/core/util/IdUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/util/IdUtil.java
@@ -1,9 +1,11 @@
package cn.hutool.core.util;
+import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.lang.ObjectId;
import cn.hutool.core.lang.Singleton;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.lang.UUID;
+import cn.hutool.core.net.NetUtil;
/**
* ID生成器工具类,此工具类中主要封装:
@@ -134,4 +136,53 @@ public class IdUtil {
public static Snowflake getSnowflake(long workerId, long datacenterId) {
return Singleton.get(Snowflake.class, workerId, datacenterId);
}
+
+ /**
+ * 获取数据中心ID
+ * 数据中心ID依赖于本地网卡MAC地址。
+ *
+ * 此算法来自于mybatis-plus#Sequence + *
+ * + * @param maxDatacenterId 最大的中心ID + * @return 数据中心ID + * @since 5.7.3 + */ + public static long getDataCenterId(long maxDatacenterId) { + long id = 1L; + final byte[] mac = NetUtil.getLocalHardwareAddress(); + if (null != mac) { + id = ((0x000000FF & (long) mac[mac.length - 2]) + | (0x0000FF00 & (((long) mac[mac.length - 1]) << 8))) >> 6; + id = id % (maxDatacenterId + 1); + } + + return id; + } + + /** + * 获取机器ID,使用进程ID配合数据中心ID生成+ * 此算法来自于mybatis-plus#Sequence + *
+ * + * @param datacenterId 数据中心ID + * @param maxWorkerId 最大的机器节点ID + * @since 5.7.3 + */ + public static long getWorkerId(long datacenterId, long maxWorkerId) { + final StringBuilder mpid = new StringBuilder(); + mpid.append(datacenterId); + try { + mpid.append(RuntimeUtil.getPid()); + } catch (UtilException igonre) { + //ignore + } + /* + * MAC + PID 的 hashcode 获取16个低位 + */ + return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java index 4cedf7426..d5fc4c9f5 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java @@ -1,5 +1,6 @@ package cn.hutool.core.util; +import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; import cn.hutool.core.text.StrBuilder; @@ -7,6 +8,7 @@ import cn.hutool.core.text.StrBuilder; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.lang.management.ManagementFactory; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; @@ -279,12 +281,33 @@ public class RuntimeUtil { return getMaxMemory() - getTotalMemory() + getFreeMemory(); } + /** + * 获取当前进程ID,首先获取进程名称,读取@前的ID值,如果不存在,则读取进程名的hash值 + * + * @return 进程ID + * @throws UtilException 进程名称为空 + * @since 5.7.3 + */ + public static int getPid() throws UtilException { + final String processName = ManagementFactory.getRuntimeMXBean().getName(); + if (StrUtil.isBlank(processName)) { + throw new UtilException("Process name is blank!"); + } + final int atIndex = processName.indexOf('@'); + if (atIndex > 0) { + return Integer.parseInt(processName.substring(0, atIndex)); + } else { + return processName.hashCode(); + } + } + /** * 处理命令,多行命令原样返回,单行命令拆分处理 + * * @param cmds 命令 * @return 处理后的命令 */ - private static String[] handleCmds(String... cmds){ + private static String[] handleCmds(String... cmds) { if (ArrayUtil.isEmpty(cmds)) { throw new NullPointerException("Command is empty !"); } @@ -306,7 +329,7 @@ public class RuntimeUtil { * @param cmd 命令,如 git commit -m 'test commit' * @return 分割后的命令 */ - private static String[] cmdSplit(String cmd){ + private static String[] cmdSplit(String cmd) { final List