Merge branch 'dev' into dev-lyhfive
# Conflicts: # deploy/sql/头程数据库统一增量-MySQL8.sql # deploy/sql/海外区域统一增量-MySQL8.sql # lastleg-service/src/main/java/com/baiyue/lastleg/service/LoadingCompletedConsumer.java # lastleg-service/src/main/resources/db/lastleg.sql # lastleg-service/src/test/java/com/baiyue/lastleg/service/LoadingCompletedConsumerTest.java
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
package com.baiyue.file.controller;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.file.service.ProductImageService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.http.CacheControl;
|
||||
import org.springframework.http.ContentDisposition;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/** 鐢ㄤ簬骞冲彴绔鍗曡鎯呮煡鐪嬪晢鎴峰晢鍝佸浘鐗囥€? */
|
||||
@RestController
|
||||
@RequestMapping("/api/platform/v1/files/product-images")
|
||||
public class PlatformProductImageController {
|
||||
@Resource private ProductImageService images;
|
||||
|
||||
@GetMapping("/{tenantId}/{fileId}")
|
||||
@PreAuthorize("hasAuthority('platform:order:list')")
|
||||
public ResponseEntity<byte[]> download(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable long tenantId,
|
||||
@PathVariable long fileId) {
|
||||
requirePlatform(user);
|
||||
if (tenantId <= 0 || fileId <= 0) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "瀹㈡埛鍜屾枃浠禝D蹇呴』澶т簬0");
|
||||
}
|
||||
var image = images.download(tenantId, fileId);
|
||||
return ResponseEntity.ok()
|
||||
.cacheControl(CacheControl.noStore())
|
||||
.contentType(MediaType.parseMediaType(image.contentType()))
|
||||
.contentLength(image.content().length)
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.inline()
|
||||
.filename(image.fileName(), StandardCharsets.UTF_8).build().toString())
|
||||
.body(image.content());
|
||||
}
|
||||
|
||||
private static void requirePlatform(AuthenticatedUser user) {
|
||||
if (user == null || !"PLATFORM".equalsIgnoreCase(user.appCode())
|
||||
|| user.tenantId() == null || user.tenantId() != 0L
|
||||
|| user.userId() == null || user.userId() <= 0) {
|
||||
throw new BizException(ResultCode.FORBIDDEN, "闇€瑕佸钩鍙扮韬唤");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ public class InternalBusinessFileService {
|
||||
"CUSTOMS_RECEIPT",
|
||||
"FINANCE_VOUCHER",
|
||||
"CONTAINER_CLEARANCE_DOCUMENT",
|
||||
"LOADING_CUSTOMS_DOCUMENT",
|
||||
"LOADING_CUSTOMS_DECLARATION",
|
||||
"CONTAINER_DELIVERY_ORDER",
|
||||
"CONTAINER_PICKUP_ORDER",
|
||||
"OVERSEAS_UNLOAD_PHOTO",
|
||||
|
||||
@@ -9,9 +9,14 @@ import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class ExportProviderFeignConfigurationTest {
|
||||
@Test void keepsOpenFeignFormEncoderOnTheRuntimeClasspath() {
|
||||
assertDoesNotThrow(() -> Class.forName("feign.form.spring.SpringFormEncoder"));
|
||||
}
|
||||
|
||||
@Test void rejectsAnOversizedProviderResponseBeforeJsonDeserialization() {
|
||||
ExportProviderFeignConfiguration configuration=new ExportProviderFeignConfiguration();
|
||||
Decoder decoder=configuration.exportProviderDecoder(HttpMessageConverters::new);
|
||||
|
||||
+3
-3
@@ -35,14 +35,14 @@ class InternalBusinessFileControllerTest {
|
||||
}
|
||||
|
||||
@Test void zeroTenantDownloadReturnsOwnedContentWithoutCaching() {
|
||||
when(service.download(0L, "LOADING_CUSTOMS_DOCUMENT", "51", 91L))
|
||||
when(service.download(0L, "LOADING_CUSTOMS_DECLARATION", "51", 91L))
|
||||
.thenReturn(new InternalBusinessFileService.Download("declaration.pdf", "application/pdf", new byte[]{1, 2}));
|
||||
|
||||
var response = controller.download("internal-token", 0L, "LOADING_CUSTOMS_DOCUMENT", "51", 91L);
|
||||
var response = controller.download("internal-token", 0L, "LOADING_CUSTOMS_DECLARATION", "51", 91L);
|
||||
|
||||
assertThat(response.getBody()).containsExactly(1, 2);
|
||||
assertThat(response.getHeaders().getCacheControl()).contains("no-store");
|
||||
verify(service).download(0L, "LOADING_CUSTOMS_DOCUMENT", "51", 91L);
|
||||
verify(service).download(0L, "LOADING_CUSTOMS_DECLARATION", "51", 91L);
|
||||
}
|
||||
|
||||
@Test void rejectsInvalidTokenAndNegativeTenantBeforeServiceAccess() {
|
||||
|
||||
@@ -77,6 +77,26 @@ class ProductImageControllerContractTest {
|
||||
assertThatThrownBy(() -> controller.upload(platform, file)).isInstanceOf(BizException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void platformDownloadUsesTenantScopeAndOrderListPermission() throws Exception {
|
||||
RequestMapping mapping = PlatformProductImageController.class.getAnnotation(RequestMapping.class);
|
||||
assertThat(mapping.value()).contains("/api/platform/v1/files/product-images");
|
||||
var download = PlatformProductImageController.class.getDeclaredMethod(
|
||||
"download", AuthenticatedUser.class, long.class, long.class);
|
||||
assertThat(download.getAnnotation(PreAuthorize.class).value())
|
||||
.isEqualTo("hasAuthority('platform:order:list')");
|
||||
|
||||
ProductImageService service = mock(ProductImageService.class);
|
||||
PlatformProductImageController controller = new PlatformProductImageController();
|
||||
ReflectionTestUtils.setField(controller, "images", service);
|
||||
when(service.download(10001L, 31L)).thenReturn(
|
||||
new ProductImageService.Download("product.png", "image/png", new byte[] {1}));
|
||||
|
||||
var platform = new AuthenticatedUser(1L, 0L, 0L, 1L, "admin", "PLATFORM", "WEB", Set.of());
|
||||
assertThat(controller.download(platform, 10001L, 31L).getBody()).containsExactly(1);
|
||||
verify(service).download(10001L, 31L);
|
||||
}
|
||||
|
||||
private static AuthenticatedUser merchant(long tenantId) {
|
||||
return new AuthenticatedUser(2L, tenantId, tenantId, 2L, "merchant", "MERCHANT", "WEB", Set.of());
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ class InternalBusinessFileServiceTest {
|
||||
});
|
||||
|
||||
var clearance = service.upload(0L, "CONTAINER_CLEARANCE_DOCUMENT", "41", 7L, file);
|
||||
var customs = service.upload(0L, "LOADING_CUSTOMS_DOCUMENT", "51", 7L, file);
|
||||
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);
|
||||
@@ -57,7 +57,7 @@ class InternalBusinessFileServiceTest {
|
||||
InternalBusinessFileService.Metadata::businessId).containsExactly(0L, "CONTAINER_CLEARANCE_DOCUMENT", "41");
|
||||
assertThat(customs).extracting(InternalBusinessFileService.Metadata::tenantId,
|
||||
InternalBusinessFileService.Metadata::businessType,
|
||||
InternalBusinessFileService.Metadata::businessId).containsExactly(0L, "LOADING_CUSTOMS_DOCUMENT", "51");
|
||||
InternalBusinessFileService.Metadata::businessId).containsExactly(0L, "LOADING_CUSTOMS_DECLARATION", "51");
|
||||
assertThat(deliveryOrder.businessType()).isEqualTo("CONTAINER_DELIVERY_ORDER");
|
||||
assertThat(pickupOrder.businessType()).isEqualTo("CONTAINER_PICKUP_ORDER");
|
||||
assertThat(unloadPhoto).extracting(InternalBusinessFileService.Metadata::tenantId,
|
||||
@@ -119,22 +119,23 @@ class InternalBusinessFileServiceTest {
|
||||
|
||||
@Test void platformMetadataAndDownloadRequireExactCompositeOwnership() {
|
||||
FileObjectEntity value = new FileObjectEntity(); value.setId(91L); value.setTenantId(0L);
|
||||
value.setBusinessType("LOADING_CUSTOMS_DOCUMENT"); value.setBusinessId("51");
|
||||
value.setBusinessType("LOADING_CUSTOMS_DECLARATION"); value.setBusinessId("51");
|
||||
value.setOriginalName("declaration.pdf"); value.setObjectKey("business-files/0/51/declaration.pdf");
|
||||
value.setContentType("application/pdf"); value.setSha256("c".repeat(64)); value.setSizeBytes(8L);
|
||||
when(files.selectOne(any())).thenReturn(value, value, null, null, null);
|
||||
when(storage.read(value.getObjectKey())).thenReturn("document".getBytes());
|
||||
|
||||
assertThat(service.metadata(0L, "LOADING_CUSTOMS_DOCUMENT", "51", 91L).fileName())
|
||||
assertThat(service.metadata(0L, "LOADING_CUSTOMS_DECLARATION", "51", 91L).fileName())
|
||||
.isEqualTo("declaration.pdf");
|
||||
assertThat(service.download(0L, "LOADING_CUSTOMS_DOCUMENT", "51", 91L).content())
|
||||
assertThat(service.download(0L, "LOADING_CUSTOMS_DECLARATION", "51", 91L).content())
|
||||
.isEqualTo("document".getBytes());
|
||||
assertThatThrownBy(() -> service.metadata(0L, "CONTAINER_CLEARANCE_DOCUMENT", "51", 91L))
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("归属不匹配");
|
||||
assertThatThrownBy(() -> service.metadata(0L, "LOADING_CUSTOMS_DOCUMENT", "52", 91L))
|
||||
assertThatThrownBy(() -> service.metadata(0L, "LOADING_CUSTOMS_DECLARATION", "52", 91L))
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("归属不匹配");
|
||||
assertThatThrownBy(() -> service.metadata(0L, "LOADING_CUSTOMS_DOCUMENT", "51", 92L))
|
||||
assertThatThrownBy(() -> service.metadata(0L, "LOADING_CUSTOMS_DECLARATION", "51", 92L))
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("归属不匹配");
|
||||
verify(storage).read(value.getObjectKey());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user