PromotionCodeUtil.java 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. package com.sqx.modules.utils;
  2. /**
  3. * 推荐码生成工具类
  4. */
  5. public class PromotionCodeUtil {
  6. /** 自定义进制字符集(避免使用易混淆的字符) */
  7. private static final char[] r = new char[]{ 'M', 'J', 'U', 'D', 'Z', 'X', '9', 'C', '7', 'P',
  8. 'E', '8', '6', 'B', 'G', 'H', 'S', '2', '5', 'F',
  9. 'R', '4', 'Q', 'W', 'K', '3', 'V', 'Y', 'T', 'N'};
  10. /** 进制长度 */
  11. private static final int binLen = r.length;
  12. /** 推荐码长度 */
  13. private static final int codeLength = 6; // 8位长度
  14. /** 用于生成唯一ID的锁对象 */
  15. private static final Object lock = new Object();
  16. /** 最后生成的时间戳,用于防止短时间内生成重复码 */
  17. private static long lastTimestamp = -1L;
  18. /** 序列号,用于同一毫秒内生成多个码时保证唯一性 */
  19. private static long sequence = 0L;
  20. /** 序列号最大值 */
  21. private static final long MAX_SEQUENCE = 99999L;
  22. /** 节点ID(在集群环境中非常重要) */
  23. private static final int NODE_ID;
  24. // 静态初始化块,初始化节点ID
  25. static {
  26. int nodeId1;
  27. // 优先从环境变量获取节点ID
  28. String nodeIdStr = System.getenv("NODE_ID");
  29. if (nodeIdStr != null && !nodeIdStr.isEmpty()) {
  30. try {
  31. nodeId1 = Integer.parseInt(nodeIdStr) & 0x3F; // 限制在0-63之间
  32. } catch (NumberFormatException e) {
  33. nodeId1 = generateRandomNodeId();
  34. }
  35. } else {
  36. // 如果没有配置,生成随机节点ID
  37. nodeId1 = generateRandomNodeId();
  38. }
  39. NODE_ID = nodeId1;
  40. System.out.println("当前服务器节点ID: " + NODE_ID);
  41. }
  42. /**
  43. * 生成随机节点ID(当环境变量未配置时使用)
  44. */
  45. private static int generateRandomNodeId() {
  46. // 使用服务器信息和当前时间生成相对唯一的节点ID
  47. try {
  48. String hostName = java.net.InetAddress.getLocalHost().getHostName();
  49. int hashCode = hostName.hashCode();
  50. // 结合当前时间戳的低8位,增加随机性
  51. long timestamp = System.currentTimeMillis() & 0xFF;
  52. return (Math.abs(hashCode) ^ (int)timestamp) & 0x3F; // 限制在0-63之间
  53. } catch (Exception e) {
  54. // 如果获取主机名失败,使用纯随机数
  55. return (int)(Math.random() * 64);
  56. }
  57. }
  58. /**
  59. * 生成唯一推荐码(支持集群环境)
  60. * @return 唯一的推荐码字符串
  61. */
  62. public static String generatePromotionCode() {
  63. // 生成基础唯一数值
  64. long uniqueValue = generateUniqueValue();
  65. // 使用进制转换算法
  66. StringBuilder sb = new StringBuilder();
  67. while (uniqueValue > 0) {
  68. int ind = (int) (uniqueValue % binLen);
  69. sb.append(r[ind]);
  70. uniqueValue = uniqueValue / binLen;
  71. }
  72. // 确保推荐码长度
  73. String code = sb.toString();
  74. if (code.length() < codeLength) {
  75. // 使用随机字符填充前面
  76. StringBuilder padding = new StringBuilder();
  77. int paddingLength = codeLength - code.length();
  78. for (int i = 0; i < paddingLength; i++) {
  79. long randomSeed = System.nanoTime() + sequence;
  80. int randomIndex = (int) (Math.abs(randomSeed) % binLen);
  81. padding.append(r[randomIndex]);
  82. }
  83. code = padding.append(sb).toString();
  84. } else if (code.length() > codeLength) {
  85. // 如果超过长度,截取前codeLength位
  86. code = code.substring(0, codeLength);
  87. }
  88. return code;
  89. }
  90. /**
  91. * 生成唯一数值,结合时间戳、节点ID和序列号
  92. * @return 唯一数值
  93. */
  94. private static long generateUniqueValue() {
  95. synchronized (lock) {
  96. long timestamp = System.currentTimeMillis();
  97. // 如果是同一毫秒,序列号加1
  98. if (timestamp == lastTimestamp) {
  99. sequence++;
  100. // 防止序列号溢出
  101. if (sequence > MAX_SEQUENCE) {
  102. // 等待下一毫秒
  103. while (System.currentTimeMillis() <= timestamp) {
  104. Thread.yield();
  105. }
  106. timestamp = System.currentTimeMillis();
  107. sequence = 0L;
  108. }
  109. } else {
  110. // 不同毫秒,序列号重置
  111. sequence = 0L;
  112. }
  113. lastTimestamp = timestamp;
  114. // 将节点ID纳入唯一性计算
  115. // 格式:时间戳(41位) + 节点ID(6位) + 序列号(17位)
  116. long result = (timestamp << 23);
  117. result |= ((long)NODE_ID << 17);
  118. result |= (sequence & 0x1FFFF); // 17位序列号
  119. return Math.abs(result);
  120. }
  121. }
  122. }