OrderNumberGenerator.java 3.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. package com.ylx.massage.utils;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.springframework.stereotype.Component;
  4. import java.time.LocalDateTime;
  5. import java.time.format.DateTimeFormatter;
  6. import java.util.concurrent.ConcurrentHashMap;
  7. import java.util.concurrent.atomic.AtomicLong;
  8. @Component
  9. @Slf4j
  10. public class OrderNumberGenerator {
  11. // 订单单号前缀
  12. public static final String KEY_PREFIX_ORDER = "YORDER";
  13. public static final String KEY_PREFIX_RECHAR = "RECHAR";
  14. public static final String KEY_PREFIX_CASH = "CASH";
  15. public static final String KEY_PREFIX_REFUND = "REFUND";
  16. public static final String KEY_PREFIX_PRODUCTORDER = "PRODUCT";
  17. private static final DateTimeFormatter DATE_FORMATTER2 = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
  18. private static final int SEQUENCE_MAX_VALUE = 99999;
  19. private static final int SEQUENCE_WARN_THRESHOLD = 90000;
  20. /**
  21. * 机器ID(用于多实例部署时区分不同节点)
  22. * 可通过配置中心或启动参数动态获取,这里使用固定值
  23. */
  24. private static final String MACHINE_ID = "01";
  25. /**
  26. * 每个前缀的序列号缓存(key 格式:prefix + yyyyMMddHHmmss)
  27. */
  28. private final ConcurrentHashMap<String, AtomicLong> sequenceCache = new ConcurrentHashMap<>();
  29. /**
  30. * 生成并返回当天的下一个自增单号。
  31. * 不依赖 Redis,使用本地原子序列生成器。
  32. *
  33. * @param prefix 订单号前缀
  34. * @return String 订单号(格式:prefix + yyyyMMddHHmmss + machineId + 5位序列号)
  35. */
  36. public String generateNextOrderNumber(String prefix) {
  37. LocalDateTime now = LocalDateTime.now();
  38. String timeStr = now.format(DATE_FORMATTER2);
  39. String cacheKey = prefix + timeStr;
  40. // 获取或创建当前秒的序列号
  41. AtomicLong sequence = sequenceCache.computeIfAbsent(cacheKey, k -> {
  42. log.debug("创建新的序列号缓存,key:{}", cacheKey);
  43. return new AtomicLong(0);
  44. });
  45. // 原子递增并获取序列号
  46. long newSequence = sequence.incrementAndGet();
  47. // 容量预警:序列号超过警戒阈值时记录警告日志
  48. if (newSequence > SEQUENCE_WARN_THRESHOLD) {
  49. log.warn("订单号序列号接近上限,当前值:{},前缀:{},时间:{}", newSequence, prefix, timeStr);
  50. }
  51. // 序列号溢出检查
  52. if (newSequence > SEQUENCE_MAX_VALUE) {
  53. log.error("订单号序列号超过上限,当前值:{},前缀:{},时间:{}", newSequence, prefix, timeStr);
  54. throw new IllegalStateException("当前秒内订单号序列号超出系统限制,请稍后重试");
  55. }
  56. // 格式:前缀 + 时间串 + 机器ID + 5位序列号
  57. return prefix + timeStr + MACHINE_ID + String.format("%05d", newSequence);
  58. }
  59. public static void main(String[] args) {
  60. OrderNumberGenerator generator = new OrderNumberGenerator();
  61. // 测试生成 10 个订单号
  62. for (int i = 0; i < 10; i++) {
  63. System.out.println(generator.generateNextOrderNumber(KEY_PREFIX_PRODUCTORDER));
  64. if (i < 9) {
  65. try {
  66. // 模拟不同时间的订单
  67. Thread.sleep(10);
  68. } catch (InterruptedException e) {
  69. e.printStackTrace();
  70. }
  71. }
  72. }
  73. }
  74. }