Test #1

Merged
admin merged 1726 commits from test into main 2026-09-17 11:52:45 +08:00
4 changed files with 21 additions and 18 deletions
Showing only changes of commit bf7579f93b - Show all commits
+1 -1
View File
@@ -15,7 +15,7 @@
| 测量图片/证据 | `controller/PlatformMeasurementImageController.java`、`InternalMeasurementImageController.java`、`service/MeasurementImageService.java` |
| 平台订单商品图片读取 | `controller/PlatformProductImageController.java` 的 `/api/platform/v1/files/product-images/{tenantId}/{fileId}`、`service/ProductImageService.java`;仅平台 `platform:order:list` 可按明确客户与文件 ID 内联读取,响应 `no-store` |
| 装柜与业务文件证据 | `controller/PlatformLoadingEvidenceController.java`、`InternalLoadingEvidenceController.java`、`service/LoadingEvidenceService.java`、`src/main/resources/openapi/loading-evidence-v1.yaml`;五类装柜证据固定为平台所有、`tenantId=0`、`businessId=containerBookingId`,不得绑定客户或入仓单。其他业务文件进入 `InternalBusinessFileController.java`、`service/InternalBusinessFileService.java` |
| 财务付款凭证、海外卸柜照片 | 通用业务文件入口 `InternalBusinessFileController.java`、`InternalBusinessFileService.java`;业务类型分别为 `FINANCE_VOUCHER`、`OVERSEAS_UNLOAD_PHOTO`,必须按客户与业务 ID 精确校验 |
| 财务付款凭证、海外卸柜照片 | 通用业务文件入口 `InternalBusinessFileController.java`、`InternalBusinessFileService.java`;`FINANCE_VOUCHER` 按客户与业务 ID 精确校验;`OVERSEAS_UNLOAD_PHOTO` 是允许同柜多客户的柜级物理证据,固定平台所有(`tenantId=0`)并按接柜会话 ID 精确校验 |
| 海外异常订单证据 | 通用内部业务文件入口;固定 `businessType=OVERSEAS_EXCEPTION_EVIDENCE`、`tenantId=异常订单所属客户ID`、`businessId=异常订单ID`,File 只校验文件复合归属,异常状态、完成门禁和证据引用归 Lastleg |
| 入库扣货凭证 | 通用内部业务文件入口;固定 `businessType=INBOUND_HOLD_EVIDENCE`、`tenantId=客户ID`、`businessId=入库单ID`,客户作用域单文件最多 10MiB;可见性与历史引用由 Firstleg 校验 |
| 报关源文件、完成文件与买单明细导出 | `src/main/resources/openapi/customs-file-and-export-v1.yaml`;文件类别 `LOADING_LIST_CUSTOMS_SOURCE`、`LOADING_LIST_CUSTOMS_RESULT` 均为客户作用域且最多 50MB,异步导出类型 `LOADING_LIST_BUY_DECLARATION_ITEMS` 由 Firstleg Provider 按固定十三列供数 |
@@ -22,7 +22,7 @@ enum BusinessFileCategory {
LOADING_CUSTOMS_DECLARATION(false, FormatPolicy.STANDARD, Limits.STANDARD, BusinessIdPolicy.POSITIVE_LONG),
CONTAINER_DELIVERY_ORDER(false, FormatPolicy.STANDARD, Limits.STANDARD, BusinessIdPolicy.POSITIVE_LONG),
CONTAINER_PICKUP_ORDER(false, FormatPolicy.STANDARD, Limits.STANDARD, BusinessIdPolicy.POSITIVE_LONG),
OVERSEAS_UNLOAD_PHOTO(true, FormatPolicy.STANDARD, Limits.STANDARD, BusinessIdPolicy.POSITIVE_LONG),
OVERSEAS_UNLOAD_PHOTO(false, FormatPolicy.STANDARD, Limits.STANDARD, BusinessIdPolicy.POSITIVE_LONG),
OVERSEAS_RELABEL_PHOTO(true, FormatPolicy.STANDARD, Limits.STANDARD, BusinessIdPolicy.POSITIVE_LONG),
OVERSEAS_EXCEPTION_EVIDENCE(true, FormatPolicy.STANDARD, Limits.STANDARD, BusinessIdPolicy.POSITIVE_LONG),
LOADING_LIST_CUSTOMS_SOURCE(true, FormatPolicy.ARBITRARY, Limits.CUSTOMS, BusinessIdPolicy.POSITIVE_LONG),
@@ -53,8 +53,9 @@ enum BusinessFileCategory {
}
void validateOwner(long tenantId) {
if (this == OCEAN_BOOKING_DOCUMENT && tenantId != 0) {
throw new BizException(ResultCode.BAD_REQUEST, "订舱附件必须归属平台");
if ((this == OCEAN_BOOKING_DOCUMENT || this == OVERSEAS_UNLOAD_PHOTO) && tenantId != 0) {
throw new BizException(ResultCode.BAD_REQUEST,
this == OCEAN_BOOKING_DOCUMENT ? "订舱附件必须归属平台" : "海外卸柜照片必须归属平台");
}
if (customerOwned && tenantId <= 0) {
throw new BizException(ResultCode.BAD_REQUEST, "该附件类型必须归属明确客户");
@@ -44,8 +44,8 @@ class InternalBusinessFileServiceTest {
when(storage.store(eq(0L), anyString(), same(file), any(BusinessFileCategory.class))).thenAnswer(invocation ->
new BusinessFileStorage.Stored("business-files/0/" + invocation.getArgument(1) + "/customs.pdf",
"application/pdf", file.getSize(), "b".repeat(64), "customs.pdf"));
when(storage.store(11L, "51", file, BusinessFileCategory.OVERSEAS_UNLOAD_PHOTO)).thenReturn(new BusinessFileStorage.Stored(
"business-files/11/51/unload.jpg", "image/jpeg", file.getSize(), "c".repeat(64), "unload.jpg"));
when(storage.store(0L, "51", file, BusinessFileCategory.OVERSEAS_UNLOAD_PHOTO)).thenReturn(new BusinessFileStorage.Stored(
"business-files/0/51/unload.jpg", "image/jpeg", file.getSize(), "c".repeat(64), "unload.jpg"));
when(files.insert(any(FileObjectEntity.class))).thenAnswer(invocation -> {
((FileObjectEntity) invocation.getArgument(0)).setId(81L); return 1;
});
@@ -54,7 +54,7 @@ class InternalBusinessFileServiceTest {
var customs = service.upload(0L, "LOADING_CUSTOMS_DECLARATION", "51", 7L, file);
var deliveryOrder = service.upload(0L, "CONTAINER_DELIVERY_ORDER", "41", 7L, file);
var pickupOrder = service.upload(0L, "CONTAINER_PICKUP_ORDER", "41", 7L, file);
var unloadPhoto = service.upload(11L, "OVERSEAS_UNLOAD_PHOTO", "51", 7L, file);
var unloadPhoto = service.upload(0L, "OVERSEAS_UNLOAD_PHOTO", "51", 7L, file);
assertThat(clearance).extracting(InternalBusinessFileService.Metadata::tenantId,
InternalBusinessFileService.Metadata::businessType,
@@ -67,12 +67,14 @@ class InternalBusinessFileServiceTest {
assertThat(unloadPhoto).extracting(InternalBusinessFileService.Metadata::tenantId,
InternalBusinessFileService.Metadata::businessType,
InternalBusinessFileService.Metadata::businessId)
.containsExactly(11L, "OVERSEAS_UNLOAD_PHOTO", "51");
.containsExactly(0L, "OVERSEAS_UNLOAD_PHOTO", "51");
verify(storage).store(0L, "41", file, BusinessFileCategory.CONTAINER_CLEARANCE_DOCUMENT);
verify(storage).store(0L, "51", file, BusinessFileCategory.LOADING_CUSTOMS_DECLARATION);
verify(storage).store(0L, "41", file, BusinessFileCategory.CONTAINER_DELIVERY_ORDER);
verify(storage).store(0L, "41", file, BusinessFileCategory.CONTAINER_PICKUP_ORDER);
verify(storage).store(11L, "51", file, BusinessFileCategory.OVERSEAS_UNLOAD_PHOTO);
verify(storage).store(0L, "51", file, BusinessFileCategory.OVERSEAS_UNLOAD_PHOTO);
assertThatThrownBy(() -> service.upload(11L, "OVERSEAS_UNLOAD_PHOTO", "51", 7L, file))
.isInstanceOf(BizException.class).hasMessageContaining("卸柜照片必须归属平台");
}
@Test void oceanBookingDocumentUsesPlatformAndBookingOwnership() {
@@ -33,11 +33,11 @@ class HttpUnloadPhotoFileGatewayTest {
@Test
void forgedImageBusinessFailureMapsToBadRequestInsteadOfInternalError() {
server.expect(requestTo("http://file-service/internal/file/v1/business-files/100/OVERSEAS_UNLOAD_PHOTO/8"))
server.expect(requestTo("http://file-service/internal/file/v1/business-files/0/OVERSEAS_UNLOAD_PHOTO/8"))
.andRespond(withSuccess("{\"code\":\"A0400\",\"message\":\"上传内容不是有效图片\",\"data\":null}",
MediaType.APPLICATION_JSON));
assertThatThrownBy(() -> gateway.upload("100", 8L, 7L,
assertThatThrownBy(() -> gateway.upload("0", 8L, 7L,
new MockMultipartFile("file", "forged.png", "image/png", "<script>".getBytes())))
.isInstanceOfSatisfying(ResponseStatusException.class,
error -> assertThat(error.getStatusCode().value()).isEqualTo(400));
@@ -46,11 +46,11 @@ class HttpUnloadPhotoFileGatewayTest {
@Test
void mismatchedPhotoMetadataMapsToBadRequest() {
server.expect(requestTo("http://file-service/internal/file/v1/business-files/100/OVERSEAS_UNLOAD_PHOTO/8/9"))
.andRespond(withSuccess("{\"code\":\"200\",\"data\":{\"fileId\":9,\"tenantId\":101,\"businessType\":\"OVERSEAS_UNLOAD_PHOTO\",\"businessId\":\"8\"}}",
server.expect(requestTo("http://file-service/internal/file/v1/business-files/0/OVERSEAS_UNLOAD_PHOTO/8/9"))
.andRespond(withSuccess("{\"code\":\"200\",\"data\":{\"fileId\":9,\"tenantId\":1,\"businessType\":\"OVERSEAS_UNLOAD_PHOTO\",\"businessId\":\"8\"}}",
MediaType.APPLICATION_JSON));
assertThatThrownBy(() -> gateway.validate("100", 8L, 9L))
assertThatThrownBy(() -> gateway.validate("0", 8L, 9L))
.isInstanceOfSatisfying(ResponseStatusException.class,
error -> assertThat(error.getStatusCode().value()).isEqualTo(400));
server.verify();
@@ -59,12 +59,12 @@ class HttpUnloadPhotoFileGatewayTest {
@Test
void downloadUsesBusinessOwnershipPathAndPreservesBlobMetadata() {
server.expect(requestTo(
"http://file-service/internal/file/v1/business-files/100/OVERSEAS_UNLOAD_PHOTO/8/9/content"))
"http://file-service/internal/file/v1/business-files/0/OVERSEAS_UNLOAD_PHOTO/8/9/content"))
.andExpect(header("X-Internal-Service-Token", "internal-token"))
.andRespond(withSuccess("photo-bytes".getBytes(), MediaType.IMAGE_JPEG)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=door.jpg"));
UnloadPhotoFileGateway.DownloadedFile result = gateway.download("100", 8L, 9L);
UnloadPhotoFileGateway.DownloadedFile result = gateway.download("0", 8L, 9L);
assertThat(result.content()).containsExactly("photo-bytes".getBytes());
assertThat(result.fileName()).isEqualTo("door.jpg");
@@ -75,10 +75,10 @@ class HttpUnloadPhotoFileGatewayTest {
@Test
void missingOwnedPhotoMapsToNotFound() {
server.expect(requestTo(
"http://file-service/internal/file/v1/business-files/100/OVERSEAS_UNLOAD_PHOTO/8/9/content"))
"http://file-service/internal/file/v1/business-files/0/OVERSEAS_UNLOAD_PHOTO/8/9/content"))
.andRespond(withStatus(HttpStatus.NOT_FOUND));
assertThatThrownBy(() -> gateway.download("100", 8L, 9L))
assertThatThrownBy(() -> gateway.download("0", 8L, 9L))
.isInstanceOfSatisfying(ResponseStatusException.class,
error -> assertThat(error.getStatusCode().value()).isEqualTo(404));
server.verify();