# 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 데이터 구조 #### 파일 슬롯 상태 ```typescript 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 업로드 세션 ```javascript { 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) ```sql 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)** ```typescript 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)** ```python 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) ```html ``` #### 템플릿 활용 방식 ```typescript // 템플릿에서 카드 클론 생성 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 클래스 (신규) #### 레이아웃 관련 ```css .b03-file__group /* 파일 그룹 컨테이너 (필수/선택) */ .b03-file__group-title /* 그룹 제목 (REQUIRED/OPTIONAL) */ .b03-file__group-content /* 그룹 내 카드 컨테이너 */ ``` #### 카드 기본 구조 ```css .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 버튼 (삭제) */ ``` #### 카드 상태 클래스 (변경자) ```css .b03-file__card--empty /* 미선택 상태 (기본) */ .b03-file__card--selected /* 파일 선택됨 상태 */ .b03-file__card--uploading /* 업로드 진행 중 상태 */ .b03-file__card--completed /* 업로드 완료 상태 */ .b03-file__card--error /* 에러 상태 */ ``` #### 파일 정보 ```css .b03-file__file-info /* 파일명 + 크기 컨테이너 */ .b03-file__file-name /* 파일명 텍스트 */ .b03-file__file-size /* 파일 크기 텍스트 */ ``` #### 진행 바 (업로드 중) ```css .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분 후" */ ``` #### 에러 메시지 ```css .b03-file__error-message /* 에러 메시지 컨테이너 */ .b03-file__error-icon /* 에러 아이콘 (❌) */ .b03-file__error-text /* 에러 텍스트 */ ``` #### 상태 표시 활용 예 ```css /* 카드 상태에 따라 표시/숨김 */ .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 명세 #### 청크 업로드 엔드포인트 ```python @router.post("/{project_id}/chunks") async def upload_chunk( project_id: UUID, session_id: str, chunk_index: int, chunk_data: UploadFile = File(...) ): """ 단일 청크 저장 - 세션 존재 확인 - 청크 저장 (임시 폴더) - 체크섬 계산 (SHA256) - DB에 기록 """ ``` #### 업로드 완료 엔드포인트 ```python @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. ✉️ 메일 전송 (비동기) - 수신자: 사용자 계정 이메일 - 템플릿: 파일 분석 완료 알림 - 포함 정보: 파일명, 분석 결과 요약 """ ``` #### 업로드 상태 조회 ```python @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) ```python 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 템플릿화 - [x] 카드 HTML 템플릿 작성 (`#file-card-template`) - [x] 그룹 컨테이너 템플릿 작성 (필수/선택) - [x] 템플릿에서 카드 엘리먼트 동적 생성 (`createFileCard()`) - [x] 카드 상태 클래스 변경 방식 구현 (`setCardState()`) - [x] 상태별 CSS 클래스 관리 (empty, selected, uploading, error 등) #### UI 렌더링 - [x] 드래그앤드롭 영역 (기존 유지) - [x] 필수/선택 파일 그룹 렌더링 - [x] 카드별 파일정보 표시 (파일명, 용량) - [x] 카드별 에러 메시지 표시 위치 및 스타일 - [x] 파일 실시간 검증 (타입 불일치, 크기 초과) #### 업로드 기능 - [~] Service Worker 등록/등록 해제 (등록 및 메시지 채널 구현, 등록 해제 UI는 보류) - [ ] IndexedDB 스키마 정의 - [x] 파일 청크 분할 함수 - [x] 개별 파일 카드 진행 바 (10초 주기) - [x] 진행 정보 표시 (용량, 속도, 예상 시간) - [~] 재방문 시 상태 복구 (localStorage 기반 세션 감지 구현, IndexedDB는 보류) - [x] 업로드 재개 버튼 (사용자 명시적 클릭) - [~] 파일 취소 시 카드 초기화 + 청크 삭제 (카드/localStorage 초기화 구현, 서버 청크 삭제 API는 보류) - [ ] 에러 핸들링 및 재시도 (최대 3회) ### 백엔드 (Python) - [x] 청크 업로드 엔드포인트 - [x] 청크 임시 저장소 관리 - [x] 청크 병합 로직 - [x] 체크섬 검증 - [x] upload_sessions 테이블 - [x] upload_chunks 테이블 - [x] 상태 조회 엔드포인트 - [~] 중복 업로드 방지 로직 (파일명 + 파일크기) (프론트 세션 감지 구현, 서버 중복 차단은 보류) - [ ] ✉️ 메타데이터 분석 완료 후 메일 발송 - [ ] 메일 템플릿 작성 ('file_analysis_complete.html') - [ ] 메일 발송 실패 시 로깅 ### DB (MariaDB) - [x] upload_sessions 테이블 - [x] upload_chunks 테이블 - [x] 청크 임시 저장소 경로 ### 보안 및 준수 - [ ] 초기 설치 시 명시적 사용자 동의 - [ ] "언제든 비활성화 가능" 안내 - [x] 업로드 세션 로그 기록 - [~] 성공/실패 기록 (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)