fix(file): allow arbitrary ocean booking documents

This commit is contained in:
2026-08-31 19:54:05 +08:00
parent 47ce84d122
commit f2a980e5c1
5 changed files with 16 additions and 12 deletions
+1 -1
View File
@@ -17,7 +17,7 @@
| 装柜与业务文件证据 | `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 精确校验 |
| 报关源文件、完成文件与买单明细导出 | `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 按固定十三列供数 |
| 订舱文件 | 通用内部业务文件入口,契约 `src/main/resources/openapi/ocean-booking-file-v1.yaml`;类型固定 `OCEAN_BOOKING_DOCUMENT`、`tenantId=订舱customerId`、`businessId=oceanBookingId`,不得绑定拖车;`BOOKING_CONFIRMATION/BILL_OF_LADING` 文档类型及有效状态由 Firstleg 保存 |
| 订舱文件 | 通用内部业务文件入口,契约 `src/main/resources/openapi/ocean-booking-file-v1.yaml`;类型固定 `OCEAN_BOOKING_DOCUMENT`、`tenantId=订舱customerId`、`businessId=oceanBookingId`,任意格式且最多 50MiB,不得绑定拖车;`BOOKING_CONFIRMATION/BILL_OF_LADING` 文档类型及有效状态由 Firstleg 保存 |
| 存储与实体 | `service/LocalQualificationStorage.java`、`MeasurementImageStorage.java`、`entity/FileObjectEntity.java` |
| 下游 OCR | `client/WeightOcrClient.java` |
| SQL/配置 | `src/main/resources/db/file.sql`、`application.yml` |
@@ -8,7 +8,7 @@ import java.util.Arrays;
/** File-owned storage policy. Business visibility and lifecycle remain in the calling service. */
enum BusinessFileCategory {
TRUCK_APPOINTMENT_ATTACHMENT(false, false),
OCEAN_BOOKING_DOCUMENT(true, false),
OCEAN_BOOKING_DOCUMENT(true, true),
CUSTOMS_DOCUMENT(false, false),
CUSTOMS_RECEIPT(false, false),
FINANCE_VOUCHER(false, false),
@@ -27,7 +27,7 @@ paths:
file:
type: string
format: binary
description: TXT/PDF/JPG/PNG,最大10MiB
description: 任意格式,最大50MiB;File保留规范化Content-Type与原始文件名
responses:
'200': {$ref: '#/components/responses/Metadata'}
/internal/file/v1/business-files/{tenantId}/OCEAN_BOOKING_DOCUMENT/{oceanBookingId}/{fileId}:
@@ -108,5 +108,5 @@ components:
description: oceanBookingId
fileName: {type: string}
sha256: {type: string, pattern: '^[0-9a-f]{64}$'}
contentType: {type: string, enum: [text/plain, application/pdf, image/jpeg, image/png]}
sizeBytes: {type: integer, format: int64, minimum: 1, maximum: 10485760}
contentType: {type: string}
sizeBytes: {type: integer, format: int64, minimum: 1, maximum: 52428800}
@@ -18,6 +18,8 @@ class OceanBookingFileContractTest {
.contains("description: 订舱customerId")
.contains("description: oceanBookingId")
.contains("x-firstleg-document-types: [BOOKING_CONFIRMATION, BILL_OF_LADING]")
.contains("任意格式,最大50MiB")
.contains("maximum: 52428800")
.contains("name: X-Internal-Service-Token", "Cache-Control")
.doesNotContain("TRUCK_APPOINTMENT_ATTACHMENT", "/api/platform/", "/api/merchant/");
}
@@ -70,11 +70,12 @@ class InternalBusinessFileServiceTest {
}
@Test void oceanBookingDocumentUsesCustomerAndBookingOwnership() {
MockMultipartFile file = new MockMultipartFile("file", "booking.pdf", "application/pdf",
"%PDF-1.4".getBytes());
when(storage.store(10001L, 61L, file, BusinessFileCategory.STANDARD_MAX_SIZE, false))
.thenReturn(new BusinessFileStorage.Stored("business-files/10001/61/booking.pdf",
"application/pdf", file.getSize(), "f".repeat(64), "booking.pdf"));
MockMultipartFile file = new MockMultipartFile("file", "booking.xlsx",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "xlsx".getBytes());
when(storage.store(10001L, 61L, file, BusinessFileCategory.CUSTOMS_MAX_SIZE, true))
.thenReturn(new BusinessFileStorage.Stored("business-files/10001/61/booking",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
file.getSize(), "f".repeat(64), "booking.xlsx"));
when(files.insert(any(FileObjectEntity.class))).thenAnswer(invocation -> {
((FileObjectEntity) invocation.getArgument(0)).setId(111L); return 1;
});
@@ -93,8 +94,9 @@ class InternalBusinessFileServiceTest {
@Test void oceanBookingDocumentRejectsWrongCustomerBookingAndTruckOwnership() {
FileObjectEntity value = new FileObjectEntity(); value.setId(111L); value.setTenantId(10001L);
value.setBusinessType("OCEAN_BOOKING_DOCUMENT"); value.setBusinessId("61");
value.setOriginalName("booking.pdf"); value.setObjectKey("business-files/10001/61/booking.pdf");
value.setContentType("application/pdf"); value.setSha256("f".repeat(64)); value.setSizeBytes(8L);
value.setOriginalName("booking.xlsx"); value.setObjectKey("business-files/10001/61/booking");
value.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
value.setSha256("f".repeat(64)); value.setSizeBytes(8L);
when(files.selectOne(any())).thenReturn(value, null, null, null);
assertThat(service.metadata(10001L, "OCEAN_BOOKING_DOCUMENT", "61", 111L).ownerScope())