백오피스 웹 페이지를 개발하다 보면 빠질 수 없는 기능이 바로 첨부파일 업로드다.
단순히 “파일을 올린다”에서 끝나는 게 아니라,
- 클라이언트 → 서버로 어떤 데이터를 넘기는지
- 서버에서 파일을 어떻게 처리하는지
- DB에는 어떤 메타데이터가 저장되는지
를 전체적으로 이해해야 안정적이고 확장 가능한 업로드 기능을 만들 수 있다.
이번 글에서는 실제 구현 과정을 기준으로 파일 업로드의 전체 Flow를 정리해봤다.
1️⃣ 클라이언트 → 서버 전송 Flow
사용자가 파일을 드래그 앤 드롭하거나 파일 선택 버튼을 누르면 input[type=file]에 파일 객체가 담기게 된다.
// drag & drop 이벤트 리스너
_container.on({
dragover: (e) => {
e.preventDefault(); // 브라우저 기본 동작 취소
},
drop: (e) => {
e.preventDefault();
const files = e.originalEvent.dataTransfer.files;
if (!checkFileType(files)) {
alert("잘못된 파일 형식입니다.");
return;
}
addFile(files);
}
}, ".drag_n_drop");
// 실제 files 객체에 담긴 정보
let file = $("input[name="fileInfo[0]file]"]'
console.log(file[0].files)
"file": {
name: "Screenshot 2025-08-28 at 10.12.02 AM.png",
lastModified: 1756343527862,
lastModifiedDate : Thu Aug 28 2025 10:12:07 GMT+0900 (Korean Standard Time)
size: 56517
type: "image/png"
webkitRelativePath: ""
}
- 허용 확장자: image/*, video/*, text/html
- 잘못된 타입이면 업로드 취소 + 경고창
저장 버튼을 클릭하면 **최초 상태(originFileArr)**와 **최종 상태(finalFileArr)**를 비교한다.
- 새로 추가된 파일 → flag = I (Insert)
- 삭제된 파일 → flag = D (Delete)
플래그를 붙여 finalFileArr를 생성한다.
convertJsonToFormData 메서드를 통해 FormData 객체에 파일과 메타데이터를 append 하고 이 객체를 서버에 전달한다.
const formData = new FormData();
formData.append("file", file);
formData.append("fileDesc", "이벤트 이미지");
👉 왜 FormData를 쓰는가?
- Blob/File 객체를 안전하게 담을 수 있다
- multipart/form-data로 자동 인코딩된다 (boundary 처리 불필요)
- 일반 필드 + 파일을 함께 전송할 수 있다
- fetch/XHR과 호환성이 우수하다
2️⃣ 서버 처리 단계
- Controller:
- multipart/form-data 파싱
- file, saveList(JSON) 등을 DTO/VO에 매핑한다
- Service:
- 실제 파일은 네이버 오브젝트 스토리지에 업로드한다
- 파일 메타데이터를 생성한다
- DB 저장:
- flag = I → Insert
- flag = D → Update(del_yn = Y, use_yn = N)
- DB 응답값:
- 업로드 성공시 아래와 같은 fileList 값을 응답값으로 내려주고, 클라이언트는 이 값들을 hidden input에 바인딩하고, 이벤트 데이터를 저장할 때 fileSeq 컬럼에 값을 저장하게 된다.
// api callback response fileList
[
{
"fileSeq": 36,
"fileDetailSeq": 1,
"fileCode": "EVENT",
"fileTypeCode": "image/png",
"fileOriginalName": "Screenshot 2025-08-28 at 10.12.02 AM.png",
"fileFullPath": "https://kr.object.ncloudstorage.com/dev-bucket-hime/fo/event/Screenshot%202025-08-28%20at%2010.12.02%E2%80%AFAM_20250926141813728.png"
},
{
"fileSeq": 36,
"fileDetailSeq": 2,
"fileCode": "EVENT",
"fileTypeCode": "image/png",
"fileOriginalName": "Screenshot 2025-08-28 at 10.12.02 AM.png",
"fileFullPath": "https://kr.object.ncloudstorage.com/dev-bucket-hime/fo/event/Screenshot%202025-08-28%20at%2010.12.02%E2%80%AFAM_20250926141813983.png"
}
]
3️⃣ DB 저장 구조
파일 관련 테이블에는 보통 아래 정보들이 들어간다.
| file_seq | 파일 그룹 번호 |
| file_detail_seq | 그룹 내 파일 순번 |
| file_code | 구분 (INQUIRY, PROFILE, EVENT 등) |
| file_type_code | MIME 타입 (image/png, video/mp4 등) |
| file_path | 상대 경로 (fo/event/xxxx) |
| file_name | 서버에 저장된 파일명 |
| file_original_name | 업로드 당시 원본 파일명 |
| file_size | 파일 크기 |
| file_full_path | CDN/스토리지 절대 경로 |
| del_yn | 삭제 여부 |
| use_yn | 사용 여부 |
| reg_user_seq / upd_user_seq | 등록/수정한 관리자 |
| reg_member_seq / upd_member_seq | 등록/수정한 유저 |
📌 file_seq + file_detail_seq 조합으로 여러 개의 파일을 그룹핑할 수 있다.
📌 file_path와 file_full_path를 분리하는 이유는, 나중에 스토리지 경로나 CDN이 바뀌어도 path는 동일하게 관리할 수 있기 때문이다.
📂 도대체 내 컴퓨터에 있는 파일이 네이버 클라우드에는 어떻게 저장되는가?
1️⃣ 브라우저에서 파일 선택
사용자가 input[type=file]이나 Drag & Drop으로 파일을 선택하면, 브라우저는 해당 파일을 File 객체로 만들어 관리한다. 이 File 객체는 브라우저가 제공하는 클라이언트 단 메타정보를 담고 있으며, 이 정보가 그대로 스토리지에 저장되는 것은 아니다.
현재까지는 컴퓨터 로컬에 있던 파일이 브라우저 메모리 버퍼에 복사된 상태일 뿐이다.
{
"name": "Screenshot.png",
"lastModified": 1756343527862,
"lastModifiedDate": "Thu Aug 28 2025 10:12:07 GMT+0900",
"size": 56517,
"type": "image/png",
"webkitRelativePath": ""
}
2️⃣ FormData로 서버 전송
브라우저에서 console.log(file)을 찍으면 위와 같이 메타 정보만 확인할 수 있다. 하지만 실제로 FormData를 통해 서버로 전송될 때는, 이 File 객체에 포함된 원본 파일의 바이트 스트림이 HTTP 요청의 body에 담기게 된다.
즉, 우리가 눈으로 보는 것보다 훨씬 많은 데이터가 전송되는 셈이며, 서버는 이 바이트 데이터를 받아 네이버 오브젝트 스토리지 같은 클라우드 스토리지에 실제 파일로 저장할 수 있다.
HTTP 요청 예시 (개략)
POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="Screenshot.png"
Content-Type: image/png
<여기에 실제 PNG 파일의 binary 데이터가 들어감>
------WebKitFormBoundary--
3️⃣ 서버에서 처리
- 서버는 multipart/form-data를 파싱해서 File 데이터를 가져옴
- 파일을 임시 디렉토리에 저장하거나 바로 클라우드 스토리지로 전달
- 필요한 메타데이터(Content-Type, Content-Length 등)를 함께 전달
4️⃣ 네이버 오브젝트 스토리지(NOS)에 저장
스토리지에는 실제 바이너리 데이터 + Object Key + 메타데이터가 저장된다.
| Object Key | fo/event/Screenshot_20250926141813728.png |
| 파일 데이터 | PNG, MP4 등 실제 바이트 스트림 |
| 헤더 기반 메타데이터 | Content-Type, Content-Length, 업로드 시각 |
'Dev Log' 카테고리의 다른 글
| Thymeleaf 개념 정리 (0) | 2025.10.19 |
|---|---|
| Postman + OpenAPI로 타입 체크되는 API 만들기 (0) | 2025.10.12 |
| React Hook Form 활용기 (0) | 2025.10.05 |
| 백오피스 파일 수정 디버깅 과정 (0) | 2025.09.28 |
| IntersectionObserver (2) | 2025.09.14 |