优化
This commit is contained in:
@@ -10,6 +10,7 @@ import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.http.*;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
@@ -43,9 +44,32 @@ public class InternalLoadingEvidenceController {
|
||||
return ApiResponse.success(service.validate(tenantId, inboundId, fileId));
|
||||
}
|
||||
|
||||
@GetMapping("/{tenantId}/{inboundId}/{fileId}/content")
|
||||
public ResponseEntity<byte[]> download(
|
||||
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
|
||||
@PathVariable long tenantId, @PathVariable long inboundId, @PathVariable long fileId) {
|
||||
requireToken(token);
|
||||
if (tenantId <= 0 || inboundId <= 0 || fileId <= 0) throw new BizException(ResultCode.BAD_REQUEST, "租户、入库单和文件ID必须大于0");
|
||||
var value = service.download(tenantId, inboundId, fileId);
|
||||
return ResponseEntity.ok().cacheControl(CacheControl.noStore())
|
||||
.contentType(safeImageMediaType(value.contentType())).contentLength(value.content().length)
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.attachment()
|
||||
.filename(value.fileName(), StandardCharsets.UTF_8).build().toString()).body(value.content());
|
||||
}
|
||||
|
||||
private void requireToken(String token) {
|
||||
if (token == null || !MessageDigest.isEqual(expectedToken, token.getBytes(StandardCharsets.UTF_8))) {
|
||||
throw new BizException(ResultCode.FORBIDDEN, "内部服务凭证无效");
|
||||
}
|
||||
}
|
||||
|
||||
private static MediaType safeImageMediaType(String value) {
|
||||
if (value == null || value.isBlank()) return MediaType.APPLICATION_OCTET_STREAM;
|
||||
try {
|
||||
MediaType mediaType = MediaType.parseMediaType(value);
|
||||
return "image".equalsIgnoreCase(mediaType.getType()) ? mediaType : MediaType.APPLICATION_OCTET_STREAM;
|
||||
} catch (org.springframework.http.InvalidMediaTypeException exception) {
|
||||
return MediaType.APPLICATION_OCTET_STREAM;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-1
@@ -36,7 +36,7 @@ public class PlatformLoadingEvidenceController {
|
||||
requirePlatform(user); requirePositive(tenantId, inboundId, fileId);
|
||||
var value = service.download(tenantId, inboundId, fileId);
|
||||
return ResponseEntity.ok().cacheControl(CacheControl.noStore())
|
||||
.contentType(MediaType.parseMediaType(value.contentType())).contentLength(value.content().length)
|
||||
.contentType(safeImageMediaType(value.contentType())).contentLength(value.content().length)
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.inline()
|
||||
.filename(value.fileName(), StandardCharsets.UTF_8).build().toString()).body(value.content());
|
||||
}
|
||||
@@ -51,4 +51,13 @@ public class PlatformLoadingEvidenceController {
|
||||
private static void requirePositive(long... ids) {
|
||||
for (long id : ids) if (id <= 0) throw new BizException(ResultCode.BAD_REQUEST, "业务ID必须大于0");
|
||||
}
|
||||
private static MediaType safeImageMediaType(String value) {
|
||||
if (value == null || value.isBlank()) return MediaType.APPLICATION_OCTET_STREAM;
|
||||
try {
|
||||
MediaType mediaType = MediaType.parseMediaType(value);
|
||||
return "image".equalsIgnoreCase(mediaType.getType()) ? mediaType : MediaType.APPLICATION_OCTET_STREAM;
|
||||
} catch (InvalidMediaTypeException exception) {
|
||||
return MediaType.APPLICATION_OCTET_STREAM;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package com.baiyue.file.controller;
|
||||
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.file.service.LoadingEvidenceService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@SpringJUnitConfig(InternalLoadingEvidenceController.class)
|
||||
@TestPropertySource(properties = "baiyue.security.internal-service-token=internal-token")
|
||||
class InternalLoadingEvidenceControllerTest {
|
||||
@MockBean LoadingEvidenceService service;
|
||||
@Resource InternalLoadingEvidenceController controller;
|
||||
|
||||
@Test void downloadsOwnedEvidenceBehindInternalToken() {
|
||||
when(service.download(81L, 91L, 101L)).thenReturn(
|
||||
new LoadingEvidenceService.Download("door.jpg", "image/jpeg", new byte[]{1, 2, 3}));
|
||||
|
||||
var response = controller.download("internal-token", 81L, 91L, 101L);
|
||||
|
||||
assertThat(response.getBody()).containsExactly(1, 2, 3);
|
||||
assertThat(response.getHeaders().getContentType().toString()).isEqualTo("image/jpeg");
|
||||
assertThat(response.getHeaders().getCacheControl()).contains("no-store");
|
||||
verify(service).download(81L, 91L, 101L);
|
||||
}
|
||||
|
||||
@Test void rejectsInvalidTokenBeforeReadingEvidence() {
|
||||
assertThatThrownBy(() -> controller.download("wrong", 81L, 91L, 101L))
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("凭证无效");
|
||||
verifyNoInteractions(service);
|
||||
}
|
||||
|
||||
@Test void fallsBackForInvalidOrNonImageContentTypes() {
|
||||
when(service.download(81L, 91L, 101L)).thenReturn(
|
||||
new LoadingEvidenceService.Download("door.jpg", "not a/media/type", new byte[]{1}));
|
||||
when(service.download(81L, 91L, 102L)).thenReturn(
|
||||
new LoadingEvidenceService.Download("door.txt", "text/plain", new byte[]{2}));
|
||||
|
||||
assertThat(controller.download("internal-token", 81L, 91L, 101L).getHeaders().getContentType())
|
||||
.isEqualTo(org.springframework.http.MediaType.APPLICATION_OCTET_STREAM);
|
||||
assertThat(controller.download("internal-token", 81L, 91L, 102L).getHeaders().getContentType())
|
||||
.isEqualTo(org.springframework.http.MediaType.APPLICATION_OCTET_STREAM);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user