From 23ce486539b268ebab6f172fa822b843c568e246 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Thu, 15 Oct 2026 17:52:56 +0800 Subject: [PATCH] fix(firstleg): reuse source manifest within work fact batches --- .../service/AppointmentWorkSourceService.java | 9 ++++-- .../AppointmentWorkSourceServiceTest.java | 30 +++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/firstleg/src/main/java/com/baiyue/firstleg/portschedule/service/AppointmentWorkSourceService.java b/firstleg/src/main/java/com/baiyue/firstleg/portschedule/service/AppointmentWorkSourceService.java index 155251389..8848f243c 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/portschedule/service/AppointmentWorkSourceService.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/portschedule/service/AppointmentWorkSourceService.java @@ -40,7 +40,10 @@ public class AppointmentWorkSourceService { return new SourcePage(selected.stream().map(this::source).toList(),complete?null:scope+":"+selected.get(selected.size()-1).id(),complete,scope); } public ContainerSource container(long id) { - return mapper.sources().stream().filter(row->row.id()==id).findFirst().map(this::source) + return container(id,mapper.sources()); + } + private ContainerSource container(long id,List rows) { + return rows.stream().filter(row->row.id()==id).findFirst().map(this::source) .orElseThrow(()->new ResponseStatusException(HttpStatus.NOT_FOUND,"未找到有效已开船柜次")); } private ContainerSource source(AppointmentWorkSourceMapper.Source row) { @@ -57,12 +60,14 @@ public class AppointmentWorkSourceService { public List facts(List requests) { if(requests==null || requests.isEmpty() || requests.size()>200) throw bad("事实请求须包含1至200项"); Set keys=new HashSet<>(); Map sources=new HashMap<>(); + // A fact batch shares one authoritative manifest, instead of scanning every sailed container per item. + var rows=mapper.sources(); return requests.stream().map(request->{ if(request==null || request.workItemKey()==null || request.workItemKey().isBlank() || !keys.add(request.workItemKey()) || !("PICKUP".equals(request.type()) || "EMPTY_RETURN".equals(request.type()) || "DELIVERY".equals(request.type()) && "DIRECT".equals(request.flow()))) throw bad("事项身份或事实类型无效"); long id=id(request.containerBookingId()); - return original.read(request,sources.computeIfAbsent(id,this::container)); + return original.read(request,sources.computeIfAbsent(id,key->container(key,rows))); }).toList(); } public String hash(Object value) { diff --git a/firstleg/src/test/java/com/baiyue/firstleg/portschedule/service/AppointmentWorkSourceServiceTest.java b/firstleg/src/test/java/com/baiyue/firstleg/portschedule/service/AppointmentWorkSourceServiceTest.java index c10d85217..6ddecbf2b 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/portschedule/service/AppointmentWorkSourceServiceTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/portschedule/service/AppointmentWorkSourceServiceTest.java @@ -7,6 +7,8 @@ import org.junit.jupiter.api.Test; import org.springframework.web.server.ResponseStatusException; import java.time.LocalDateTime; import java.util.List; +import java.util.stream.IntStream; +import com.baiyue.firstleg.portschedule.dto.AppointmentWorkSourceDtos.*; import static org.mockito.Mockito.*; import static org.junit.jupiter.api.Assertions.*; @@ -42,4 +44,32 @@ class AppointmentWorkSourceServiceTest { assertEquals(401,assertThrows(ResponseStatusException.class,()->guarded.discover(null,null,20)).getStatusCode().value()); verifyNoInteractions(mapper); } + @Test void factBatchReadsManifestOnceAndPreservesEachContainerIdentity() { + when(mapper.sources()).thenReturn(IntStream.rangeClosed(1,100).mapToObj(id->source(id,1)).toList()); + when(mapper.cargo(anyLong())).thenReturn(List.of()); + var fact=mock(Fact.class); + when(facts.read(any(),any())).thenAnswer(invocation->{ + FactRequest request=invocation.getArgument(0); ContainerSource source=invocation.getArgument(1); + assertEquals(request.containerBookingId(),source.containerBookingId()); + return fact; + }); + var requests=IntStream.rangeClosed(1,100).boxed().flatMap(id->List.of( + request("PICKUP-"+id,"PICKUP",id),request("RETURN-"+id,"EMPTY_RETURN",id)).stream()).toList(); + assertEquals(200,service.facts(requests).size()); + verify(mapper,times(1)).sources(); + for(long id=1;id<=100;id++) verify(mapper,times(1)).cargo(id); + verify(facts,times(200)).read(any(),any()); + } + @Test void nextFactBatchReadsFreshManifestAndRejectsRemovedContainer() { + when(mapper.sources()).thenReturn(List.of(source(1,1)),List.of()); + when(mapper.cargo(1)).thenReturn(List.of()); + var request=request("PICKUP-1","PICKUP",1); + service.facts(List.of(request)); + assertEquals(404,assertThrows(ResponseStatusException.class,()->service.facts(List.of(request))).getStatusCode().value()); + verify(mapper,times(2)).sources(); + verify(facts,times(1)).read(any(),any()); + } + private FactRequest request(String key,String type,long id) { + return new FactRequest(key,type,"TRANSIT",Long.toString(id),null,null,null,List.of(),List.of(),List.of()); + } }