|
|
@@ -0,0 +1,424 @@
|
|
|
+package com.fs.utils;
|
|
|
+
|
|
|
+import java.math.BigInteger;
|
|
|
+import java.security.MessageDigest;
|
|
|
+import java.security.NoSuchAlgorithmException;
|
|
|
+import java.security.SecureRandom;
|
|
|
+import java.util.concurrent.ThreadLocalRandom;
|
|
|
+import java.util.concurrent.atomic.AtomicLong;
|
|
|
+import java.util.concurrent.locks.ReentrantReadWriteLock;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 短码生成工具类 - 高并发、高唯一性短码生成器
|
|
|
+ * 修复版本:解决数组越界和线程获取问题
|
|
|
+ *
|
|
|
+ * @author ToolKit
|
|
|
+ * @version 1.0.2
|
|
|
+ */
|
|
|
+public final class ShortCodeGeneratorUtils {
|
|
|
+
|
|
|
+ // 私有构造器,防止实例化
|
|
|
+ private ShortCodeGeneratorUtils() {
|
|
|
+ throw new AssertionError("Cannot instantiate utility class");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 字符集定义枚举
|
|
|
+ */
|
|
|
+ public enum CharsetType {
|
|
|
+
|
|
|
+ /** URL安全字符集(推荐) */
|
|
|
+ URL_SAFE("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
|
|
|
+
|
|
|
+ private final String chars;
|
|
|
+
|
|
|
+ CharsetType(String chars) {
|
|
|
+ this.chars = chars;
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getChars() {
|
|
|
+ return chars;
|
|
|
+ }
|
|
|
+
|
|
|
+ public char[] getCharArray() {
|
|
|
+ return chars.toCharArray();
|
|
|
+ }
|
|
|
+
|
|
|
+ public int size() {
|
|
|
+ return chars.length();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 默认配置
|
|
|
+ private static final CharsetType DEFAULT_CHARSET = CharsetType.URL_SAFE;
|
|
|
+
|
|
|
+ // 单例实例(延迟加载)
|
|
|
+ private static class InstanceHolder {
|
|
|
+ static final ShortCodeGenerator INSTANCE = new ShortCodeGenerator(DEFAULT_CHARSET);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取默认生成器实例
|
|
|
+ */
|
|
|
+ public static ShortCodeGenerator getInstance() {
|
|
|
+ return InstanceHolder.INSTANCE;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取指定字符集的生成器实例
|
|
|
+ */
|
|
|
+ public static ShortCodeGenerator getInstance(CharsetType charsetType) {
|
|
|
+ return new ShortCodeGenerator(charsetType);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 快速生成8位短码(使用默认配置)
|
|
|
+ */
|
|
|
+ public static String generate8() {
|
|
|
+ return getInstance().generate8CharCode();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 快速生成6位短码(使用默认配置)
|
|
|
+ */
|
|
|
+ public static String generate6() {
|
|
|
+ return getInstance().generate6CharCode();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 快速生成指定长度短码(使用默认配置)
|
|
|
+ */
|
|
|
+ public static String generate(int length) {
|
|
|
+ return getInstance().generateCode(length);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 短码生成器核心实现 - 完全修复版本
|
|
|
+ */
|
|
|
+ public static class ShortCodeGenerator {
|
|
|
+
|
|
|
+ private final char[] charset;
|
|
|
+ private final int charsetSize;
|
|
|
+ private final AtomicLong counter = new AtomicLong(0);
|
|
|
+ private volatile long lastTimestamp = 0;
|
|
|
+ private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
|
|
|
+ private final SecureRandom secureRandom;
|
|
|
+ private static final long PROCESS_ID = getProcessId();
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 使用默认字符集创建生成器
|
|
|
+ */
|
|
|
+ public ShortCodeGenerator() {
|
|
|
+ this(DEFAULT_CHARSET);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 使用指定字符集创建生成器
|
|
|
+ */
|
|
|
+ public ShortCodeGenerator(CharsetType charsetType) {
|
|
|
+ this.charset = charsetType.getCharArray();
|
|
|
+ this.charsetSize = charsetType.size();
|
|
|
+ this.secureRandom = new SecureRandom();
|
|
|
+ // 初始化安全随机数生成器
|
|
|
+ secureRandom.nextBytes(new byte[16]);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成8位短码
|
|
|
+ */
|
|
|
+ public String generate8CharCode() {
|
|
|
+ return generateCode(8);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成6位短码
|
|
|
+ */
|
|
|
+ public String generate6CharCode() {
|
|
|
+ return generateCode(6);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成指定长度短码
|
|
|
+ */
|
|
|
+ public String generateCode(int length) {
|
|
|
+ validateLength(length);
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 组合多种生成策略降低碰撞概率
|
|
|
+ String codeByTime = generateByTimestamp(length);
|
|
|
+ String codeByHash = generateByHash(length);
|
|
|
+
|
|
|
+ return mixAndFinalize(codeByTime, codeByHash, length);
|
|
|
+ } catch (Exception e) {
|
|
|
+ // 如果任何方法失败,使用安全的降级方案
|
|
|
+ return generateSafeFallbackCode(length);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 批量生成短码
|
|
|
+ */
|
|
|
+ public String[] generateBatch(int count, int length) {
|
|
|
+ validateLength(length);
|
|
|
+
|
|
|
+ String[] codes = new String[count];
|
|
|
+
|
|
|
+ for (int i = 0; i < count; i++) {
|
|
|
+ codes[i] = generateCode(length);
|
|
|
+ }
|
|
|
+
|
|
|
+ return codes;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取字符集大小
|
|
|
+ */
|
|
|
+ public int getCharsetSize() {
|
|
|
+ return charsetSize;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取字符集
|
|
|
+ */
|
|
|
+ public String getCharsetString() {
|
|
|
+ return new String(charset);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证长度参数
|
|
|
+ */
|
|
|
+ private void validateLength(int length) {
|
|
|
+ if (length < 4 || length > 16) {
|
|
|
+ throw new IllegalArgumentException("Length must be between 4 and 16");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 安全的降级生成方案
|
|
|
+ */
|
|
|
+ private String generateSafeFallbackCode(int length) {
|
|
|
+ char[] result = new char[length];
|
|
|
+ ThreadLocalRandom random = ThreadLocalRandom.current();
|
|
|
+
|
|
|
+ for (int i = 0; i < length; i++) {
|
|
|
+ // 使用安全的随机数生成,确保索引为正数
|
|
|
+ int index = random.nextInt(charsetSize);
|
|
|
+ result[i] = charset[index];
|
|
|
+ }
|
|
|
+
|
|
|
+ return new String(result);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 基于时间戳生成 - 修复线程ID获取问题
|
|
|
+ */
|
|
|
+ private String generateByTimestamp(int length) {
|
|
|
+ long currentTime = System.currentTimeMillis();
|
|
|
+ long sequence;
|
|
|
+
|
|
|
+ lock.writeLock().lock();
|
|
|
+ try {
|
|
|
+ if (currentTime == lastTimestamp) {
|
|
|
+ sequence = counter.incrementAndGet();
|
|
|
+ } else {
|
|
|
+ lastTimestamp = currentTime;
|
|
|
+ counter.set(0);
|
|
|
+ sequence = 0;
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ lock.writeLock().unlock();
|
|
|
+ }
|
|
|
+
|
|
|
+ ThreadLocalRandom random = ThreadLocalRandom.current();
|
|
|
+ long randomPart = random.nextLong();
|
|
|
+
|
|
|
+ // 获取当前线程ID
|
|
|
+ long threadId = Thread.currentThread().getId();
|
|
|
+
|
|
|
+ // 组合各种熵源
|
|
|
+ long combined = currentTime ^ (sequence << 16) ^ randomPart;
|
|
|
+ combined ^= System.nanoTime();
|
|
|
+ combined ^= (PROCESS_ID << 48);
|
|
|
+ combined ^= (threadId << 32);
|
|
|
+
|
|
|
+ // 确保为正数
|
|
|
+ combined = Math.abs(combined);
|
|
|
+ if (combined == Long.MIN_VALUE) {
|
|
|
+ combined = Long.MAX_VALUE; // 处理边界情况
|
|
|
+ }
|
|
|
+
|
|
|
+ return convertToBaseSafe(combined, length);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 基于哈希算法生成 - 修复线程ID获取问题
|
|
|
+ */
|
|
|
+ private String generateByHash(int length) {
|
|
|
+ try {
|
|
|
+ MessageDigest md = MessageDigest.getInstance("SHA-256");
|
|
|
+
|
|
|
+ // 使用多个熵源确保唯一性
|
|
|
+ StringBuilder entropy = new StringBuilder();
|
|
|
+ entropy.append(System.nanoTime())
|
|
|
+ .append(Thread.currentThread().getId()) // 修复这里
|
|
|
+ .append(System.identityHashCode(this))
|
|
|
+ .append(secureRandom.nextLong())
|
|
|
+ .append(counter.get())
|
|
|
+ .append(Runtime.getRuntime().freeMemory());
|
|
|
+
|
|
|
+ byte[] hash = md.digest(entropy.toString().getBytes());
|
|
|
+
|
|
|
+ // 使用BigInteger避免负数和溢出问题
|
|
|
+ BigInteger bigInt = new BigInteger(1, hash); // 正数模式
|
|
|
+ BigInteger modValue = BigInteger.valueOf(charsetSize).pow(length);
|
|
|
+
|
|
|
+ // 取模确保值在范围内
|
|
|
+ BigInteger result = bigInt.mod(modValue);
|
|
|
+
|
|
|
+ return convertBigIntegerToBase(result, length);
|
|
|
+
|
|
|
+ } catch (NoSuchAlgorithmException e) {
|
|
|
+ // 降级方案:使用增强随机数
|
|
|
+ return generateEnhancedRandomCode(length);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 增强随机数生成
|
|
|
+ */
|
|
|
+ private String generateEnhancedRandomCode(int length) {
|
|
|
+ char[] result = new char[length];
|
|
|
+
|
|
|
+ for (int i = 0; i < length; i++) {
|
|
|
+ // 使用安全的随机索引生成
|
|
|
+ int index = secureRandom.nextInt(charsetSize);
|
|
|
+ result[i] = charset[index];
|
|
|
+ }
|
|
|
+
|
|
|
+ return new String(result);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 安全的进制转换 - 完全修复版本
|
|
|
+ */
|
|
|
+ private String convertToBaseSafe(long number, int length) {
|
|
|
+ char[] result = new char[length];
|
|
|
+
|
|
|
+ // 确保number为正数
|
|
|
+ long temp = Math.abs(number);
|
|
|
+ if (temp == 0) {
|
|
|
+ temp = System.nanoTime() & 0x7FFFFFFF; // 使用纳秒时间作为备用值
|
|
|
+ }
|
|
|
+
|
|
|
+ // 基本转换
|
|
|
+ for (int i = length - 1; i >= 0; i--) {
|
|
|
+ long remainder = temp % charsetSize;
|
|
|
+ int index = (int) remainder;
|
|
|
+
|
|
|
+ // 确保索引在有效范围内
|
|
|
+ if (index < 0) {
|
|
|
+ index = (int) Math.abs(remainder);
|
|
|
+ }
|
|
|
+ index = index % charsetSize;
|
|
|
+
|
|
|
+ result[i] = charset[index];
|
|
|
+ temp = temp / charsetSize;
|
|
|
+ }
|
|
|
+
|
|
|
+ return new String(result);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * BigInteger版本的安全进制转换
|
|
|
+ */
|
|
|
+ private String convertBigIntegerToBase(BigInteger number, int length) {
|
|
|
+ char[] result = new char[length];
|
|
|
+ BigInteger base = BigInteger.valueOf(charsetSize);
|
|
|
+ BigInteger temp = number;
|
|
|
+
|
|
|
+ // 确保temp为正数
|
|
|
+ if (temp.signum() < 0) {
|
|
|
+ temp = temp.abs();
|
|
|
+ }
|
|
|
+
|
|
|
+ for (int i = length - 1; i >= 0; i--) {
|
|
|
+ BigInteger[] divResult = temp.divideAndRemainder(base);
|
|
|
+ int index = divResult[1].intValue();
|
|
|
+
|
|
|
+ // 确保索引为正数且在范围内
|
|
|
+ if (index < 0) {
|
|
|
+ index = -index;
|
|
|
+ }
|
|
|
+ index = index % charsetSize;
|
|
|
+
|
|
|
+ result[i] = charset[index];
|
|
|
+ temp = divResult[0];
|
|
|
+ }
|
|
|
+
|
|
|
+ return new String(result);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 混合并最终化代码
|
|
|
+ */
|
|
|
+ private String mixAndFinalize(String code1, String code2, int length) {
|
|
|
+ char[] result = new char[length];
|
|
|
+
|
|
|
+ for (int i = 0; i < length; i++) {
|
|
|
+ char c1 = code1.charAt(i % code1.length());
|
|
|
+ char c2 = code2.charAt((i + 1) % code2.length());
|
|
|
+
|
|
|
+ // 使用安全的混合方法
|
|
|
+ int mixed = safeMixChars(c1, c2, i);
|
|
|
+ result[i] = charset[mixed];
|
|
|
+ }
|
|
|
+
|
|
|
+ return new String(result);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 安全的字符混合
|
|
|
+ */
|
|
|
+ private int safeMixChars(char c1, char c2, int position) {
|
|
|
+ long l1 = (long) c1;
|
|
|
+ long l2 = (long) c2;
|
|
|
+
|
|
|
+ // 使用不同的混合策略,但都确保结果为非负数
|
|
|
+ long mixedLong;
|
|
|
+ switch (position % 4) {
|
|
|
+ case 0:
|
|
|
+ mixedLong = (l1 + l2 * 31L) % charsetSize;
|
|
|
+ break;
|
|
|
+ case 1:
|
|
|
+ mixedLong = (l1 ^ l2) % charsetSize;
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ mixedLong = (l1 * 17L + l2) % charsetSize;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ mixedLong = (l1 * 3L + l2 * 7L) % charsetSize;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 确保结果为正数
|
|
|
+ int mixed = (int) Math.abs(mixedLong) % charsetSize;
|
|
|
+ return mixed;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取进程ID
|
|
|
+ */
|
|
|
+ private static long getProcessId() {
|
|
|
+ try {
|
|
|
+ String processName = java.lang.management.ManagementFactory
|
|
|
+ .getRuntimeMXBean()
|
|
|
+ .getName();
|
|
|
+ String pidStr = processName.split("@")[0];
|
|
|
+ return Long.parseLong(pidStr) & 0xFFFF; // 限制范围
|
|
|
+ } catch (Exception e) {
|
|
|
+ // 返回备用值
|
|
|
+ return Thread.currentThread().getId() & 0xFFFF;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|