|
|
@@ -49,6 +49,7 @@ import java.util.Calendar;
|
|
|
|
|
|
import static com.ruoyi.system.domain.flash.FlashDeliveryStatus.ACCEPTED;
|
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
|
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
|
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
|
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
|
@@ -1907,6 +1908,73 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
return input;
|
|
|
}
|
|
|
|
|
|
+ @Test
|
|
|
+ void generateOrderNoUsesTimestampAndSequenceFormat() {
|
|
|
+ String first = FlashDeliveryApplicationService.generateOrderNo();
|
|
|
+ String second = FlashDeliveryApplicationService.generateOrderNo();
|
|
|
+
|
|
|
+ assertTrue(isFlashOrderNo(first), "应为 98 前缀 17 位纯数字,实际: " + first);
|
|
|
+ assertTrue(isFlashOrderNo(second), "应为 98 前缀 17 位纯数字,实际: " + second);
|
|
|
+ assertNotEquals(first, second, "同毫秒连续生成应由序列位区分,不得同号");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void createRegeneratesOrderNoWhenUniqueKeyConflicts() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ when(fixture.pricingMapper.selectAtTime(anyString(), anyInt())).thenReturn(List.of(pricing()));
|
|
|
+ when(fixture.routeService.calculate(any(), any(), anyInt())).thenReturn(new RouteDistance(3200, 600, "ROUTE"));
|
|
|
+ when(fixture.userMapper.selectOrdinaryUserIdsByNormalizedPhone(anyString())).thenReturn(List.of());
|
|
|
+ org.springframework.dao.DataIntegrityViolationException orderNoConflict =
|
|
|
+ new org.springframework.dao.DataIntegrityViolationException("insert failed",
|
|
|
+ new java.sql.SQLException(
|
|
|
+ "Duplicate entry '98001' for key 'flash_delivery_order.uk_flash_order_no'"));
|
|
|
+ // 捕获器拿到的是同一对象引用,重试改单号后两次取值相同;改为每次调用时记录当时单号
|
|
|
+ java.util.List<String> attempted = new java.util.ArrayList<>();
|
|
|
+ doAnswer(invocation -> {
|
|
|
+ FlashDeliveryOrder order = invocation.getArgument(0);
|
|
|
+ attempted.add(order.getOrderNo());
|
|
|
+ if (attempted.size() == 1) throw orderNoConflict;
|
|
|
+ order.setId(99L);
|
|
|
+ return 1;
|
|
|
+ }).when(fixture.orderMapper).insert(any(FlashDeliveryOrder.class));
|
|
|
+
|
|
|
+ fixture.service.create(7L, createRequest());
|
|
|
+
|
|
|
+ verify(fixture.orderMapper, times(2)).insert(any(FlashDeliveryOrder.class));
|
|
|
+ assertEquals(2, attempted.size());
|
|
|
+ assertNotEquals(attempted.get(0), attempted.get(1), "撞号重试后应换新单号");
|
|
|
+ assertTrue(isFlashOrderNo(attempted.get(1)), "重试后仍应为新格式单号");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void createKeepsIdempotencyFallbackForNonOrderNoConflicts() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ when(fixture.pricingMapper.selectAtTime(anyString(), anyInt())).thenReturn(List.of(pricing()));
|
|
|
+ when(fixture.routeService.calculate(any(), any(), anyInt())).thenReturn(new RouteDistance(3200, 600, "ROUTE"));
|
|
|
+ when(fixture.userMapper.selectOrdinaryUserIdsByNormalizedPhone(anyString())).thenReturn(List.of());
|
|
|
+ org.springframework.dao.DataIntegrityViolationException idempotentConflict =
|
|
|
+ new org.springframework.dao.DataIntegrityViolationException("insert failed",
|
|
|
+ new java.sql.SQLException(
|
|
|
+ "Duplicate entry '7-req-1' for key 'flash_delivery_order.uk_user_request'"));
|
|
|
+ doThrow(idempotentConflict).when(fixture.orderMapper).insert(any(FlashDeliveryOrder.class));
|
|
|
+ // 前置幂等查询第一次放行(null),插入冲突后 catch 内的复查返回并发已建订单
|
|
|
+ FlashDeliveryOrder concurrent = new FlashDeliveryOrder();
|
|
|
+ concurrent.setId(55L);
|
|
|
+ concurrent.setOrderNo("981789000000011");
|
|
|
+ when(fixture.orderMapper.selectByUserRequestId(eq(7L), anyString())).thenReturn(null, concurrent);
|
|
|
+
|
|
|
+ var detail = fixture.service.create(7L, createRequest());
|
|
|
+
|
|
|
+ assertEquals(55L, detail.getId());
|
|
|
+ verify(fixture.orderMapper, times(1)).insert(any(FlashDeliveryOrder.class));
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 闪送新格式单号:98 开头、17 位纯数字。 */
|
|
|
+ private static boolean isFlashOrderNo(String orderNo) {
|
|
|
+ return orderNo != null && orderNo.startsWith("98") && orderNo.length() == 17
|
|
|
+ && orderNo.chars().allMatch(Character::isDigit);
|
|
|
+ }
|
|
|
+
|
|
|
private static class Fixture {
|
|
|
final FlashDeliveryOrderMapper orderMapper = mock(FlashDeliveryOrderMapper.class);
|
|
|
final FlashDeliveryPricingMapper pricingMapper = mock(FlashDeliveryPricingMapper.class);
|