티스토리 뷰
반응형
Node에서 활용하는 멀티파트 파일의 다운로드를 진행했었다면, 이번 글에서는 멀티파트 파일의 업로드 및 다운로드를 Java에서 진행하는 것으로 작성해보려 합니다.
먼저 FileController 입니다.
FileController.java
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.UUID;
@RestController
@RequiredArgsConstructor
@RequestMapping("/v1/files")
public class FileController {
private final FileService fileService;
@PostMapping
public ResponseEntity<?> upload(@RequestParam("data") MultipartFile data,
@RequestParam("dirName") String dirName) throws IOException {
File file = fileService.upload(data, dirName);
UploadResponse uploadResponse = UploadResponse.builder()
.fileId(file.getId().toString())
.originName(file.getName())
.path(file.getPath())
.size(file.getSize())
.extension(file.getExtension())
.build();
return ResponseEntity.ok().body(uploadResponse);
}
@GetMapping("/{id}")
public ResponseEntity<Resource> download(@PathVariable("id") String id) throws IOException {
File downloadFile = fileService.download(id);
return ResponseEntity.ok()
.contentType(MediaType.valueOf(MediaType.APPLICATION_OCTET_STREAM_VALUE))
.contentLength(downloadFile.getSize())
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + downloadFile.getFullName())
.header(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate")
.header(HttpHeaders.PRAGMA, "no-cache")
.header(HttpHeaders.EXPIRES, "0")
.body(downloadFile.getResource());
}
}
그 다음으로 이를 수행하는 FileService 클래스입니다.
FileService.java
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException
import static org.springframework.util.StringUtils.getFilenameExtension;
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class FileService {
private final FileRepository fileRepository;
private final FileUploadRepository fileUploadRepository;
public File upload(MultipartFile multipartFile, String dirName) throws IOException {
String originalFilename = multipartFile.getOriginalFilename();
String uploadPath = fileUploadRepository.upload(multipartFile, dirName);
File file = new File(originalFilename, uploadPath, getFilenameExtension(originalFilename), multipartFile.getSize());
return fileRepository.save(file);
}
@Transactional(readOnly = true)
public File download(final String id) throws IOException {
File file = fileRepository.findById(id)
.orElseThrow();
String uploadPath = file.getPath();
Resource downloadResource = fileUploadRepository.download(uploadPath, file.getFullName());
file.setResource(downloadResource);
return file;
}
}
FileRepository의 경우 JpaRepository이므로 데이터 이력을 위하여 필요한 Repository입니다. 우리에게 필요한 것은 AWS S3에 파일을 업로드하는 소스이므로 FileUploadRepository 내부를 확인해보겠습니다.
FileUploadRepository.java
import com.amazonaws.AmazonServiceException;
import com.amazonaws.SdkClientException;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.*;
import com.amazonaws.util.IOUtils;
import kr.co.packagename.encryption.Sha256Encryption;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Optional;
import static org.springframework.util.StringUtils.getFilename;
import static org.springframework.util.StringUtils.getFilenameExtension;
@Service
@RequiredArgsConstructor
public class FileUploadRepository {
private final AmazonS3Client amazonS3Client;
private final Sha256Encryption sha256Encryption;
@Value("${cloud.aws.s3.bucket}")
private String bucket;
@Value("${spring.profiles.active}")
private String profile;
@Value("${cloud.aws.s3.bucketUrl}")
private String bucketUrl;
public String upload(MultipartFile multipartFile, String dirName) throws IOException {
File uploadFile = convert(multipartFile).orElseThrow(() -> new IllegalArgumentException("MultipartFile -> File로 전환이 실패했습니다."));
return upload(uploadFile, dirName);
}
private String upload(File uploadFile, String dirName) throws IOException {
String extension = getFilenameExtension(uploadFile.getName());
String encryptFileName = sha256Encryption.encryptString(getFilename(uploadFile.getName())) + "." + extension;
String fileName = Path.of(profilePath(), dirName, datePath(), encryptFileName).toString();
String uploadImageUrl = putS3(uploadFile, fileName);
removeNewFile(uploadFile.getPath());
return uploadImageUrl;
}
private String putS3(File uploadFile, String fileName) {
amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, uploadFile).withCannedAcl(CannedAccessControlList.PublicRead));
return amazonS3Client.getUrl(bucket, fileName).toString();
}
private void removeNewFile(String filePath) throws IOException {
Files.delete(Paths.get(filePath));
}
private Optional<File> convert(MultipartFile file) throws IOException {
File convertFile = new File(file.getOriginalFilename());
if (convertFile.createNewFile()) {
try (FileOutputStream fos = new FileOutputStream(convertFile)) {
fos.write(file.getBytes());
}
return Optional.of(convertFile);
}
return Optional.empty();
}
private String datePath() {
return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy" + File.separator
+ "MM" + File.separator + "dd"));
}
private String profilePath() {
return ProfilePath.valueOf(profile.toUpperCase())
.getPath();
}
public Resource download(String path, String fileName) throws IOException {
S3Object headerOverrideObject = null;
ByteArrayResource byteArrayResource = null;
try {
ResponseHeaderOverrides headerOverrides = new ResponseHeaderOverrides()
.withCacheControl("No-cache")
.withContentDisposition("attachment; filename=" + fileName);
GetObjectRequest getObjectRequestHeaderOverride = new GetObjectRequest(bucket, path.replace(bucketUrl,""))
.withResponseHeaders(headerOverrides);
headerOverrideObject = amazonS3Client.getObject(getObjectRequestHeaderOverride);
byteArrayResource = new ByteArrayResource(IOUtils.toByteArray(headerOverrideObject.getObjectContent()));
} catch (AmazonServiceException e) {
e.printStackTrace();
} catch (SdkClientException e) {
e.printStackTrace();
} finally {
if (headerOverrideObject != null) {
headerOverrideObject.close();
}
}
return byteArrayResource;
}
@Getter
enum ProfilePath {
DEV("development"), STAGE("development"), PROD("production"), TEST("test");
private String path;
ProfilePath(String path) {
this.path = path;
}
}
}
보시게 되면 먼저 운영환경에 따라 프로필로 구분을 지어 파일 업로드에 대한 처리를 진행합니다. AWS에서 계정은 이미 발급이 받았다는 가정 하에, 할당 받은 버킷을 application.yml 내부에 값을 저장하고 그 값을 추출하여 적용하는 것으로 개발 진행하였습니다. 암호화 모듈의 경우 각 회사별로 사용하는 방식이 다르기 때문에 예시로 SHA256 암호화를 진행하였습니다.
반응형
'Server' 카테고리의 다른 글
Java 의 LocalDateTime과 Hibernate (0) | 2021.06.23 |
---|---|
[Java] LocalDate, LocalTime, LocalDateTime 파보기 (0) | 2021.06.22 |
[Java] 암호화의 기초와 적용해보기 (0) | 2021.06.21 |
Nginx 설치와 기본 환경 설정 (0) | 2021.06.18 |
API Throttling 에 대해서 (0) | 2021.06.17 |
PM2 를 활용한 서비스 운영하기 (0) | 2021.06.15 |
댓글
공지사항