## v0.9.20260325_144654 ### Features - API Key Authentication System - Job Worker System - V2 Backup Versioning ### Bug Fixes - get_processor_results_by_job column mapping Co-authored-by: OpenCode
991 lines
22 KiB
Markdown
991 lines
22 KiB
Markdown
# Rust 開發規範 - Momentry Core
|
||
|
||
| 項目 | 內容 |
|
||
|------|------|
|
||
| 建立者 | Warren |
|
||
| 建立時間 | 2026-03-16 |
|
||
| 文件版本 | V1.0 |
|
||
|
||
---
|
||
|
||
## 版本歷史
|
||
|
||
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|
||
|------|------|------|--------|-----------|
|
||
| V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
|
||
| V1.1 | 2026-03-21 | 新增 PythonExecutor 模組說明 | OpenCode | - |
|
||
|
||
---
|
||
|
||
本規範定義 Momentry Core 專案的 Rust 開發標準,確保程式碼品質與一致性。
|
||
|
||
## 1. 專案結構
|
||
|
||
### 1.1 目錄架構
|
||
|
||
```
|
||
src/
|
||
├── main.rs # CLI 入口點
|
||
├── lib.rs # 函式庫導出
|
||
├── cli/
|
||
│ ├── mod.rs
|
||
│ └── commands/ # CLI 命令模組
|
||
├── core/
|
||
│ ├── mod.rs
|
||
│ ├── chunk/ # 影片分段邏輯
|
||
│ │ ├── mod.rs
|
||
│ │ ├── splitter.rs
|
||
│ │ └── types.rs
|
||
│ ├── db/ # 資料庫抽象層
|
||
│ │ ├── mod.rs
|
||
│ │ ├── postgres_db.rs
|
||
│ │ ├── mongodb_db.rs
|
||
│ │ ├── redis_db.rs
|
||
│ │ └── qdrant_db.rs
|
||
│ ├── processor/ # 影片處理器
|
||
│ │ ├── mod.rs
|
||
│ │ ├── executor.rs # Python 腳本統一執行器 (含超時控制)
|
||
│ │ ├── asr.rs # 語音識別
|
||
│ │ ├── asrx.rs # 說話者分離
|
||
│ │ ├── ocr.rs # 文字辨識
|
||
│ │ ├── yolo.rs # 物件偵測
|
||
│ │ ├── face.rs # 人臉偵測
|
||
│ │ └── pose.rs # 姿態估計
|
||
│ ├── embedding/ # 向量嵌入
|
||
│ ├── probe/ # ffprobe 整合
|
||
│ ├── storage/ # 檔案管理
|
||
│ └── thumbnail/ # 縮圖生成
|
||
├── api/ # HTTP API
|
||
│ ├── mod.rs
|
||
│ └── routes/
|
||
├── player/ # 影片播放
|
||
└── watcher/ # 檔案監控
|
||
```
|
||
|
||
### 1.2 模組設計原則
|
||
|
||
- **單一職責**: 每個模組專注於一項功能
|
||
- **介面抽象**: 使用 trait 定義資料庫、操作器等介面
|
||
- **依賴注入**: 透過建構函式注入依賴
|
||
|
||
```rust
|
||
pub trait VideoProcessor: Send + Sync {
|
||
async fn process(&self, video_path: &str) -> Result<ProcessResult>;
|
||
}
|
||
```
|
||
|
||
## 2. 程式碼風格
|
||
|
||
### 2.1 命名規範
|
||
|
||
| 類型 | 規範 | 範例 |
|
||
|------|------|------|
|
||
| 結構體/列舉 | PascalCase | `VideoRecord`, `ChunkType` |
|
||
| 函式/變數 | snake_case | `get_video_by_uuid` |
|
||
| Trait | PascalCase + er 尾碼 | `Database`, `ChunkStore` |
|
||
| 檔案 | snake_case | `postgres_db.rs` |
|
||
| 常量 | SCREAMING_SNAKE_CASE | `MAX_CHUNK_SIZE` |
|
||
| 模組 | snake_case | `chunk`, `processor` |
|
||
|
||
### 2.2 匯入順序
|
||
|
||
```rust
|
||
// 1. 標準庫
|
||
use std::path::Path;
|
||
use std::process::Command;
|
||
|
||
// 2. 外部庫
|
||
use anyhow::{Context, Result};
|
||
use async_trait::async_trait;
|
||
use serde::{Deserialize, Serialize};
|
||
use tokio::fs;
|
||
|
||
// 3. 內部模組
|
||
use crate::core::chunk::Chunk;
|
||
use crate::core::db::PostgresDb;
|
||
```
|
||
|
||
### 2.3 行寬與格式
|
||
|
||
- 最大行寬: 100 字元
|
||
- 使用 4 空格縮排
|
||
- 啟用 clippy 與 fmt
|
||
|
||
```bash
|
||
# 格式化
|
||
cargo fmt
|
||
|
||
# 檢查格式
|
||
cargo fmt -- --check
|
||
|
||
# Lint
|
||
cargo clippy --all-features
|
||
```
|
||
|
||
## 3. 錯誤處理
|
||
|
||
### 3.1 錯誤類型選擇
|
||
|
||
| 情境 | 錯誤類型 | 原因 |
|
||
|------|----------|------|
|
||
| 應用程式 | `anyhow::Result<T>` | 提供靈活的錯誤傳播 |
|
||
| 函式庫 | `thiserror` | 定義明確的錯誤類型 |
|
||
| API 錯誤 | 自定義 Error enum | 提供客戶端錯誤碼 |
|
||
|
||
### 3.2 錯誤處理範例
|
||
|
||
```rust
|
||
use anyhow::{Context, Result, bail};
|
||
|
||
fn process_video(video_path: &str) -> Result<VideoMetadata> {
|
||
// 使用 context 提供錯誤上下文
|
||
let output = Command::new("ffprobe")
|
||
.args(["-v", "quiet", "-print_format", "json", "-show_format", video_path])
|
||
.output()
|
||
.context("Failed to run ffprobe")?;
|
||
|
||
// 使用 bail 進行早期返回
|
||
if !output.status.success() {
|
||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||
bail!("ffprobe failed: {}", stderr);
|
||
}
|
||
|
||
// 解析輸出
|
||
let metadata: Metadata = serde_json::from_slice(&output.stdout)
|
||
.context("Failed to parse ffprobe output")?;
|
||
|
||
Ok(metadata)
|
||
}
|
||
```
|
||
|
||
### 3.3 自定義錯誤 (適用於函式庫)
|
||
|
||
```rust
|
||
use thiserror::Error;
|
||
|
||
#[derive(Error, Debug)]
|
||
pub enum VideoError {
|
||
#[error("Video not found: {0}")]
|
||
NotFound(String),
|
||
|
||
#[error("Invalid codec: {0}")]
|
||
InvalidCodec(String),
|
||
|
||
#[error("Processing failed: {0}")]
|
||
ProcessingError(#[from] std::io::Error),
|
||
}
|
||
```
|
||
|
||
## 4. 异步編程
|
||
|
||
### 4.1 Tokio 配置
|
||
|
||
```rust
|
||
// Cargo.toml
|
||
tokio = { version = "1", features = ["full"] }
|
||
```
|
||
|
||
### 4.2 Async Trait
|
||
|
||
```rust
|
||
use async_trait::async_trait;
|
||
|
||
#[async_trait]
|
||
pub trait Database: Send + Sync {
|
||
async fn init() -> Result<Self>
|
||
where Self: Sized;
|
||
|
||
async fn get_video(&self, uuid: &str) -> Result<Option<VideoRecord>>;
|
||
|
||
async fn store_chunk(&self, chunk: &Chunk) -> Result<()>;
|
||
}
|
||
```
|
||
|
||
### 4.3 避免常見陷阱
|
||
|
||
```rust
|
||
// ❌ 錯誤: 在同步上下文中調用 async 函式
|
||
fn bad_example() {
|
||
let result = db.get_video("xxx"); // 編譯錯誤
|
||
}
|
||
|
||
// ✅ 正確: 使用 #[tokio::main]
|
||
#[tokio::main]
|
||
async fn main() {
|
||
let result = db.get_video("xxx").await;
|
||
}
|
||
|
||
// ❌ 錯誤: 阻塞執行緒池
|
||
async fn bad_practice() {
|
||
let data = std::fs::read_to_string("file.txt").unwrap(); // 阻塞
|
||
}
|
||
|
||
// ✅ 正確: 使用 tokio::fs
|
||
async fn good_practice() {
|
||
let data = tokio::fs::read_to_string("file.txt").await.unwrap();
|
||
}
|
||
```
|
||
|
||
## 5. 外部程序整合
|
||
|
||
當需要使用 Python 生態系工具 (如 faster-whisper, YOLO) 時:
|
||
|
||
```rust
|
||
pub async fn process_asr(video_path: &str, output_path: &str) -> Result<AsrResult> {
|
||
let script_path = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||
.join("scripts")
|
||
.join("asr_processor.py");
|
||
|
||
// 使用 venv 中的 Python,確保版本隔離
|
||
let venv_python = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||
.join("venv")
|
||
.join("bin")
|
||
.join("python");
|
||
|
||
// 執行腳本
|
||
let output = Command::new(venv_python)
|
||
.arg(script_path)
|
||
.arg(video_path)
|
||
.arg(output_path)
|
||
.output()
|
||
.context("Failed to run ASR processor")?;
|
||
|
||
if !output.status.success() {
|
||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||
bail!("ASR failed: {}", stderr);
|
||
}
|
||
|
||
// 讀取輸出
|
||
let json_str = std::fs::read_to_string(output_path)
|
||
.context("Failed to read ASR output")?;
|
||
|
||
let result: AsrResult = serde_json::from_str(&json_str)
|
||
.context("Failed to parse ASR output")?;
|
||
|
||
Ok(result)
|
||
}
|
||
```
|
||
|
||
### 5.2 進度回報
|
||
|
||
透過 stderr 回報進度,供 Rust 端解析:
|
||
|
||
```python
|
||
# Python 腳本
|
||
import sys
|
||
|
||
print(f"ASR_START", file=sys.stderr)
|
||
print(f"ASR_LANGUAGE:{detected_lang}", file=sys.stderr)
|
||
print(f"ASR_PROGRESS:{count}", file=sys.stderr)
|
||
print(f"ASR_COMPLETE:{total}", file=sys.stderr)
|
||
```
|
||
|
||
```rust
|
||
// Rust 端解析
|
||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||
for line in stderr.lines() {
|
||
if line.starts_with("ASR_PROGRESS:") {
|
||
let count = line.trim_start_matches("ASR_PROGRESS:");
|
||
println!("[ASR] Processed {} segments...", count);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.3 PythonExecutor 統一執行器
|
||
|
||
使用 `PythonExecutor` 封裝 Python 腳本執行邏輯:
|
||
|
||
```rust
|
||
use momentry_core::core::processor::{PythonExecutor, validate_python_env};
|
||
|
||
// 驗證 Python 環境
|
||
fn init() -> Result<()> {
|
||
validate_python_env()?;
|
||
Ok(())
|
||
}
|
||
|
||
// 使用 Executor 執行腳本
|
||
async fn run_script() -> Result<()> {
|
||
let executor = PythonExecutor::new()?;
|
||
|
||
executor.run(
|
||
"asr_processor.py",
|
||
&["/path/to/video.mp4", "/path/to/output.json"],
|
||
Some("job-uuid"),
|
||
"ASR",
|
||
Some(Duration::from_secs(3600)), // 1小時超時
|
||
).await?;
|
||
|
||
Ok(())
|
||
}
|
||
```
|
||
|
||
#### Processor 超時設定
|
||
|
||
| Processor | 超時 | 說明 |
|
||
|----------|------|------|
|
||
| ASR | 1 小時 | 語音識別 |
|
||
| ASRx | 2 小時 | 說話者分離 |
|
||
| YOLO | 2 小時 | 物件偵測 |
|
||
| OCR | 2 小時 | 文字辨識 |
|
||
| Face | 2 小時 | 人臉偵測 |
|
||
| Pose | 2 小時 | 姿態估計 |
|
||
| Cut | 1 小時 | 場景偵測 |
|
||
|
||
---
|
||
|
||
## 6. Python 與 Node.js 混用規範
|
||
|
||
本專案同時使用 Python 和 Node.js (n8n),需建立明確的版本隔離與管理規範。
|
||
|
||
### 6.1 架構概述
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ Momentry Core │
|
||
│ │
|
||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||
│ │ Rust │ │ Python │ │ Node.js │ │
|
||
│ │ (Core) │───▶ │ (Scripts) │ │ (n8n) │ │
|
||
│ │ │ │ │ │ │ │
|
||
│ │ - CLI │ │ - ASR │ │ - Workflow │ │
|
||
│ │ - DB │ │ - Thumb │ │ - API │ │
|
||
│ │ - Storage │ │ - OCR │ │ - Webhooks │ │
|
||
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||
│ │ │ │ │
|
||
│ ▼ ▼ ▼ │
|
||
│ ┌─────────────────────────────────────────────────────┐ │
|
||
│ │ 資料庫 / 檔案系統 / Qdrant │ │
|
||
│ └─────────────────────────────────────────────────────┘ │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 6.2 Python 版本管理
|
||
|
||
#### 6.2.1 版本鎖定
|
||
|
||
| 版本 | 用途 | 路徑 |
|
||
|------|------|------|
|
||
| 3.11.14 | 影片處理腳本 | `/opt/homebrew/bin/python3.11` |
|
||
|
||
#### 6.2.2 虛擬環境
|
||
|
||
使用專案隔離的 venv:
|
||
|
||
```bash
|
||
# 建立虛擬環境
|
||
cd /Users/accusys/momentry_core_0.1
|
||
python3.11 -m venv venv
|
||
|
||
# 啟用
|
||
source venv/bin/activate
|
||
|
||
# 安裝依賴
|
||
pip install faster-whisper opencv-python python-dotenv
|
||
```
|
||
|
||
#### 6.2.3 Rust 呼叫 Python
|
||
|
||
```rust
|
||
let venv_python = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||
.join("venv")
|
||
.join("bin")
|
||
.join("python");
|
||
|
||
let output = Command::new(venv_python)
|
||
.arg(script_path)
|
||
.arg(video_path)
|
||
.output()
|
||
.context("Failed to run Python script")?;
|
||
```
|
||
|
||
### 6.3 Node.js 版本管理
|
||
|
||
#### 6.3.1 版本鎖定
|
||
|
||
參考 `docs/NODEJS.md`:
|
||
|
||
| 版本 | 用途 | 路徑 |
|
||
|------|------|------|
|
||
| 22.22.1 | n8n | `/opt/homebrew/opt/node@22/bin/node` |
|
||
|
||
#### 6.3.2 n8n 服務配置
|
||
|
||
使用 launchd plist 隔離:
|
||
|
||
```xml
|
||
<!-- com.momentry.n8n.main.plist -->
|
||
<key>ProgramArguments</key>
|
||
<array>
|
||
<string>/opt/homebrew/opt/node@22/bin/node</string>
|
||
<string>/opt/homebrew/lib/node_modules/n8n/bin/n8n</string>
|
||
<string>start</string>
|
||
</array>
|
||
|
||
<key>EnvironmentVariables</key>
|
||
<dict>
|
||
<key>PATH</key>
|
||
<string>/opt/homebrew/opt/node@22/bin:/opt/homebrew/bin:...</string>
|
||
</dict>
|
||
```
|
||
|
||
### 6.4 Python + Node.js 共存原則
|
||
|
||
#### 6.4.1 隔離原則
|
||
|
||
| 原則 | 說明 |
|
||
|------|------|
|
||
| **獨立路徑** | Python 用 venv 路徑,Node.js 用 node@22 路徑 |
|
||
| **獨立環境** | n8n 服務使用 launchd plist,不與 Rust 共享環境 |
|
||
| **明確版本** | 所有腳本明確指定直譯器路徑 |
|
||
| **PORT 分配** | n8n: 5678/5679, API: 另行分配 |
|
||
|
||
#### 6.4.2 環境變數隔離
|
||
|
||
```bash
|
||
# Rust 專案 .env
|
||
DATABASE_URL=postgres://...
|
||
|
||
# n8n plist
|
||
N8N_ENCRYPTION_KEY=xxx
|
||
N8N_BASIC_AUTH_ACTIVE=true
|
||
|
||
# 勿混用,避免 Rust 讀到 n8n 環境變數
|
||
```
|
||
|
||
### 6.5 工作流程整合
|
||
|
||
#### 6.5.1 Rust → Python
|
||
|
||
```
|
||
Rust CLI ──▶ Python Script ──▶ JSON Output ──▶ Rust Parse
|
||
│ │
|
||
└── venv/bin/python └── faster-whisper
|
||
```
|
||
|
||
#### 6.5.2 Rust → n8n Webhook
|
||
|
||
```rust
|
||
// 觸發 n8n workflow
|
||
use reqwest;
|
||
|
||
pub async fn trigger_n8n_webhook(webhook_url: &str, payload: &str) -> Result<()> {
|
||
let client = reqwest::Client::new();
|
||
client.post(webhook_url)
|
||
.json(payload)
|
||
.send()
|
||
.await
|
||
.context("Failed to trigger n8n webhook")?;
|
||
Ok(())
|
||
}
|
||
```
|
||
|
||
#### 6.5.3 n8n → Rust API
|
||
|
||
```
|
||
n8n Workflow ──▶ HTTP Request Node ──▶ Rust API Server
|
||
│
|
||
┌───────┴───────┐
|
||
│ axum server │
|
||
│ /api/webhook │
|
||
└───────────────┘
|
||
```
|
||
|
||
### 6.6 監控配置
|
||
|
||
#### 6.6.1 獨立監控腳本
|
||
|
||
```bash
|
||
# monitor/service/node_monitor.sh
|
||
# 監控 n8n Node.js 版本
|
||
|
||
# monitor/service/python_monitor.sh
|
||
# 監控 Python 腳本執行狀態
|
||
```
|
||
|
||
#### 6.6.2 健康檢查
|
||
|
||
```yaml
|
||
# monitor_config.yaml
|
||
services:
|
||
- name: "n8n"
|
||
type: "http"
|
||
port: 5678
|
||
check_url: "http://localhost:5678/"
|
||
|
||
- name: "Python Scripts"
|
||
type: "process"
|
||
check: "pgrep -f asr_processor.py"
|
||
```
|
||
|
||
### 6.7 排程管理
|
||
|
||
#### 6.7.1 備份排程 (Python 腳本)
|
||
|
||
```bash
|
||
# crontab
|
||
0 3 * * * /Users/accusys/momentry/scripts/backup_all.sh
|
||
```
|
||
|
||
#### 6.7.2 n8n 工作流排程
|
||
|
||
- 由 n8n 內建排程節點管理
|
||
- 不與 crontab 衝突
|
||
|
||
### 6.8 故障排除
|
||
|
||
#### 6.8.1 常見問題
|
||
|
||
| 問題 | 原因 | 解決方案 |
|
||
|------|------|----------|
|
||
| n8n 版本警告 | 使用 Node 25.x | 確認 plist 使用 node@22 |
|
||
| Python 腳本找不到模組 | 未啟用 venv | 使用 venv/bin/python |
|
||
| 執行權限錯誤 | shebang 錯誤 | 確認 #!/opt/homebrew/bin/python3.11 |
|
||
| Port 被佔用 | 多個服務使用相同 port | 分配獨立 port |
|
||
|
||
#### 6.8.2 診斷命令
|
||
|
||
```bash
|
||
# 檢查 Python 版本
|
||
which python
|
||
/opt/homebrew/bin/python3.11 --version
|
||
|
||
# 檢查 Node.js 版本
|
||
/opt/homebrew/opt/node@22/bin/node --version
|
||
|
||
# 檢查 n8n 程序
|
||
ps aux | grep n8n
|
||
|
||
# 檢查 Python 程序
|
||
ps aux | grep python
|
||
|
||
# 檢查 Port 佔用
|
||
lsof -i :5678 # n8n
|
||
```
|
||
|
||
### 6.9 新增服務決策
|
||
|
||
```
|
||
新服務需要哪種執行環境?
|
||
│
|
||
├─ Python 腳本 ──▶ 使用專案 venv
|
||
│ (路徑: venv/bin/python)
|
||
│
|
||
├─ Node.js 工具 ──▶ 評估版本需求
|
||
│ │
|
||
│ ├─ 支援 Node 22 ──▶ 使用 node@22
|
||
│ │
|
||
│ └─ 需要其他版本 ──▶ 安裝新版本 (如 node@20)
|
||
│
|
||
└─ 現有服務依賴 ──▶ 根據現有服務配置
|
||
```
|
||
|
||
### 6.10 文件維護
|
||
|
||
當新增 Python 或 Node.js 服務時:
|
||
|
||
1. 更新本文檔的版本表格
|
||
2. 建立對應的監控腳本
|
||
3. 如需 launchd plist,建立並加入 `momentry_runtime/plist/`
|
||
4. 更新 `docs/NODEJS.md` 或 `docs/PYTHON.md`
|
||
|
||
### 5.2 進度回報
|
||
|
||
透過 stderr 回報進度,供 Rust 端解析:
|
||
|
||
```python
|
||
# Python 腳本
|
||
import sys
|
||
|
||
print(f"ASR_START", file=sys.stderr)
|
||
print(f"ASR_LANGUAGE:{detected_lang}", file=sys.stderr)
|
||
print(f"ASR_PROGRESS:{count}", file=sys.stderr)
|
||
print(f"ASR_COMPLETE:{total}", file=sys.stderr)
|
||
```
|
||
|
||
```rust
|
||
// Rust 端解析
|
||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||
for line in stderr.lines() {
|
||
if line.starts_with("ASR_PROGRESS:") {
|
||
let count = line.trim_start_matches("ASR_PROGRESS:");
|
||
println!("[ASR] Processed {} segments...", count);
|
||
}
|
||
}
|
||
```
|
||
|
||
## 7. 測試策略
|
||
|
||
### 6.1 單元測試
|
||
|
||
```rust
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn test_chunk_creation() {
|
||
let chunk = Chunk::new(
|
||
"test-uuid".to_string(),
|
||
0,
|
||
ChunkType::Sentence,
|
||
0.0,
|
||
10.0,
|
||
serde_json::json!({"text": "Hello"}),
|
||
);
|
||
|
||
assert_eq!(chunk.uuid, "test-uuid");
|
||
assert_eq!(chunk.chunk_type, ChunkType::Sentence);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 6.2 整合測試
|
||
|
||
```rust
|
||
#[cfg(test)]
|
||
mod integration {
|
||
use super::*;
|
||
|
||
#[tokio::test]
|
||
async fn test_database_connection() {
|
||
let db = PostgresDb::init().await.unwrap();
|
||
let videos = db.list_videos().await.unwrap();
|
||
assert!(videos.len() >= 0);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 6.3 測試資料
|
||
|
||
- 使用測試資料庫隔離測試環境
|
||
- 避免在測試中使用真實敏感資料
|
||
- 使用 mock 物件模擬外部依賴
|
||
|
||
```rust
|
||
#[cfg(test)]
|
||
mod mocks {
|
||
pub struct MockVideoProcessor {
|
||
pub result: AsrResult,
|
||
}
|
||
|
||
impl VideoProcessor for MockVideoProcessor {
|
||
async fn process(&self, _video_path: &str) -> Result<AsrResult> {
|
||
Ok(self.result.clone())
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 8. 日誌與監控
|
||
|
||
### 7.1 日誌規範
|
||
|
||
- **使用 tracing**: 不要使用 `println!`
|
||
- **結構化日誌**: 使用訊息 + 欄位
|
||
|
||
```rust
|
||
use tracing::{info, warn, error};
|
||
|
||
fn process_video(uuid: &str) -> Result<()> {
|
||
info!(uuid = uuid, "Starting video processing");
|
||
|
||
match do_processing(uuid) {
|
||
Ok(_) => info!(uuid = uuid, "Processing completed"),
|
||
Err(e) => {
|
||
error!(uuid = uuid, error = %e, "Processing failed");
|
||
return Err(e);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 7.2 初始化日誌
|
||
|
||
```rust
|
||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||
|
||
fn init_logging() {
|
||
tracing_subscriber::registry()
|
||
.with(
|
||
tracing_subscriber::EnvFilter::try_from_default_env()
|
||
.unwrap_or_else(|_| "momentry_core=info,tokio=warn".into()),
|
||
)
|
||
.with(tracing_subscriber::fmt::layer())
|
||
.init();
|
||
}
|
||
```
|
||
|
||
## 9. 性能優化
|
||
|
||
### 8.1 避免拷貝
|
||
|
||
```rust
|
||
// ❌ 拷貝
|
||
let data = record.clone();
|
||
|
||
// ✅ 引用
|
||
fn process(data: &Data) { }
|
||
|
||
// ✅ 或使用 Arc 共用
|
||
use std::sync::Arc;
|
||
let shared = Arc::new(data);
|
||
```
|
||
|
||
### 8.2 批量操作
|
||
|
||
```rust
|
||
// ❌ 逐筆插入
|
||
for item in items {
|
||
db.insert(&item).await?;
|
||
}
|
||
|
||
// ✅ 批量插入
|
||
db.insert_batch(&items).await?;
|
||
```
|
||
|
||
### 8.3 連線池
|
||
|
||
```rust
|
||
// 使用 sqlx 連線池
|
||
let pool = SqlxPool::connect(&DATABASE_URL).await?;
|
||
let db = PostgresDb::new(pool);
|
||
```
|
||
|
||
## 10. 安全考量
|
||
|
||
### 9.1 敏感資訊
|
||
|
||
- **不要**將密碼、API Key 寫入程式碼
|
||
- 使用環境變數或設定檔
|
||
- .env 檔案加入 .gitignore
|
||
|
||
```rust
|
||
// ❌ 硬編碼密碼
|
||
let password = "secret123";
|
||
|
||
// ✅ 使用環境變數
|
||
let password = std::env::var("DATABASE_PASSWORD")
|
||
.context("DATABASE_PASSWORD must be set")?;
|
||
```
|
||
|
||
### 9.2 命令注入
|
||
|
||
```rust
|
||
// ❌ 危險: 直接使用使用者輸入
|
||
let cmd = format!("ffprobe {}", user_input);
|
||
|
||
// ✅ 安全: 使用參數化
|
||
Command::new("ffprobe")
|
||
.arg(user_input) // 自動轉義
|
||
.output();
|
||
```
|
||
|
||
## 11. 文件編寫
|
||
|
||
### 10.1 結構體/函式文件
|
||
|
||
```rust
|
||
/// 代表一個影片記錄
|
||
///
|
||
/// # Fields
|
||
/// * `id` - 資料庫 ID
|
||
/// * `uuid` - 唯一識別碼
|
||
/// * `duration` - 影片時長 (秒)
|
||
///
|
||
/// # Example
|
||
/// ```
|
||
/// let video = VideoRecord {
|
||
/// id: 1,
|
||
/// uuid: "abc123".to_string(),
|
||
/// duration: 120.5,
|
||
/// };
|
||
/// ```
|
||
pub struct VideoRecord {
|
||
pub id: i64,
|
||
pub uuid: String,
|
||
pub duration: f64,
|
||
}
|
||
```
|
||
|
||
### 10.2 API 文件
|
||
|
||
```rust
|
||
/// 取得影片記錄
|
||
///
|
||
/// # Arguments
|
||
/// * `uuid` - 影片的 UUID
|
||
///
|
||
/// # Returns
|
||
/// * `Ok(Some(VideoRecord))` - 找到影片
|
||
/// * `Ok(None)` - 影片不存在
|
||
/// * `Err` - 資料庫錯誤
|
||
///
|
||
/// # Errors
|
||
/// 如果資料庫連線失敗,返回資料庫錯誤
|
||
pub async fn get_video(&self, uuid: &str) -> Result<Option<VideoRecord>>;
|
||
```
|
||
|
||
## 12. CLI 命令設計
|
||
|
||
### 11.1 命令結構
|
||
|
||
使用 clap derive:
|
||
|
||
```rust
|
||
use clap::{Parser, Subcommand};
|
||
|
||
#[derive(Parser)]
|
||
#[command(name = "momentry")]
|
||
#[command(about = "Digital asset management system")]
|
||
struct Cli {
|
||
#[command(subcommand)]
|
||
command: Commands,
|
||
}
|
||
|
||
#[derive(Subcommand)]
|
||
enum Commands {
|
||
/// Register a video file
|
||
Register {
|
||
/// Path to video file
|
||
path: String,
|
||
},
|
||
/// Process video
|
||
Process {
|
||
/// UUID or path
|
||
target: String,
|
||
},
|
||
/// Generate chunks
|
||
Chunk {
|
||
/// Video UUID
|
||
uuid: String,
|
||
},
|
||
}
|
||
```
|
||
|
||
### 11.2 錯誤處理
|
||
|
||
```rust
|
||
match &cli.command {
|
||
Commands::Register { path } => {
|
||
if !Path::new(path).exists() {
|
||
eprintln!("Error: File not found: {}", path);
|
||
std::process::exit(1);
|
||
}
|
||
// ...
|
||
}
|
||
}
|
||
```
|
||
|
||
## 13. 依賴管理
|
||
|
||
### 12.1 版本約束
|
||
|
||
```toml
|
||
# Cargo.toml
|
||
[dependencies]
|
||
anyhow = "1.0" # 精確版本
|
||
tokio = { version = "1", features = ["full"] } # 範圍版本
|
||
serde = "1.0" # 精確版本
|
||
```
|
||
|
||
### 12.2 避免依賴地獄
|
||
|
||
- 審查依賴數量
|
||
- 優先使用標準庫
|
||
- 選擇維護良好的套件
|
||
|
||
## 14. 建構與部署
|
||
|
||
### 13.1 建構命令
|
||
|
||
```bash
|
||
# 開發建構
|
||
cargo build
|
||
|
||
# 發布建構
|
||
cargo build --release
|
||
|
||
# 單一二進制
|
||
cargo build --bin momentry
|
||
```
|
||
|
||
### 13.2 檢查清單
|
||
|
||
```bash
|
||
# 格式化
|
||
cargo fmt -- --check
|
||
|
||
# Lint
|
||
cargo clippy --all-features -- -D warnings
|
||
|
||
# 類型檢查
|
||
cargo check --all-features
|
||
|
||
# 測試
|
||
cargo test
|
||
```
|
||
|
||
## 15. 版本控制
|
||
|
||
### 14.1 提交訊息規範
|
||
|
||
```
|
||
<type>(<scope>): <subject>
|
||
|
||
<body>
|
||
|
||
<footer>
|
||
```
|
||
|
||
類型:
|
||
- `feat`: 新功能
|
||
- `fix`: 錯誤修復
|
||
- `docs`: 文件變更
|
||
- `style`: 格式調整
|
||
- `refactor`: 重構
|
||
- `test`: 測試變更
|
||
- `chore`: 建構/工具變更
|
||
|
||
範例:
|
||
```
|
||
feat(processor): Add ASR progress reporting
|
||
|
||
- Add stderr parsing for progress updates
|
||
- Support ASR_START, ASR_PROGRESS, ASR_COMPLETE markers
|
||
|
||
Closes #123
|
||
```
|
||
|
||
### 14.2 分支策略
|
||
|
||
- `main`: 穩定版本
|
||
- `feature/*`: 新功能開發
|
||
- `fix/*`: 錯誤修復
|
||
- `refactor/*`: 重構
|
||
|
||
---
|
||
|
||
## 附錄: 快速參考
|
||
|
||
### 建構
|
||
```bash
|
||
cargo build --release
|
||
cargo run -- --help
|
||
```
|
||
|
||
### 品質檢查
|
||
```bash
|
||
cargo fmt -- --check
|
||
cargo clippy --all-features
|
||
cargo check --all-features
|
||
cargo test
|
||
```
|
||
|
||
### 依賴
|
||
```bash
|
||
cargo add <package>
|
||
cargo tree
|
||
cargo outdated
|
||
```
|