Read frozen dispatch driver license through its exact driver owner

This commit is contained in:
2026-10-20 10:38:20 +08:00
parent 135ffa662d
commit c76b9888f5
3 changed files with 154 additions and 4 deletions
@@ -223,14 +223,37 @@ public class DispatchAggregateService implements DispatchExecutionPort,DispatchA
requirePlatform(actor);var row=required(actor,number,false);requireEditing(row,row.getRecordVersion()); requirePlatform(actor);var row=required(actor,number,false);requireEditing(row,row.getRecordVersion());
return evidence.upload(row.getId(),kind,actor.operatorId(),file); return evidence.upload(row.getId(),kind,actor.operatorId(),file);
} }
/** 列表权限含详情只读,但文件仍限当前整车复合归属。 */ /** 列表权限含详情只读;仅冻结MASTER证照走司机归属,其余保持本车附件复合归属。 */
public com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway.File evidenceMetadata(Actor actor,String number, public com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway.File evidenceMetadata(Actor actor,String number,
com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway.Kind kind,long fileId) { com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway.Kind kind,long fileId) {
requirePlatform(actor);var row=required(actor,number,false);if(fileId<=0)throw bad("文件引用无效");return evidence.metadata(row.getId(),kind,fileId); requirePlatform(actor);var row=required(actor,number,false);if(fileId<=0)throw bad("文件引用无效");
var driverId=masterEvidenceDriverId(row,kind,fileId);
if(driverId!=null)return driverEvidenceMetadata(fleet.dispatchDriverLicense(driverId,fileId).metadata(),fileId);
return evidence.metadata(row.getId(),kind,fileId);
} }
/** 内容仅经服务端鉴权代理,不返回匿名存储URL。 */ /** 内容沿同一次确定的归属代理读取,不尝试类别回退,不返回匿名存储URL。 */
public byte[] evidenceContent(Actor actor,String number,com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway.Kind kind,long fileId) { public byte[] evidenceContent(Actor actor,String number,com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway.Kind kind,long fileId) {
requirePlatform(actor);var row=required(actor,number,false);if(fileId<=0)throw bad("文件引用无效");return evidence.download(row.getId(),kind,fileId); requirePlatform(actor);var row=required(actor,number,false);if(fileId<=0)throw bad("文件引用无效");
var driverId=masterEvidenceDriverId(row,kind,fileId);
if(driverId!=null) {
var file=fleet.dispatchDriverLicense(driverId,fileId);driverEvidenceMetadata(file.metadata(),fileId);return file.content();
}
return evidence.download(row.getId(),kind,fileId);
}
/** 未命中冻结MASTER引用的文件仅可按原排车归属读取,保留编辑中尚未保存的本车外配上传。 */
private Long masterEvidenceDriverId(DispatchAggregate row,com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway.Kind kind,long fileId) {
if(kind!=com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway.Kind.DRIVER)return null;
var resources=read(row.getResourcesJson(),Resources.class);var driver=resources==null?null:resources.driver();
if(driver==null || driver.mode()!=ResourceMode.MASTER || driver.evidence()==null || driver.evidence().fileIds()==null
|| !driver.evidence().fileIds().contains(fileId))return null;
if(driver.driverId()==null || driver.driverId()<=0)throw bad("排车冻结司机档案身份无效,不能读取证照");
return driver.driverId();
}
/** 司机域已执行当前/历史关联及File四元组校验,转换元数据时仍保留请求文件身份。 */
private static com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway.File driverEvidenceMetadata(
com.baiyue.lastleg.fleet.driver.DriverModels.File file,long fileId) {
if(file==null || !Long.toString(fileId).equals(file.fileId()))throw bad("司机证照回执文件身份不一致");
return new com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway.File(fileId,file.fileName(),file.contentType(),file.sizeBytes());
} }
/** 空单或同仓多客户选货一次建车,与资源关系、来源锁和账本同事务。 */ /** 空单或同仓多客户选货一次建车,与资源关系、来源锁和账本同事务。 */
@@ -34,6 +34,11 @@ public class FleetTripExecutionService {
private final ObjectMapper json; private final ObjectMapper json;
private final FleetBookingReservations bookingReservations; private final FleetBookingReservations bookingReservations;
/** 排车域先核对冻结司机及附件引用;本域继续校验该司机当前或历史证照归属。 */
public DriverModels.Download dispatchDriverLicense(long driverId,long fileId) {
return drivers.download(driverId,fileId);
}
public FleetTripExecutionService(FleetTripMapper trips, FleetTripDispatchMapper bindings, FleetResourceMapper resources, public FleetTripExecutionService(FleetTripMapper trips, FleetTripDispatchMapper bindings, FleetResourceMapper resources,
FleetDispatchGateway dispatches, FleetTripService history, DriverService drivers, FleetDispatchGateway dispatches, FleetTripService history, DriverService drivers,
VehicleRepository vehicles, ObjectMapper json, FleetBookingReservations bookingReservations) { VehicleRepository vehicles, ObjectMapper json, FleetBookingReservations bookingReservations) {
@@ -35,6 +35,128 @@ import static com.baiyue.lastleg.dispatch.port.DispatchTypes.*;
/** 主单/资源/占用/账本真实SQL回归;外部目录与选货权威提供者使用受控输入。 */ /** 主单/资源/占用/账本真实SQL回归;外部目录与选货权威提供者使用受控输入。 */
public class DispatchAggregatePersistenceTest { public class DispatchAggregatePersistenceTest {
/** 正式司机附件从排车冻结引用选出真实司机,再复用司机域及File的读取边界。 */
@Test void masterDriverEvidenceUsesFrozenDriverOwnerForMetadataAndContent() throws Exception {
var e=new DriverEvidenceFixture();var number=e.dispatch(ResourceMode.MASTER,71L,List.of(81L));
e.allowFile(81L);var before=e.f.db.queryForMap("SELECT resources_json,record_version FROM ll_dispatch_order WHERE dispatch_no=?",number);
assertThat(e.f.service.evidenceMetadata(e.f.actor,number,com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway.Kind.DRIVER,81L).fileId()).isEqualTo(81L);
assertThat(e.f.service.evidenceContent(e.f.actor,number,com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway.Kind.DRIVER,81L)).containsExactly(1,2,3);
verify(e.files,times(2)).metadata(71L,81L);verify(e.files,times(2)).download(71L,81L);verifyNoInteractions(e.dispatchFiles);
assertThat(e.f.db.queryForMap("SELECT resources_json,record_version FROM ll_dispatch_order WHERE dispatch_no=?",number)).isEqualTo(before);
}
/** 主档后来换证不改写本车冻结证照;旧附件必须仍满足该司机的历史关联规则。 */
@Test void frozenHistoricalDriverLicenseRemainsReadableAfterMasterReplacement() throws Exception {
var e=new DriverEvidenceFixture();var number=e.dispatch(ResourceMode.MASTER,71L,List.of(81L));
e.driver.setLicenseFileId(82L);when(e.history.selectCount(any())).thenReturn(1L);e.allowFile(81L);
assertThat(e.f.service.evidenceMetadata(e.f.actor,number,com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway.Kind.DRIVER,81L).fileId()).isEqualTo(81L);
assertThat(e.f.service.evidenceContent(e.f.actor,number,com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway.Kind.DRIVER,81L)).containsExactly(1,2,3);
verify(e.history,times(2)).selectCount(any());verify(e.files,times(2)).metadata(71L,81L);verifyNoInteractions(e.dispatchFiles);
}
/** 外配附件仍限本车证据;不能因为共用DRIVER展示类型而改走正式司机档案。 */
@Test void externalDriverEvidenceRetainsExactDispatchOwnerForBothReads() throws Exception {
var e=new DriverEvidenceFixture();var number=e.dispatch(ResourceMode.EXTERNAL,null,List.of(81L));
var dispatch=e.f.repository.find(number).getId();var kind=com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway.Kind.DRIVER;
var metadata=new com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway.File(81L,"external.pdf","application/pdf",3L);
when(e.dispatchFiles.metadata(dispatch,kind,81L)).thenReturn(metadata);when(e.dispatchFiles.download(dispatch,kind,81L)).thenReturn(new byte[]{4,5,6});
assertThat(e.f.service.evidenceMetadata(e.f.actor,number,kind,81L)).isEqualTo(metadata);
assertThat(e.f.service.evidenceContent(e.f.actor,number,kind,81L)).containsExactly(4,5,6);
verify(e.dispatchFiles).metadata(dispatch,kind,81L);verify(e.dispatchFiles).download(dispatch,kind,81L);
verifyNoInteractions(e.driverRows,e.history,e.files);
}
/** 冻结引用不绕过司机当前或历史关联,提供者明确拒绝后不得尝试排车类别。 */
@Test void unlinkedFrozenMasterFileCannotFallBackToDispatchEvidence() throws Exception {
var e=new DriverEvidenceFixture();var number=e.dispatch(ResourceMode.MASTER,71L,List.of(81L));
e.driver.setLicenseFileId(82L);when(e.history.selectCount(any())).thenReturn(0L);
var kind=com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway.Kind.DRIVER;
assertThatThrownBy(()->e.f.service.evidenceMetadata(e.f.actor,number,kind,81L)).hasMessageContaining("未关联");
assertThatThrownBy(()->e.f.service.evidenceContent(e.f.actor,number,kind,81L)).hasMessageContaining("未关联");
verifyNoInteractions(e.files,e.dispatchFiles);
}
/** File拒绝主档四元组时直接失败,不以另一业务类别或其他司机重新读取。 */
@Test void masterFileProviderRejectionCannotFallBackToAnotherCategory() throws Exception {
var e=new DriverEvidenceFixture();var number=e.dispatch(ResourceMode.MASTER,71L,List.of(81L));
when(e.files.metadata(71L,81L)).thenThrow(new BizException(com.baiyue.common.core.api.ResultCode.NOT_FOUND,"业务附件不存在或归属不匹配"));
var kind=com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway.Kind.DRIVER;
assertThatThrownBy(()->e.f.service.evidenceMetadata(e.f.actor,number,kind,81L)).hasMessageContaining("归属不匹配");
assertThatThrownBy(()->e.f.service.evidenceContent(e.f.actor,number,kind,81L)).hasMessageContaining("归属不匹配");
verify(e.files,never()).download(anyLong(),anyLong());verifyNoInteractions(e.dispatchFiles);
}
/** MASTER改外配的编辑尚未保存时,新上传文件只走本车归属,不误借旧司机白名单。 */
@Test void unsavedExternalUploadOnMasterDispatchRetainsOriginalDispatchFileReads() throws Exception {
var e=new DriverEvidenceFixture();var number=e.dispatch(ResourceMode.MASTER,71L,List.of(81L));
var dispatch=e.f.repository.find(number).getId();var kind=com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway.Kind.DRIVER;
var metadata=new com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway.File(91L,"new-external.pdf","application/pdf",3L);
when(e.dispatchFiles.metadata(dispatch,kind,91L)).thenReturn(metadata);when(e.dispatchFiles.download(dispatch,kind,91L)).thenReturn(new byte[]{4,5,6});
assertThat(e.f.service.evidenceMetadata(e.f.actor,number,kind,91L)).isEqualTo(metadata);
assertThat(e.f.service.evidenceContent(e.f.actor,number,kind,91L)).containsExactly(4,5,6);
verifyNoInteractions(e.driverRows,e.history,e.files);
assertThat(e.f.db.queryForObject("SELECT resources_json FROM ll_dispatch_order WHERE dispatch_no=?",String.class,number)).contains("MASTER").doesNotContain("91");
}
/** 未列入MASTER冻结集合或声明为外配的档案文件只能接受本车四元组核验,失败后不转司机域。 */
@Test void foreignOrWrongModeMasterFilesNeverGainDriverDomainAccess() throws Exception {
for(var mode:List.of(ResourceMode.MASTER,ResourceMode.EXTERNAL)) {
var e=new DriverEvidenceFixture();var number=e.dispatch(mode,71L,mode==ResourceMode.MASTER?List.of(81L):List.of(91L));
var dispatch=e.f.repository.find(number).getId();var kind=com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway.Kind.DRIVER;
when(e.dispatchFiles.metadata(dispatch,kind,91L)).thenThrow(new BizException(com.baiyue.common.core.api.ResultCode.NOT_FOUND,"业务附件不存在或归属不匹配"));
when(e.dispatchFiles.download(dispatch,kind,91L)).thenThrow(new BizException(com.baiyue.common.core.api.ResultCode.NOT_FOUND,"业务附件不存在或归属不匹配"));
assertThatThrownBy(()->e.f.service.evidenceMetadata(e.f.actor,number,kind,91L)).hasMessageContaining("归属不匹配");
assertThatThrownBy(()->e.f.service.evidenceContent(e.f.actor,number,kind,91L)).hasMessageContaining("归属不匹配");
verifyNoInteractions(e.driverRows,e.history,e.files);
}
}
/** 命中冻结文件集合仍须有本车真实正数司机身份,不能拿车号或其他司机替代。 */
@Test void masterFileWithMissingOrInvalidDriverIdentityFailsBeforeAnyFileProvider() throws Exception {
for(Long driverId:Arrays.asList(null,0L,-1L)) {
var e=new DriverEvidenceFixture();var number=e.dispatch(ResourceMode.MASTER,driverId,List.of(81L));
var kind=com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway.Kind.DRIVER;
assertThatThrownBy(()->e.f.service.evidenceMetadata(e.f.actor,number,kind,81L)).hasMessageContaining("身份无效");
assertThatThrownBy(()->e.f.service.evidenceContent(e.f.actor,number,kind,81L)).hasMessageContaining("身份无效");
verifyNoInteractions(e.driverRows,e.history,e.files,e.dispatchFiles);
}
}
/** 无法解释的资源模式不选择任何文件域,防止异常快照放宽读取。 */
@Test void unknownDriverModeFailsBeforeEitherFileProvider() throws Exception {
var e=new DriverEvidenceFixture();var number=e.dispatch(ResourceMode.MASTER,71L,List.of(81L));
e.f.db.update("UPDATE ll_dispatch_order SET resources_json=REPLACE(resources_json,'MASTER','UNKNOWN') WHERE dispatch_no=?",number);
var kind=com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway.Kind.DRIVER;
assertThatThrownBy(()->e.f.service.evidenceMetadata(e.f.actor,number,kind,81L)).hasMessageContaining("快照读取失败");
assertThatThrownBy(()->e.f.service.evidenceContent(e.f.actor,number,kind,81L)).hasMessageContaining("快照读取失败");
verifyNoInteractions(e.driverRows,e.history,e.files,e.dispatchFiles);
}
/** 使用真实排车Mapper/资源JSON及司机Service,只替代外部文件内容和主档持久提供者。 */
private static final class DriverEvidenceFixture {
final Fixture f;final DriverMapper driverRows=mock(DriverMapper.class);final DriverHistoryMapper history=mock(DriverHistoryMapper.class);
final DriverFileGateway files=mock(DriverFileGateway.class);final DriverEntity driver=new DriverEntity();
final com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway dispatchFiles;
/** 真实司机Service继续执行当前/历史附件关联检查,不由排车测试替身伪装通过。 */
DriverEvidenceFixture() throws Exception {
f=new Fixture();driver.setId(71L);driver.setLicenseFileId(81L);when(driverRows.selectById(71L)).thenReturn(driver);
var drivers=new DriverService(driverRows,history,files,f.json,mock(FleetWarehouseDirectory.class));
org.springframework.test.util.ReflectionTestUtils.setField(f.fleet,"drivers",drivers);
dispatchFiles=(com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway)org.springframework.test.util.ReflectionTestUtils.getField(f.service,"evidence");
}
/** 在真实主单保存服务端资源快照,HTTP请求本身不能指定司机、模式或允许文件集合。 */
String dispatch(ResourceMode mode,Long driverId,List<Long> ids) throws Exception {
var created=f.tx.execute(t->f.service.create(f.actor,f.create("evidence-"+UUID.randomUUID(),List.of())));
var resource=new DispatchContracts.Resources(null,new DispatchContracts.DriverSnapshot(mode,driverId,"测试司机","123456789",
new DispatchContracts.DriverEvidence("TEST-LICENSE",LocalDate.of(2099,1,1),ids,Instant.parse("2098-01-01T00:00:00Z"))));
f.db.update("UPDATE ll_dispatch_order SET resources_json=? WHERE dispatch_no=?",f.json.writeValueAsString(resource),created.dispatchNo());return created.dispatchNo();
}
/** File内容固定为离线测试字节;真实复合归属由既有两个HTTP网关合同分别覆盖。 */
void allowFile(long fileId) {
var metadata=new DriverModels.File(Long.toString(fileId),"driver.pdf","application/pdf",3L);
when(files.metadata(71L,fileId)).thenReturn(metadata);when(files.download(71L,fileId)).thenReturn(new byte[]{1,2,3});
}
}
/** 空单零版本取消使用真实来源规则,成功及原键重放均不制造来源、库存或重复命令。 */ /** 空单零版本取消使用真实来源规则,成功及原键重放均不制造来源、库存或重复命令。 */
@Test void emptyVersionZeroCancellationUsesRealSourceRuleAndPreservesOriginalReplay() throws Exception { @Test void emptyVersionZeroCancellationUsesRealSourceRuleAndPreservesOriginalReplay() throws Exception {
var f=new Fixture();var created=f.tx.execute(t->f.service.create(f.actor,f.create("empty-real-cancel",List.of()))); var f=new Fixture();var created=f.tx.execute(t->f.service.create(f.actor,f.create("empty-real-cancel",List.of())));