|
@@ -1,95 +1,91 @@
|
|
|
package com.ylx.massage.utils;
|
|
package com.ylx.massage.utils;
|
|
|
|
|
|
|
|
-import com.ylx.common.constant.MassageConstants;
|
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
-import org.springframework.data.redis.core.StringRedisTemplate;
|
|
|
|
|
-import org.springframework.data.redis.core.script.DefaultRedisScript;
|
|
|
|
|
import org.springframework.stereotype.Component;
|
|
import org.springframework.stereotype.Component;
|
|
|
|
|
|
|
|
-import java.time.*;
|
|
|
|
|
|
|
+import java.time.LocalDateTime;
|
|
|
import java.time.format.DateTimeFormatter;
|
|
import java.time.format.DateTimeFormatter;
|
|
|
-import java.util.Collections;
|
|
|
|
|
|
|
+import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
|
+import java.util.concurrent.atomic.AtomicLong;
|
|
|
|
|
|
|
|
@Component
|
|
@Component
|
|
|
@Slf4j
|
|
@Slf4j
|
|
|
public class OrderNumberGenerator {
|
|
public class OrderNumberGenerator {
|
|
|
|
|
|
|
|
- //订单单号前缀
|
|
|
|
|
|
|
+ // 订单单号前缀
|
|
|
public static final String KEY_PREFIX_ORDER = "YORDER";
|
|
public static final String KEY_PREFIX_ORDER = "YORDER";
|
|
|
-
|
|
|
|
|
public static final String KEY_PREFIX_RECHAR = "RECHAR";
|
|
public static final String KEY_PREFIX_RECHAR = "RECHAR";
|
|
|
-
|
|
|
|
|
public static final String KEY_PREFIX_CASH = "CASH";
|
|
public static final String KEY_PREFIX_CASH = "CASH";
|
|
|
-
|
|
|
|
|
public static final String KEY_PREFIX_REFUND = "REFUND";
|
|
public static final String KEY_PREFIX_REFUND = "REFUND";
|
|
|
-
|
|
|
|
|
public static final String KEY_PREFIX_PRODUCTORDER = "PRODUCT";
|
|
public static final String KEY_PREFIX_PRODUCTORDER = "PRODUCT";
|
|
|
|
|
|
|
|
- private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
|
|
|
|
|
|
|
+ private static final DateTimeFormatter DATE_FORMATTER2 = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
|
|
|
|
|
|
|
|
- private final StringRedisTemplate stringRedisTemplate;
|
|
|
|
|
|
|
+ private static final int SEQUENCE_MAX_VALUE = 99999;
|
|
|
|
|
|
|
|
- public OrderNumberGenerator(StringRedisTemplate stringRedisTemplate) {
|
|
|
|
|
- this.stringRedisTemplate = stringRedisTemplate;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ private static final int SEQUENCE_WARN_THRESHOLD = 90000;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 生成并返回当天的下一个自增单号。
|
|
|
|
|
- *
|
|
|
|
|
- * @param prefix 订单号前缀
|
|
|
|
|
- * @return String 当天的下一个单号(格式:yyyyMMdd0001)
|
|
|
|
|
|
|
+ * 机器ID(用于多实例部署时区分不同节点)
|
|
|
|
|
+ * 可通过配置中心或启动参数动态获取,这里使用固定值
|
|
|
*/
|
|
*/
|
|
|
- public String generateNextOrderNumber(String prefix) {
|
|
|
|
|
- LocalDate today = LocalDate.now();
|
|
|
|
|
- String currentDateStr = today.format(DATE_FORMATTER);
|
|
|
|
|
-
|
|
|
|
|
- String key = MassageConstants.NUMBER + prefix + currentDateStr;
|
|
|
|
|
- Long incrementResult = incrementByScript(key);
|
|
|
|
|
- int sequence = incrementResult.intValue();
|
|
|
|
|
- return prefix + currentDateStr + String.format("%05d", sequence);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ private static final String MACHINE_ID = "01";
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 每个前缀的序列号缓存(key 格式:prefix + yyyyMMddHHmmss)
|
|
|
|
|
+ */
|
|
|
|
|
+ private final ConcurrentHashMap<String, AtomicLong> sequenceCache = new ConcurrentHashMap<>();
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 使用Lua脚本原子性递增Redis键值
|
|
|
|
|
|
|
+ * 生成并返回当天的下一个自增单号。
|
|
|
|
|
+ * 不依赖 Redis,使用本地原子序列生成器。
|
|
|
*
|
|
*
|
|
|
- * @param key Redis键名
|
|
|
|
|
- * @return Long递增后的键值
|
|
|
|
|
|
|
+ * @param prefix 订单号前缀
|
|
|
|
|
+ * @return String 订单号(格式:prefix + yyyyMMddHHmmss + machineId + 5位序列号)
|
|
|
*/
|
|
*/
|
|
|
- public Long incrementByScript(String key) {
|
|
|
|
|
- log.info("key的值:{}", key);
|
|
|
|
|
- if (key == null || key.trim().isEmpty()) {
|
|
|
|
|
- throw new IllegalArgumentException("Key cannot be null or empty");
|
|
|
|
|
|
|
+ public String generateNextOrderNumber(String prefix) {
|
|
|
|
|
+ LocalDateTime now = LocalDateTime.now();
|
|
|
|
|
+ String timeStr = now.format(DATE_FORMATTER2);
|
|
|
|
|
+ String cacheKey = prefix + timeStr;
|
|
|
|
|
+
|
|
|
|
|
+ // 获取或创建当前秒的序列号
|
|
|
|
|
+ AtomicLong sequence = sequenceCache.computeIfAbsent(cacheKey, k -> {
|
|
|
|
|
+ log.debug("创建新的序列号缓存,key:{}", cacheKey);
|
|
|
|
|
+ return new AtomicLong(0);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 原子递增并获取序列号
|
|
|
|
|
+ long newSequence = sequence.incrementAndGet();
|
|
|
|
|
+
|
|
|
|
|
+ // 容量预警:序列号超过警戒阈值时记录警告日志
|
|
|
|
|
+ if (newSequence > SEQUENCE_WARN_THRESHOLD) {
|
|
|
|
|
+ log.warn("订单号序列号接近上限,当前值:{},前缀:{},时间:{}", newSequence, prefix, timeStr);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 假设 "script" 是一个从外部文件或安全的静态资源加载的Lua脚本
|
|
|
|
|
- String script = "return redis.call('INCR', KEYS[1])";
|
|
|
|
|
- DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- Long execute = stringRedisTemplate.execute(redisScript, Collections.singletonList(key));
|
|
|
|
|
- log.info("递增后的键值:{}", execute);
|
|
|
|
|
- stringRedisTemplate.expireAt(key, LocalDateTime.of(LocalDate.now(), LocalTime.MAX).atZone(ZoneId.systemDefault()).toInstant());
|
|
|
|
|
- return execute;
|
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- // 根据业务需求,这里可以选择抛出运行时异常或者返回特定的错误码
|
|
|
|
|
- throw new RuntimeException("Failed to execute Redis script", e);
|
|
|
|
|
|
|
+ // 序列号溢出检查
|
|
|
|
|
+ if (newSequence > SEQUENCE_MAX_VALUE) {
|
|
|
|
|
+ log.error("订单号序列号超过上限,当前值:{},前缀:{},时间:{}", newSequence, prefix, timeStr);
|
|
|
|
|
+ throw new IllegalStateException("当前秒内订单号序列号超出系统限制,请稍后重试");
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
|
|
+ // 格式:前缀 + 时间串 + 机器ID + 5位序列号
|
|
|
|
|
+ return prefix + timeStr + MACHINE_ID + String.format("%05d", newSequence);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
public static void main(String[] args) {
|
|
public static void main(String[] args) {
|
|
|
- DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
|
|
|
- LocalDateTime todayStart = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
|
|
|
|
|
- LocalDateTime todayEnd = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);
|
|
|
|
|
-
|
|
|
|
|
- Instant instant = todayStart.atZone(ZoneId.systemDefault()).toInstant();
|
|
|
|
|
-
|
|
|
|
|
- System.out.println("当前时间,当天的开始时间(日期+时分秒):" + todayStart.format(dtf));
|
|
|
|
|
- System.out.println("当前时间,当天的结束时间(日期+时分秒):" + todayEnd.format(dtf));
|
|
|
|
|
- System.out.println("当前的时间(只有日期)" + LocalDate.now());
|
|
|
|
|
- System.out.println(instant);
|
|
|
|
|
|
|
+ OrderNumberGenerator generator = new OrderNumberGenerator();
|
|
|
|
|
+
|
|
|
|
|
+ // 测试生成 10 个订单号
|
|
|
|
|
+ for (int i = 0; i < 10; i++) {
|
|
|
|
|
+ System.out.println(generator.generateNextOrderNumber(KEY_PREFIX_PRODUCTORDER));
|
|
|
|
|
+ if (i < 9) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 模拟不同时间的订单
|
|
|
|
|
+ Thread.sleep(10);
|
|
|
|
|
+ } catch (InterruptedException e) {
|
|
|
|
|
+ e.printStackTrace();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
}
|
|
}
|