fix(firstleg): reuse source manifest within work fact batches
This commit is contained in:
+7
-2
@@ -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);
|
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) {
|
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<AppointmentWorkSourceMapper.Source> rows) {
|
||||||
|
return rows.stream().filter(row->row.id()==id).findFirst().map(this::source)
|
||||||
.orElseThrow(()->new ResponseStatusException(HttpStatus.NOT_FOUND,"未找到有效已开船柜次"));
|
.orElseThrow(()->new ResponseStatusException(HttpStatus.NOT_FOUND,"未找到有效已开船柜次"));
|
||||||
}
|
}
|
||||||
private ContainerSource source(AppointmentWorkSourceMapper.Source row) {
|
private ContainerSource source(AppointmentWorkSourceMapper.Source row) {
|
||||||
@@ -57,12 +60,14 @@ public class AppointmentWorkSourceService {
|
|||||||
public List<Fact> facts(List<FactRequest> requests) {
|
public List<Fact> facts(List<FactRequest> requests) {
|
||||||
if(requests==null || requests.isEmpty() || requests.size()>200) throw bad("事实请求须包含1至200项");
|
if(requests==null || requests.isEmpty() || requests.size()>200) throw bad("事实请求须包含1至200项");
|
||||||
Set<String> keys=new HashSet<>(); Map<Long,ContainerSource> sources=new HashMap<>();
|
Set<String> keys=new HashSet<>(); Map<Long,ContainerSource> 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->{
|
return requests.stream().map(request->{
|
||||||
if(request==null || request.workItemKey()==null || request.workItemKey().isBlank() || !keys.add(request.workItemKey())
|
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())))
|
|| !("PICKUP".equals(request.type()) || "EMPTY_RETURN".equals(request.type()) || "DELIVERY".equals(request.type()) && "DIRECT".equals(request.flow())))
|
||||||
throw bad("事项身份或事实类型无效");
|
throw bad("事项身份或事实类型无效");
|
||||||
long id=id(request.containerBookingId());
|
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();
|
}).toList();
|
||||||
}
|
}
|
||||||
public String hash(Object value) {
|
public String hash(Object value) {
|
||||||
|
|||||||
+30
@@ -7,6 +7,8 @@ import org.junit.jupiter.api.Test;
|
|||||||
import org.springframework.web.server.ResponseStatusException;
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
import com.baiyue.firstleg.portschedule.dto.AppointmentWorkSourceDtos.*;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
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());
|
assertEquals(401,assertThrows(ResponseStatusException.class,()->guarded.discover(null,null,20)).getStatusCode().value());
|
||||||
verifyNoInteractions(mapper);
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user