Files
Aislo/.agent/complete_and_old/plan_b03_file_input_redesign.md
T
2026-07-10 16:41:22 +09:00

32 KiB

B03 FileInput 페이지 재설계 및 대용량 파일 업로드 통합 계획서

작성일: 2026-07-09
상태: 최종 검토 대기
범위: 파일 입력 UI/UX 개선 + 30GB 대용량 파일 안전 업로드 (Service Worker + Chunked Upload)


1. 현황 분석

1.1 현재 구조

  • 파일 선택: 드래그앤드롭 또는 클릭으로 다중 파일 선택
  • 파일 목록: 텍스트 리스트 형태로 표시
  • 필수/선택 항목: 프론트엔드에서만 검증 (뚜렷한 구분 없음)
  • 파일 크기 제한: 기존 설정 (변경: 30GB로 상향)
  • 삭제 기능: 없음
  • 대용량 업로드: 지원하지 않음 (브라우저 탭 닫으면 중단)

1.2 문제점

  • 복수 파일 선택 시 사용자 체험 개선 필요
  • 필수/선택 파일 구분이 명확하지 않음
  • 파일 개별 삭제 불가능
  • 20GB 이상 대용량 파일 업로드 시 탭 닫으면 중단
  • 업로드 진행 상황 시각화 부족

2. 재설계 목표

2.1 UI/UX 개선 사항

  1. 카드 기반 UI: 각 파일 타입을 개별 카드로 시각화
  2. 그룹핑: 파일 타입별 그룹 (필수 vs 선택)
  3. 아이콘 표시: 파일 타입 아이콘으로 직관성 향상
  4. 개별 삭제: 각 카드의 X 버튼으로 취소 기능
  5. 메타정보: 파일명, 용량(MB), 파일 타입 표시

2.2 대용량 파일 업로드 기능

  1. Service Worker: 브라우저 백그라운드 업로드
  2. 청크 분할: 1GB 단위로 파일 분할 (config 조절 가능)
  3. 진행률 표시: 개별 파일별 진행 바 (용량, 속도, 예상시간)
  4. 중단 및 재개: 브라우저 닫아도 계속, 재방문 시 자동 감지
  5. 메일 알림: 분석 완료 후 사용자 이메일로 발송

2.3 기술 요구사항

  • 기존 라우터 / 저장소 로직 변경 최소화
  • TypeScript / CSS 전용 (UI 레이어)
  • Service Worker 별도 구현
  • 백엔드: 청크 업로드 엔드포인트 추가
  • DB: upload_sessions, upload_chunks 테이블 추가

3. 상세 설계 - 파일 선택 UI

3.1 파일 분류 체계

필수 파일 그룹 (Required)

파일 타입 확장자 설명
포인트클라우드 .las, .laz 3D 포인트 데이터 (정확히 1개)
좌표계 정의 .prj EPSG 좌표계 정보
래스터 좌표 .tfw 래스터 파일의 지리좌표 매핑

선택 파일 그룹 (Optional)

파일 타입 확장자 설명
지형 래스터 .tif DEM (디지털 고도 모델)
기존 도면 .dxf 참조용 도면 데이터

3.2 페이지 전체 레이아웃

헤더 (기존 유지)

Title: "파일 입력"
Subtitle: "3D 포인트클라우드 및 지형 데이터 업로드"

드래그앤드롭 영역 (기존 유지)

┌─────────────────────────────────────────┐
│ 파일을 여기에 드래그하거나 클릭하세요   │
│                                         │
│         📁 파일 선택                    │
│                                         │
│ 지원 형식: .las, .laz, .tif, .tfw, .prj, .dxf
└─────────────────────────────────────────┘

카드 그룹 영역 (신규)

필수 파일 그룹

┌─────────────────────────────────────────────────────────┐
│ REQUIRED FILES (필수)                                   │
├─────────────────────────────────────────────────────────┤
│  ┌──────────────────────┐  ┌──────────────────────┐    │
│  │ ☁️  Point Cloud  ❌  │  │ 🌍  Projection  ❌  │    │
│  │ (LAS/LAZ)            │  │ (.prj)               │    │
│  │                      │  │                      │    │
│  │ [Select File]        │  │ [Select File]        │    │
│  └──────────────────────┘  └──────────────────────┘    │
│  ┌──────────────────────┐                               │
│  │ 📏  Raster Coord ❌  │                               │
│  │ (.tfw)               │                               │
│  │                      │                               │
│  │ [Select File]        │                               │
│  └──────────────────────┘                               │
└─────────────────────────────────────────────────────────┘

선택 파일 그룹

┌─────────────────────────────────────────────────────────┐
│ OPTIONAL FILES (선택)                                   │
├─────────────────────────────────────────────────────────┤
│  ┌──────────────────────┐  ┌──────────────────────┐    │
│  │ 🗺️  Terrain DEM  ❌  │  │ 📐  CAD Drawing  ❌ │    │
│  │ (.tif)               │  │ (.dxf)               │    │
│  │                      │  │                      │    │
│  │ [Select File]        │  │ [Select File]        │    │
│  └──────────────────────┘  └──────────────────────┘    │
└─────────────────────────────────────────────────────────┘

3.3 카드 상태별 표시

파일 선택 전 상태 (미선택)

┌──────────────────────────────────┐
│ ☁️  Point Cloud             ❌   │
│ (LAS/LAZ)                        │
│                                  │
│        [Select File]             │
└──────────────────────────────────┘

파일 선택 후 상태 (선택됨)

┌──────────────────────────────────┐
│ ☁️  Point Cloud             ❌   │
│ sample.las                       │
│ 245.67 MB                        │
└──────────────────────────────────┘

파일 업로드 중 상태 (진행 중)

┌──────────────────────────────────────────┐
│ ☁️  Point Cloud                    ❌   │
│ sample.las | 245.67 MB                   │
├──────────────────────────────────────────┤
│ ████████████████░░░░░░░░░░░░░░░░░░░░  │
│ 진행: 123.45 GB / 245.67 GB (50%)       │
│ 속도: 25.3 MB/s                         │
│ 예상 완료: 약 17분 후                  │
└──────────────────────────────────────────┘

파일 에러 상태 (타입 불일치, 크기 초과 등)

┌──────────────────────────────────────────┐
│ ☁️  Point Cloud                    ❌   │
│ sample.las                               │
│ 245.67 MB                                │
├──────────────────────────────────────────┤
│ ❌ Error: File size exceeds 30 GB       │
└──────────────────────────────────────────┘

에러 메시지 표시 위치:

  • 카드의 하단 (또는 프로그래스 바 자리)에 빨간색 배경
  • 아이콘:
  • 메시지: 구체적인 에러 이유 (타입 불일치, 크기 초과, 중복 등)

3.4 파일 선택 흐름

  1. 사용자가 드래그앤드롭 영역 또는 "파일 선택" 버튼 클릭
  2. 파일 익스플로러 열림
  3. 1개 이상 파일 선택 가능 → 복수 번 반복 가능
  4. 선택된 각 파일이 해당 카드에 자동 배치:
    • .las/.laz → Point Cloud 슬롯
    • .prj → Projection 슬롯
    • .tfw → Raster Coord 슬롯
    • .tif → Terrain DEM 슬롯
    • .dxf → CAD Drawing 슬롯

파일이 배치될 때 실시간 검증:

  • 파일명 표시
  • 파일 크기 표시 (MB 단위)
  • 타입 불일치 시 에러 표시
  • 크기 초과 시 에러 표시

3.5 파일 개별 제거

동작:

  1. 카드 우상단 X 버튼 클릭
  2. 파일 카드 초기화 (미선택 상태로 복귀)
  3. 업로드 중인 파일이면 임시 청크 자동 삭제 (IndexedDB + 백엔드 임시 폴더)

3.6 업로드 전 최종 검증

  • 필수 파일 3개 (LAS/LAZ, PRJ, TFW) 확인
  • LAS/LAZ 파일 정확히 1개 확인
  • 각 파일 크기 초과 여부 확인 (최대 30GB)
  • 에러 없는 상태: "Upload" 버튼 활성화
  • 에러 있는 상태: "Upload" 버튼 비활성화 + 에러 메시지 표시

4. 대용량 파일 업로드 기술 아키텍처

4.1 Service Worker란?

❌ PC 제어 (위험한 개념)
   - 사용자의 PC 리소스를 강제로 점유
   - 보안 위험 ⚠️

✅ 브라우저 제어 (안전한 개념)
   - 브라우저 프로세스 내에서만 동작
   - 사용자가 명시적으로 허가한 범위 내에서만 작동
   - 브라우저 설정에서 언제든 비활성화 가능
   - 웹 표준 API (W3C)

안전성:

  • 브라우저 샌드박스 내에서만 작동
  • 네트워크 요청만 가능 (파일 시스템 직접 접근 X)
  • 사용자 동의 없이 설치 불가능
  • 메모리/리소스는 브라우저가 관리

4.2 청크 업로드 흐름

【파일 선택 및 업로드 시작】
  ↓
【Service Worker 등록 확인】
  ├─ 없으면: 새로 등록
  └─ 있으면: 기존 활용
  ↓
【파일을 청크로 분할】
  ├─ 청크 크기: 1GB (config 조절 가능)
  ├─ 예: 30GB = 30개 청크
  └─ 각 청크 메타데이터 저장 (IndexedDB)
  ↓
【사용자가 "업로드 시작" 버튼 클릭】
  ├─ Service Worker에 시작 신호
  ├─ 청크 1: POST /api/projects/{id}/chunks
  ├─ 청크 2: POST /api/projects/{id}/chunks
  └─ ...계속 순차 업로드
  ↓
【UI 업데이트 (10초 주기)】
  ├─ 현재 진행: 15 / 30 chunks
  ├─ 용량: 15GB / 30GB
  ├─ 속도: 25 MB/s
  └─ 예상 시간: 1시간 23분
  ↓
【브라우저/탭 닫음】
  ├─ Service Worker는 계속 실행
  ├─ IndexedDB에 상태 저장
  └─ 백그라운드 업로드 계속
  ↓
【사용자가 다시 페이지 방문】
  ├─ IndexedDB 확인
  ├─ 중단된 세션 감지
  ├─ "중단된 업로드 감지: 15/30 chunks"
  ├─ [업로드 재개] 버튼 표시
  └─ (사용자 클릭 대기)
  ↓
【사용자가 "업로드 재개" 버튼 클릭】
  ├─ Service Worker에 재개 신호
  ├─ 완료된 청크는 스킵
  ├─ 청크 16부터 계속 업로드
  └─ 진행상황 10초 주기 업데이트
  ↓
【모든 청크 업로드 완료】
  ├─ Service Worker → 백엔드 "완료" 신호
  ├─ 백엔드에서 청크 병합
  └─ IndexedDB 정리
  ↓
【파일 분석 시작】(백엔드 비동기)
  ├─ 포인트클라우드 메타데이터 분석
  ├─ 좌표계 추출 (EPSG)
  └─ 기하학적 정보 추출
  ↓
【분석 완료 후 메일 전송】
  ├─ 사용자 계정 이메일로 발송
  ├─ 메일 내용: "파일 분석 완료"
  ├─ 포함 정보: 파일명, 분석 결과 요약
  └─ UI에도 성공 메시지 표시

4.3 재방문 시 UI

사용자가 다시 페이지에 접근했을 때:

┌────────────────────────────────────────────────┐
│ ⚠️  중단된 업로드 감지됨                      │
├────────────────────────────────────────────────┤
│ 파일: sample.las (245.67 GB)                  │
├────────────────────────────────────────────────┤
│ ████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░  │
│ 진행: 123.45 GB / 245.67 GB (완료: 50%)      │
│ 마지막 속도: 25.3 MB/s                        │
│ 예상 완료: 약 17분 후                        │
│                                                │
│ [업로드 재개] [새로운 파일] [취소]            │
└────────────────────────────────────────────────┘

5. 기술 구현 명세

5.1 파일 변경

frontend/

  • B03_FileInput_UI_Page.ts - 카드 기반 UI + Service Worker 통합
  • B03_FileInput_UI_Style.css - 카드 스타일 + 진행 바 스타일
  • B03_FileInput_ServiceWorker.ts - 백그라운드 청크 업로드 처리 (신규)
  • (선택) 별도 컴포넌트로 분리: B03_FileInput_Card.ts

backend/

  • B03_FileInput_Router.py - 기존 업로드 API + 청크 업로드 엔드포인트 추가
  • B03_FileInput_Repository.py - 변경 없음
  • B03_FileInput_Engine.py - 필요 시 청크 병합 로직 추가
  • (신규) 메일 발송 로직 통합 (분석 완료 후)

db/

  • upload_sessions 테이블 생성
  • upload_chunks 테이블 생성

5.2 데이터 구조

파일 슬롯 상태

type FileSlot = "las_laz" | "prj" | "tfw" | "tif" | "dxf";

interface FileSlotState {
  slot: FileSlot;
  label: string;
  icon: string;
  is_required: boolean;
  file?: File;
  upload_session_id?: string;
  upload_status?: "pending" | "uploading" | "completed" | "failed";
  error?: string;
}

IndexedDB 업로드 세션

{
  key: "upload_session_{project_id}_{file_name_hash}_{file_size}",
  value: {
    upload_session_id: "uuid",
    project_id: "uuid",
    file_name: "sample.las",
    file_size_bytes: 30 * 1024 * 1024 * 1024,  // 30GB
    file_size_mb: 30720,
    chunk_size_bytes: 1024 * 1024 * 1024,      // 1GB
    total_chunks: 30,
    completed_chunks: 15,
    start_time: 1720531200000,
    last_update: 1720531800000,
    last_speed_mbs: 25.3,
    status: "paused",  // "paused" | "uploading" | "completed"
    chunks: [
      { index: 0, status: "completed" },
      { index: 1, status: "completed" },
      // ...
      { index: 15, status: "pending" }
    ]
  }
}

// 중복 감지 기준:
// - 파일명 + 파일크기 같으면: 기존 세션 재개
// - 다르면: 새 세션 생성

백엔드 DB (MariaDB)

CREATE TABLE upload_sessions (
  id VARCHAR(36) PRIMARY KEY,
  project_id VARCHAR(36) NOT NULL,
  original_filename VARCHAR(255),
  file_size_bytes BIGINT,
  chunk_size_bytes INT,
  total_chunks INT,
  completed_chunks INT,
  status ENUM('in_progress', 'completed', 'failed'),
  created_at TIMESTAMP,
  updated_at TIMESTAMP,
  FOREIGN KEY (project_id) REFERENCES projects(id)
);

CREATE TABLE upload_chunks (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  session_id VARCHAR(36),
  chunk_index INT,
  chunk_hash VARCHAR(64),
  size_bytes INT,
  stored_at VARCHAR(255),
  completed_at TIMESTAMP,
  FOREIGN KEY (session_id) REFERENCES upload_sessions(id),
  UNIQUE KEY (session_id, chunk_index)
);

5.3 설정 파라미터 (config)

프론트엔드 (config_frontend.ts)

export const UPLOAD_CHUNK_SIZE_MB = 1024;  // 1GB (조절 가능)
export const UPLOAD_MAX_MB = 30 * 1024;    // 30GB
export const UPLOAD_MAX_FILES = 5;
export const UPLOAD_ALLOWED_EXT = [".las", ".laz", ".tif", ".tfw", ".prj", ".dxf"];
export const PROGRESS_UPDATE_INTERVAL_MS = 10000;  // 10초
export const SERVICE_WORKER_PATH = "/service-worker.js";

백엔드 (config_system.py)

UPLOAD_CHUNK_SIZE_BYTES = 1024 * 1024 * 1024  # 1GB
UPLOAD_MAX_BYTES = 30 * 1024 * 1024 * 1024    # 30GB
UPLOAD_MAX_FILES = 5
CHUNK_TEMP_DIR = "storage/chunks_temp"
CHUNK_RETENTION_HOURS = 24
MERGE_TIMEOUT_SECONDS = 3600
SEND_ANALYSIS_COMPLETION_EMAIL = True
EMAIL_RETRY_COUNT = 3

5.4 UI 템플릿화 (Template-driven Components)

목표: 재사용 가능한 카드 컴포넌트를 템플릿으로 작성

카드 템플릿 (HTML)

<!-- B03_FileInput_Card_Template.html -->
<template id="file-card-template">
  <div class="b03-file__card" data-slot-id="">
    <div class="b03-file__card-header">
      <span class="b03-file__card-icon" aria-label="파일 타입"></span>
      <span class="b03-file__card-label"></span>
      <button class="b03-file__card-remove" aria-label="파일 제거"></button>
    </div>
    
    <div class="b03-file__card-content">
      <div class="b03-file__file-info">
        <span class="b03-file__file-name"></span>
        <span class="b03-file__file-size"></span>
      </div>
      
      <div class="b03-file__progress-section" style="display: none;">
        <div class="b03-file__progress-bar-container">
          <div class="b03-file__progress-bar"></div>
        </div>
        <div class="b03-file__progress-info">
          <span class="b03-file__progress-bytes"></span>
          <span class="b03-file__progress-speed"></span>
          <span class="b03-file__progress-eta"></span>
        </div>
      </div>
      
      <div class="b03-file__error-message" style="display: none;"></div>
    </div>
  </div>
</template>

템플릿 활용 방식

// 템플릿에서 카드 클론 생성
function createFileCard(slot: FileSlot, slotConfig: SlotConfig): HTMLElement {
  const template = document.getElementById('file-card-template');
  const card = template.content.cloneNode(true) as DocumentFragment;
  
  const cardElement = card.querySelector('.b03-file__card');
  cardElement.setAttribute('data-slot-id', slot);
  
  card.querySelector('.b03-file__card-icon').textContent = slotConfig.icon;
  card.querySelector('.b03-file__card-label').textContent = slotConfig.label;
  
  return cardElement;
}

// 그룹 템플릿으로 필수/선택 파일 그룹 생성
function renderFileGroups(requiredSlots: FileSlot[], optionalSlots: FileSlot[]) {
  const requiredGroup = createCardGroup('REQUIRED FILES', requiredSlots);
  const optionalGroup = createCardGroup('OPTIONAL FILES', optionalSlots);
  
  return [requiredGroup, optionalGroup];
}

템플릿화의 장점:

  • 카드 상태 (미선택/선택됨/진행중/에러) 변경 시 className만 수정
  • DOM 리렌더링 최소화
  • 코드 재사용성 극대화
  • 일관된 UI 유지

5.5 함수 목록

함수 역할
createFileCard() 템플릿에서 카드 엘리먼트 생성
createCardGroup() 필수/선택 파일 그룹 생성
initializeSlots() 모든 카드 슬롯 초기화
renderFileGroups() 필수/선택 파일 그룹 렌더링
onFileSelected(files) 파일 선택 처리
assignFileToSlot(file) 파일 → 슬롯 자동 배치
removeFile(slot) 파일 제거 + 청크 삭제
validateSlots() 최종 검증
setCardState(slot, state) 카드 상태 변경 (className 수정)
startChunkedUpload() Service Worker에 업로드 시작 신호
resumeChunkedUpload() Service Worker에 재개 신호
detectPausedUploads() IndexedDB에서 중단된 세션 감지
updateProgressBar() 진행 바 실시간 업데이트 (10초 주기)
showErrorMessage(slot, error) 카드 에러 메시지 표시
clearErrorMessage(slot) 카드 에러 메시지 제거

5.6 CSS 클래스 (신규)

레이아웃 관련

.b03-file__group                  /* 파일 그룹 컨테이너 (필수/선택) */
.b03-file__group-title            /* 그룹 제목 (REQUIRED/OPTIONAL) */
.b03-file__group-content          /* 그룹 내 카드 컨테이너 */

카드 기본 구조

.b03-file__card                   /* 개별 카드 컨테이너 */
.b03-file__card-header            /* 카드 헤더 (아이콘, 라벨, X 버튼) */
.b03-file__card-content           /* 카드 본문 (파일정보, 진행바, 에러) */
.b03-file__card-icon              /* 파일 타입 아이콘 */
.b03-file__card-label             /* 파일 카테고리 라벨 */
.b03-file__card-remove            /* X 버튼 (삭제) */

카드 상태 클래스 (변경자)

.b03-file__card--empty            /* 미선택 상태 (기본) */
.b03-file__card--selected         /* 파일 선택됨 상태 */
.b03-file__card--uploading        /* 업로드 진행 중 상태 */
.b03-file__card--completed        /* 업로드 완료 상태 */
.b03-file__card--error            /* 에러 상태 */

파일 정보

.b03-file__file-info              /* 파일명 + 크기 컨테이너 */
.b03-file__file-name              /* 파일명 텍스트 */
.b03-file__file-size              /* 파일 크기 텍스트 */

진행 바 (업로드 중)

.b03-file__progress-section       /* 진행 바 섹션 컨테이너 */
.b03-file__progress-bar-container /* 진행 바 외부 컨테이너 */
.b03-file__progress-bar           /* 진행 바 (fill) */
.b03-file__progress-info          /* 진행 정보 컨테이너 */
.b03-file__progress-bytes         /* "123.45 GB / 245.67 GB (50%)" */
.b03-file__progress-speed         /* "25.3 MB/s" */
.b03-file__progress-eta           /* "약 17분 후" */

에러 메시지

.b03-file__error-message          /* 에러 메시지 컨테이너 */
.b03-file__error-icon             /* 에러 아이콘 (❌) */
.b03-file__error-text             /* 에러 텍스트 */

상태 표시 활용 예

/* 카드 상태에 따라 표시/숨김 */
.b03-file__card--empty .b03-file__file-info { display: none; }
.b03-file__card--empty .b03-file__progress-section { display: none; }
.b03-file__card--empty .b03-file__error-message { display: none; }

.b03-file__card--selected .b03-file__file-info { display: block; }
.b03-file__card--selected .b03-file__progress-section { display: none; }

.b03-file__card--uploading .b03-file__progress-section { display: block; }
.b03-file__card--uploading .b03-file__file-info { opacity: 0.7; }

.b03-file__card--error .b03-file__error-message { display: block; }
.b03-file__card--error .b03-file__progress-section { display: none; }

5.7 백엔드 API 명세

청크 업로드 엔드포인트

@router.post("/{project_id}/chunks")
async def upload_chunk(
    project_id: UUID,
    session_id: str,
    chunk_index: int,
    chunk_data: UploadFile = File(...)
):
    """
    단일 청크 저장
    - 세션 존재 확인
    - 청크 저장 (임시 폴더)
    - 체크섬 계산 (SHA256)
    - DB에 기록
    """

업로드 완료 엔드포인트

@router.post("/{project_id}/finalize")
async def finalize_upload(
    project_id: UUID,
    session_id: str,
    total_chunks: int
):
    """
    모든 청크 업로드 완료 후 병합
    1. 모든 청크 존재 확인
    2. 청크 순서대로 병합
    3. 최종 파일 검증
    4. B03_FileInput/input/ 이동
    5. 메타데이터 분석 (asyncio.to_thread)
    6. input_files 테이블 기록
    7. 임시 청크 삭제
    8. ✉️ 메일 전송 (비동기)
       - 수신자: 사용자 계정 이메일
       - 템플릿: 파일 분석 완료 알림
       - 포함 정보: 파일명, 분석 결과 요약
    """

업로드 상태 조회

@router.get("/{project_id}/upload-status/{session_id}")
async def get_upload_status(project_id: UUID, session_id: str):
    """
    업로드 진행 상황 조회 (재방문 시)
    - 완료된 청크 수
    - 전체 청크 수
    - 파일 크기
    - 현재 상태
    """

5.8 메일 발송 로직

참고: 기존 인증키 메일 로직 (A07_Register, common_util_email.py)

async def send_file_analysis_complete_email(
    user_email: str,
    project_name: str,
    file_name: str,
    file_size_mb: float,
    analysis_metadata: dict
):
    """
    파일 분석 완료 메일 발송
    
    메일 템플릿: 'file_analysis_complete.html'
    Context:
    {
      "user_name": "홍길동",
      "project_name": "산림 프로젝트 A",
      "file_name": "sample.las",
      "file_size_mb": 245.67,
      "epsg": 5179,
      "point_count": 1234567,
      "x_min": 127.1234,
      "x_max": 127.5678,
      "y_min": 37.1234,
      "y_max": 37.5678,
      "z_min": 100.5,
      "z_max": 250.3
    }
    """

6. 구현 체크리스트

프론트엔드 (TypeScript)

UI 템플릿화

  • 카드 HTML 템플릿 작성 (#file-card-template)
  • 그룹 컨테이너 템플릿 작성 (필수/선택)
  • 템플릿에서 카드 엘리먼트 동적 생성 (createFileCard())
  • 카드 상태 클래스 변경 방식 구현 (setCardState())
  • 상태별 CSS 클래스 관리 (empty, selected, uploading, error 등)

UI 렌더링

  • 드래그앤드롭 영역 (기존 유지)
  • 필수/선택 파일 그룹 렌더링
  • 카드별 파일정보 표시 (파일명, 용량)
  • 카드별 에러 메시지 표시 위치 및 스타일
  • 파일 실시간 검증 (타입 불일치, 크기 초과)

업로드 기능

  • [~] Service Worker 등록/등록 해제 (등록 및 메시지 채널 구현, 등록 해제 UI는 보류)
  • IndexedDB 스키마 정의
  • 파일 청크 분할 함수
  • 개별 파일 카드 진행 바 (10초 주기)
  • 진행 정보 표시 (용량, 속도, 예상 시간)
  • [~] 재방문 시 상태 복구 (localStorage 기반 세션 감지 구현, IndexedDB는 보류)
  • 업로드 재개 버튼 (사용자 명시적 클릭)
  • [~] 파일 취소 시 카드 초기화 + 청크 삭제 (카드/localStorage 초기화 구현, 서버 청크 삭제 API는 보류)
  • 에러 핸들링 및 재시도 (최대 3회)

백엔드 (Python)

  • 청크 업로드 엔드포인트
  • 청크 임시 저장소 관리
  • 청크 병합 로직
  • 체크섬 검증
  • upload_sessions 테이블
  • upload_chunks 테이블
  • 상태 조회 엔드포인트
  • [~] 중복 업로드 방지 로직 (파일명 + 파일크기) (프론트 세션 감지 구현, 서버 중복 차단은 보류)
  • ✉️ 메타데이터 분석 완료 후 메일 발송
  • 메일 템플릿 작성 ('file_analysis_complete.html')
  • 메일 발송 실패 시 로깅

DB (MariaDB)

  • upload_sessions 테이블
  • upload_chunks 테이블
  • 청크 임시 저장소 경로

보안 및 준수

  • 초기 설치 시 명시적 사용자 동의
  • "언제든 비활성화 가능" 안내
  • 업로드 세션 로그 기록
  • [~] 성공/실패 기록 (upload_sessions 상태 갱신 구현, 별도 audit log는 보류)

7. 예상 구현 일정

단계 작업 예상 시간 비고
1 계획 검토 및 피드백 1h
2 HTML 템플릿 작성 1.5h 카드 및 그룹 템플릿
3 CSS 클래스 정의 + 상태별 스타일 1.5h 템플릿화 지원
4 카드 생성 및 렌더링 함수 2h createFileCard(), setCardState()
5 파일 선택 및 배치 로직 1.5h assignFileToSlot(), 실시간 검증
6 에러 메시지 표시 1h 카드 내 에러 표시
7 Service Worker 기초 구현 2h 등록/메시지 처리
8 IndexedDB 스키마 설계 1.5h 데이터 구조
9 백엔드 API (청크 업로드) 3h 저장소 관리
10 개별 파일 카드 진행 바 1.5h 용량/속도/예상시간 표시
11 재방문 상태 복구 2h 세션 복원 + 재개 버튼
12 파일 취소 시 청크 삭제 1h 카드 초기화
13 메타데이터 분석 완료 후 메일 발송 2h 템플릿 + SMTP 통합
14 에러 핸들링/재시도 2h 네트워크 오류 대응
15 테스트 및 QA 2h 대용량 파일 테스트
25h (병렬 작업 시 단축 가능)

8. 최종 요구사항 정리 (확정 사항)

UI/UX 개선

  • 템플릿화: HTML 템플릿으로 카드 생성, 상태별 CSS 클래스 관리
  • 카드 기반 레이아웃: 필수/선택 파일 구분, 개별 카드 슬롯
  • 아이콘 표시: 파일 타입별 이모지 아이콘
  • 메타정보: 파일명, 용량(MB) 실시간 표시
  • 개별 삭제: X 버튼으로 카드 초기화 가능
  • 에러 표시: 카드 하단에 에러 메시지 (색상 구분)
  • 실시간 검증: 파일 선택 시 타입, 크기 즉시 확인

대용량 파일 업로드

  • Service Worker: 브라우저 백그라운드 청크 업로드
  • 청크 분할: 1GB 단위 (config UPLOAD_CHUNK_SIZE_MB = 1024)
  • 최대 크기: 30GB (config UPLOAD_MAX_MB = 30 * 1024)
  • 복수 파일: 드래그앤드롭/파일 선택 다중 반복 가능

진행률 표시

  • 위치: 개별 파일 카드 하단 (파일 업로드 중 상태)
  • 프로그래스 바: 시각적 진행률 (%)
  • 진행 정보: "123.45 GB / 245.67 GB (50%)"
  • 전송 속도: "25.3 MB/s" (10초 주기 측정)
  • 예상 완료: "약 17분 후" (실시간 계산)
  • 업데이트 주기: 10초 (config PROGRESS_UPDATE_INTERVAL_MS = 10000)

중단 및 재개

  • 중복 감지 기준: 파일명 + 파일크기
    • 같으면: 기존 세션 복구
    • 다르면: 새 세션 생성
  • 백그라운드 진행: 브라우저/탭 닫아도 Service Worker 계속 실행
  • 재방문 감지: IndexedDB에서 중단된 세션 자동 감지
  • 사용자 제어: [업로드 재개] 버튼 명시적 클릭 필요 (자동 재개 X)
  • 청크 재개: 완료된 청크는 스킵, 남은 청크부터 계속

파일 취소

  • 동작: 카드 우상단 X 버튼 클릭
  • 효과: 카드 초기화 → 미선택 상태로 복귀
  • 임시 파일 정리: 업로드 중이면 임시 청크 자동 삭제
    • IndexedDB 세션 정보 삭제
    • 백엔드 임시 폴더의 청크 파일 삭제

메일 알림

  • 발송 시점: 청크 병합 + 메타데이터 분석 완료 후
  • 수신자: 사용자 계정 이메일
  • 내용: 파일 분석 완료 알림
    • 파일명, 파일크기
    • 분석 결과 요약 (포인트 개수, EPSG, 좌표 범위 등)
  • 참고: 기존 인증키 메일 로직 (common_util_email.py)
  • 실패 처리: 메일 발송 실패 시 로깅하고 업로드는 완료 처리

9. 추가 고려사항

  • 드래그앤드롭 반복 가능성: 여러 번에 걸쳐 파일 추가 가능
  • 브라우저 호환성: 모던 브라우저 (Chrome, Firefox, Safari, Edge)
  • 접근성: 키보드 네비게이션, 스크린 리더 지원
  • 성능: 수십 GB 파일 선택 시 UI 반응성 유지
  • 보안: HTTPS 필수, 청크 전송 무결성 검증 (SHA256)