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.keygen.IKeyGenerator; 019 020import java.util.concurrent.ThreadLocalRandom; 021 022/** 023 * ULID: 对比UUID的优势在于可排序性和性能。 024 * <p> 025 * 特点: 026 * 1、保证 id 生成的顺序为时间顺序,越往后生成的 ID 值越大; 027 * 2、可以按照生成的时间进行排序,而不需要全局协调; 028 * 3、生成速度快; 029 * <p> 030 * <p>参考:<a href="https://github.com/ulid/spec">Sequence</a> 031 */ 032public class ULIDKeyGenerator implements IKeyGenerator { 033 034 private static final char[] ENCODING_CHARS = { 035 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 036 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 037 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 038 'Y', 'Z' 039 }; 040 041 private static final long TIMESTAMP_OVERFLOW_MASK = 0xFFFF_0000_0000_0000L; 042 043 private static final ThreadLocal<StringBuilder> THREAD_LOCAL_BUILDER = 044 ThreadLocal.withInitial(() -> new StringBuilder(26)); 045 046 private long lastTimestamp = 0; 047 048 private long lastRandom = 0; 049 050 @Override 051 public Object generate(Object entity, String keyColumn) { 052 return nextId(); 053 } 054 055 /** 056 * 生成一个 ULID 057 * 058 * @return ULID 059 */ 060 public String nextId() { 061 return generateULID(System.currentTimeMillis()).toLowerCase(); 062 } 063 064 /** 065 * 生成一个严格单调的 ULID 066 * 067 * @return ULID 068 */ 069 public synchronized String nextMonotonicId() { 070 long timestamp = System.currentTimeMillis(); 071 if (timestamp > lastTimestamp) { 072 lastTimestamp = timestamp; 073 lastRandom = ThreadLocalRandom.current().nextLong(); 074 } else { 075 lastRandom++; 076 if (lastRandom == 0) { 077 timestamp = waitNextMillis(lastTimestamp); 078 lastTimestamp = timestamp; 079 lastRandom = ThreadLocalRandom.current().nextLong(); 080 } 081 } 082 return generateULID(lastTimestamp, lastRandom).toLowerCase(); 083 } 084 085 private String generateULID(long timestamp) { 086 return generateULID(timestamp, ThreadLocalRandom.current().nextLong()); 087 } 088 089 private String generateULID(long timestamp, long random) { 090 checkTimestamp(timestamp); 091 StringBuilder builder = THREAD_LOCAL_BUILDER.get(); 092 builder.setLength(0); 093 094 appendCrockford(builder, timestamp, 10); 095 appendCrockford(builder, random, 16); 096 097 return builder.toString(); 098 } 099 100 private long waitNextMillis(long lastTimestamp) { 101 long timestamp = System.currentTimeMillis(); 102 while (timestamp <= lastTimestamp) { 103 timestamp = System.currentTimeMillis(); 104 } 105 return timestamp; 106 } 107 108 private static void appendCrockford(StringBuilder builder, long value, int count) { 109 for (int i = (count - 1) * 5; i >= 0; i -= 5) { 110 int index = (int) ((value >>> i) & 0x1F); 111 builder.append(ENCODING_CHARS[index]); 112 } 113 } 114 115 private static void checkTimestamp(long timestamp) { 116 if ((timestamp & TIMESTAMP_OVERFLOW_MASK) != 0) { 117 throw new IllegalArgumentException("ULID does not support timestamps after +10889-08-02T05:31:50.655Z!"); 118 } 119 } 120}