S3에 저장된 자료들을 ZIP형식으로 다운로드 받고 싶다는 요청이 들어와서 작업한 부분을 정리했다.
/**
* File > ZIP > MultiDownload
*/
public void multiDownload(List<String> filePathList, HttpServletResponse response) throws IOException {
List<S3ObjectInputStream> inputList = new ArrayList<>();
SimpleDateFormat dayFormat = new SimpleDateFormat("yyyyMMddHH:mm:ss");
Date nowDate = Calendar.getInstance().getTime();
String nowDay = dayFormat.format(nowDate).replace(":", "");
String zipFileName = "ALZ_" + nowDay;
FileInputStream fileInputStream = null;
for (String filePath : filePathList) {
S3Object fullObject = s3Client.getObject(bucketName, filePath);
S3ObjectInputStream objectInputStream = fullObject.getObjectContent();
inputList.add(objectInputStream);
}
List<File> fileList = convertListS3ObjectInputStreamToFile(inputList);
File zipFile = File.createTempFile(zipFileName, ".zip");
zipFile.deleteOnExit(); //23.12 add
try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFile))) {
byte[] buf = new byte[1024];
for (File file : fileList) {
if (file.exists()) {
try (FileInputStream fis = new FileInputStream(file)) {
ZipEntry zipEntry = new ZipEntry(file.getName());
out.putNextEntry(zipEntry);
int len;
while ((len = fis.read(buf)) > 0) {
out.write(buf, 0, len);
}
out.closeEntry();
}
}
}
for (File file : fileList) {
deleteFile(file);
}
}
//파일 bytes 변환
byte[] bytes = FileCopyUtils.copyToByteArray(zipFile);
response.setContentType("application/zip;");
response.addHeader("Content-Disposition", "attachment; filename=\"" + zipFileName + ".zip" + "\";");
//파일 copy (inputstream, outpustream)
FileCopyUtils.copy(bytes, response.getOutputStream());
}
//s3추출해온 s3Object를 File로 변환해줘야함
private List<File> convertListS3ObjectInputStreamToFile(List<String> fileNameList, List<S3ObjectInputStream> inputStream) throws IOException {
List<File> files = new ArrayList<>();
int count = 0;
for (S3ObjectInputStream stream : inputStream) {
String path = stream.getHttpRequest().getURI().getPath();
String[] split = path.split("/");
String fileName = fileNameList.get(count);
count++;
File file = convertS3ObjectInputStreamToFile(stream, fileName);
stream.close();
files.add(file);
}
return files;
}
private File convertS3ObjectInputStreamToFile(S3ObjectInputStream inputStream, String fileName) throws IOException{
File file = new File(fileName);
try (OutputStream outputStream = new FileOutputStream(file)) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
}
return file;
}
- 기억을 위한 해석본
/**
* KJE File > ZIP > MultiDownload
* @param : filePathList > 파일 저장 위치 (from DB)
*/
public void multiDownload(List<String> filePathList, HttpServletResponse response) throws IOException {
SimpleDateFormat dayFormat = new SimpleDateFormat("yyyyMMddHH:mm:ss");
Date nowDate = Calendar.getInstance().getTime();
String nowDay = dayFormat.format(nowDate).replace(":", "");
String zipFileName = "CA_" + nowDay; //zip으로 떤질 파일 명
List<S3ObjectInputStream> inputList = new ArrayList<>(); //s3에서 가져온 Content
for (String filePath : filePathList) {
//s3에서 제공하는 aws sdk를 통해 s3버킷에서 객체 꺼내오는 getObject
S3Object fullObject = s3Client.getObject(bucketName, filePath);
//S3Object에 대해 getObjectContent를 호출하여 객체의 내용을 가져올 수 있습니다.
//다음 결과가 반환됩니다. S3오브젝트퍼트스트림표준 Java로 동작하는InputStream객체입니다.
S3ObjectInputStream objectInputStream = fullObject.getObjectContent();
//Zip안에 넣기 위해서 List에 s3에서 받아온 객체 넣기
inputList.add(objectInputStream);
}
//S3ObjectInputStream를 바로 ZipEntry에 넣을 수 없기에 File로 convert 진행
List<File> fileList = convertListS3ObjectInputStreamToFile(inputList);
//임시 파일 생성
File zipFile = File.createTempFile(zipFileName, ".zip");
//new ZipOutputStream zip파일 압축 시작
try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFile))) {
byte[] buf = new byte[1024];
for (File file : fileList) {
if (file.exists()) {
//압축 당할 파일 new FileInputStream(file)
try (FileInputStream fis = new FileInputStream(file)) {
ZipEntry zipEntry = new ZipEntry(file.getName());
//압축 파일에 저장될 파일 : zipEntry
out.putNextEntry(zipEntry);
int len;
//파일 읽기
while ((len = fis.read(buf)) > 0) {
//위에서 읽은 파일을 ZipOutputStream에 write
out.write(buf, 0, len);
}
//zip파일 압축 종료
out.closeEntry();
}
}
}
//위에서 저장을 위해 생성한 파일들 삭제
for (File file : fileList) {
deleteFile(file);
}
}
//파일 bytes 변환
byte[] bytes = FileCopyUtils.copyToByteArray(zipFile);
response.setContentType("application/zip;");
response.addHeader("Content-Disposition", "attachment; filename=\"" + zipFileName + ".zip" + "\";");
//파일 copy (inputstream, outpustream)
FileCopyUtils.copy(bytes, response.getOutputStream());
}
//S3ObjectInputStream List >> FILE로 변환
private List<File> convertListS3ObjectInputStreamToFile(List<S3ObjectInputStream> inputStream) throws IOException {
List<File> files = new ArrayList<>();
for (S3ObjectInputStream stream : inputStream) {
String path = stream.getHttpRequest().getURI().getPath();
String[] split = path.split("/");
String fileName = split[split.length - 1];
File file = convertS3ObjectInputStreamToFile(stream,fileName);
files.add(file);
}
return files;
}
//S3ObjectInputStream 단건 >> FILE 변환
private File convertS3ObjectInputStreamToFile(S3ObjectInputStream inputStream, String fileName) throws IOException{
File file = new File(fileName);
//FileOutputStream 데이터 파일을 바이트 스트림으로 저장하기 위해서 사용된다.
try (OutputStream outputStream = new FileOutputStream(file)) {
byte[] buffer = new byte[1024];
int bytesRead;
//inputStream.read 메소드는 입력 스트림에서 데이터 바이트를 읽는다.
//데이터의 다음 바이트를 반환하거나 파일 끝에 도달하면 -1을 반환한다.
//계속 읽다가 파일 끝에 도달하면 읽은 아이를 bytesRead에 넣고 while문 안의 작업을 진행
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
}
//요기서 return 하는 file은 위의 new FileOutputStream(file) 안으로
return file;
}
- outputStream
https://www.baeldung.com/java-outputstream
- try with resources
파일 저장 및 다운로드 작업에서 이해하기 오래걸렸던 부분이 inputStream, outputStream이다.
- inputStream
바이트 기반 입력 스트림의 최상 클래스
"this abstract class is the superclass of all classes representing an input stream of bytes"
이곳에서 주목할 부부은 represenging an input stream of bytes 이다.
그럼 stream은 무엇일까?
stream : 개별 바이트나 문자열인 데이터의 원헌, 파일을 읽거나 쓸 때, 네트워크 소켓을 거쳐 통신할때 쓰이는 추상적 개념 쉽게 생각하면 데이터 전송 통로이다.
bytes : 가볍게 데이터라고 생각하자 종합적으로 inputstream은 데이터가 들어오는 통로의 역할에 관해 규정하고 있는 추상 클래스이다. 지금까지 총 정리를 하자면 inputStream은 결론적으로 읽는 행위를 하는 아이이다.
현재 inputStream에는 데이터를 읽는 기능과 관련된 메서드가 3개 존재한다.
https://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html
읽기의 메서드들의 확실한 부분은 inputStream으로 읽은 데이터는 다시 돌아가서 읽을 수 없다는 부분이다.
그외
데이터를 스킵하는 행위
데이터가 얼마나 남았는지
읽었던 데이터를 특정 시점부터 다시 읽는다는 행위
그리고 종료를 지원하고 있다.
- OutputStream
데이터가 나가는 통로 역할, 즉 데이터를 쓰는 행동이 가능하다.
보통 데이터를 파일, 메모리, 네트워크로 보내는 상황이 자주 발생한다.
write :
write 자세한 정보
//write(byte[] b, int off, int length) : 오버로드된 버전의 write()
//It can write “length” number of bytes from the byte array as specified by
//the argument starting at an offset determined by “off” to the OutputStream
//들어간 값은 Hello World!
public static void fileOutputStreamByteSubSequence(
String file, String data) throws IOException {
byte[] bytes = data.getBytes(); //hello world의 bytes를 읽고
try (OutputStream out = new FileOutputStream(file)) {
out.write(bytes, 6, 5);
// (length : 5) 5개 작성, (from bytes) hello world!,
//(starting at an offset) 6번재에서 시작
// 결과적으로 hello world의 bytes를 담아서 해당 bytes를 6에서 시작 5글자 작성을
// out write가 처리 한다.
}
}
결과 : World
flush (데이터 비우기) :
close :
- FileOutPutStream
FileOutputStream
https://docs.oracle.com/javase/8/docs/api/java/io/FileOutputStream.html
FileOutputStream(String fileName) : 주어진 이름의 파일을 쓰기 위한 객체를 생성
FileOutputStream(String fileName, boolean append) : 주어진 append 값에 따라 새로운 파일을 생성하거나 기존 내용에 추가
FileOutputStream(String fileName) : 주어진 이름의 파일을 쓰기 위한 객체를 생성
- ETC
while ((bytesRead = inputStream.read(buffer)) != -1) 작업 추가 정보
https://www.techiedelight.com/ko/how-to-read-file-using-inputstream-java/
ZipOutputStream, ZipInputStream >
out : 파일 압축
in : 압축 파일 읽기
2023.12 - temp file이 삭제되지 않은 상태를 확인, 아래 명령어를 추가해주자.
zipFile.deleteOnExit();
createTempFile의 공식 doc을 확인해보면 자동으로 삭제 되기를 원한다면 deleteOnExit 을 사용하도록 이야기한다....
언제나 공식 doc은 자세히 읽어보자..
작업 참고 자료
https://shanepark.tistory.com/273
https://madplay.github.io/post/java-file-zip
개선 작업 예정을 위한 링크
https://gksdudrb922.tistory.com/234
S3 공식문서 참고 (getObjectContent 외 S3Object 관련 메소드)
S3Object 참고문서
https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/examples-s3-objects.html#download-object
InputStream 의문 해결을 위한 참고
https://www.techiedelight.com/ko/how-to-read-file-using-inputstream-java/
'개발 > AWS' 카테고리의 다른 글
AWS + Lambda@Edge + CloudFront 이미지 리사이징 (1) | 2023.08.18 |
---|---|
[AWS] AWS + S3 + Spring boot File 업로드 (0) | 2023.08.07 |
[AWS] Application 백그라운드 (0) | 2023.01.17 |
[AWS] 서버에 파일 복사하기 (0) | 2023.01.17 |
[AWS] EC2 생성, SSH로 접근하기 (0) | 2023.01.17 |