Test #1
@@ -48,9 +48,16 @@ public interface OrderDeclarationSyncMapper {
|
||||
SnapshotRow lockSnapshot(@Param("tenantId") long tenantId, @Param("orderId") long orderId);
|
||||
|
||||
@Select("SELECT tenant_id tenantId,order_id orderId,event_id eventId,order_version orderVersion,request_hash requestHash "
|
||||
+ "FROM fl_order_declaration_snapshot WHERE tenant_id=#{tenantId} AND event_id=#{eventId}")
|
||||
+ "FROM fl_order_declaration_event WHERE tenant_id=#{tenantId} AND event_id=#{eventId}")
|
||||
SnapshotRow byEvent(@Param("tenantId") long tenantId, @Param("eventId") String eventId);
|
||||
|
||||
@Insert("INSERT INTO fl_order_declaration_event "
|
||||
+ "(tenant_id,event_id,order_id,order_version,request_hash,consumed_at) VALUES "
|
||||
+ "(#{tenantId},#{eventId},#{orderId},#{orderVersion},#{requestHash},CURRENT_TIMESTAMP(3))")
|
||||
int insertConsumedEvent(@Param("tenantId") long tenantId, @Param("eventId") String eventId,
|
||||
@Param("orderId") long orderId, @Param("orderVersion") int orderVersion,
|
||||
@Param("requestHash") String requestHash);
|
||||
|
||||
@Insert("""
|
||||
INSERT INTO fl_order_declaration_snapshot
|
||||
(tenant_id,order_id,event_id,order_version,request_hash,customer_remark,expected_arrival_at,
|
||||
|
||||
+20
-3
@@ -12,6 +12,7 @@ import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
@@ -62,12 +63,16 @@ public class OrderDeclarationSyncService {
|
||||
return;
|
||||
}
|
||||
var current = mapper.lockSnapshot(event.tenantId(), event.orderId());
|
||||
if (current != null && event.version() < current.orderVersion()) return;
|
||||
if (current != null && event.version() < current.orderVersion()) {
|
||||
recordEvent(event, hash);
|
||||
return;
|
||||
}
|
||||
if (current != null && event.version() == current.orderVersion()) {
|
||||
throw conflict("同一订单版本已存在不同申报事件");
|
||||
}
|
||||
String cargoesJson = write(event.cargoes());
|
||||
String changesJson = write(event.changes());
|
||||
recordEvent(event, hash);
|
||||
int saved = current == null
|
||||
? mapper.insertSnapshot(event.tenantId(), event.orderId(), event.eventId(), event.version(), hash,
|
||||
event.customerRemark(), event.expectedArrivalAt(), normalizeChannel(event.channelCode()),
|
||||
@@ -83,6 +88,15 @@ public class OrderDeclarationSyncService {
|
||||
}
|
||||
}
|
||||
|
||||
private void recordEvent(UpdatedEvent event, String hash) {
|
||||
try {
|
||||
if (mapper.insertConsumedEvent(event.tenantId(), event.eventId(), event.orderId(),
|
||||
event.version(), hash) != 1) throw conflict("订单申报事件幂等记录未保存");
|
||||
} catch (DuplicateKeyException duplicate) {
|
||||
throw conflict("eventId已用于其他申报事件");
|
||||
}
|
||||
}
|
||||
|
||||
private static void requireValidationRequest(ChannelChangeValidationRequest request) {
|
||||
if (request == null || request.tenantId() <= 0 || request.orderId() <= 0 || request.orderVersion() < 0
|
||||
|| !StringUtils.hasText(request.channelCode()) || request.channelCode().length() > 64) {
|
||||
@@ -95,8 +109,11 @@ public class OrderDeclarationSyncService {
|
||||
|| event.eventId().length() > 128 || event.tenantId() <= 0 || event.orderId() <= 0
|
||||
|| event.version() < 0 || event.operatorId() <= 0 || event.occurredAt() == null
|
||||
|| !StringUtils.hasText(event.orderNo()) || !StringUtils.hasText(event.inboundCode())
|
||||
|| event.cargoes() == null || event.cargoes().isEmpty()
|
||||
|| !StringUtils.hasText(event.channelCode()) || !StringUtils.hasText(event.channelName())) {
|
||||
|| event.cargoes() == null || event.cargoes().isEmpty() || event.cargoes().size() > 500
|
||||
|| event.changes() == null || !StringUtils.hasText(event.channelCode()) || event.channelCode().length() > 64
|
||||
|| !StringUtils.hasText(event.channelName()) || event.channelName().length() > 128
|
||||
|| event.inboundCode().length() > 40
|
||||
|| event.customerRemark() != null && event.customerRemark().length() > 1000) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "订单申报更新事件不完整");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -839,6 +839,17 @@ CREATE TABLE IF NOT EXISTS fl_order_declaration_snapshot (
|
||||
KEY idx_order_declaration_version (tenant_id, order_id, order_version)
|
||||
) ENGINE=InnoDB COMMENT='Order客户申报版本快照;与箱码、实测、托盘、PDA和计费事实隔离';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_order_declaration_event (
|
||||
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和隔离键',
|
||||
event_id VARCHAR(128) NOT NULL COMMENT '永久保留的Order Outbox事件ID',
|
||||
order_id BIGINT UNSIGNED NOT NULL COMMENT 'Order订单ID',
|
||||
order_version INT NOT NULL COMMENT '事件携带的Order聚合版本',
|
||||
request_hash CHAR(64) NOT NULL COMMENT '完整事件SHA-256,用于异参重放拒绝',
|
||||
consumed_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (tenant_id, event_id),
|
||||
KEY idx_order_declaration_event_order (tenant_id, order_id, order_version)
|
||||
) ENGINE=InnoDB COMMENT='Order客户申报事件永久幂等账本';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_customer_dimension_config (
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '配置ID',
|
||||
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
|
||||
|
||||
+3
@@ -69,6 +69,7 @@ class OrderDeclarationSyncServiceTest {
|
||||
when(mapper.lockInbound(10001L, 77L)).thenReturn(row("WAIT_MEASURE", null, null, null, null));
|
||||
when(mapper.insertSnapshot(anyLong(), anyLong(), anyString(), anyInt(), anyString(), any(), any(),
|
||||
anyString(), anyString(), anyString(), anyString(), any())).thenReturn(1);
|
||||
when(mapper.insertConsumedEvent(anyLong(), anyString(), anyLong(), anyInt(), anyString())).thenReturn(1);
|
||||
when(mapper.updateAllowedSnapshot(10001L, 88L, 77L, "FL-77", "RK-77", "客户新备注",
|
||||
event.expectedArrivalAt(), "MATSON_NEW", "新渠道", 9L)).thenReturn(1);
|
||||
|
||||
@@ -84,6 +85,7 @@ class OrderDeclarationSyncServiceTest {
|
||||
when(mapper.lockInbound(10001L, 77L)).thenReturn(row("WAIT_MEASURE", null, null, null, null));
|
||||
when(mapper.lockSnapshot(10001L, 77L)).thenReturn(
|
||||
new OrderDeclarationSyncMapper.SnapshotRow(10001L, 77L, "event-5", 5, "hash"));
|
||||
when(mapper.insertConsumedEvent(anyLong(), anyString(), anyLong(), anyInt(), anyString())).thenReturn(1);
|
||||
|
||||
service.consume(event);
|
||||
|
||||
@@ -91,6 +93,7 @@ class OrderDeclarationSyncServiceTest {
|
||||
any(), anyString(), anyString(), anyString(), anyString(), any());
|
||||
verify(mapper, never()).updateAllowedSnapshot(anyLong(), anyLong(), anyLong(), anyString(), anyString(),
|
||||
any(), any(), anyString(), anyString(), anyLong());
|
||||
verify(mapper).insertConsumedEvent(anyLong(), anyString(), anyLong(), anyInt(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user