feat(file): support seven delivery document categories

This commit is contained in:
2026-09-09 11:11:32 +08:00
parent cde8acf111
commit 3627239708
11 changed files with 240 additions and 3 deletions
@@ -24,7 +24,7 @@ class InternalBusinessFileControllerTest {
@Test void zeroTenantUploadAndMetadataRemainBehindInternalToken() {
var file = new MockMultipartFile("file", "clearance.pdf", "application/pdf", "%PDF-1.4".getBytes());
var metadata = new InternalBusinessFileService.Metadata(81L, "PLATFORM", 0L, "CONTAINER_CLEARANCE_DOCUMENT",
"41", "clearance.pdf", "hash", "application/pdf", file.getSize());
"41", "clearance.pdf", "hash", "application/pdf", file.getSize(), java.time.LocalDateTime.of(2026, 9, 9, 12, 0));
when(service.upload(0L, "CONTAINER_CLEARANCE_DOCUMENT", "41", 7L, file)).thenReturn(metadata);
when(service.metadata(0L, "CONTAINER_CLEARANCE_DOCUMENT", "41", 81L)).thenReturn(metadata);
@@ -0,0 +1,91 @@
package com.baiyue.file.service;
import com.baiyue.file.entity.FileObjectEntity;
import com.baiyue.file.mapper.FileObjectMapper;
import com.baiyue.file.controller.InternalBusinessFileController;
import com.baiyue.common.core.exception.BizException;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.multipart.MultipartFile;
import java.nio.file.Path;
import java.util.concurrent.atomic.AtomicLong;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import static org.mockito.ArgumentMatchers.*;
class DeliveryBusinessFileTest {
@TempDir Path root;
@BeforeAll static void metadata() {
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), "delivery-files"), FileObjectEntity.class);
}
@ParameterizedTest
@ValueSource(strings={"DELIVERY_BOL","DELIVERY_CUSTOMER_DOCUMENT","DELIVERY_BILL_OF_LADING","DELIVERY_POD","DELIVERY_PHOTO","DELIVERY_LOADING_PHOTO","DELIVERY_EXPRESS_RECEIPT"})
void immutableUploadsPreserveOldContentAndRealTimestamp(String type) {
var storage = storage();
var files = mock(FileObjectMapper.class);
var service = service(files, storage);
var ids = new AtomicLong(800);
when(files.insert(any(FileObjectEntity.class))).thenAnswer(call -> {
FileObjectEntity row = call.getArgument(0); row.setId(ids.incrementAndGet()); return 1;
});
var first = service.upload(11, type, "71", 9, new MockMultipartFile("file", "资料 🚢.bin", "application/octet-stream", new byte[]{0,1,2}));
var second = service.upload(11, type, "71", 9, new MockMultipartFile("file", "资料 🚢.bin", "application/octet-stream", new byte[]{3,4}));
var captor = org.mockito.ArgumentCaptor.forClass(FileObjectEntity.class);
verify(files, times(2)).insert(captor.capture());
assertThat(first.fileId()).isNotEqualTo(second.fileId());
assertThat(first.createdAt()).isEqualTo(captor.getAllValues().get(0).getCreatedAt()).isNotNull();
assertThat(first.ownerScope()).isEqualTo("CUSTOMER");
assertThat(storage.read(captor.getAllValues().get(0).getObjectKey())).containsExactly(0,1,2);
assertThat(storage.read(captor.getAllValues().get(1).getObjectKey())).containsExactly(3,4);
verify(files, never()).updateById(any(FileObjectEntity.class));
}
@ParameterizedTest
@ValueSource(strings={"DELIVERY_BOL","DELIVERY_CUSTOMER_DOCUMENT","DELIVERY_BILL_OF_LADING","DELIVERY_POD","DELIVERY_PHOTO","DELIVERY_LOADING_PHOTO","DELIVERY_EXPRESS_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);
var file = mock(MultipartFile.class);
when(file.isEmpty()).thenReturn(false); when(file.getSize()).thenReturn(52428801L);
assertThatThrownBy(() -> storage.store(11,"71",file,category)).isInstanceOf(BizException.class).hasMessageContaining("50MB");
when(file.getSize()).thenReturn(52428800L); when(file.getContentType()).thenReturn("application/x-custom");
when(file.getOriginalFilename()).thenReturn("boundary.custom");
doAnswer(call -> { try (var out=new java.io.RandomAccessFile(((Path)call.getArgument(0)).toFile(),"rw")) { out.setLength(52428800L); } return null; }).when(file).transferTo(any(Path.class));
var stored=storage.store(11,"71",file,category);
assertThat(java.nio.file.Files.size(root.resolve(stored.objectKey()))).isEqualTo(52428800L);
}
@ParameterizedTest
@ValueSource(strings={"DELIVERY_BOL","DELIVERY_CUSTOMER_DOCUMENT","DELIVERY_BILL_OF_LADING","DELIVERY_POD","DELIVERY_PHOTO","DELIVERY_LOADING_PHOTO","DELIVERY_EXPRESS_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 -> {
LambdaQueryWrapper<FileObjectEntity> query=call.getArgument(0);
assertThat(query.getSqlSegment()).contains("tenant_id", "business_type", "business_id", "id", "status", "deleted");
assertThat(query.getParamNameValuePairs().values()).containsExactlyInAnyOrder(12L,type,"72",802L,"ACTIVE",false);
return null;
});
assertThatThrownBy(() -> service.download(12,type,"72",802)).isInstanceOf(BizException.class).hasMessageContaining("归属不匹配");
assertThatThrownBy(() -> service.metadata(0,type,"71",801)).isInstanceOf(BizException.class);
assertThatThrownBy(() -> service.metadata(11,type,"not-an-id",801)).isInstanceOf(BizException.class);
var controller=new InternalBusinessFileController();
ReflectionTestUtils.setField(controller,"expected","test-internal"); ReflectionTestUtils.setField(controller,"service",service);
assertThatThrownBy(() -> controller.download("bad",11,type,"71",801)).isInstanceOf(BizException.class);
verifyNoInteractions(storage);
}
private BusinessFileStorage storage() {
var storage=new BusinessFileStorage(); ReflectionTestUtils.setField(storage,"configuredRoot",root); storage.initializeRoot(); return storage;
}
private InternalBusinessFileService service(FileObjectMapper files, BusinessFileStorage storage) {
var service=new InternalBusinessFileService(); ReflectionTestUtils.setField(service,"files",files); ReflectionTestUtils.setField(service,"storage",storage); return service;
}
}