From 2d5d3fc43ef7015f46cfd0c3ffc4e685a90fdf53 Mon Sep 17 00:00:00 2001
From: zhanghl <253316343@qq.com>
Date: 星期四, 17 四月 2025 09:36:42 +0800
Subject: [PATCH] Snowflake ID
---
aps-common/aps-common-core/src/main/java/com/aps/common/core/utils/uuid/Sequence.java | 299 ++++++++++++++++++++++++++++++++++++++++++
aps-common/aps-common-core/src/main/java/com/aps/common/core/enums/SystemClock.java | 78 +++++++++++
2 files changed, 377 insertions(+), 0 deletions(-)
diff --git a/aps-common/aps-common-core/src/main/java/com/aps/common/core/enums/SystemClock.java b/aps-common/aps-common-core/src/main/java/com/aps/common/core/enums/SystemClock.java
new file mode 100644
index 0000000..778c4b2
--- /dev/null
+++ b/aps-common/aps-common-core/src/main/java/com/aps/common/core/enums/SystemClock.java
@@ -0,0 +1,78 @@
+package com.aps.common.core.enums;
+
+import java.sql.Timestamp;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * System Clock
+ * <p>
+ * 鍒╃敤ScheduledExecutorService瀹炵幇楂樺苟鍙戝満鏅笅System.curentTimeMillis()鐨勬�ц兘闂鐨勪紭鍖�.
+ *
+ * @author lry
+ */
+public enum SystemClock {
+
+ // ====
+
+ INSTANCE(1);
+
+ private final long period;
+ private final AtomicLong nowTime;
+ private boolean started = false;
+ private ScheduledExecutorService executorService;
+
+ SystemClock(long period) {
+ this.period = period;
+ this.nowTime = new AtomicLong(System.currentTimeMillis());
+ }
+
+ /**
+ * The initialize scheduled executor service
+ */
+ public void initialize() {
+ if (started) {
+ return;
+ }
+
+ this.executorService = new ScheduledThreadPoolExecutor(1, r -> {
+ Thread thread = new Thread(r, "system-clock");
+ thread.setDaemon(true);
+ return thread;
+ });
+ executorService.scheduleAtFixedRate(() -> nowTime.set(System.currentTimeMillis()),
+ this.period, this.period, TimeUnit.MILLISECONDS);
+ Runtime.getRuntime().addShutdownHook(new Thread(this::destroy));
+ started = true;
+ }
+
+ /**
+ * The get current time milliseconds
+ *
+ * @return long time
+ */
+ public long currentTimeMillis() {
+ return started ? nowTime.get() : System.currentTimeMillis();
+ }
+
+ /**
+ * The get string current time
+ *
+ * @return string time
+ */
+ public String currentTime() {
+ return new Timestamp(currentTimeMillis()).toString();
+ }
+
+ /**
+ * The destroy of executor service
+ */
+ public void destroy() {
+ if (executorService != null) {
+ executorService.shutdown();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/aps-common/aps-common-core/src/main/java/com/aps/common/core/utils/uuid/Sequence.java b/aps-common/aps-common-core/src/main/java/com/aps/common/core/utils/uuid/Sequence.java
new file mode 100644
index 0000000..ab8822f
--- /dev/null
+++ b/aps-common/aps-common-core/src/main/java/com/aps/common/core/utils/uuid/Sequence.java
@@ -0,0 +1,299 @@
+package com.aps.common.core.utils.uuid;
+
+
+import com.aps.common.core.enums.SystemClock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import java.lang.management.ManagementFactory;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.util.Enumeration;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.regex.Pattern;
+
+/**
+ * 鍩轰簬Twitter鐨凷nowflake绠楁硶瀹炵幇鍒嗗竷寮忛珮鏁堟湁搴廔D鐢熶骇榛戠鎶�(sequence)鈥斺�斿崌绾х増Snowflake
+ *
+ * <br>
+ * SnowFlake鐨勭粨鏋勫涓�(姣忛儴鍒嗙敤-鍒嗗紑):<br>
+ * <br>
+ * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>
+ * <br>
+ * 1浣嶆爣璇嗭紝鐢变簬long鍩烘湰绫诲瀷鍦↗ava涓槸甯︾鍙风殑锛屾渶楂樹綅鏄鍙蜂綅锛屾鏁版槸0锛岃礋鏁版槸1锛屾墍浠d涓�鑸槸姝f暟锛屾渶楂樹綅鏄�0<br>
+ * <br>
+ * 41浣嶆椂闂存埅(姣绾�)锛屾敞鎰忥紝41浣嶆椂闂存埅涓嶆槸瀛樺偍褰撳墠鏃堕棿鐨勬椂闂存埅锛岃�屾槸瀛樺偍鏃堕棿鎴殑宸�硷紙褰撳墠鏃堕棿鎴� - 寮�濮嬫椂闂存埅)
+ * 寰楀埌鐨勫�硷級锛岃繖閲岀殑鐨勫紑濮嬫椂闂存埅锛屼竴鑸槸鎴戜滑鐨刬d鐢熸垚鍣ㄥ紑濮嬩娇鐢ㄧ殑鏃堕棿锛岀敱鎴戜滑绋嬪簭鏉ユ寚瀹氱殑锛堝涓婼TART_TIME灞炴�э級銆�41浣嶇殑鏃堕棿鎴紝鍙互浣跨敤69骞达紝骞碩 = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
+ * <br>
+ * 10浣嶇殑鏁版嵁鏈哄櫒浣嶏紝鍙互閮ㄧ讲鍦�1024涓妭鐐癸紝鍖呮嫭5浣峝ataCenterId鍜�5浣峸orkerId<br>
+ * <br>
+ * 12浣嶅簭鍒楋紝姣鍐呯殑璁℃暟锛�12浣嶇殑璁℃暟椤哄簭鍙锋敮鎸佹瘡涓妭鐐规瘡姣(鍚屼竴鏈哄櫒锛屽悓涓�鏃堕棿鎴�)浜х敓4096涓狪D搴忓彿<br>
+ * <br>
+ * <br>
+ * 鍔犺捣鏉ュ垰濂�64浣嶏紝涓轰竴涓狶ong鍨嬨��<br>
+ * SnowFlake鐨勪紭鐐规槸锛屾暣浣撲笂鎸夌収鏃堕棿鑷鎺掑簭锛屽苟涓旀暣涓垎甯冨紡绯荤粺鍐呬笉浼氫骇鐢烮D纰版挒(鐢辨暟鎹腑蹇僆D鍜屾満鍣↖D浣滃尯鍒�)锛屽苟涓旀晥鐜囪緝楂橈紝缁忔祴璇曪紝SnowFlake姣忕鑳藉浜х敓26涓嘔D宸﹀彸銆�
+ * <p>
+ * <p>
+ * 鐗规�э細
+ * 1.鏀寔鑷畾涔夊厑璁告椂闂村洖鎷ㄧ殑鑼冨洿<p>
+ * 2.瑙e喅璺ㄦ绉掕捣濮嬪�兼瘡娆′负0寮�濮嬬殑鎯呭喌锛堥伩鍏嶆湯灏惧繀瀹氫负鍋舵暟锛岃�屼笉渚夸簬鍙栦綑浣跨敤闂锛�<p>
+ * 3.瑙e喅楂樺苟鍙戝満鏅腑鑾峰彇鏃堕棿鎴虫�ц兘闂<p>
+ * 4.鏀拺鏍规嵁IP鏈熬鏁版嵁浣滀负workerId
+ * 5.鏃堕棿鍥炴嫧鏂规鎬濊�冿細1024涓妭鐐逛腑鍒嗛厤10涓偣浣滀负鏃堕棿鍥炴嫧搴忓彿锛堣繛缁�10娆℃椂闂村洖鎷ㄧ殑姒傜巼杈冨皬锛�
+ * <p>
+ * 甯歌闂:
+ * 1.鏃堕棿鍥炴嫧闂
+ * 2.鏈哄櫒id鐨勫垎閰嶅拰鍥炴敹闂
+ * 3.鏈哄櫒id鐨勪笂闄愰棶棰�
+ *
+ * @author lry
+ * @version 3.0
+ */
+@Component
+public class Sequence {
+
+ private static final Logger log = LoggerFactory.getLogger(Sequence.class);
+
+ /**
+ * 鏃堕棿璧峰鏍囪鐐癸紝浣滀负鍩哄噯锛屼竴鑸彇绯荤粺鐨勬渶杩戞椂闂达紙涓�鏃︾‘瀹氫笉鑳藉彉鍔級
+ */
+ private final long twepoch = 1519740777809L;
+
+ /**
+ * 5浣嶇殑鏈烘埧id
+ */
+ private final long datacenterIdBits = 5L;
+ /**
+ * 5浣嶇殑鏈哄櫒id
+ */
+ private final long workerIdBits = 5L;
+ /**
+ * 姣忔绉掑唴浜х敓鐨刬d鏁�: 2鐨�12娆℃柟涓�
+ */
+ private final long sequenceBits = 12L;
+
+ protected final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
+ protected final long maxWorkerId = -1L ^ (-1L << workerIdBits);
+
+ private final long workerIdShift = sequenceBits;
+ private final long datacenterIdShift = sequenceBits + workerIdBits;
+
+ /**
+ * 鏃堕棿鎴冲乏绉诲姩浣�
+ */
+ private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
+ private final long sequenceMask = -1L ^ (-1L << sequenceBits);
+
+ /**
+ * 鎵�灞炴満鎴縤d
+ */
+ private final long datacenterId;
+ /**
+ * 鎵�灞炴満鍣╥d
+ */
+ private final long workerId;
+ /**
+ * 骞跺彂鎺у埗搴忓垪
+ */
+ private long sequence = 0L;
+
+ /**
+ * 涓婃鐢熶骇 ID 鏃堕棿鎴�
+ */
+ private long lastTimestamp = -1L;
+
+ private static volatile InetAddress LOCAL_ADDRESS = null;
+ private static final Pattern IP_PATTERN = Pattern.compile("\\d{1,3}(\\.\\d{1,3}){3,5}$");
+
+ public Sequence() {
+ this.datacenterId = getDatacenterId();
+ this.workerId = getMaxWorkerId(datacenterId);
+ }
+
+ /**
+ * 鏈夊弬鏋勯�犲櫒
+ *
+ * @param workerId 宸ヤ綔鏈哄櫒 ID
+ * @param datacenterId 搴忓垪鍙�
+ */
+ public Sequence(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;
+ }
+
+ /**
+ * 鍩轰簬缃戝崱MAC鍦板潃璁$畻浣欐暟浣滀负鏁版嵁涓績
+ * <p>
+ * 鍙嚜瀹氭墿灞�
+ */
+ protected long getDatacenterId() {
+ long id = 0L;
+ try {
+ NetworkInterface network = NetworkInterface.getByInetAddress(getLocalAddress());
+ if (null == network) {
+ id = 1L;
+ } else {
+ byte[] mac = network.getHardwareAddress();
+ if (null != mac) {
+ id = ((0x000000FF & (long) mac[mac.length - 2]) | (0x0000FF00 & (((long) mac[mac.length - 1]) << 8))) >> 6;
+ id = id % (maxDatacenterId + 1);
+ }
+ }
+ } catch (Exception e) {
+ log.warn(" getDatacenterId: " + e.getMessage());
+ }
+
+ return id;
+ }
+
+ /**
+ * 鍩轰簬 MAC + PID 鐨� hashcode 鑾峰彇16涓綆浣�
+ * <p>
+ * 鍙嚜瀹氭墿灞�
+ */
+ protected long getMaxWorkerId(long datacenterId) {
+ StringBuilder mpId = new StringBuilder();
+ mpId.append(datacenterId);
+ String name = ManagementFactory.getRuntimeMXBean().getName();
+ if (name != null && name.length() > 0) {
+ // GET jvmPid
+ mpId.append(name.split("@")[0]);
+ }
+
+ // MAC + PID 鐨� hashcode 鑾峰彇16涓綆浣�
+ return (mpId.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
+ }
+
+ /**
+ * 鑾峰彇涓嬩竴涓� ID
+ *
+ * @return next id
+ */
+ public synchronized long nextId() {
+ long timestamp = timeGen();
+ // 闂扮
+ if (timestamp < lastTimestamp) {
+ long offset = lastTimestamp - timestamp;
+ if (offset <= 5) {
+ try {
+ // 浼戠湢鍙屽�嶅樊鍊煎悗閲嶆柊鑾峰彇锛屽啀娆℃牎楠�
+ wait(offset << 1);
+ timestamp = timeGen();
+ if (timestamp < lastTimestamp) {
+ throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", offset));
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", offset));
+ }
+ }
+
+ if (lastTimestamp == timestamp) {
+ // 鐩稿悓姣鍐咃紝搴忓垪鍙疯嚜澧�
+ sequence = (sequence + 1) & sequenceMask;
+ if (sequence == 0) {
+ // 鍚屼竴姣鐨勫簭鍒楁暟宸茬粡杈惧埌鏈�澶�
+ timestamp = tilNextMillis(lastTimestamp);
+ }
+ } else {
+ // 涓嶅悓姣鍐咃紝搴忓垪鍙风疆涓� 1 - 3 闅忔満鏁�
+ sequence = ThreadLocalRandom.current().nextLong(1, 3);
+ }
+
+ lastTimestamp = timestamp;
+
+ // 鏃堕棿鎴抽儴鍒� | 鏁版嵁涓績閮ㄥ垎 | 鏈哄櫒鏍囪瘑閮ㄥ垎 | 搴忓垪鍙烽儴鍒�
+ return ((timestamp - twepoch) << timestampLeftShift)
+ | (datacenterId << datacenterIdShift)
+ | (workerId << workerIdShift)
+ | sequence;
+ }
+
+ protected long tilNextMillis(long lastTimestamp) {
+ long timestamp = timeGen();
+ while (timestamp <= lastTimestamp) {
+ timestamp = timeGen();
+ }
+
+ return timestamp;
+ }
+
+ protected long timeGen() {
+ return SystemClock.INSTANCE.currentTimeMillis();
+ }
+
+ /**
+ * Find first valid IP from local network card
+ *
+ * @return first valid local IP
+ */
+ public static InetAddress getLocalAddress() {
+ if (LOCAL_ADDRESS != null) {
+ return LOCAL_ADDRESS;
+ }
+
+ LOCAL_ADDRESS = getLocalAddress0();
+ return LOCAL_ADDRESS;
+ }
+
+ private static InetAddress getLocalAddress0() {
+ InetAddress localAddress = null;
+ try {
+ localAddress = InetAddress.getLocalHost();
+ if (isValidAddress(localAddress)) {
+ return localAddress;
+ }
+ } catch (Throwable e) {
+ log.warn("Failed to retrieving ip address, " + e.getMessage(), e);
+ }
+
+ try {
+ Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
+ if (interfaces != null) {
+ while (interfaces.hasMoreElements()) {
+ try {
+ NetworkInterface network = interfaces.nextElement();
+ Enumeration<InetAddress> addresses = network.getInetAddresses();
+ while (addresses.hasMoreElements()) {
+ try {
+ InetAddress address = addresses.nextElement();
+ if (isValidAddress(address)) {
+ return address;
+ }
+ } catch (Throwable e) {
+ log.warn("Failed to retrieving ip address, " + e.getMessage(), e);
+ }
+ }
+ } catch (Throwable e) {
+ log.warn("Failed to retrieving ip address, " + e.getMessage(), e);
+ }
+ }
+ }
+ } catch (Throwable e) {
+ log.warn("Failed to retrieving ip address, " + e.getMessage(), e);
+ }
+
+ log.error("Could not get local host ip address, will use 127.0.0.1 instead.");
+ return localAddress;
+ }
+
+ private static boolean isValidAddress(InetAddress address) {
+ if (address == null || address.isLoopbackAddress()) {
+ return false;
+ }
+
+ String name = address.getHostAddress();
+ return (name != null && !"0.0.0.0".equals(name) && !"127.0.0.1".equals(name) && IP_PATTERN.matcher(name).matches());
+ }
+
+}
\ No newline at end of file
--
Gitblit v1.9.3