Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package leets.bookmark.domain.file.application.dto.response;

import lombok.Builder;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.MediaType;

@Builder
public record FetchedThumbnailResponse(
InputStreamResource inputStreamResource,
MediaType mediaType
) {}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ public enum FileErrorCode implements ErrorCode {
FILE_NOT_FOUND_EXCEPTION(404,"해당하는 파일을 찾을 수 없습니다."),
INVALID_FILE_EXTENSION(400, "올바르지 않은 파일 확장자입니다."),
FILE_OWNER_MISMATCH_EXCEPTION(403, "파일 소유자만 접근 가능합니다."),
S3_UPLOAD_EXCEPTION(500, "파일 업로드에 실패하였습니다.");
S3_UPLOAD_EXCEPTION(500, "파일 업로드에 실패하였습니다."),
IMAGE_FETCH_EXCEPTION(500, "이미지를 가져올 수 없습니다.");

private final int errorCode;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package leets.bookmark.domain.file.application.exception;

import leets.bookmark.global.common.exception.BusinessException;

public class ImageFetchException extends BusinessException {
public ImageFetchException(){
super(FileErrorCode.IMAGE_FETCH_EXCEPTION);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

import leets.bookmark.domain.bookmark.domain.entity.Bookmark;
import leets.bookmark.domain.file.application.dto.request.FileSaveRequest;
import leets.bookmark.domain.file.application.dto.response.FetchedThumbnailResponse;
import leets.bookmark.domain.file.application.dto.response.FileResponse;
import leets.bookmark.domain.file.domain.entity.File;
import leets.bookmark.domain.file.domain.entity.enums.FileType;
import leets.bookmark.domain.user.domain.entity.User;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;

@Component
Expand Down Expand Up @@ -41,4 +44,12 @@ public FileResponse toFileResponse(File file){
.updatedAt(file.getUpdatedAt())
.build();
}

public FetchedThumbnailResponse toFetchedThumbnailResponse(InputStreamResource inputStreamResource,
MediaType mediaType){
return FetchedThumbnailResponse.builder()
.inputStreamResource(inputStreamResource)
.mediaType(mediaType)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
import leets.bookmark.domain.bookmark.domain.entity.Bookmark;
import leets.bookmark.domain.file.application.dto.request.FileSaveRequest;
import leets.bookmark.domain.file.application.dto.request.FileUpdateRequest;
import leets.bookmark.domain.file.application.dto.response.FetchedThumbnailResponse;
import leets.bookmark.domain.file.application.dto.response.FileResponse;
import leets.bookmark.domain.file.application.dto.response.PresignedUrlResponse;
import leets.bookmark.domain.file.application.exception.FileOwnerMismatchException;
import leets.bookmark.domain.file.application.exception.ImageFetchException;
import leets.bookmark.domain.file.application.exception.InvalidFileExtensionException;
import leets.bookmark.domain.file.application.mapper.FileMapper;
import leets.bookmark.domain.file.application.mapper.PreSignedMapper;
Expand All @@ -16,10 +18,20 @@
import leets.bookmark.domain.user.domain.entity.User;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.util.Optional;

@Slf4j
@Validated
@Service
@RequiredArgsConstructor
Expand Down Expand Up @@ -99,21 +111,40 @@ public void deleteFile(User user, Bookmark bookmark) {
fileDeleteService.delete(file);
}

private void validateFileOwner(User user, File file){
if(!(user.getId().equals(file.getUser().getId()))){
throw new FileOwnerMismatchException();
public FetchedThumbnailResponse getThumbnailImage(String thumbnailUrl) {
try {
String fileName = extractFileNameWithExtension(thumbnailUrl);
FileType fileType = getValidatedFileType(thumbnailUrl);

URI uri = URI.create(thumbnailUrl);

URL url = new URL(uri.toASCIIString());

HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");

InputStream inputStream = connection.getInputStream();

String contentType = Optional.ofNullable(connection.getContentType())
.orElse("application/octet-stream");
MediaType mediaType = MediaType.parseMediaType(contentType);

return fileMapper.toFetchedThumbnailResponse(new InputStreamResource(inputStream), mediaType);

} catch (Exception e){
log.warn("썸네일 이미지를 가져오는 중 오류 발생. URL: {}", thumbnailUrl, e);
throw new ImageFetchException();
}
}

private String getExtension(String fileName){
if(fileName == null || !fileName.contains(".")){
throw new InvalidFileExtensionException();
private void validateFileOwner(User user, File file){
if(!(user.getId().equals(file.getUser().getId()))){
throw new FileOwnerMismatchException();
}
return fileName.substring(fileName.lastIndexOf(".")).toLowerCase();
}

private FileType getValidatedFileType(String fileName){
return FileType.fromExtension(getExtension(getExtension(fileName)))
return FileType.fromFileName(fileName)
.orElseThrow(InvalidFileExtensionException::new);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,6 @@ public enum FileType {

private final String extension;

public static Optional<FileType> fromExtension(String ext){
if(ext == null){
return Optional.empty();
}
return Stream.of(FileType.values())
.filter(fileType -> fileType.getExtension().equalsIgnoreCase(ext))
.findFirst();
}

public static Optional<FileType> fromFileName(String fileName) {
return Stream.of(FileType.values())
.filter(fileType -> fileName.toLowerCase().contains(fileType.getExtension()))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package leets.bookmark.domain.file.presentation;

import io.swagger.v3.oas.annotations.tags.Tag;
import leets.bookmark.domain.file.application.dto.response.FetchedThumbnailResponse;
import leets.bookmark.domain.file.application.dto.response.PresignedUrlResponse;
import leets.bookmark.domain.file.application.usecase.FileUseCase;
import leets.bookmark.global.common.response.CommonResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@Tag(name = "FILE", description = "파일 API")
Expand All @@ -21,4 +24,12 @@ public CommonResponse<PresignedUrlResponse> getPresignedUrl(@RequestParam String
return CommonResponse.createSuccess(FileResponseCode.PRE_SIGNED_URL_SUCCESS.getMessage(), response);
}

@GetMapping("/thumbnail-image")
public ResponseEntity<Resource> getThumbnailImage(@RequestParam String thumbnailUrl) {
FetchedThumbnailResponse response = fileUseCase.getThumbnailImage(thumbnailUrl);
return ResponseEntity.ok()
.contentType(response.mediaType())
.body(response.inputStreamResource());
}

}