From f2a980e5c1f173543bb7a23f7a9c0c870d76df43 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Mon, 31 Aug 2026 19:54:05 +0800 Subject: [PATCH] fix(file): allow arbitrary ocean booking documents --- file/AGENTS.md | 2 +- .../file/service/BusinessFileCategory.java | 2 +- .../resources/openapi/ocean-booking-file-v1.yaml | 6 +++--- .../config/OceanBookingFileContractTest.java | 2 ++ .../service/InternalBusinessFileServiceTest.java | 16 +++++++++------- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/file/AGENTS.md b/file/AGENTS.md index 75251eada..56be4d5de 100644 --- a/file/AGENTS.md +++ b/file/AGENTS.md @@ -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` | diff --git a/file/src/main/java/com/baiyue/file/service/BusinessFileCategory.java b/file/src/main/java/com/baiyue/file/service/BusinessFileCategory.java index 9826e2fb5..fdb59dbb7 100644 --- a/file/src/main/java/com/baiyue/file/service/BusinessFileCategory.java +++ b/file/src/main/java/com/baiyue/file/service/BusinessFileCategory.java @@ -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), diff --git a/file/src/main/resources/openapi/ocean-booking-file-v1.yaml b/file/src/main/resources/openapi/ocean-booking-file-v1.yaml index a58af9548..97797d0ce 100644 --- a/file/src/main/resources/openapi/ocean-booking-file-v1.yaml +++ b/file/src/main/resources/openapi/ocean-booking-file-v1.yaml @@ -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} diff --git a/file/src/test/java/com/baiyue/file/config/OceanBookingFileContractTest.java b/file/src/test/java/com/baiyue/file/config/OceanBookingFileContractTest.java index 215f783ba..8a08ea3ee 100644 --- a/file/src/test/java/com/baiyue/file/config/OceanBookingFileContractTest.java +++ b/file/src/test/java/com/baiyue/file/config/OceanBookingFileContractTest.java @@ -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/"); } diff --git a/file/src/test/java/com/baiyue/file/service/InternalBusinessFileServiceTest.java b/file/src/test/java/com/baiyue/file/service/InternalBusinessFileServiceTest.java index b173cd3a5..5f493379e 100644 --- a/file/src/test/java/com/baiyue/file/service/InternalBusinessFileServiceTest.java +++ b/file/src/test/java/com/baiyue/file/service/InternalBusinessFileServiceTest.java @@ -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())