Files
PROJECT-CONTORL/openspec/changes/archive/2025-12-29-add-document-management/design.md
beabigegg 3108fe1dff feat: implement document management module
- Backend (FastAPI):
  - Attachment and AttachmentVersion models with migration
  - FileStorageService with SHA-256 checksum validation
  - File type validation (whitelist/blacklist)
  - Full CRUD API with version control support
  - Audit trail integration for upload/download/delete
  - Configurable upload directory and file size limit

- Frontend (React + Vite):
  - AttachmentUpload component with drag & drop
  - AttachmentList component with download/delete
  - TaskAttachments combined component
  - Attachments service for API calls

- Testing:
  - 12 tests for storage service and API endpoints

- OpenSpec:
  - add-document-management change archived

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 22:03:05 +08:00

4.4 KiB
Raw Blame History

Context

文件管理是專案系統的核心功能,需要考慮:

  • 檔案存儲策略(本地 vs NAS
  • 安全需求(加密、浮水印)
  • 版本控制邏輯
  • 大檔案處理

Goals / Non-Goals

Goals:

  • 提供任務層級的檔案附件功能
  • 支援基本 CRUD 操作
  • 整合現有 Audit Trail
  • 為未來 NAS 整合預留擴展性

Non-Goals:

  • 即時協作編輯(不在此範圍)
  • 全文搜尋(未來功能)
  • 檔案預覽(未來功能)

Decisions

1. 檔案存儲策略

Decision: 使用本地檔案系統 + 環境變數配置路徑

Rationale:

  • 開發階段使用本地存儲簡化設置
  • 生產環境透過環境變數指向 NAS 掛載點
  • 路徑結構:{UPLOAD_DIR}/{project_id}/{task_id}/{attachment_id}/{version}/

Alternatives considered:

  • 直接 NAS 整合 - 開發環境設置複雜
  • S3 相容存儲 - 增加外部依賴

2. 版本控制模型

Decision: 主表 + 版本歷史表分離

pjctrl_attachments (主表,存儲最新版本資訊)
├── id, task_id, filename, current_version, ...

pjctrl_attachment_versions (歷史表)
├── id, attachment_id, version, file_path, ...

Rationale:

  • 主表快速查詢當前附件
  • 歷史表保留所有版本
  • 上傳同名檔案 → 建立新版本 → 更新主表 current_version

3. 加密策略

Decision: 使用 Fernet (基於 AES-128-CBC) 對稱加密

Rationale:

  • Python cryptography 庫內建支援
  • 自動處理 IV、padding、HMAC 驗證
  • 比原生 AES-256 更安全(防止實作錯誤)
  • 效能足夠(非大規模加密場景)

實作方式:

  • 加密金鑰存儲於環境變數 ENCRYPTION_KEY
  • 僅對機密專案的附件加密
  • 加密狀態存於 is_encrypted 欄位

4. 浮水印策略

Decision: 下載時動態生成浮水印

Rationale:

  • 不修改原始檔案
  • 每次下載包含當下使用者資訊
  • 使用 Pillow (圖片) 和 PyMuPDF (PDF) 處理

浮水印內容:

  • 使用者姓名 + 工號
  • 下載時間
  • 機密等級(如適用)

5. 檔案大小限制

Decision: 預設 50MB可透過環境變數調整

MAX_FILE_SIZE = int(os.getenv("MAX_FILE_SIZE_MB", 50)) * 1024 * 1024

Rationale:

  • 避免記憶體溢出
  • 大檔案使用串流處理
  • 生產環境可依需求調整

Data Model

-- 附件主表
pjctrl_attachments
├── id: UUID (PK)
├── task_id: UUID (FK -> tasks)
├── filename: VARCHAR(255) -- 顯示名稱
├── original_filename: VARCHAR(255) -- 原始上傳名稱
├── mime_type: VARCHAR(100)
├── file_size: BIGINT
├── current_version: INT DEFAULT 1
├── is_encrypted: BOOLEAN DEFAULT false
├── uploaded_by: UUID (FK -> users)
├── is_deleted: BOOLEAN DEFAULT false
├── created_at: TIMESTAMP
└── updated_at: TIMESTAMP

-- 版本歷史表
pjctrl_attachment_versions
├── id: UUID (PK)
├── attachment_id: UUID (FK -> attachments)
├── version: INT
├── file_path: VARCHAR(1000) -- 實際存儲路徑
├── file_size: BIGINT
├── checksum: VARCHAR(64) -- SHA-256
├── uploaded_by: UUID (FK -> users)
├── created_at: TIMESTAMP
└── INDEX (attachment_id, version)

API Design

POST   /api/tasks/{task_id}/attachments          # 上傳附件
GET    /api/tasks/{task_id}/attachments          # 列出附件
GET    /api/attachments/{id}                     # 取得附件資訊
GET    /api/attachments/{id}/download            # 下載附件
GET    /api/attachments/{id}/download?version=2  # 下載特定版本
DELETE /api/attachments/{id}                     # 刪除附件(軟刪除)
GET    /api/attachments/{id}/versions            # 版本歷史
POST   /api/attachments/{id}/restore/{version}   # 回復特定版本

Risks / Trade-offs

Risk Mitigation
大檔案記憶體溢出 使用串流上傳/下載
加密金鑰洩漏 僅存於環境變數,定期輪換
浮水印處理耗時 限制支援的檔案類型,非同步處理大檔案
NAS 不可用 本地存儲 fallback監控告警

Migration Plan

  1. Phase 1: 建立模型、基本 CRUD、本地存儲
  2. Phase 2: 版本控制
  3. Phase 3: 加密與浮水印(可選)

Open Questions

  • NAS 掛載點路徑確認
  • 生產環境加密金鑰管理方式KMS? Vault?
  • 是否需要支援拖放上傳多檔案?