260322:1648 Correct Coresspondence / Doing RFA / Correct CI
This commit is contained in:
@@ -1,16 +1,19 @@
|
||||
# 3.3 File Storage and Handling
|
||||
|
||||
---
|
||||
|
||||
title: 'Data & Storage: File Storage and Handling (Two-Phase)'
|
||||
version: 1.8.0
|
||||
status: drafted
|
||||
owner: Nattanin Peancharoen
|
||||
last_updated: 2026-02-22
|
||||
related:
|
||||
|
||||
- specs/01-requirements/01-03.10-file-handling.md (Merged)
|
||||
- specs/03-Data-and-Storage/ADR-003-file-storage-approach.md (Merged)
|
||||
- specs/02-architecture/02-01-system-architecture.md
|
||||
- ADR-006-security-best-practices
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview and Core Infrastructure Requirements
|
||||
@@ -18,12 +21,15 @@ related:
|
||||
เอกสารฉบับนี้รวบรวมข้อกำหนดการจัดการไฟล์และการจัดเก็บไฟล์ (File Storage Approach) สำหรับ LCBP3-DMS โดยมีข้อบังคับด้าน Infrastructure และ Security ที่สำคัญมากดังต่อไปนี้:
|
||||
|
||||
### 1.1 Infrastructure Requirement (การจัดเก็บและ Mount Volume)
|
||||
|
||||
**สำคัญ (CRITICAL SPECIFICATION):**
|
||||
|
||||
1. **Outside Webroot:** ไฟล์รูปและเอกสารทั้งหมดต้องถูกจัดเก็บไว้ **ภายนอก Webroot ของ Application** ห้ามเก็บไฟล์รูปหรือเอกสารไว้ใน Container หรือโฟลเดอร์ Webroot เด็ดขาด เพื่อป้องกันการเข้าถึงไฟล์โดยตรงจากสาธารณะ (Direct Public Access)
|
||||
2. **QNAP Volume Mount:** ต้องใช้ **QNAP Volume Mount เข้า Docker** (Mount external volume from QNAP NAS to Docker container) สำหรับเป็นพื้นที่เก็บไฟล์ Storage ให้ Container ดึงไปใช้งาน
|
||||
3. **Authenticated Endpoint:** ไฟล์ต้องถูกเข้าถึงและให้บริการดาวน์โหลดผ่าน Authenticated Endpoint ในฝั่ง Backend เท่านั้น โดยต้องผ่านการตรวจสอบสิทธิ์ (RBAC / Junction Table) เสียก่อน
|
||||
|
||||
### 1.2 Access & Security Rules
|
||||
|
||||
- **Virus Scan:** ต้องมีการ scan virus สำหรับไฟล์ที่อัปโหลดทั้งหมด โดยใช้ ClamAV หรือบริการ third-party ก่อนการบันทึก
|
||||
- **Whitelist File Types:** อนุญาตเฉพาะเอกสารตามที่กำหนด: PDF, DWG, DOCX, XLSX, ZIP
|
||||
- **Max File Size:** ขนาดไฟล์สูงสุดไม่เกิน 50MB ต่อไฟล์ (Total max 500MB per form submission)
|
||||
@@ -36,7 +42,9 @@ related:
|
||||
## 2. Two-Phase File Storage Approach (ADR-003)
|
||||
|
||||
### 2.1 Context and Problem Statement
|
||||
|
||||
LCBP3-DMS ต้องจัดการ File Uploads สำหรับ Attachments ของเอกสาร (PDF, DWG, DOCX, etc.) โดยต้องรับมือกับปัญหา:
|
||||
|
||||
1. **Orphan Files:** User อัปโหลดไฟล์แล้วไม่ Submit Form ทำให้ไฟล์ค้างใน Storage
|
||||
2. **Transaction Integrity:** ถ้า Database Transaction Rollback ไฟล์ยังอยู่ใน Storage ต้องสอดคล้องกับ Database Record
|
||||
3. **Virus Scanning:** ต้อง Scan ไฟล์ก่อน Save เข้าระบบถาวร
|
||||
@@ -44,6 +52,7 @@ LCBP3-DMS ต้องจัดการ File Uploads สำหรับ Attachm
|
||||
5. **Storage Organization:** จัดเก็บไฟล์แยกเป็นสัดส่วน (เพื่อไม่ให้ QNAP Storage กระจัดกระจายและจำกัดขนาดได้)
|
||||
|
||||
### 2.2 Decision Drivers
|
||||
|
||||
- **Data Integrity:** File และ Database Record ต้อง Consistent
|
||||
- **Security:** ป้องกัน Virus และ Malicious Files
|
||||
- **User Experience:** Upload ต้องรวดเร็ว ไม่ Block UI (ถ้าอัปโหลดพร้อม Submit อาจทำให้ระบบดูค้าง)
|
||||
@@ -51,11 +60,13 @@ LCBP3-DMS ต้องจัดการ File Uploads สำหรับ Attachm
|
||||
- **Auditability:** ติดตามประวัติ File Operations ได้
|
||||
|
||||
### 2.3 Considered Options & Decision
|
||||
|
||||
- **Option 1:** Direct Upload to Permanent Storage (ทิ้งไฟล์ถ้า Transaction Fail / ได้ Orphan Files) - ❌
|
||||
- **Option 2:** Upload after Form Submission (UX แย่ ผู้ใช้ต้องรออัปโหลดรวดเดียวท้ายสุด) - ❌
|
||||
- **Option 3: Two-Phase Storage (Temp → Permanent) ⭐ (Selected Option)** - ✅
|
||||
|
||||
**แนวทาง Two-Phase Storage (Temp → Permanent):**
|
||||
|
||||
1. **Phase 1 (Upload):** ไฟล์ถูกอัปโหลดเข้าโฟลเดอร์ `temp/` ได้รับ `temp_id`
|
||||
2. **Phase 2 (Commit):** เมื่อ User กด Submit ฟอร์มสำเร็จ ระบบจะย้ายไฟล์จาก `temp/` ไปยัง `permanent/{YYYY}/{MM}/` และบันทึกลง Database ใน Transaction เดียวกัน
|
||||
3. **Cleanup:** มี Cron Job ทำหน้าที่ลบไฟล์ใน `temp/` ที่ค้างเกินกำหนด (เช่น 24 ชั่วโมง)
|
||||
@@ -65,6 +76,7 @@ LCBP3-DMS ต้องจัดการ File Uploads สำหรับ Attachm
|
||||
## 3. Implementation Details
|
||||
|
||||
### 3.1 Database Schema
|
||||
|
||||
```sql
|
||||
CREATE TABLE attachments (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
@@ -89,6 +101,7 @@ CREATE TABLE attachments (
|
||||
```
|
||||
|
||||
### 3.2 Two-Phase Storage Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User as Client
|
||||
@@ -205,7 +218,12 @@ export class FileStorageService {
|
||||
}
|
||||
|
||||
// Phase 2: Commit to Permanent (within Transaction Manager)
|
||||
async commitFiles(tempIds: string[], entityId: number, entityType: string, manager: EntityManager): Promise<Attachment[]> {
|
||||
async commitFiles(
|
||||
tempIds: string[],
|
||||
entityId: number,
|
||||
entityType: string,
|
||||
manager: EntityManager
|
||||
): Promise<Attachment[]> {
|
||||
const attachments = [];
|
||||
|
||||
for (const tempId of tempIds) {
|
||||
@@ -227,13 +245,17 @@ export class FileStorageService {
|
||||
await fs.move(tempAttachment.file_path, permanentPath);
|
||||
|
||||
// Update Database record
|
||||
await manager.update(Attachment, { id: tempAttachment.id }, {
|
||||
file_path: permanentPath,
|
||||
stored_filename: permanentFilename,
|
||||
is_temporary: false,
|
||||
temp_id: null,
|
||||
expires_at: null,
|
||||
});
|
||||
await manager.update(
|
||||
Attachment,
|
||||
{ id: tempAttachment.id },
|
||||
{
|
||||
file_path: permanentPath,
|
||||
stored_filename: permanentFilename,
|
||||
is_temporary: false,
|
||||
temp_id: null,
|
||||
expires_at: null,
|
||||
}
|
||||
);
|
||||
|
||||
attachments.push(tempAttachment);
|
||||
}
|
||||
@@ -283,7 +305,9 @@ export class FileStorageService {
|
||||
```
|
||||
|
||||
### 3.4 API Controller Context
|
||||
|
||||
ในส่วนของตัว Controller ฝ่ายรับข้อมูลจะต้องแยกระหว่าง Uploading กับ Comit:
|
||||
|
||||
1. `POST /attachments/upload` ใช้เพื่อรับไฟล์และ Return `temp_id` แก่ User ทันที
|
||||
2. `POST /correspondences` หรือ Object อื่นๆ ใช้เพื่อ Commit Database โดยจะรับ `temp_file_ids: []` พ่วงมากับ Body form
|
||||
|
||||
@@ -292,6 +316,7 @@ export class FileStorageService {
|
||||
## 4. Consequences & Mitigation Strategies
|
||||
|
||||
### Positive Consequences
|
||||
|
||||
1. ✅ **Fast Upload UX:** User upload แบบ Async ก่อน Submit ดำเนินการลื่นไหล
|
||||
2. ✅ **No Orphan Files:** เกิดระบบ Auto-cleanup จัดการไฟล์หมดอายุโดยอัตโนมัติ ไม่เปลืองสเปซ QNAP
|
||||
3. ✅ **Transaction Safe:** Rollback ได้สมบูรณ์หากบันทึกฐานข้อมูลผิดพลาด ไฟล์จะถูก Cron จัดการให้ทีหลังไม่ตกค้างในระบบ
|
||||
@@ -300,16 +325,18 @@ export class FileStorageService {
|
||||
6. ✅ **Storage Organization:** จัดเก็บอย่างเป็นระเบียบ ด้วยรูปแบบ YYYY/MM ลดคอขวด IO Operations ในระบบ
|
||||
|
||||
### Negative Consequences & Mitigations
|
||||
|
||||
1. ❌ **Complexity:** ต้อง Implement 2 phases ซึ่งซับซ้อนขึ้น
|
||||
👉 *Mitigation:* รวบ Logic ทุกอย่างให้เป็น Service ชั้นเดียว (`FileStorageService`) เพื่อให้จัดการง่ายและเรียกใช้ง่ายที่สุด
|
||||
👉 _Mitigation:_ รวบ Logic ทุกอย่างให้เป็น Service ชั้นเดียว (`FileStorageService`) เพื่อให้จัดการง่ายและเรียกใช้ง่ายที่สุด
|
||||
2. ❌ **Extra Storage:** ต้องใช้พื้นที่ QNAP ในส่วน Temp directory ควบคู่ไปกับแบบ Permanent
|
||||
👉 *Mitigation:* คอย Monitor และปรับรอบความถี่ของการ Cleanup หากไฟล์มีปริมาณไหลเวียนเยอะมาก
|
||||
👉 _Mitigation:_ คอย Monitor และปรับรอบความถี่ของการ Cleanup หากไฟล์มีปริมาณไหลเวียนเยอะมาก
|
||||
3. ❌ **Edge Cases:** อาจเกิดประเด็นเรื่อง File lock หรือ missing temp files
|
||||
👉 *Mitigation:* ทำ Proper error handling พร้อม Logging ให้ตรวจสอบได้ง่าย
|
||||
👉 _Mitigation:_ ทำ Proper error handling พร้อม Logging ให้ตรวจสอบได้ง่าย
|
||||
|
||||
---
|
||||
|
||||
## 5. Performance Optimization Consideration
|
||||
|
||||
- **Streaming:** ใช้ multipart/form-data streaming เพิ่อลดภาระ Memory ของฝั่งเครื่องเซิฟเวอร์ (NestJS) ขณะสูบไฟล์ใหญ่ๆ
|
||||
- **Compression:** พิจารณาเรื่องการบีบอัดสำหรับไฟล์ขนาดใหญ่หรือบางประเภท
|
||||
- **Deduplication Check:** สามารถใช้งาน Field `checksum` ดักการ Commit ด้วยข้อมูลชุดเดิมที่เคยถูกอัปโหลดเพื่อประหยัดพื้นที่จัดเก็บ (Deduplicate)
|
||||
|
||||
Reference in New Issue
Block a user