18 KiB
MarkBase開發指南
##專案概述
MarkBase - Momentry Display Engine
Rust Axum Web伺服器,提供 Markdown渲染與檔案樹管理功能。
-技術棧:Rust 1.92+, Axum 0.7, SQLite, pulldown-cmark
-目標平台:macOS(含音訊控制功能)
-資料庫:Per-user SQLite in data/users/<user_id>.sqlite
##核心指令
#建構與測試
cargo build #建構專案
cargo test #域行所有測試
cargo test test_insert #執行特定測試
cargo clippy #代碼品質檢查
#執行伺服器
cargo run -- display #啟動顯示伺服器(預設 port 11438)
cargo run -- display -p8080 #自訂 port
cargo run -- display README.md #顯示指定 Markdown檔案
#渲染工具
cargo run -- render <FILE> #渲染 Markdown(輸出到 stdout)
cargo run -- render <FILE> -o output.html #輸出到檔案
##架構概覽
###核心模組
| 模組 | 檔案 | 功能 |
|---|---|---|
| CLI入口 | main.rs |
clap指令解析 |
| Web伺服器 | server.rs |
Axum REST API(18+路由) |
| 檔案樹管理 | filetree/mod.rs |
SQLite CRUD操作 |
| Markdown渲染 | render.rs |
pulldown-cmark轉換 |
| 音訊控制 | audio.rs |
macOS音訊裝置管理 |
| 指令隊列 | command.rs |
WebSocket指令處理 |
###資料庫結構
-位置:data/users/<user_id>.sqlite
-表:file_registry, file_nodes, file_locations
-每個 user_id獨立資料庫
File Tree核心說明(重點章節)
###架構設計
File Tree是 MarkBase的核心模組,提供檔案樹管理功能。
核心結構:
FileTree {
user_id: String, //用戶ID
nodes: Vec<FileNode>, //節點列表
}
節點類型:
- Folder -資料夾節點(可包含子節點)
- File -檔案節點(指向實體檔案)
###資料庫設計
SQLite表結構:
| 表名 | 功能 |
|---|---|
| file_registry | 檔案註冊資訊 |
| file_nodes | 檔案樹節點 |
| file_locations | 檔案位置記錄 |
節點欄位:
node_id, label, aliases_json, file_uuid, sha256,
parent_id, children_json, node_type, icon, color,
bg_color, file_size, registered_at, created_at,
updated_at, sort_order
###公開API(13個函數)
| 函數名 | 功能 | 檔案位置 |
|---|---|---|
| user_db_path | 取得DB路徑 | mod.rs:58 |
| init_user_db | 初始化DB | mod.rs:62 |
| open_user_db | 開啟DB | mod.rs:74 |
| load | 載入檔案樹 | mod.rs:79 |
| insert_node | 插入節點 | mod.rs:112 |
| update_node | 更新節點 | mod.rs:149 |
| update_node_alias | 更新別名 | mod.rs:187 |
| delete_node | 刪除節點 | mod.rs:214 |
| move_node | 移動節點 | mod.rs:220 |
| build_tree | 建立樹狀結構 | mod.rs:240 |
| new_folder | 建立資料夾節點 | node.rs:27 |
| new_file_node | 建立檔案節點 | node.rs:300 |
| add_location | 新增檔案位置 | mod.rs:346 |
REST API(7個路由)
| 路由 | 方法 | 功能 | server.rs行號 |
|---|---|---|---|
/api/v2/tree/:user_id |
GET | 取得檔案樹 | 61 |
/api/v2/tree/:user_id |
DELETE | 刪除所有節點 | 64 |
/api/v2/tree/:user_id/node |
POST | 建立節點 | 62 |
/api/v2/tree/:user_id/node/:node_id |
PUT | 更新節點 | 63 |
/api/v2/tree/:user_id/node/:node_id |
DELETE | 刪除節點 | 63 |
/api/v2/tree/:user_id/node/:node_id/move |
PUT | 移動節點 | 71 |
/api/v2/tree/:user_id/node/:node_id/alias |
PATCH | 更新別名 | 72 |
/api/v2/tree/:user_id/restore |
POST | 從外部API恢復 | 65 |
Query參數:
mode-顯示模式
使用範例:
curl http://localhost:11438/api/v2/tree/demo?mode=tree
curl http://localhost:11438/api/v2/tree/demo?mode=list
DisplayMode(顯示模式)
顯示模式選項:
| 模式 | 檔案 | 用途 |
|---|---|---|
| tree | modes/tree.rs |
樹狀顯示 |
| list | modes/list.rs |
列表顯示 |
| grid_sm | modes/grid_sm.rs |
小格狀顯示 |
| grid_lg | modes/grid_lg.rs |
大格狀顯示 |
DisplayMode trait定義(mode.rs:19):
pub trait DisplayMode: Send + Sync {
fn name(&self) -> &'static str;
fn render(&self, tree: &FileTree) -> Value;
fn sort_options(&self) -> Vec<SortOption>;
fn filter_options(&self) -> Vec<FilterOption>;
}
###檔案轉換功能(convert.rs)
支援格式轉換:
| 工具 | 支援格式 |
|---|---|
| textutil(macOS內建) | doc, docx, rtf |
| macOS工具 | pages, key, numbers |
| soffice/qlmanage | pptx, ppt, xlsx, xls, odt, epub |
核心函數:
is_doc_ext(ext)- 檢查是否為文檔格式get_cached_preview(file, ext)- 生成緩存預覽
緩存目錄: data/cache/
###測試覆蓋現況
已測試(7個):
- ✅ init_and_load_empty_tree
- ✅ insert_and_load_node
- ✅ update_node
- ✅ delete_node
- ✅ move_node
- ✅ update_alias(zh_tw等多語言別名)
- ✅ build_tree(樹狀結構)
待補測試:
- ❌ convert.rs - 檔案轉換功能
- ❌ modes/*.rs - DisplayMode渲染
- ❌ API路由 - REST endpoint測試
###開發範例
新增節點範例:
//建立資料夾
let folder = FileTree::new_folder("Videos", None);
tree.insert_node(&conn, &folder)?;
//建立檔案節點
let (file_node, register_sql) = FileTree::new_file_node(
"demo.mp4",
"abc123def456...",
Some("sha256hash"),
"demo.mp4",
Some(1024000),
Some("video/mp4"),
None,
Some(folder.node_id),
);
tree.insert_node(&conn, &file_node)?;
查詢範例:
//載入檔案樹
let tree = FileTree::load(&conn, &user_id)?;
//建立樹狀結構(parent-child關係)
let roots = tree.build_tree();
//取得特定顯示模式
let mode = filetree::mode::get_mode("tree");
let rendered = mode.render(&tree);
##測試執行
###執行測試
cargo test #域行所有測試
cargo test test_insert #執行特定測試
cargo test -- --nocapture #顯示詳細輸出
rm data/users/test_*.sqlite #清理暫存資料庫
###測試現況
| 模組 | 狀態 | 說明 |
|---|---|---|
| filetree/mod.rs | ✅已測試 | 7個 CRUD測試 |
| filetree/convert.rs | ❌待補 | 檔案轉換測試 |
| filetree/modes/*.rs | ❌待補 | DisplayMode測試 |
| server.rs(API路由) | ❌待補 | API handler測試 |
| render.rs | ❌待補 | Markdown渲染測試 |
| audio.rs | ❌待補 | macOS音訊功能測試 |
###測試清理
測試會產生暫存資料庫:data/users/test_*.sqlite
階段性任務結束後應手動清除:
rm data/users/test_*.sqlite
##展示執行
###啟動伺服器
cargo run -- display #啟動(自動開啟瀏覽器)
cargo run -- render <file> #渲染 Markdown檔案
###Demo資料
data/users/demo.sqlite- 50節點範例資料- 5個 Folder節點
- 45個 File節點
data/cache/-範例檔案- 29ffd4c12ef6481da6bee7ae4c36a89f.jpg
- 2c62f90aacc542a9bcfa0c65b63be02a.txt
###Demo資料庫結構
Home(根資料夾)
├── Movies(子資料夾,包含影片檔案)
├── Marketing(子資料夾,包含行銷素材)
├── Cartoons(子資料夾,包含動畫檔案)
└── Other(子資料夾,包含其他檔案)
macOS環境需求
###必要工具
- SwitchAudioSource -音訊裝置切換CLI
brew install switchaudio-source SwitchAudioSource -a #列出所有音訊裝置
macOS限定功能
| 功能 | 依賴 | 說明 |
|---|---|---|
| 音訊裝置切換 | SwitchAudioSource | /devices API |
| 音量控制 | osascript | /volume API |
| 語音測試 | say命令 | /command API(test_voice) |
| 文檔轉換 | textutil | convert.rs(doc/rtf轉換) |
CI/CD配置(Gitea Actions)
Access Token 配置
Token資訊:
- Token名稱: OpenCode_M4Mini
- 建立日期: 2026-05-16
- 擁有者: warren (Warren Lo)
- 權限範圍: repo (完整倉庫權限)
- 用途: CI/CD部署、倉庫管理、自動化操作
Token值: c5e025496ebc3c7408a971d64a33bd56aac9186c
使用方式:
# API認證
curl -H "Authorization: token c5e025496ebc3c7408a971d64a33bd56aac9186c" \
https://gitea.momentry.ddns.net/api/v1/user
# Git推送(需配置遠端)
git remote set-url origin https://oauth2:c5e025496ebc3c7408a971d64a33bd56aac9186c@gitea.momentry.ddns.net/warren/markbase.git
安全提醒:
- ⚠️ 此Token僅用於本機開發環境
- ⚠️ 請勿提交到公開倉庫
- ⚠️ 定期更換Token(建議每90天)
- ⚠️ 如需撤銷:Gitea → Settings → Applications → Access Tokens → Delete
###環境資訊
- Gitea Server: https://gitea.momentry.ddns.net
- Gitea版本: 1.25.3(支援 Actions)
- Runner: 本機Mac(實機測試)
- Workflow:
.gitea/workflows/*.yml
Runner配置步驟
1. 取得 Runner Token
- 登入 Gitea: https://gitea.momentry.ddns.net
- Settings → Actions → Runners →建立新 Runner
2. 下載並安裝 Runner
# macOS ARM版本
wget https://dl.gitea.com/act_runner/latest/act_runner-darwin-arm64
chmod +x act_runner-darwin-arm64
sudo mv act_runner-darwin-arm64 /usr/local/bin/act_runner
3. 註冊 Runner
act_runner register --instance https://gitea.momentry.ddns.net --token <YOUR_TOKEN>
4.啟動 Runner
act_runner daemon
###Workflow範例
# .gitea/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Install SwitchAudioSource
run: brew install switchaudio-source
- name: Run tests
run: cargo test --all
- name: Run clippy
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Check formatting
run: cargo fmt -- --check
- name: Clean test databases
run: rm -f data/users/test_*.sqlite
##開發環境設定
###開發環境API Key
server.rs:192包含開發環境 API key:
let api_key = "muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69";
let api_url = "http://localhost:3002/api/v1/files";
用途:restore_tree功能從外部 API恢復檔案樹。
改進建議: -應改用環境變數配置
- 建立
.env.example範例
###環境變數配置(待實作)
# .env(未來配置)
RESTORE_API_KEY=muser_your_api_key_here
RESTORE_API_URL=http://localhost:3002
SERVER_PORT=11438
DB_DIR=data/users
##代碼風格
###Rust標準工具
cargo fmt #代碼格式化
cargo clippy #代碼品質檢查
現有clippy警告:
- server.rs:609 -未使用變數
state - server.rs:1020 -未使用變數
pg_url
##專案結構
markbase/
├── src/
│ ├── main.rs # CLI入口
│ ├── lib.rs #模組宣告
│ ├── server.rs # Web伺服器(18+路由)
│ ├── render.rs # Markdown渲染
│ ├── audio.rs # macOS音訊
│ ├── command.rs #指令隊列
│ ├── page.html # HTML模板
│ └── filetree/
│ ├── mod.rs #檔案樹核心(553行)
│ ├── convert.rs #檔案轉換(253行)
│ ├── mode.rs # DisplayMode trait(43行)
│ ├── node.rs #節點定義(82行)
│ └── modes/
│ ├── tree.rs #樹狀模式(57行)
│ ├── list.rs #列表模式(87行)
│ ├── grid_sm.rs #小格狀模式(72行)
│ ├── grid_lg.rs #大格狀模式(83行)
│ └── mod.rs #模式匯出(4行)
├── data/
│ ├── users/ # SQLite資料庫
│ │ ├── demo.sqlite # Demo資料(50節點)
│ │ └── test_*.sqlite #測試暫存(已清理)
│ └── cache/ #檔案緩存
│ ├── *.jpg #圖片緩存
│ └── *.txt #文本緩存
├── tests/ #整合測試(待建立)
├── examples/ #範例檔案(待建立)
├── .gitea/
│ └ workflows/
│ ├── test.yml #測試workflow(待建立)
│ ├── build.yml #建構workflow(待建立)
│ └── release.yml #發布workflow(待建立)
├── Cargo.toml # Rust配置
├── Cargo.lock #依賴鎖定
└── AGENTS.md # 本文件
##常見問題
###測試暫存檔清理
rm data/users/test_*.sqlite
###音訊功能無效
確認 SwitchAudioSource已安裝:
brew install switchaudio-source
SwitchAudioSource -a #列出音訊裝置
###File Tree API測試
curl http://localhost:11438/api/v2/tree/demo
curl http://localhost:11438/api/v2/tree/demo?mode=tree
###CI/CD Runner連接失敗
確認 Runner已配置並啟動:
act_runner list #查看 Runner狀態
act_runner daemon #啟動 Runner
###CI/CD日志查看方式
方法1:通過 Gitea Web UI查看(推薦)
https://gitea.momentry.ddns.net/warren/markbase/actions
方法2:通過 API獲取詳細日志
步驟:
-
獲取所有 Actions Runs
curl -H "Authorization: token c5e025496ebc3c7408a971d64a33bd56aac9186c" \ https://gitea.momentry.ddns.net/api/v1/repos/warren/markbase/actions/runs | jq '.workflow_runs[] | {id, status, conclusion}' -
獲取特定 Run的所有 Jobs
curl -H "Authorization: token c5e025496ebc3c7408a971d64a33bd56aac9186c" \ https://gitea.momentry.ddns.net/api/v1/repos/warren/markbase/actions/runs/<RUN_ID>/jobs | jq '.jobs[] | {id, name, status, conclusion}' -
獲取 Job詳細日志
curl -H "Authorization: token c5e025496ebc3c7408a971d64a33bd56aac9186c" \ https://gitea.momentry.ddns.net/api/v1/repos/warren/markbase/actions/jobs/<JOB_ID>/logs
範例:查看 Run ID 6的 test job日志
# Step 1: 獲取 jobs列表
curl -s -H "Authorization: token c5e025496ebc3c7408a971d64a33bd56aac9186c" \
https://gitea.momentry.ddns.net/api/v1/repos/warren/markbase/actions/runs/6/jobs | jq '.jobs[] | {id, name}'
# Step 2:獲取 job 11(test job)的完整日志
curl -s -H "Authorization: token c5e025496ebc3c7408a971d64a33bd56aac9186c" \
https://gitea.momentry.ddns.net/api/v1/repos/warren/markbase/actions/jobs/11/logs
日志格式:
- 每行以時間戳開頭:
2026-05-16T08:26:07.5859940Z - 包含完整執行過程、命令輸出、錯誤信息
- 錯誤行通常包含
##[error]標記
常見錯誤診斷:
conditional binary operator expected→ bash腳本語法問題Process completed with exit code 2→腳本執行失敗Unable to pull refs/heads/v3→ Git clone問題(通常可忽略)`
##部署狀況記錄(2026-05-16)
###已完成部署
倉庫建立:
- ✅ warren/markbase 倉庫已建立(ID: 27)
- ✅ URL: https://gitea.momentry.ddns.net/warren/markbase
- ✅ 代碼已推送(4 commits)
Runner配置:
- ✅ Runner已註冊(ID: 2)
- ✅ Runner名稱: accusys-Mac-mini-M4-2.local
- ✅ Runner標籤: macos-latest:host, macos-arm64:host
- ✅ Runner進程運行中(PID: 90905)
Actions狀態:
- ✅ Actions已觸發並執行
- ✅ Run ID 7: 成功(test + build jobs均通過) ← 最新
- ⚠️ Run ID 1-6: 失敗(Setup Rust bash兼容性問題)
- ✅ 本地測試全部通過(62 tests)
- ✅ Clippy檢查已通過
- ✅ 格式檢查已通過
###CI問題已解決(2026-05-16)
問題根本原因:
- GitHub Action(actions-rust-lang/setup-rust-toolchain@v1)與Gitea Runner bash環境不兼容
- 錯誤:
conditional binary operator expected(line 2)
解決方案:
- 替換為原生rustup安裝:
curl | sh.rustup.rs - 在所有cargo命令前添加:
source $HOME/.cargo/env
結果:
- ✅ Run ID 7: 成功(5分22秒,正常執行)
- ✅ test job: success
- ✅ build job: success
- ✅ CI/CD完全正常運行
Commits記錄:
##部署測試經驗(2026-05-16)
###核心方法論
CI日志獲取方法:
#1.獲取 Run列表
curl -H "Authorization: token <TOKEN>" \
<GITEA_URL>/api/v1/repos/<OWNER>/<REPO>/actions/runs
#2.獲取 Job ID
curl -H "Authorization: token <TOKEN>" \
<GITEA_URL>/api/v1/repos/<OWNER>/<REPO>/actions/runs/<RUN_ID>/jobs
#3.獲取 Job日志
curl -H "Authorization: token <TOKEN>" \
<GITEA_URL>/api/v1/repos/<OWNER>/<REPO>/actions/jobs/<JOB_ID>/logs
問題診斷思路:
觀察現象 →獲取日志 →定位錯誤 →分析原因 →設計方案 →實施修復 →验证結果
關鍵認知:
- GitHub Actions ≠Gitea Runner環境(不完全兼容)
- 原生工具安裝 >Action依賴(更穩定可靠)
- 環境變數載入是必要步驟(
source $HOME/.cargo/env)
測試結果: -失敗→成功迭代:6次失敗 →1次成功 -執行時間對比:5秒(失敗)→5分22秒(成功) -修復方案:原生rustup安裝替代GitHub Action
最後更新:2026-05-16 版本:1.4(部署測試經驗版)