본문 바로가기

Dev Log

백오피스 파일 업로드 Flow 정리

728x90

백오피스 웹 페이지를 개발하다 보면 빠질 수 없는 기능이 바로 첨부파일 업로드다.
단순히 “파일을 올린다”에서 끝나는 게 아니라,

  • 클라이언트 → 서버로 어떤 데이터를 넘기는지
  • 서버에서 파일을 어떻게 처리하는지
  • 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: ""
}
 
checkFileType을 통해 파일 validation 과정을 거친다.
  • 허용 확장자: 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️⃣ 서버 처리 단계

 

  1. Controller:
    • multipart/form-data 파싱
    • file, saveList(JSON) 등을 DTO/VO에 매핑한다
  2. Service:
    • 실제 파일은 네이버 오브젝트 스토리지에 업로드한다
    • 파일 메타데이터를 생성한다
  3. DB 저장:
    • flag = I → Insert
    • flag = D → Update(del_yn = Y, use_yn = N)
  4. 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️⃣ 서버에서 처리

  1. 서버는 multipart/form-data를 파싱해서 File 데이터를 가져옴
  2. 파일을 임시 디렉토리에 저장하거나 바로 클라우드 스토리지로 전달
  3. 필요한 메타데이터(Content-Type, Content-Length 등)를 함께 전달

 

4️⃣ 네이버 오브젝트 스토리지(NOS)에 저장

스토리지에는 실제 바이너리 데이터 + Object Key + 메타데이터가 저장된다.

항목예시
Object Key fo/event/Screenshot_20250926141813728.png
파일 데이터 PNG, MP4 등 실제 바이트 스트림
헤더 기반 메타데이터 Content-Type, Content-Length, 업로드 시각
728x90

'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