fix(file): converge delivery documents to three categories
This commit is contained in:
@@ -7,13 +7,9 @@ import java.util.Arrays;
|
||||
|
||||
/** File-owned storage policy. Business visibility and lifecycle remain in the calling service. */
|
||||
enum BusinessFileCategory {
|
||||
DELIVERY_BOL(true, FormatPolicy.ARBITRARY, Limits.CUSTOMS, BusinessIdPolicy.POSITIVE_LONG),
|
||||
DELIVERY_CUSTOMER_DOCUMENT(true, FormatPolicy.ARBITRARY, Limits.CUSTOMS, BusinessIdPolicy.POSITIVE_LONG),
|
||||
DELIVERY_BILL_OF_LADING(true, FormatPolicy.ARBITRARY, Limits.CUSTOMS, BusinessIdPolicy.POSITIVE_LONG),
|
||||
DELIVERY_POD(true, FormatPolicy.ARBITRARY, Limits.CUSTOMS, BusinessIdPolicy.POSITIVE_LONG),
|
||||
DELIVERY_PHOTO(true, FormatPolicy.ARBITRARY, Limits.CUSTOMS, BusinessIdPolicy.POSITIVE_LONG),
|
||||
DELIVERY_LOADING_PHOTO(true, FormatPolicy.ARBITRARY, Limits.CUSTOMS, BusinessIdPolicy.POSITIVE_LONG),
|
||||
DELIVERY_EXPRESS_RECEIPT(true, FormatPolicy.ARBITRARY, Limits.CUSTOMS, BusinessIdPolicy.POSITIVE_LONG),
|
||||
DELIVERY_BOL(true, FormatPolicy.ARBITRARY, Limits.CUSTOMS, BusinessIdPolicy.POSITIVE_LONG),
|
||||
DELIVERY_RECEIPT(true, FormatPolicy.ARBITRARY, Limits.CUSTOMS, BusinessIdPolicy.POSITIVE_LONG),
|
||||
TRUCK_APPOINTMENT_ATTACHMENT(false, FormatPolicy.STANDARD, Limits.STANDARD, BusinessIdPolicy.POSITIVE_LONG),
|
||||
OCEAN_BOOKING_DOCUMENT(false, FormatPolicy.ARBITRARY, Limits.CUSTOMS, BusinessIdPolicy.POSITIVE_LONG),
|
||||
CUSTOMS_DOCUMENT(false, FormatPolicy.STANDARD, Limits.STANDARD, BusinessIdPolicy.POSITIVE_LONG),
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: 派送单七类资料 File 内部契约
|
||||
title: 派送单三类资料 File 内部契约
|
||||
version: 1.0.0
|
||||
description: >-
|
||||
tenantId为派送单所属客户,businessId为deliveryOrderId内部Long的十进制字符串,不是派送单号或BOL号。
|
||||
任意格式,非空单文件最多50MiB(52428800字节)。每类最多20文件由Lastleg控制,七类不强制必传。
|
||||
任意格式,非空单文件最多50MiB(52428800字节)。每类0至20文件由Lastleg控制,三类不强制必传。
|
||||
totalCategoryCount固定3,计数为非空类别x/3,不是文件数;不增加完成门禁或签名识别。
|
||||
上传只创建不可变对象,不自动绑定。Lastleg原子保存分类绑定与版本,替换/删除仅解绑并保留历史证据,
|
||||
用户下载前由Lastleg检查当前引用或获准历史引用和权限,再调用File精确归属下载。不得按BOL号跨客户复用。
|
||||
x-category-mapping:
|
||||
BOL: DELIVERY_BOL
|
||||
CUSTOMER_DOCUMENT: DELIVERY_CUSTOMER_DOCUMENT
|
||||
BILL_OF_LADING: DELIVERY_BILL_OF_LADING
|
||||
POD: DELIVERY_POD
|
||||
DELIVERY_PHOTO: DELIVERY_PHOTO
|
||||
LOADING_PHOTO: DELIVERY_LOADING_PHOTO
|
||||
EXPRESS_RECEIPT: DELIVERY_EXPRESS_RECEIPT
|
||||
BOL: DELIVERY_BOL
|
||||
RECEIPT: DELIVERY_RECEIPT
|
||||
x-category-titles:
|
||||
LOADING_PHOTO: 装车照片
|
||||
BOL: BOL文件(含签名)
|
||||
RECEIPT: 签收文件(POD签收/快递签收)
|
||||
paths:
|
||||
/internal/file/v1/business-files/{tenantId}/{businessType}/{businessId}:
|
||||
parameters:
|
||||
@@ -75,7 +76,7 @@ components:
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
enum: [DELIVERY_BOL, DELIVERY_CUSTOMER_DOCUMENT, DELIVERY_BILL_OF_LADING, DELIVERY_POD, DELIVERY_PHOTO, DELIVERY_LOADING_PHOTO, DELIVERY_EXPRESS_RECEIPT]
|
||||
enum: [DELIVERY_LOADING_PHOTO, DELIVERY_BOL, DELIVERY_RECEIPT]
|
||||
responses:
|
||||
Metadata:
|
||||
description: 业务成功/失败使用HTTP200,成功code为字符串200;无效内部令牌拒绝
|
||||
|
||||
@@ -28,7 +28,7 @@ class DeliveryBusinessFileTest {
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings={"DELIVERY_BOL","DELIVERY_CUSTOMER_DOCUMENT","DELIVERY_BILL_OF_LADING","DELIVERY_POD","DELIVERY_PHOTO","DELIVERY_LOADING_PHOTO","DELIVERY_EXPRESS_RECEIPT"})
|
||||
@ValueSource(strings={"DELIVERY_LOADING_PHOTO","DELIVERY_BOL","DELIVERY_RECEIPT"})
|
||||
void immutableUploadsPreserveOldContentAndRealTimestamp(String type) {
|
||||
var storage = storage();
|
||||
var files = mock(FileObjectMapper.class);
|
||||
@@ -50,7 +50,7 @@ class DeliveryBusinessFileTest {
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings={"DELIVERY_BOL","DELIVERY_CUSTOMER_DOCUMENT","DELIVERY_BILL_OF_LADING","DELIVERY_POD","DELIVERY_PHOTO","DELIVERY_LOADING_PHOTO","DELIVERY_EXPRESS_RECEIPT"})
|
||||
@ValueSource(strings={"DELIVERY_LOADING_PHOTO","DELIVERY_BOL","DELIVERY_RECEIPT"})
|
||||
void enforcesFiftyMibAndNonemptyBoundary(String type) throws Exception {
|
||||
var storage = storage(); var category = BusinessFileCategory.require(type);
|
||||
assertThatThrownBy(() -> storage.store(11,"71",new MockMultipartFile("file",new byte[0]),category)).isInstanceOf(BizException.class);
|
||||
@@ -65,7 +65,7 @@ class DeliveryBusinessFileTest {
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings={"DELIVERY_BOL","DELIVERY_CUSTOMER_DOCUMENT","DELIVERY_BILL_OF_LADING","DELIVERY_POD","DELIVERY_PHOTO","DELIVERY_LOADING_PHOTO","DELIVERY_EXPRESS_RECEIPT"})
|
||||
@ValueSource(strings={"DELIVERY_LOADING_PHOTO","DELIVERY_BOL","DELIVERY_RECEIPT"})
|
||||
void downloadQueriesEveryOwnershipKeyAndRejectsUnauthorizedAccess(String type) {
|
||||
var files=mock(FileObjectMapper.class); var storage=mock(BusinessFileStorage.class); var service=service(files,storage);
|
||||
when(files.selectOne(any())).thenAnswer(call -> {
|
||||
@@ -82,6 +82,13 @@ class DeliveryBusinessFileTest {
|
||||
assertThatThrownBy(() -> controller.download("bad",11,type,"71",801)).isInstanceOf(BizException.class);
|
||||
verifyNoInteractions(storage);
|
||||
}
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings={"DELIVERY_CUSTOMER_DOCUMENT","DELIVERY_BILL_OF_LADING","DELIVERY_POD","DELIVERY_PHOTO","DELIVERY_EXPRESS_RECEIPT"})
|
||||
void rejectsRemovedCategoriesWithoutAliases(String type) {
|
||||
assertThatThrownBy(() -> BusinessFileCategory.require(type))
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("不支持的附件业务类型");
|
||||
}
|
||||
|
||||
private BusinessFileStorage storage() {
|
||||
var storage=new BusinessFileStorage(); ReflectionTestUtils.setField(storage,"configuredRoot",root); storage.initializeRoot(); return storage;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.baiyue.file.service;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class DeliveryFileContractTest {
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void freezesThreeOrderedCategoriesAndMatchesRuntimePolicy() throws Exception {
|
||||
String text = Files.readString(Path.of("src/main/resources/openapi/delivery-files-v1.yaml"));
|
||||
Map<String, Object> yaml = new Yaml().load(text);
|
||||
Map<String, Object> info = (Map<String, Object>) yaml.get("info");
|
||||
Map<String, String> mapping = (Map<String, String>) info.get("x-category-mapping");
|
||||
assertThat(mapping.keySet()).containsExactly("LOADING_PHOTO", "BOL", "RECEIPT");
|
||||
List<String> types = List.of("DELIVERY_LOADING_PHOTO", "DELIVERY_BOL", "DELIVERY_RECEIPT");
|
||||
assertThat(mapping.values()).containsExactlyElementsOf(types);
|
||||
Map<String, String> titles = (Map<String, String>) info.get("x-category-titles");
|
||||
assertThat(titles.keySet()).containsExactlyElementsOf(mapping.keySet());
|
||||
assertThat(titles.values()).containsExactly("装车照片", "BOL文件(含签名)", "签收文件(POD签收/快递签收)");
|
||||
Map<String, Object> components = (Map<String, Object>) yaml.get("components");
|
||||
Map<String, Object> parameters = (Map<String, Object>) components.get("parameters");
|
||||
Map<String, Object> type = (Map<String, Object>) parameters.get("Type");
|
||||
Map<String, Object> schema = (Map<String, Object>) type.get("schema");
|
||||
assertThat((List<String>) schema.get("enum")).containsExactlyElementsOf(types);
|
||||
assertThat(Arrays.stream(BusinessFileCategory.values()).map(Enum::name)
|
||||
.filter(name -> name.startsWith("DELIVERY_")).toList()).containsExactlyElementsOf(types);
|
||||
assertThat(text).contains("totalCategoryCount固定3", "非空类别x/3", "每类0至20", "不强制必传", "不增加完成门禁或签名识别");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user