[File] 이미지 다운로드 뷰 방식 변경
This commit is contained in:
@@ -14,14 +14,16 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.ContentDisposition;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
@Slf4j
|
||||
@Tag(
|
||||
@@ -183,10 +185,10 @@ public class FileController {
|
||||
summary = "파일 보기"
|
||||
, description = "권한 확인 후 Nginx X-Accel-Redirect로 파일을 inline 조회합니다.")
|
||||
@GetMapping("/view/{fileUuid}")
|
||||
public ResponseEntity<Void> fileView(
|
||||
public ResponseEntity<Resource> fileView(
|
||||
@PathVariable String fileUuid
|
||||
, HttpServletRequest request
|
||||
) {
|
||||
) throws IOException {
|
||||
FileDownloadDto fileDownloadDto = new FileDownloadDto();
|
||||
fileDownloadDto.setFileUuid(fileUuid);
|
||||
fileDownloadDto.setEventType("VIEW");
|
||||
@@ -200,33 +202,36 @@ public class FileController {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
if (fileService.isRedirectMode() || fileService.isLocalTestRequest(request)) {
|
||||
return ResponseEntity.status(HttpStatus.FOUND)
|
||||
.location(URI.create(fileService.buildPublicViewUrl(target.getSavePath())))
|
||||
.build();
|
||||
Path filePath = fileService.resolveStoredFilePath(target.getSavePath());
|
||||
|
||||
if (!Files.exists(filePath) || !Files.isRegularFile(filePath)) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add("X-Accel-Redirect", fileService.buildAccelRedirectPath(target.getSavePath()));
|
||||
headers.add(HttpHeaders.CONTENT_TYPE,
|
||||
target.getContentType() == null ? "application/octet-stream" : target.getContentType());
|
||||
headers.add(HttpHeaders.CONTENT_DISPOSITION,
|
||||
Resource resource = new InputStreamResource(Files.newInputStream(filePath));
|
||||
String contentType = target.getContentType() == null
|
||||
? "application/octet-stream"
|
||||
: target.getContentType();
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.parseMediaType(contentType))
|
||||
.contentLength(Files.size(filePath))
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||
ContentDisposition.inline()
|
||||
.filename(target.getOriginName(), StandardCharsets.UTF_8)
|
||||
.build()
|
||||
.toString());
|
||||
|
||||
return new ResponseEntity<>(headers, HttpStatus.OK);
|
||||
.toString())
|
||||
.body(resource);
|
||||
}
|
||||
|
||||
@Operation(
|
||||
summary = "파일 다운로드"
|
||||
, description = "권한 확인 후 파일 다운로드 로그를 남기고 redirect 또는 Nginx X-Accel-Redirect로 파일 다운로드 합니다.")
|
||||
@GetMapping("/download/{fileUuid}")
|
||||
public ResponseEntity<Void> fileDownload(
|
||||
public ResponseEntity<Resource> fileDownload(
|
||||
@PathVariable String fileUuid
|
||||
, HttpServletRequest request
|
||||
) {
|
||||
) throws IOException {
|
||||
FileDownloadDto fileDownloadDto = new FileDownloadDto();
|
||||
fileDownloadDto.setFileUuid(fileUuid);
|
||||
fileDownloadDto.setEventType("DOWNLOAD");
|
||||
@@ -240,22 +245,25 @@ public class FileController {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
if (fileService.isRedirectMode() || fileService.isLocalTestRequest(request)) {
|
||||
return ResponseEntity.status(HttpStatus.FOUND)
|
||||
.location(URI.create(fileService.buildPublicDownloadUrl(target.getSavePath())))
|
||||
.build();
|
||||
Path filePath = fileService.resolveStoredFilePath(target.getSavePath());
|
||||
|
||||
if (!Files.exists(filePath) || !Files.isRegularFile(filePath)) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add("X-Accel-Redirect", fileService.buildAccelRedirectPath(target.getSavePath()));
|
||||
headers.add(HttpHeaders.CONTENT_TYPE,
|
||||
target.getContentType() == null ? "application/octet-stream" : target.getContentType());
|
||||
headers.add(HttpHeaders.CONTENT_DISPOSITION,
|
||||
Resource resource = new InputStreamResource(Files.newInputStream(filePath));
|
||||
String contentType = target.getContentType() == null
|
||||
? "application/octet-stream"
|
||||
: target.getContentType();
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.parseMediaType(contentType))
|
||||
.contentLength(Files.size(filePath))
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||
ContentDisposition.attachment()
|
||||
.filename(target.getOriginName(), StandardCharsets.UTF_8)
|
||||
.build()
|
||||
.toString());
|
||||
|
||||
return new ResponseEntity<>(headers, HttpStatus.OK);
|
||||
.toString())
|
||||
.body(resource);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,12 +47,6 @@ public class FileService {
|
||||
@Value("${file.upload.final-root}")
|
||||
private String uploadFinalRoot;
|
||||
|
||||
@Value("${file.download.redirect-prefix:/protected-files}")
|
||||
private String fileDownloadRedirectPrefix;
|
||||
|
||||
@Value("${file.download.mode:accel}")
|
||||
private String fileDownloadMode;
|
||||
|
||||
private final FileMapper fileMapper;
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
@@ -656,63 +650,20 @@ public class FileService {
|
||||
return fileDetailInfo;
|
||||
}
|
||||
|
||||
public String buildAccelRedirectPath(String savePath) {
|
||||
public Path resolveStoredFilePath(String savePath) {
|
||||
if (savePath == null || savePath.isBlank()) {
|
||||
throw new IllegalArgumentException("savePath is empty");
|
||||
}
|
||||
|
||||
String prefix = (fileDownloadRedirectPrefix == null || fileDownloadRedirectPrefix.isBlank())
|
||||
? "/protected-files"
|
||||
: fileDownloadRedirectPrefix.trim();
|
||||
Path root = Paths.get(uploadFinalRoot).normalize().toAbsolutePath();
|
||||
String relativePath = savePath.startsWith("/") ? savePath.substring(1) : savePath;
|
||||
|
||||
if (!prefix.startsWith("/")) {
|
||||
prefix = "/" + prefix;
|
||||
Path resolved = root.resolve(relativePath).normalize();
|
||||
|
||||
if (!resolved.startsWith(root)) {
|
||||
throw new IllegalArgumentException("invalid savePath");
|
||||
}
|
||||
|
||||
return prefix + savePath;
|
||||
}
|
||||
|
||||
public boolean isRedirectMode() {
|
||||
return "redirect".equalsIgnoreCase(fileDownloadMode);
|
||||
}
|
||||
|
||||
public String buildPublicViewUrl(String savePath) {
|
||||
return buildPublicTestUrl("/public-test-view", savePath);
|
||||
}
|
||||
|
||||
public String buildPublicDownloadUrl(String savePath) {
|
||||
return buildPublicTestUrl("/public-test-download", savePath);
|
||||
}
|
||||
|
||||
private String buildPublicTestUrl(String prefix, String savePath) {
|
||||
if (savePath == null || savePath.isBlank()) {
|
||||
throw new IllegalArgumentException("savePath is empty");
|
||||
}
|
||||
|
||||
String baseUrl = publicBaseUrl == null ? "" : publicBaseUrl.trim();
|
||||
if (baseUrl.endsWith("/")) {
|
||||
baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
|
||||
}
|
||||
|
||||
return baseUrl + prefix + savePath;
|
||||
}
|
||||
|
||||
public boolean isLocalTestRequest(HttpServletRequest request) {
|
||||
return containsLocalhost(request.getHeader("Referer"))
|
||||
|| containsLocalhost(request.getHeader("Origin"))
|
||||
|| containsLocalhost(request.getHeader("Host"))
|
||||
|| containsLocalhost(request.getHeader("X-Forwarded-Host"));
|
||||
}
|
||||
|
||||
public boolean containsLocalhost(String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String lower = value.toLowerCase();
|
||||
return lower.contains("localhost")
|
||||
|| lower.contains("127.0.0.1")
|
||||
|| lower.contains("[::1]")
|
||||
|| lower.contains("::1");
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,9 +52,6 @@ file:
|
||||
interrupt-seconds: 30
|
||||
auth-cache:
|
||||
ttl-seconds: 20
|
||||
download:
|
||||
mode: redirect
|
||||
redirect-prefix: /public-test-files
|
||||
|
||||
springdoc:
|
||||
api-docs:
|
||||
|
||||
@@ -54,9 +54,6 @@ file:
|
||||
interrupt-seconds: 30
|
||||
auth-cache:
|
||||
ttl-seconds: 20
|
||||
download:
|
||||
mode: accel
|
||||
redirect-prefix: /protected-files
|
||||
|
||||
springdoc:
|
||||
api-docs:
|
||||
|
||||
Reference in New Issue
Block a user