32 KiB
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 개선 사항
- 카드 기반 UI: 각 파일 타입을 개별 카드로 시각화
- 그룹핑: 파일 타입별 그룹 (필수 vs 선택)
- 아이콘 표시: 파일 타입 아이콘으로 직관성 향상
- 개별 삭제: 각 카드의 X 버튼으로 취소 기능
- 메타정보: 파일명, 용량(MB), 파일 타입 표시
2.2 대용량 파일 업로드 기능
- Service Worker: 브라우저 백그라운드 업로드
- 청크 분할: 1GB 단위로 파일 분할 (config 조절 가능)
- 진행률 표시: 개별 파일별 진행 바 (용량, 속도, 예상시간)
- 중단 및 재개: 브라우저 닫아도 계속, 재방문 시 자동 감지
- 메일 알림: 분석 완료 후 사용자 이메일로 발송
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개 이상 파일 선택 가능 → 복수 번 반복 가능
- 선택된 각 파일이 해당 카드에 자동 배치:
.las/.laz→ Point Cloud 슬롯.prj→ Projection 슬롯.tfw→ Raster Coord 슬롯.tif→ Terrain DEM 슬롯.dxf→ CAD Drawing 슬롯
파일이 배치될 때 실시간 검증:
- 파일명 표시
- 파일 크기 표시 (MB 단위)
- 타입 불일치 시 에러 표시
- 크기 초과 시 에러 표시
3.5 파일 개별 제거
동작:
- 카드 우상단 X 버튼 클릭
- 파일 카드 초기화 (미선택 상태로 복귀)
- 업로드 중인 파일이면 임시 청크 자동 삭제 (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)