Files
SnapOtter/apps/docs/zh-TW/guide/architecture.md
T
SnapOtterandGitHub 4963ab3bbd feat(docs-i18n): translate all documentation into 20 languages
All 181 docs markdown files translated into 20 languages (apps/docs/<locale>/**). Companion to the i18n code PR; admin-merged because the file count exceeds GitHub's per-PR CI trigger limit. Validated by pnpm i18n:check (all surfaces, 0 stale/missing) and a clean all-locale docs build.
2026-07-11 13:52:47 +08:00

124 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
description: "SnapOtter 的 monorepo 結構、app 與套件架構、請求生命週期,以及資源占用。"
i18n_source_hash: 9e8f80499a37
i18n_provenance: human
i18n_output_hash: 733f35af8cb1
---
# 架構 {#architecture}
SnapOtter 是一個以 pnpm workspaces 與 Turborepo 管理的 monorepo。它以 3 容器的 Docker Compose 堆疊部署:SnapOtter app 映像檔、PostgreSQL 17 與 Redis 8。
## 專案結構 {#project-structure}
```
snapotter/
├── apps/
│ ├── api/ # Fastify backend
│ ├── web/ # React + Vite frontend
│ └── docs/ # This VitePress site
├── packages/
│ ├── image-engine/ # Sharp-based image operations
│ ├── media-engine/ # FFmpeg spawn + progress parsing
│ ├── doc-engine/ # qpdf, LibreOffice, ghostscript wrappers
│ ├── ai/ # Python AI model bridge
│ └── shared/ # Types, constants, i18n
└── docker/ # Dockerfile and Compose config
```
## 套件 {#packages}
### `@snapotter/image-engine` {#snapotter-image-engine}
以 [Sharp](https://sharp.pixelplumbing.com/) 為基礎建置的核心影像處理程式庫。它處理所有非 AI 的操作:resize、crop、rotate、flip、convert、compress、strip metadata,以及色彩調整(亮度、對比、飽和度、灰階、懷舊、反轉、色版)。
此套件沒有網路相依,完全在程序內執行。
### `@snapotter/ai` {#snapotter-ai}
一個橋接層,呼叫 Python 指令碼進行 ML 操作。首次使用時,橋接層會啟動一個常駐的 Python 分派器程序,預先匯入沉重的程式庫(PIL、NumPy、MediaPipe、rembg),使後續的 AI 呼叫可略過匯入負擔。若分派器尚未就緒,橋接層會退回為每個請求產生一個全新的 Python 子程序。
**模型不會預先載入。** 每個工具指令碼會在請求時從磁碟載入其模型權重,並在請求結束時捨棄。完整的記憶體剖析請參閱[資源占用](#resource-footprint)。
支援的操作:背景移除(rembg/BiRefNet)、放大(RealESRGAN)、臉部模糊(MediaPipe)、臉部強化(GFPGAN/CodeFormer)、物件擦除(LaMa ONNX)、OCRPaddleOCR/Tesseract)、上色(DDColor)、噪點移除、紅眼移除、相片修復、護照相片產生、透明度修正(BiRefNet HR-matting),以及內容感知縮放(Go caire 二進位檔)。
Python 指令碼位於 `packages/ai/python/`。Docker 映像檔會在建置期間預先下載所有模型權重,因此容器可完全離線運作。
### `@snapotter/shared` {#snapotter-shared}
前端與後端共用的 TypeScript 型別、常數(例如 `APP_VERSION` 與工具定義)以及 i18n 翻譯字串。
## 應用程式 {#applications}
### API`apps/api` {#api-apps-api}
一個 Fastify v5 伺服器,公開橫跨五種模態(image、video、audio、PDF、file)的 241 個工具路由,負責處理:
- 檔案上傳、暫存工作區管理,以及持久化檔案儲存
- 具版本鏈的使用者檔案資料庫(`user_files` 資料表)- 每個處理結果都連回其來源檔案並記錄套用了哪個工具,並為 Files 頁面自動產生縮圖
- 工具執行(將每個工具請求路由至影像引擎或 AI 橋接層)
- 管線協調(依序串接多個工具)
- 透過 BullMQ 工作佇列(poolsimage、media、ai、docs、system)進行具並行控制的批次處理
- 使用者驗證、RBAC(admin/user 角色與完整權限集)、API 金鑰管理,以及速率限制
- 團隊管理 - 僅限 admin 的 CRUD;使用者透過其個人檔案上的 `team` 欄位指派至團隊
- 執行階段設定 - `settings` 資料表中的鍵值儲存,可控制 `disabledTools``enableExperimentalTools``loginAttemptLimit` 與其他運維旋鈕,無需重新部署
- 透過資料庫支援的設定進行自訂品牌與執行階段偏好設定
- 位於 `/api/docs` 的 Scalar/OpenAPI 文件
- 在正式環境中以 SPA 形式提供已建置的前端
主要相依套件:Fastify、Drizzle ORMpg-core、node-postgres)、Sharp、BullMQ、ioredis、以及用於驗證的 Zod。
伺服器會在 SIGTERM/SIGINT 時處理平順關機:排空 HTTP 連線、停止 BullMQ workers、關閉 Python 分派器,並關閉資料庫連線。
### Web`apps/web` {#web-apps-web}
一個以 Vite 建置的 React 19 單頁應用程式。使用 Zustand 進行狀態管理、Tailwind CSS v4 進行樣式設計、Lucide 提供圖示。透過 REST 與 SSE(用於進度追蹤)與 API 溝通。
頁面包含工具工作區、用於管理持久化上傳與結果的 Files 頁面、自動化/管線建構器,以及管理員設定面板。
已建置的前端在正式環境中由 Fastify 後端提供,因此 Docker 容器中沒有獨立的 web 伺服器。
### Docs`apps/docs` {#docs-apps-docs}
即此 VitePress 網站。在推送至 `main` 時自動部署至 Cloudflare Pages。
## 請求如何流動 {#how-a-request-flows}
1. 使用者在 web UI 中選取工具並上傳檔案。
2. 前端將含檔案與設定的 multipart POST 送往 `/api/v1/tools/:section/:toolId`
3. API 路由以 Zod 驗證輸入,然後分派處理。
4. 對於標準工具,工作會依模態排入適當的 BullMQ poolimage、media 或 docs)。程序內的 BullMQ worker 會根據 EXIF 中繼資料自動校正影像方向、執行工具的處理函式,並回傳結果。
5. 對於 AI 工具,TypeScript 橋接層會將請求送往常駐的 Python 分派器(或退回產生一個全新的子程序),等待其完成,並讀取輸出檔案。
6. 工作進度會持久化至 PostgreSQL 中的 `jobs` 資料表,因此狀態可在容器重新啟動後保留。即時更新透過位於 `/api/v1/jobs/:jobId/progress` 的 SSE 傳遞。
7. API 回傳一個 `jobId``downloadUrl`。使用者從 `/api/v1/download/:jobId/:filename` 下載處理後的檔案。
對於管線,API 會將每個步驟的輸出作為下一步的輸入,依序執行。
對於批次處理,API 會使用具逐步驟子工作的 BullMQ flows,並回傳一個含所有處理後檔案的 ZIP 檔案。
## 資源占用 {#resource-footprint}
SnapOtter 的設計以低閒置記憶體使用為目標。啟動時不會預先載入或保持任何暖機狀態。
### 閒置時 {#at-idle}
Node.js/Fastify 程序、PostgreSQL 與 Redis 都在執行。三個容器(Node.js 程序、Postgres 與 Redis)的典型閒置 RAM 為 **約 200-300 MB**。沒有 Python 程序,記憶體中也沒有模型權重。
### 什麼會啟動,以及何時啟動 {#what-starts-and-when}
| 元件 | 啟動時機 | 作用時的記憶體 |
|-----------|-------------|---------------------|
| Fastify 伺服器 + Postgres + Redis | 容器啟動 | 合計約 200-300 MB |
| BullMQ workers | 容器啟動(程序內) | 每個 pool 一個 workerimage、media、ai、docs、system |
| Python 分派器 | 首次 AI 工具請求 | Python 直譯器 + 預先匯入的程式庫(PIL、NumPy、MediaPipe、rembg- 無模型權重 |
| AI 模型權重 | 特定工具的請求期間 | 從磁碟載入,請求結束時釋放 |
### 模型載入 {#model-loading}
所有模型權重檔案(合計數 GB)始終位於 `/opt/models/` 的磁碟上。每個 AI 工具指令碼僅在請求期間將自己的模型載入記憶體,之後即釋放。有些指令碼會在推論後明確呼叫 `del model``torch.cuda.empty_cache()`,以確保記憶體立即歸還。
請求之間沒有模型快取。連續執行同一個 AI 工具時,每次都會重新載入模型。這使得閒置記憶體趨近於零,代價是每個 AI 請求都會有模型載入延遲。
### 首次 AI 請求的冷啟動 {#first-ai-request-cold-start}
容器啟動時 Python 分派器並未執行。首次 AI 請求會並行觸發兩件事:分派器開始在背景暖機,而請求本身則退回為一次性的 Python 子程序產生。一旦分派器發出就緒信號,所有後續 AI 請求都會直接使用它,並略過子程序產生的成本。