chore(lastleg): instrument appointment latency from restored 662dead baseline
This commit is contained in:
-16
@@ -1,16 +0,0 @@
|
||||
package com.baiyue.lastleg.appointmentplanning;
|
||||
|
||||
import org.springframework.stereotype.Repository;
|
||||
import java.util.List;
|
||||
|
||||
/** 单次生成只批量读取核实与分配原料,业务解析仍按实际访问顺序。 */
|
||||
@Repository
|
||||
public class AppointmentCargoReadRepository {
|
||||
private final AppointmentCargoVerificationMapper mapper;
|
||||
/** 复用本域既有批量查询。 */
|
||||
public AppointmentCargoReadRepository(AppointmentCargoVerificationMapper mapper) { this.mapper=mapper; }
|
||||
/** 取得每客户来源的最新核实记录,暂不解析货件 JSON。 */
|
||||
public List<AppointmentCargoVerification> latest(List<AppointmentCoverageService.SourceRef> refs) { return refs.isEmpty()?List.of():mapper.latestBatch(refs); }
|
||||
/** 分配数量仅取活动明细,未知数量保持单独计数。 */
|
||||
public List<AppointmentCargoVerificationMapper.SourceAllocation> allocations(List<AppointmentCoverageService.SourceRef> refs) { return refs.isEmpty()?List.of():mapper.allocatedBatch(refs); }
|
||||
}
|
||||
-18
@@ -21,7 +21,6 @@ public class AppointmentCargoVerificationService {
|
||||
private final AppointmentSourceService sources;
|
||||
private final AppointmentCargoVerificationMapper mapper;
|
||||
private final ObjectMapper json;
|
||||
@jakarta.annotation.Resource private AppointmentCargoReadRepository readRepository;
|
||||
|
||||
public AppointmentCargoVerificationService(AppointmentSourceService sources, AppointmentCargoVerificationMapper mapper,
|
||||
ObjectMapper json) {
|
||||
@@ -51,23 +50,6 @@ public class AppointmentCargoVerificationService {
|
||||
public record Inspection(AppointmentCoverageService.SourceRef reference,Context context,ResponseStatusException error) {
|
||||
public Context requireContext() { if(error!=null) throw error; return context; }
|
||||
}
|
||||
/** 只预取本次已授权来源的原料;核实 JSON 与数量判断按原 anyMatch 实际访问顺序执行。 */
|
||||
public java.util.function.Function<AppointmentSourceService.Source,Context> prepareInspection(List<AppointmentSourceService.Source> resolved) {
|
||||
var refs=resolved.stream().map(s->new AppointmentCoverageService.SourceRef(s.customerId(),s.sourceKey(),s.destinationKey())).distinct().toList();
|
||||
var latest=new HashMap<List<String>,AppointmentCargoVerification>();
|
||||
var allocations=new HashMap<List<String>,AppointmentCargoVerificationMapper.Allocation>();
|
||||
for(int start=0;start<refs.size();start+=100) {
|
||||
var batch=refs.subList(start,Math.min(start+100,refs.size()));
|
||||
readRepository.latest(batch).forEach(r->latest.put(List.of(r.getCustomerId(),r.getSourceKey()),r));
|
||||
readRepository.allocations(batch).forEach(r->allocations.put(List.of(r.customerId(),r.sourceKey()),new AppointmentCargoVerificationMapper.Allocation(r.packages(),r.unknownItems())));
|
||||
}
|
||||
var allowed=new HashSet<>(resolved);
|
||||
return source->{
|
||||
if(!allowed.contains(source)) throw new IllegalArgumentException("货物核实来源不属于本次读取范围");
|
||||
var key=List.of(source.customerId(),source.sourceKey());
|
||||
return inspect(source,latest.get(key),allocations.getOrDefault(key,new AppointmentCargoVerificationMapper.Allocation(0,0)));
|
||||
};
|
||||
}
|
||||
/** Shares source and warehouse reads within this call only; never caches mutable business facts across requests. */
|
||||
public List<Inspection> inspectBatch(List<AppointmentCoverageService.SourceRef> refs) {
|
||||
var result=new ArrayList<Inspection>();
|
||||
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
package com.baiyue.lastleg.appointmentplanning;
|
||||
|
||||
import com.baiyue.lastleg.entity.LastlegOrder;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
|
||||
import com.baiyue.lastleg.shipping.entity.ContainerShippingGate;
|
||||
import com.baiyue.lastleg.shipping.mapper.ContainerShippingGateMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import java.util.List;
|
||||
|
||||
/** 同次需求生成的来源原料读取,不保存跨调用状态。 */
|
||||
@Repository
|
||||
public class AppointmentSourceReadRepository {
|
||||
private final LastlegOrderMapper orders;
|
||||
private final ContainerShippingGateMapper shipping;
|
||||
private final AppointmentCoverageMapper destinations;
|
||||
/** 仅组合本域既有批量持久查询。 */
|
||||
public AppointmentSourceReadRepository(LastlegOrderMapper orders,ContainerShippingGateMapper shipping,AppointmentCoverageMapper destinations) {
|
||||
this.orders=orders;this.shipping=shipping;this.destinations=destinations;
|
||||
}
|
||||
/** 读取所选订单,客户归属继续由来源服务逐需求核验。 */
|
||||
public List<LastlegOrder> orders(List<Long> ids) { return ids.isEmpty()?List.of():orders.selectBatchIds(ids); }
|
||||
/** 读取实际船节点门禁,不从计划时间推断。 */
|
||||
public List<ContainerShippingGate> gates(List<String> ids) { return ids.isEmpty()?List.of():shipping.selectBatchIds(ids); }
|
||||
/** 转运目的地仍取原业务权威关系。 */
|
||||
public List<AppointmentCoverageMapper.Source> destinations(List<AppointmentCoverageMapper.OrderRef> refs) { return refs.isEmpty()?List.of():destinations.sources(refs); }
|
||||
}
|
||||
-85
@@ -26,7 +26,6 @@ public class AppointmentSourceService {
|
||||
private final AppointmentWarehouseClient warehouses;
|
||||
private final String token;
|
||||
@jakarta.annotation.Resource private AppointmentCoverageMapper destinationFacts;
|
||||
@jakarta.annotation.Resource private AppointmentSourceReadRepository readRepository;
|
||||
public AppointmentSourceService(LastlegOrderMapper orders, ContainerShippingGateMapper shipping,
|
||||
AppointmentExternalSourceGateway external, AppointmentWarehouseClient warehouses,
|
||||
@Value("${baiyue.security.internal-service-token:}") String token) {
|
||||
@@ -73,96 +72,12 @@ public class AppointmentSourceService {
|
||||
public List<Source> resolveBatch(List<AppointmentCoverageService.SourceRef> requested) {
|
||||
return AppointmentSourceService.this.resolveBatch(requested,true,this).stream().map(Resolution::requireSource).toList();
|
||||
}
|
||||
/** 同页只合并原料查询;来源规范化、重复与权限检查仍按每份需求独立执行。 */
|
||||
public List<OrderGroup> resolveOrderGroups(List<List<AppointmentExternalSourceGateway.OrderRef>> groups) {
|
||||
var canonical=new java.util.ArrayList<List<AppointmentCoverageService.SourceRef>>();
|
||||
var errors=new java.util.ArrayList<RuntimeException>();
|
||||
var original=new LinkedHashSet<AppointmentExternalSourceGateway.OrderRef>();
|
||||
for(var group:groups) {
|
||||
RuntimeException error=null;
|
||||
if(group==null || group.isEmpty() || group.size()>100) error=new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE,"Firstleg需求缺少完整客户货源关系");
|
||||
else if(group.stream().anyMatch(r->r==null || blank(r.customerId()) || r.orderId()==null || r.orderId()<=0)) error=bad("请选择完整的真实货源");
|
||||
errors.add(error);if(error==null) original.addAll(group);
|
||||
}
|
||||
var aliases=new java.util.HashMap<AppointmentExternalSourceGateway.OrderRef,AppointmentExternalSourceGateway.Source>();
|
||||
var originals=new java.util.ArrayList<>(original);
|
||||
for(int start=0;start<originals.size();start+=100) {
|
||||
for(var booking:external.resolveOrders(originals.subList(start,Math.min(start+100,originals.size())))) {
|
||||
var key=new AppointmentExternalSourceGateway.OrderRef(booking.customerId(),booking.orderId());
|
||||
if(aliases.put(key,booking)!=null) throw new IllegalStateException("外接原订单关联重复");
|
||||
}
|
||||
}
|
||||
var bookingRefs=new LinkedHashSet<AppointmentExternalSourceGateway.SourceRef>();
|
||||
var orderIds=new LinkedHashSet<Long>();
|
||||
for(int i=0;i<groups.size();i++) {
|
||||
if(errors.get(i)!=null) { canonical.add(List.of());continue; }
|
||||
var refs=groups.get(i).stream().distinct().map(ref->{
|
||||
var alias=aliases.get(ref);
|
||||
return new AppointmentCoverageService.SourceRef(ref.customerId(),alias==null?"ORDER:"+ref.orderId():"BOOKING:"+alias.bookingNo(),null);
|
||||
}).toList();
|
||||
// 保留原 canonical 的数量校验,不能以全页去重掩盖单需求重复。
|
||||
if(refs.size()!=groups.get(i).size()) errors.set(i,new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE,"来源键必须明确为ORDER或BOOKING"));
|
||||
refs=refs.stream().distinct().toList();canonical.add(refs);
|
||||
for(var ref:refs) if(ref.sourceKey().startsWith("ORDER:")) orderIds.add(orderId(ref.sourceKey()));
|
||||
else bookingRefs.add(new AppointmentExternalSourceGateway.SourceRef(ref.customerId(),ref.sourceKey().substring(8)));
|
||||
}
|
||||
var bookingMap=new java.util.HashMap<String,AppointmentExternalSourceGateway.Source>();
|
||||
var bookings=new java.util.ArrayList<>(bookingRefs);
|
||||
for(int start=0;start<bookings.size();start+=100) for(var booking:external.resolve(bookings.subList(start,Math.min(start+100,bookings.size())))) {
|
||||
if(bookingMap.put(booking.customerId()+"\n"+booking.bookingNo(),booking)!=null) throw bad("外接来源重复,请先核对");
|
||||
if(booking.orderId()!=null) orderIds.add(booking.orderId());
|
||||
}
|
||||
var orderMap=new java.util.LinkedHashMap<Long,com.baiyue.lastleg.entity.LastlegOrder>();
|
||||
var gates=new java.util.HashMap<String,Boolean>();
|
||||
var destinations=new java.util.HashMap<Long,AppointmentCoverageMapper.Source>();
|
||||
var ids=new java.util.ArrayList<>(orderIds);
|
||||
for(int start=0;start<ids.size();start+=100) {
|
||||
var rows=readRepository.orders(ids.subList(start,Math.min(start+100,ids.size())));
|
||||
rows.forEach(row->{if(orderMap.put(row.getId(),row)!=null) throw new IllegalStateException("原订单来源重复");});
|
||||
}
|
||||
var containers=orderMap.values().stream().filter(o->"FIRST_LEG".equals(o.getOrderType())).map(com.baiyue.lastleg.entity.LastlegOrder::getSourceContainerBookingId).filter(Objects::nonNull).distinct().toList();
|
||||
for(int start=0;start<containers.size();start+=100) readRepository.gates(containers.subList(start,Math.min(start+100,containers.size()))).forEach(g->{
|
||||
if(gates.put(g.getContainerBookingId(),g.getNodeSequence()!=null && g.getNodeSequence()>=40)!=null) throw new IllegalStateException("原船节点门禁重复");
|
||||
});
|
||||
var transfers=orderMap.values().stream().filter(o->"WAREHOUSE_TRANSFER".equals(o.getOrderType())).map(o->new AppointmentCoverageMapper.OrderRef(o.getCustomerId(),o.getId())).toList();
|
||||
for(int start=0;start<transfers.size();start+=100) readRepository.destinations(transfers.subList(start,Math.min(start+100,transfers.size()))).forEach(d->{
|
||||
if(destinations.put(d.orderId(),d)!=null) throw new IllegalStateException("原目的地来源重复");
|
||||
});
|
||||
var result=new java.util.ArrayList<OrderGroup>();RuntimeException infrastructure=null;
|
||||
for(int i=0;i<groups.size();i++) {
|
||||
var refs=canonical.get(i);RuntimeException error=errors.get(i);var resolved=new java.util.ArrayList<Source>();
|
||||
if(error==null) error=infrastructure;
|
||||
if(error==null) try {
|
||||
var keys=new java.util.HashSet<String>();var materialized=new java.util.HashSet<Long>();
|
||||
for(var ref:refs) {
|
||||
if(blank(ref.sourceKey()) || ref.sourceKey().length()>160 || blank(ref.customerId()) || ref.customerId().length()>128) throw bad("请选择完整的真实货源");
|
||||
if(!keys.add(ref.sourceKey())) throw bad("同一来源货物不能重复选择");
|
||||
}
|
||||
var directory=catalog();
|
||||
for(var ref:refs) {
|
||||
var source=resolve(ref.sourceKey(),ref.customerId(),ref.destinationKey(),orderMap,bookingMap,gates,directory,destinations);
|
||||
if(source.orderId()!=null && !materialized.add(source.orderId())) throw bad("同一货物存在重复来源,请保留原登记来源");
|
||||
resolved.add(source);
|
||||
}
|
||||
} catch(RuntimeException failed) {
|
||||
error=failed;
|
||||
// 基础设施失败不重试、不变为空数据,延后到原需求访问位置抛出。
|
||||
if(!(failed instanceof ResponseStatusException status) || status.getStatusCode().value()>=500) infrastructure=failed;
|
||||
}
|
||||
result.add(new OrderGroup(refs,List.copyOf(resolved),error));
|
||||
}
|
||||
return List.copyOf(result);
|
||||
}
|
||||
/** 读取失败不赋值,下次显式调用仍按原目录接口重读。 */
|
||||
private List<AppointmentWarehouseClient.Warehouse> catalog() {
|
||||
if(directory==null) directory=AppointmentSourceService.this.catalog();
|
||||
return directory;
|
||||
}
|
||||
}
|
||||
public record OrderGroup(List<AppointmentCoverageService.SourceRef> references,List<Source> sources,RuntimeException error) {
|
||||
/** 业务前置错误仍在原需求解析位置抛出,由原边界决定是否保留未就绪需求。 */
|
||||
public List<Source> requireSources() { if(error!=null) throw error;return sources; }
|
||||
}
|
||||
/** Read-only inspection keeps each source's prerequisite failure separate; infrastructure errors still escape. */
|
||||
public record Resolution(AppointmentCoverageService.SourceRef reference,Source source,ResponseStatusException error) {
|
||||
public Source requireSource() { if(error!=null) throw error; return source; }
|
||||
|
||||
Reference in New Issue
Block a user