📚 Multipart 파일 업로드: 개념부터 클라우드 저장까지 정리
1️⃣ HTTP 기반 Multipart 업로드 개요
| 항목 | 설명 |
|---|
| 전송 방식 | HTTP POST 요청, Content-Type: multipart/form-data |
| 구조 | 각 form 필드가 boundary로 구분된 파트로 구성됨 |
| 구분 기준 | Content-Disposition 헤더의 filename 존재 여부로 "파일/텍스트" 구분 |
| 예시 | 파일 + 텍스트 데이터가 함께 전달됨 (Input, textarea, file 등) |
2️⃣ Spring에서 Multipart 파일 처리
| 항목 | 설명 |
|---|
| 핵심 클래스 | MultipartFile, MultipartResolver, MultipartHttpServletRequest |
| 주요 메서드 | getOriginalFilename(), getBytes(), getInputStream(), transferTo() |
| 처리 과정 | HTTP 요청 → MultipartResolver 파싱 → MultipartFile 객체 생성 |
| 파일 수신 예 | @RequestParam("file") MultipartFile file |
| 다중 파일 | MultipartFile[], List<MultipartFile>로 처리 |
3️⃣ MultipartFile vs File 비교
| 구분 | MultipartFile | File |
|---|
| 정의 | 업로드된 파일을 추상화한 객체 (Spring 제공) | 서버 로컬의 물리적 파일 (java.io.File) |
| 생성 위치 | 클라이언트 요청 시 자동 생성 | 개발자가 저장 경로 지정 후 생성 |
| 용도 | 요청으로 받은 파일 처리 | 디스크에 저장하거나 조작할 때 |
| 변환 방법 | file.transferTo(new File(...)) 또는 InputStream 직접 저장 | |
4️⃣ MultipartFile → File 변환 과정
| 단계 | 설명 |
|---|
| ① 수신 | 클라이언트가 multipart/form-data로 파일 전송 |
| ② 파싱 | Spring이 이를 MultipartFile로 파싱 |
| ③ 추출 | 바이너리: getInputStream(), getBytes() |
| ④ 저장 | transferTo(File) 또는 직접 FileOutputStream 사용 |
5️⃣ 클라우드 저장소 업로드 전략
✅ 단일 업로드 (Single PUT Upload)
- 사용 예: 5MB 이하의 파일
- 코드 간단 (
putObject 한 번 호출)
✅ 분할 업로드 (Multipart Upload)
- 사용 예: 대용량 파일, 안정성 중요할 때
- 구조:
- 업로드 초기화 (
uploadId)
- 파일을 일정 크기로 분할
- 조각을 병렬로 업로드
- 업로드 완료 요청 (
completeMultipartUpload)
- 장점:
- 병렬 처리로 빠름
- 실패 시 해당 파트만 재업로드 가능
- 메모리 효율 좋음
6️⃣ 연결 흐름 다이어그램 (요약)
scss
복사편집
[클라이언트]
↓ (multipart/form-data 요청)
[Spring 서버]
↓ (MultipartResolver 파싱)
[MultipartFile 객체]
↓
[File 변환 (transferTo or stream)]
↓
[로컬 저장 or 클라우드 전송 (S3 등)]
↳ (단일 업로드 or 분할 업로드)
📦 파일 다운로드 뷰란?
Spring MVC에서 클라이언트가 파일을 다운로드할 수 있도록 응답을 구성해주는 뷰(View) 객체 또는 컨트롤러 로직.
- 업로드: 클라이언트 → 서버
- 다운로드: 서버 → 클라이언트 (브라우저가 저장 창 띄우는 거)
Spring에서는 파일을 HTTP 응답(ResponseEntity 또는 StreamingResponseBody)로 만들어서 다운로드하도록 처리해.
✅ 기본적인 파일 다운로드 방법 (Spring 방식)
📄 1. 일반적인 파일 다운로드 컨트롤러
java
복사편집
@GetMapping("/download")
public ResponseEntity<Resource> download(@RequestParam String filename) throws IOException {
Path path = Paths.get("upload-dir/" + filename);
Resource resource = new UrlResource(path.toUri());
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"")
.body(resource);
}
🔍 설명:
| 요소 | 의미 |
|---|
Resource | 다운로드할 파일 자원 (UrlResource, FileSystemResource 등) |
Content-Disposition | attachment; filename="..." → 다운로드 창 뜨게 함 |
MediaType | 보통 application/octet-stream (범용 이진 파일 타입) |
ResponseEntity | 직접 HTTP 응답 구성 가능 |
💎 옛날 방식: View 객체 사용 (예: AbstractView 상속)
Spring 3 이전 또는 커스터마이징된 View로 다운로드 구현할 때 사용하던 방식
java
복사편집
public class FileDownloadView extends AbstractView {
public FileDownloadView() {
setContentType("application/download; charset=utf-8");
}
@Override
protected void renderMergedOutputModel(Map<String, Object> model,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
File file = (File) model.get("downloadFile");
response.setContentType(getContentType());
response.setContentLength((int) file.length());
response.setHeader("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"");
try (InputStream in = new FileInputStream(file);
OutputStream out = response.getOutputStream()) {
FileCopyUtils.copy(in, out);
}
}
}
- 지금은 거의 쓰지 않고,
ResponseEntity<Resource>로 대체하는 추세
🧠 정리
| 구분 | 설명 |
|---|
| 업로드 | 클라이언트가 파일을 multipart/form-data로 전송 |
| 다운로드 | 서버가 파일을 HTTP 응답의 body에 실어서 내려줌 |
| 파일 다운로드 뷰 | 파일을 내려주는 응답(파일 스트림 + Content-Disposition 설정) |
| 추천 방식 | ResponseEntity<Resource> 또는 StreamingResponseBody |
📚 파일 업로드 & 다운로드 + 첨부파일 시스템 설계 정리
🧱 1. 기본 구조: 파일 업로드와 다운로드 개념
| 구분 | 설명 |
|---|
| 📤 업로드 | 클라이언트 → 서버, 파일 전송 (multipart/form-data) |
| 📥 다운로드 | 서버 → 클라이언트, 파일 바이너리 전송 (Content-Disposition: attachment) |
| 📎 첨부파일 기능 | 사용자가 업로드한 파일을 저장하고, 이후에 다시 열람하거나 다운로드할 수 있는 기능 |
🧩 2. Spring에서 MultipartFile 처리 방식
| 항목 | 설명 |
|---|
| MultipartFile | Spring에서 HTTP 요청의 파일 파트를 추상화한 객체 |
| File | Java에서 실제 디스크에 존재하는 파일 |
| 처리 흐름 | MultipartFile → InputStream or byte[] → File or 클라우드 저장소로 전송 |
| 파일 저장 방식 | transferTo(File), getInputStream(), 또는 클라우드 SDK 이용 |
🗃️ 3. 첨부파일 저장 전략
✅ 주요 전략 2가지
| 전략 | 설명 |
|---|
| DB 직접 저장 (BLOB) | 파일을 DB에 직접 넣는 방식 (소규모 시스템에서만 사용) |
| DB + 파일시스템/S3 | 파일은 서버 디스크나 S3에 저장하고, DB엔 메타정보만 저장 (대세) |
📁 DB에 저장되는 메타정보 예시
sql
복사편집
CREATE TABLE file (
id BIGINT,
original_name VARCHAR,
s3_key VARCHAR,
content_type VARCHAR,
size BIGINT,
uploaded_by BIGINT,
uploaded_at DATETIME
);
🚚 4. 다운로드 처리
| 방식 | 설명 |
|---|
| ResponseEntity | 가장 보편적인 Spring 방식 |
| HttpServletResponse + Stream | 대용량 파일 스트리밍에 유리 |
| Presigned URL | 클라이언트가 직접 클라우드(S3 등)에서 다운로드 (서버 무부하) |
⚙️ 5. S3에 저장하는 다양한 전략
✅ 전략 1: 서버에서 S3로 바로 업로드
- 서버가
MultipartFile을 받아 s3Client.putObject()로 업로드
- 간단하지만 서버 리소스 소모
✅ 전략 2: Presigned URL (서명된 URL)을 통한 직접 업로드
- 서버가 클라이언트에 업로드용 Presigned URL을 발급
- 클라이언트가 해당 URL로 S3에 직접 PUT
- 서버는 업로드 확정 시점에만 개입
java
복사편집
PresignedPutObjectRequest url = presigner.presignPutObject(...);
js
복사편집
// 클라이언트에서 직접 S3로 업로드
fetch(presignedUrl, { method: "PUT", body: fileBlob });
🧠 6. 임시 저장 전략 정리
| 전략 | 설명 | 활용 시점 |
|---|
| 메모리 임시 저장 | MultipartFile.getBytes() → 임시 저장 | 파일 작을 때만 |
| 디스크 임시 저장 | 파일을 /tmp 등에 저장 후 확정 시 클라우드 전송 | 중간 검토/승인 필요할 때 |
| S3 임시 업로드 | upload/temp/... 경로에 저장 → 확정 시 이동 | 서버리스/대용량 시스템에 유리 |
| S3 Presigned URL 업로드 | 클라이언트가 직접 S3에 업로드, 서버는 확정만 처리 | 확장성과 효율성이 가장 좋음 |
🧰 7. 3번 + Presigned URL 전략 (추천)
사용자 업로드 → S3에 임시 저장 (temp/) → 확정 시 S3 내부 이동 + DB 저장
흐름 요약
markdown
복사편집
1. 서버: presigned PUT URL 생성 → 프론트로 전달
2. 프론트: S3에 직접 파일 PUT (upload/temp/)
3. 서버: 확정 요청 시 → S3 경로 이동 or 태그 변경
4. 서버: DB에 정식 파일 정보 저장
- 서버 무부하
- 사용자 "확정" 전까지 불필요한 저장 방지
- 정리 작업도 자동화 가능 (temp 경로 정기 삭제)
✅ 최종 정리 요약표
| 항목 | 추천 전략 |
|---|
| 파일 업로드 처리 | MultipartFile → S3 or File |
| 대용량 업로드 | Presigned URL + 클라이언트 직접 PUT |
| 확정되기 전 상태 유지 | S3에 임시 저장 (temp/) or 서버 디스크에 임시 파일 |
| 첨부파일 시스템 구성 | S3 Key + 파일 정보만 DB에 저장, 실제 파일은 클라우드 |
| 다운로드 처리 | S3에서 서버 스트리밍 or Presigned GET URL |