260710_0
This commit is contained in:
@@ -0,0 +1,824 @@
|
||||
# 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
|
||||
<!-- 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>
|
||||
```
|
||||
|
||||
#### 템플릿 활용 방식
|
||||
```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)
|
||||
Reference in New Issue
Block a user