fix: replay completed pallet operations

This commit is contained in:
2026-08-25 18:21:39 +08:00
parent ca69434698
commit b6226f1ca6
2 changed files with 41 additions and 0 deletions
@@ -71,6 +71,9 @@ public class PdaPalletOperationService {
public PalletOperationResponses.Operation assemble(PdaWarehouseIdentity identity,
PalletOperationRequests.Assemble request) {
PalletOperationResponses.Operation replay = replayIfPresent(identity, "PALLET_ASSEMBLE",
request.idempotencyKey(), request);
if (replay != null) return replay;
Map<String, PalletPhysicalGateway.PalletReference> pallets = resolvePallets(identity.overseasWarehouseId(),
request.target(), request.sources());
PalletPhysicalGateway.PalletReference target = pallets.get(code(request.target().palletCode()));
@@ -85,6 +88,9 @@ public class PdaPalletOperationService {
public PalletOperationResponses.Operation disassemble(PdaWarehouseIdentity identity,
PalletOperationRequests.Disassemble request) {
PalletOperationResponses.Operation replay = replayIfPresent(identity, "PALLET_DISASSEMBLE",
request.idempotencyKey(), request);
if (replay != null) return replay;
LinkedHashMap<String, PalletOperationRequests.PalletExpectation> expectations = new LinkedHashMap<>();
expectations.put(code(request.source().palletCode()), request.source());
request.targets().forEach(value -> expectations.put(code(value.target().palletCode()), value.target()));
@@ -109,6 +115,9 @@ public class PdaPalletOperationService {
public PalletOperationResponses.Operation swap(PdaWarehouseIdentity identity,
PalletOperationRequests.Swap request) {
PalletOperationResponses.Operation replay = replayIfPresent(identity, "PALLET_SWAP",
request.idempotencyKey(), request);
if (replay != null) return replay;
Map<String, PalletPhysicalGateway.PalletReference> pallets = resolvePallets(identity.overseasWarehouseId(),
request.source(), List.of(request.target()));
PalletPhysicalGateway.PalletReference source = pallets.get(code(request.source().palletCode()));
@@ -129,6 +138,9 @@ public class PdaPalletOperationService {
public PalletOperationResponses.Operation unbind(PdaWarehouseIdentity identity,
PalletOperationRequests.Unbind request) {
PalletOperationResponses.Operation replay = replayIfPresent(identity, "PALLET_UNBIND",
request.idempotencyKey(), request);
if (replay != null) return replay;
Map<String, PalletPhysicalGateway.PalletReference> pallets = resolvePallets(identity.overseasWarehouseId(),
List.of(request.source()));
PalletPhysicalGateway.PalletReference source = pallets.get(code(request.source().palletCode()));
@@ -413,6 +425,15 @@ public class PdaPalletOperationService {
return value;
}
private PalletOperationResponses.Operation replayIfPresent(PdaWarehouseIdentity identity, String type,
String key, Object request) {
String normalizedKey = required(key, "幂等键不能为空");
PdaPalletOperation existing = mapper.findOperation(identity.operatorKey(), normalizedKey);
if (existing == null) return null;
validateExisting(existing, type, hash(request));
return read(existing).asReplay();
}
private void saveTransitions(long operationId, List<PalletPhysicalGateway.Transition> transitions) {
newTransaction.executeWithoutResult(status -> {
PdaPalletOperation operation = mapper.selectById(operationId);
@@ -27,6 +27,7 @@ import org.springframework.transaction.support.DefaultTransactionStatus;
import org.springframework.web.server.ResponseStatusException;
import java.util.List;
import java.time.LocalDateTime;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.jupiter.api.Assertions.*;
@@ -119,6 +120,25 @@ class PdaPalletOperationServiceTest {
assertEquals("PDAOPS-LONG-LOCATION-CODE", ledger.getValue().getWarehouseLocationCode());
}
@Test void exactReplayReturnsStoredResultBeforeValidatingChangedPalletVersions() throws Exception {
var request = new PalletOperationRequests.Swap("idem-replay",
new PalletOperationRequests.PalletExpectation("SOURCE", 1),
new PalletOperationRequests.PalletExpectation("TARGET", 2), "swap");
PdaPalletOperation existing = new PdaPalletOperation(); existing.setId(500L);
existing.setOperationType("PALLET_SWAP"); existing.setStatus("COMPLETED");
existing.setRequestHash(ReflectionTestUtils.invokeMethod(service, "hash", request));
var stored = new com.baiyue.lastleg.palletops.dto.PalletOperationResponses.Operation(
500L, "PALLET_SWAP", "idem-replay", "COMPLETED", false, LocalDateTime.now(), List.of());
existing.setResultJson(new ObjectMapper().findAndRegisterModules().writeValueAsString(stored));
when(mapper.findOperation("7", "idem-replay")).thenReturn(existing);
var replay = service.swap(identity(), request);
assertTrue(replay.replayed());
assertEquals(500L, replay.operationId());
verifyNoInteractions(physical);
}
private static PdaWarehouseIdentity identity() { return new PdaWarehouseIdentity("7", "picker", 88L); }
private static PalletPhysicalGateway.PalletReference pallet(long id, String code, long warehouse, int version) {
return new PalletPhysicalGateway.PalletReference(id, code, warehouse, 1000L + id,