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());
return evidence.upload(row.getId(),kind,actor.operatorId(),file);
}
/** 列表权限含详情只读,但文件仍限当前整车复合归属。 */
/** 列表权限含详情只读;仅冻结MASTER证照走司机归属,其余保持本车附件复合归属。 */
public com.baiyue.lastleg.dispatch.gateway.DispatchEvidenceGateway.File evidenceMetadata(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.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) {
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 FleetBookingReservations bookingReservations;
/** 排车域先核对冻结司机及附件引用;本域继续校验该司机当前或历史证照归属。 */
public DriverModels.Download dispatchDriverLicense(long driverId,long fileId) {
return drivers.download(driverId,fileId);
}
public FleetTripExecutionService(FleetTripMapper trips, FleetTripDispatchMapper bindings, FleetResourceMapper resources,
FleetDispatchGateway dispatches, FleetTripService history, DriverService drivers,
VehicleRepository vehicles, ObjectMapper json, FleetBookingReservations bookingReservations) {
@@ -35,6 +35,128 @@ import static com.baiyue.lastleg.dispatch.port.DispatchTypes.*;
/** 主单/资源/占用/账本真实SQL回归;外部目录与选货权威提供者使用受控输入。 */
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 {
var f=new Fixture();var created=f.tx.execute(t->f.service.create(f.actor,f.create("empty-real-cancel",List.of())));