From be79b90a4a16d04c34aa1877c98fd346d6d3a2f3 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Wed, 21 Oct 2026 13:45:21 +0800 Subject: [PATCH] fix(fleet): hash pooled dispatch source keys without object cast --- .../fleet/trip/FleetBookingService.java | 3 +- .../trip/FleetBookingRequestHashTest.java | 30 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 lastleg-service/src/test/java/com/baiyue/lastleg/fleet/trip/FleetBookingRequestHashTest.java diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/fleet/trip/FleetBookingService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/fleet/trip/FleetBookingService.java index 6d84bf475..82e53d986 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/fleet/trip/FleetBookingService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/fleet/trip/FleetBookingService.java @@ -303,7 +303,8 @@ public class FleetBookingService { private static void requireTrip(FleetTripEntity trip,long version){if("PICKUP".equals(trip.getTripPurpose()))throw conflict("取货车次不能承接出库拼车预订");if(!"PLANNED".equals(trip.getStatus()))throw conflict("仅计划中的车次允许管理外接预订");if(trip.getRecordVersion()!=version)throw conflict("车次已被修改,请刷新");} private static void requireVersion(FleetBookingEntity row,long version){if(row.getRecordVersion()!=version)throw conflict("外接预订已被修改,请刷新");} private String write(Object value){try{return json.writeValueAsString(value);}catch(Exception error){throw new IllegalStateException(error);}} - private String hash(Object value){try{ObjectNode node=json.valueToTree(value);node.remove("idempotencyKey");node.remove("tripRecordVersion");return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(write(node).getBytes(StandardCharsets.UTF_8)));}catch(Exception error){throw new IllegalStateException(error);}} + /** 请求对象排除重放元数据;排车来源键的数组必须保留全部元素及顺序。 */ + private String hash(Object value){try{com.fasterxml.jackson.databind.JsonNode node=json.valueToTree(value);if(node instanceof ObjectNode object){object.remove("idempotencyKey");object.remove("tripRecordVersion");}return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(write(node).getBytes(StandardCharsets.UTF_8)));}catch(Exception error){throw new IllegalStateException(error);}} private static LocalDateTime now(){return LocalDateTime.now(ZoneOffset.UTC);} private static String trim(String value){return value==null||value.isBlank()?null:value.trim();} private static String reason(String value,String fallback){return trim(value)==null?fallback:trim(value);} diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/fleet/trip/FleetBookingRequestHashTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/fleet/trip/FleetBookingRequestHashTest.java new file mode 100644 index 000000000..6231251a1 --- /dev/null +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/fleet/trip/FleetBookingRequestHashTest.java @@ -0,0 +1,30 @@ +package com.baiyue.lastleg.fleet.trip; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; +import java.util.List; +import static org.junit.jupiter.api.Assertions.*; + +class FleetBookingRequestHashTest { + final FleetBookingService service = new FleetBookingService(null, null, null, null, null, null, + new ObjectMapper().findAndRegisterModules(), null, null, null); + + private String hash(Object value) { return ReflectionTestUtils.invokeMethod(service, "hash", value); } + + @Test void incomingDispatchSourceKeyAcceptsArrayAndPreservesBookingAndCommandIdentity() { + var source = List.of("FB202610210004", "pool-arrange-command"); + var digest = assertDoesNotThrow(() -> hash(source)); + assertEquals(64, digest.length()); + assertEquals(digest, hash(source)); + assertNotEquals(digest, hash(List.of("FB202610210005", "pool-arrange-command"))); + assertNotEquals(digest, hash(List.of("FB202610210004", "another-command"))); + assertNotEquals(digest, hash(List.of("pool-arrange-command", "FB202610210004"))); + } + + @Test void bookingCommandsStillIgnoreReplayKeyAndTripVersionButRetainBookingVersion() { + var digest = hash(new FleetBookingModels.Command(8L, 4L, "command-original", "TEST")); + assertEquals(digest, hash(new FleetBookingModels.Command(9L, 4L, "command-replay", "TEST"))); + assertNotEquals(digest, hash(new FleetBookingModels.Command(8L, 5L, "command-original", "TEST"))); + } +}