001/*
002 *  Copyright (c) 2022-2025, Mybatis-Flex (fuhai999@gmail.com).
003 *  <p>
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *  <p>
008 *  http://www.apache.org/licenses/LICENSE-2.0
009 *  <p>
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package com.mybatisflex.core.keygen.impl;
017
018import com.mybatisflex.core.exception.FlexExceptions;
019import com.mybatisflex.core.keygen.IKeyGenerator;
020import com.mybatisflex.core.util.StringUtil;
021import org.apache.ibatis.logging.LogFactory;
022
023import java.lang.management.ManagementFactory;
024import java.net.InetAddress;
025import java.net.NetworkInterface;
026
027/**
028 * <p>雪花算法 ID 生成器。
029 *
030 * <ul>
031 *     <li>最高 1 位固定值 0,因为生成的 ID 是正整数;
032 *     <li>接下来 41 位存储毫秒级时间戳,2 ^ 41 / ( 1000 * 60 * 60 * 24 * 365) = 69,大概可以使用 69 年;
033 *     <li>再接下 10 位存储机器码,包括 5 位 dataCenterId 和 5 位 workerId,最多可以部署 2 ^ 10 = 1024 台机器;
034 *     <li>最后 12 位存储序列号,同一毫秒时间戳时,通过这个递增的序列号来区分,即对于同一台机器而言,同一毫秒时间戳下,可以生成 2 ^ 12 = 4096 个不重复 ID。
035 * </ul>
036 *
037 * <p>优化自开源项目:<a href="https://gitee.com/yu120/sequence">Sequence</a>
038 *
039 * @author 王帅
040 * @since 2023-05-12
041 */
042public class SnowFlakeIDKeyGenerator implements IKeyGenerator {
043
044    /**
045     * 工作机器 ID 占用的位数(5bit)。
046     */
047    private static final long WORKER_ID_BITS = 5L;
048    /**
049     * 数据中心 ID 占用的位数(5bit)。
050     */
051    private static final long DATA_CENTER_ID_BITS = 5L;
052    /**
053     * 序号占用的位数(12bit)。
054     */
055    private static final long SEQUENCE_BITS = 12L;
056    /**
057     * 工作机器 ID 占用 5bit 时的最大值 31。
058     */
059    private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
060    /**
061     * 数据中心 ID 占用 5bit 时的最大值 31。
062     */
063    private static final long MAX_DATA_CENTER_ID = ~(-1L << DATA_CENTER_ID_BITS);
064    /**
065     * 序号掩码,用于与自增后的序列号进行位“与”操作,如果值为 0,则代表自增后的序列号超过了 4095。
066     */
067    private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);
068    /**
069     * 工作机器 ID 位需要左移的位数(12bit)。
070     */
071    private static final long WORK_ID_SHIFT = SEQUENCE_BITS;
072    /**
073     * 数据中心 ID 位需要左移的位数(12bit + 5bit)。
074     */
075    private static final long DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
076    /**
077     * 时间戳需要左移的位数(12bit + 5bit + 5bit)。
078     */
079    private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATA_CENTER_ID_BITS;
080    /**
081     * 时间起始标记点,一旦确定不能变动(2023-04-02 13:01:00)。
082     */
083    private static long twepoch = 1680411660000L;
084    /**
085     * 可容忍的时间偏移量。
086     */
087    private static long offsetPeriod = 5L;
088
089    /**
090     * 工作机器 ID。
091     */
092    private final long workerId;
093    /**
094     * 数据中心 ID。
095     */
096    private final long dataCenterId;
097
098    /**
099     * IP 地址信息,用来生成工作机器 ID 和数据中心 ID。
100     */
101    protected InetAddress address;
102    /**
103     * 同一毫秒内的最新序号,最大值可为(2^12 - 1 = 4095)。
104     */
105    private long sequence;
106    /**
107     * 上次生产 ID 时间戳。
108     */
109    private long lastTimeMillis = -1L;
110
111    /**
112     * 雪花算法 ID 生成器。
113     */
114    public SnowFlakeIDKeyGenerator() {
115        this(null);
116    }
117
118    /**
119     * 根据 IP 地址计算数据中心 ID 和工作机器 ID 生成数据库 ID。
120     *
121     * @param address IP 地址
122     */
123    public SnowFlakeIDKeyGenerator(InetAddress address) {
124        this.address = address;
125        this.dataCenterId = getDataCenterId(MAX_DATA_CENTER_ID);
126        this.workerId = getWorkerId(dataCenterId, MAX_WORKER_ID);
127    }
128
129    /**
130     * 根据数据中心 ID 和工作机器 ID 生成数据库 ID。
131     *
132     * @param workerId     工作机器 ID
133     * @param dataCenterId 数据中心 ID
134     */
135    public SnowFlakeIDKeyGenerator(long workerId, long dataCenterId) {
136        if (workerId > MAX_WORKER_ID || workerId < 0) {
137            throw new IllegalArgumentException(
138                String.format("workerId must be greater than 0 and less than %d.", MAX_WORKER_ID));
139        }
140        if (dataCenterId > MAX_DATA_CENTER_ID || dataCenterId < 0) {
141            throw new IllegalArgumentException(
142                String.format("dataCenterId must be greater than 0 and less than %d.", MAX_DATA_CENTER_ID));
143        }
144        this.workerId = workerId;
145        this.dataCenterId = dataCenterId;
146    }
147
148    /**
149     * 根据 MAC + PID 的 hashCode 获取 16 个低位生成工作机器 ID。
150     */
151    protected long getWorkerId(long dataCenterId, long maxWorkerId) {
152        StringBuilder mpId = new StringBuilder();
153        mpId.append(dataCenterId);
154        String name = ManagementFactory.getRuntimeMXBean().getName();
155        if (StringUtil.isNotBlank(name)) {
156            // GET jvmPid
157            mpId.append(name.split("@")[0]);
158        }
159        // MAC + PID 的 hashCode 获取16个低位
160        return (mpId.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
161    }
162
163    /**
164     * 根据网卡 MAC 地址计算余数作为数据中心 ID。
165     */
166    protected long getDataCenterId(long maxDataCenterId) {
167        long id = 1L;
168        try {
169            if (address == null) {
170                address = InetAddress.getLocalHost();
171            }
172            NetworkInterface network = NetworkInterface.getByInetAddress(address);
173            if (null != network) {
174                byte[] mac = network.getHardwareAddress();
175                if (null != mac) {
176                    id = ((0x000000FF & (long) mac[mac.length - 2]) | (0x0000FF00 & (((long) mac[mac.length - 1]) << 8))) >> 6;
177                    id = id % (maxDataCenterId + 1);
178                }
179            }
180        } catch (Exception e) {
181            LogFactory.getLog(SnowFlakeIDKeyGenerator.class).error(e.toString(), e);
182        }
183        return id;
184    }
185
186    @Override
187    public Object generate(Object entity, String keyColumn) {
188        return nextId();
189    }
190
191    /**
192     * 获取下一个 ID。
193     */
194    public synchronized long nextId() {
195        long currentTimeMillis = System.currentTimeMillis();
196        // 当前时间小于上一次生成 ID 使用的时间,可能出现服务器时钟回拨问题。
197        if (currentTimeMillis < lastTimeMillis) {
198            long offset = lastTimeMillis - currentTimeMillis;
199            // 在可容忍的时间差值之内等待时间恢复正常
200            if (offset <= offsetPeriod) {
201                try {
202                    wait(offset << 1L);
203                    currentTimeMillis = System.currentTimeMillis();
204                    if (currentTimeMillis < lastTimeMillis) {
205                        throw FlexExceptions.wrap("Clock moved backwards, please check the time. Current timestamp: %d, last used timestamp: %d", currentTimeMillis, lastTimeMillis);
206                    }
207                } catch (InterruptedException e) {
208                    throw FlexExceptions.wrap(e);
209                }
210            } else {
211                throw FlexExceptions.wrap("Clock moved backwards, please check the time. Current timestamp: %d, last used timestamp: %d", currentTimeMillis, lastTimeMillis);
212            }
213        }
214
215        if (currentTimeMillis == lastTimeMillis) {
216            // 相同毫秒内,序列号自增
217            sequence = (sequence + 1) & SEQUENCE_MASK;
218            if (sequence == 0) {
219                // 同一毫秒的序列数已经达到最大
220                currentTimeMillis = tilNextMillis(lastTimeMillis);
221            }
222        } else {
223            // 不同毫秒内,序列号置为 0。
224            sequence = 0L;
225        }
226
227        // 记录最后一次使用的毫秒时间戳
228        lastTimeMillis = currentTimeMillis;
229
230        // 时间戳部分 | 数据中心部分 | 机器标识部分 | 序列号部分
231        return ((currentTimeMillis - twepoch) << TIMESTAMP_SHIFT)
232            | (dataCenterId << DATA_CENTER_ID_SHIFT)
233            | (workerId << WORK_ID_SHIFT)
234            | sequence;
235    }
236
237    /**
238     * 获取指定时间戳的接下来的时间戳。
239     */
240    private long tilNextMillis(long lastTimestamp) {
241        long currentTimeMillis = System.currentTimeMillis();
242        while (currentTimeMillis <= lastTimestamp) {
243            currentTimeMillis = System.currentTimeMillis();
244        }
245        return currentTimeMillis;
246    }
247
248    public static long getTwepoch() {
249        return twepoch;
250    }
251
252    public static void setTwepoch(long twepoch) {
253        SnowFlakeIDKeyGenerator.twepoch = twepoch;
254    }
255
256    public static long getOffsetPeriod() {
257        return offsetPeriod;
258    }
259
260    public static void setOffsetPeriod(long offsetPeriod) {
261        SnowFlakeIDKeyGenerator.offsetPeriod = offsetPeriod;
262    }
263
264    public long getWorkerId() {
265        return workerId;
266    }
267
268    public long getDataCenterId() {
269        return dataCenterId;
270    }
271
272    public InetAddress getAddress() {
273        return address;
274    }
275
276    public void setAddress(InetAddress address) {
277        this.address = address;
278    }
279
280    public long getSequence() {
281        return sequence;
282    }
283
284    public void setSequence(long sequence) {
285        this.sequence = sequence;
286    }
287
288    public long getLastTimeMillis() {
289        return lastTimeMillis;
290    }
291
292    public void setLastTimeMillis(long lastTimeMillis) {
293        this.lastTimeMillis = lastTimeMillis;
294    }
295}