|
@@ -32,6 +32,7 @@ import java.time.Instant;
|
|
|
import java.time.ZoneId;
|
|
import java.time.ZoneId;
|
|
|
import java.time.format.DateTimeFormatter;
|
|
import java.time.format.DateTimeFormatter;
|
|
|
import java.util.*;
|
|
import java.util.*;
|
|
|
|
|
+import java.util.concurrent.atomic.AtomicLong;
|
|
|
import java.util.function.Function;
|
|
import java.util.function.Function;
|
|
|
|
|
|
|
|
import static com.ruoyi.system.domain.flash.FlashDeliveryStatus.*;
|
|
import static com.ruoyi.system.domain.flash.FlashDeliveryStatus.*;
|
|
@@ -63,6 +64,8 @@ public class FlashDeliveryApplicationService {
|
|
|
private static final long SCHEDULE_SLOT_MILLIS = 30L * 60L * 1000L;
|
|
private static final long SCHEDULE_SLOT_MILLIS = 30L * 60L * 1000L;
|
|
|
private static final long MAX_SCHEDULE_DELAY_MILLIS = 3L * 24L * 60L * 60L * 1000L;
|
|
private static final long MAX_SCHEDULE_DELAY_MILLIS = 3L * 24L * 60L * 60L * 1000L;
|
|
|
private static final SecureRandom PIN_RANDOM = new SecureRandom();
|
|
private static final SecureRandom PIN_RANDOM = new SecureRandom();
|
|
|
|
|
+ /** 订单号同毫秒递增序列(01-99 循环):同一毫秒内的并发由原子递增区分,避免同号。 */
|
|
|
|
|
+ private static final AtomicLong ORDER_NO_SEQ = new AtomicLong();
|
|
|
private final FlashDeliveryOrderMapper orderMapper;
|
|
private final FlashDeliveryOrderMapper orderMapper;
|
|
|
private final FlashDeliveryPricingMapper pricingMapper;
|
|
private final FlashDeliveryPricingMapper pricingMapper;
|
|
|
private final FlashDeliveryOrderImageMapper imageMapper;
|
|
private final FlashDeliveryOrderImageMapper imageMapper;
|
|
@@ -172,7 +175,7 @@ public class FlashDeliveryApplicationService {
|
|
|
String payType = normalizePayType(request.getPayType());
|
|
String payType = normalizePayType(request.getPayType());
|
|
|
List<String> senderImages = validateProofUrls(request.getSenderImageUrls(), false);
|
|
List<String> senderImages = validateProofUrls(request.getSenderImageUrls(), false);
|
|
|
FlashDeliveryOrder order = new FlashDeliveryOrder();
|
|
FlashDeliveryOrder order = new FlashDeliveryOrder();
|
|
|
- order.setOrderNo("FD" + UUID.randomUUID().toString().replace("-", "").substring(0, 24).toUpperCase(Locale.ROOT));
|
|
|
|
|
|
|
+ order.setOrderNo(generateOrderNo());
|
|
|
order.setClientRequestId(requestId);
|
|
order.setClientRequestId(requestId);
|
|
|
order.setUserId(userId);
|
|
order.setUserId(userId);
|
|
|
order.setServiceType(request.getServiceType());
|
|
order.setServiceType(request.getServiceType());
|
|
@@ -220,10 +223,17 @@ public class FlashDeliveryApplicationService {
|
|
|
try {
|
|
try {
|
|
|
orderMapper.insert(order);
|
|
orderMapper.insert(order);
|
|
|
} catch (DataIntegrityViolationException duplicate) {
|
|
} catch (DataIntegrityViolationException duplicate) {
|
|
|
- // 并发重复请求可能同时通过前置查询,最终由数据库唯一键完成幂等收口。
|
|
|
|
|
- FlashDeliveryOrder concurrent = orderMapper.selectByUserRequestId(userId, requestId);
|
|
|
|
|
- if (concurrent != null) return participantDetail(concurrent, true, false);
|
|
|
|
|
- throw duplicate;
|
|
|
|
|
|
|
+ // order_no 撞号兜底(时钟回拨/多实例等理论情形):唯一索引 uk_flash_order_no 拒绝后换号重插一次。
|
|
|
|
|
+ if (isOrderNoConflict(duplicate)) {
|
|
|
|
|
+ order.setId(null);
|
|
|
|
|
+ order.setOrderNo(generateOrderNo());
|
|
|
|
|
+ orderMapper.insert(order);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 并发重复请求可能同时通过前置查询,最终由 userId+clientRequestId 唯一键完成幂等收口。
|
|
|
|
|
+ FlashDeliveryOrder concurrent = orderMapper.selectByUserRequestId(userId, requestId);
|
|
|
|
|
+ if (concurrent != null) return participantDetail(concurrent, true, false);
|
|
|
|
|
+ throw duplicate;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
saveImages(order.getId(), "SENDER", "USER", userId, senderImages, now);
|
|
saveImages(order.getId(), "SENDER", "USER", userId, senderImages, now);
|
|
|
writeLog(order.getId(), null, WAITING_ACCEPTANCE, "USER", userId, null, now);
|
|
writeLog(order.getId(), null, WAITING_ACCEPTANCE, "USER", userId, null, now);
|
|
@@ -233,6 +243,24 @@ public class FlashDeliveryApplicationService {
|
|
|
return participantDetail(order, true, false);
|
|
return participantDetail(order, true, false);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 闪送订单号:98 + 13 位毫秒时间戳 + 2 位同毫秒递增序列,共 17 位纯数字(2026-09-22 起)。
|
|
|
|
|
+ * 与外卖订单(99 + 毫秒时间戳)风格同构,便于电话报单与按时间排查;
|
|
|
|
|
+ * 同毫秒并发由 JVM 内原子递增序列区分,时钟回拨/多实例等理论撞号由
|
|
|
|
|
+ * order_no 唯一索引(uk_flash_order_no)兜底,插入冲突换号重试。
|
|
|
|
|
+ * 2026-09-22 之前的存量单为「FD + UUID 前 24 位大写」旧格式,保持不变。
|
|
|
|
|
+ */
|
|
|
|
|
+ static String generateOrderNo() {
|
|
|
|
|
+ long seq = ORDER_NO_SEQ.updateAndGet(current -> (current + 1) % 100);
|
|
|
|
|
+ return "98" + System.currentTimeMillis() + String.format(Locale.ROOT, "%02d", seq);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 判断唯一键冲突是否来自订单号(区别于 userId+clientRequestId 幂等键)。 */
|
|
|
|
|
+ private static boolean isOrderNoConflict(DataIntegrityViolationException exception) {
|
|
|
|
|
+ String message = exception.getMostSpecificCause().getMessage();
|
|
|
|
|
+ return message != null && message.contains("uk_flash_order_no");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/** 分页查询当前用户参与的订单卡片;role=sender 按寄件人、receiver 按收件人过滤。 */
|
|
/** 分页查询当前用户参与的订单卡片;role=sender 按寄件人、receiver 按收件人过滤。 */
|
|
|
public IPage<FlashDeliveryUserOrderListView> userOrders(Long userId, int pageNum, int pageSize, String role) {
|
|
public IPage<FlashDeliveryUserOrderListView> userOrders(Long userId, int pageNum, int pageSize, String role) {
|
|
|
String selectedRole = hasText(role) ? role.trim().toLowerCase(Locale.ROOT) : "sender";
|
|
String selectedRole = hasText(role) ? role.trim().toLowerCase(Locale.ROOT) : "sender";
|