优化
This commit is contained in:
+59
@@ -0,0 +1,59 @@
|
||||
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.InternalBusinessFileService;
|
||||
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/merchant/v1/files/order-batch-imports")
|
||||
public class MerchantOrderBatchImportFileController {
|
||||
private static final String ERROR_REPORT = "ORDER_BATCH_IMPORT_ERROR_REPORT";
|
||||
|
||||
@Resource private InternalBusinessFileService files;
|
||||
|
||||
@GetMapping("/{batchNo}/error-reports/{fileId}")
|
||||
@PreAuthorize("hasAuthority('merchant:order:create')")
|
||||
public ResponseEntity<byte[]> downloadErrorReport(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable String batchNo,
|
||||
@PathVariable long fileId) {
|
||||
var value = files.download(merchantTenant(user), ERROR_REPORT, batchNo, fileId);
|
||||
MediaType contentType;
|
||||
try {
|
||||
contentType = MediaType.parseMediaType(value.contentType());
|
||||
} catch (IllegalArgumentException exception) {
|
||||
contentType = MediaType.APPLICATION_OCTET_STREAM;
|
||||
}
|
||||
return ResponseEntity.ok()
|
||||
.cacheControl(CacheControl.noStore())
|
||||
.contentType(contentType)
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||
ContentDisposition.attachment().filename(value.fileName(), StandardCharsets.UTF_8).build().toString())
|
||||
.contentLength(value.content().length)
|
||||
.body(value.content());
|
||||
}
|
||||
|
||||
private static long merchantTenant(AuthenticatedUser user) {
|
||||
if (user == null || !"MERCHANT".equalsIgnoreCase(user.appCode())
|
||||
|| user.tenantId() == null || user.tenantId() <= 0
|
||||
|| user.userId() == null || user.userId() <= 0) {
|
||||
throw new BizException(ResultCode.FORBIDDEN, "Merchant identity is required");
|
||||
}
|
||||
return user.tenantId();
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,23 @@ info:
|
||||
x-lifecycle-owner: ORDER
|
||||
x-file-object-semantics: IMMUTABLE_CREATE_ONLY
|
||||
paths:
|
||||
/api/merchant/v1/files/order-batch-imports/{batchNo}/error-reports/{fileId}:
|
||||
get:
|
||||
summary: 客户下载本客户批量导入错误报告
|
||||
description: 需要merchant:order:create;客户作用域只取登录态,文件类别固定ORDER_BATCH_IMPORT_ERROR_REPORT。
|
||||
security: [{bearerAuth: []}]
|
||||
parameters:
|
||||
- {$ref: '#/components/parameters/BatchNo'}
|
||||
- {$ref: '#/components/parameters/FileId'}
|
||||
responses:
|
||||
'200':
|
||||
description: 精确归属的XLSX错误报告
|
||||
headers:
|
||||
Cache-Control: {schema: {type: string, enum: [no-store]}}
|
||||
Content-Disposition: {schema: {type: string}}
|
||||
content:
|
||||
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
|
||||
schema: {type: string, format: binary}
|
||||
/internal/file/v1/business-files/{tenantId}/{businessType}/{batchNo}:
|
||||
post:
|
||||
summary: Order上传批量导入源文件或错误报告
|
||||
@@ -62,6 +79,7 @@ paths:
|
||||
schema: {type: string, format: binary}
|
||||
components:
|
||||
securitySchemes:
|
||||
bearerAuth: {type: http, scheme: bearer, bearerFormat: JWT}
|
||||
internalServiceToken: {type: apiKey, in: header, name: X-Internal-Service-Token}
|
||||
parameters:
|
||||
OperatorId:
|
||||
|
||||
+2
@@ -27,6 +27,8 @@ class QualificationAndOrderFileOpenApiContractTest {
|
||||
|
||||
assertThat(yaml)
|
||||
.contains("ORDER_BATCH_IMPORT_SOURCE", "ORDER_BATCH_IMPORT_ERROR_REPORT")
|
||||
.contains("/api/merchant/v1/files/order-batch-imports/{batchNo}/error-reports/{fileId}:")
|
||||
.contains("需要merchant:order:create")
|
||||
.contains("ownerScope: {type: string, enum: [CUSTOMER]}")
|
||||
.contains("pattern: '^OBI-[0-9A-F]{20}$'")
|
||||
.contains("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package com.baiyue.file.controller;
|
||||
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.file.service.InternalBusinessFileService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class MerchantOrderBatchImportFileControllerTest {
|
||||
@Test
|
||||
void downloadsOnlyTheAuthenticatedCustomersErrorReportAsNoStoreXlsx() {
|
||||
var files = mock(InternalBusinessFileService.class);
|
||||
var controller = new MerchantOrderBatchImportFileController();
|
||||
ReflectionTestUtils.setField(controller, "files", files);
|
||||
String batchNo = "OBI-0123456789ABCDEF0123";
|
||||
byte[] content = {0x50, 0x4b, 0x03, 0x04};
|
||||
when(files.download(10001L, "ORDER_BATCH_IMPORT_ERROR_REPORT", batchNo, 81L))
|
||||
.thenReturn(new InternalBusinessFileService.Download("order-batch-errors.xlsx",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", content));
|
||||
|
||||
var response = controller.downloadErrorReport(merchant(), batchNo, 81L);
|
||||
|
||||
assertThat(response.getBody()).containsExactly(content);
|
||||
assertThat(response.getHeaders().getContentType()).isEqualTo(MediaType.parseMediaType(
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"));
|
||||
assertThat(response.getHeaders().getCacheControl()).contains("no-store");
|
||||
assertThat(response.getHeaders().getContentDisposition().getType()).isEqualTo("attachment");
|
||||
assertThat(response.getHeaders().getContentDisposition().getFilename()).isEqualTo("order-batch-errors.xlsx");
|
||||
verify(files).download(10001L, "ORDER_BATCH_IMPORT_ERROR_REPORT", batchNo, 81L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void routeRequiresOrderCreateAndRejectsPlatformIdentity() throws Exception {
|
||||
var method = MerchantOrderBatchImportFileController.class.getMethod("downloadErrorReport",
|
||||
AuthenticatedUser.class, String.class, long.class);
|
||||
assertThat(method.getAnnotation(PreAuthorize.class).value())
|
||||
.isEqualTo("hasAuthority('merchant:order:create')");
|
||||
|
||||
var files = mock(InternalBusinessFileService.class);
|
||||
var controller = new MerchantOrderBatchImportFileController();
|
||||
ReflectionTestUtils.setField(controller, "files", files);
|
||||
var platform = new AuthenticatedUser(1L, 0L, 0L, 1L, "admin", "PLATFORM", "WEB", Set.of());
|
||||
|
||||
assertThatThrownBy(() -> controller.downloadErrorReport(platform,
|
||||
"OBI-0123456789ABCDEF0123", 81L)).isInstanceOf(BizException.class);
|
||||
verifyNoInteractions(files);
|
||||
}
|
||||
|
||||
private static AuthenticatedUser merchant() {
|
||||
return new AuthenticatedUser(2L, 10001L, 10001L, 2L, "merchant", "MERCHANT", "WEB", Set.of());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user