2026-10-02 13:12:49 +08:00
2026-10-02 13:12:49 +08:00
2026-10-02 13:12:49 +08:00
2026-10-02 13:12:49 +08:00
2026-10-02 13:12:49 +08:00
2026-10-02 13:12:49 +08:00
2026-10-02 13:12:49 +08:00
2026-10-02 13:12:49 +08:00
2026-10-02 13:12:49 +08:00
2026-10-02 13:12:49 +08:00
2026-10-02 13:12:49 +08:00
2026-10-02 13:12:49 +08:00
2026-10-02 13:11:03 +08:00

alterdb

以 SQLite 為底層存儲格式的自定義數據庫引擎,附帶開箱即用的 Web 管理介面。

狀態:早期開發中 — axum HTTP API 骨架、Askama 渲染的首頁與 SQLite 存儲層(rusqlite + 連接池)已就緒,數據模型 Account(帳戶)與 Database(外部數據庫連接,mysql / postgres)的 CRUD 與 REST API 已可用;Web 介面的登入 / 會話(Cookie + 進程內存會話存儲)、變更密碼與數據庫連接管理頁已完成,連接詳情頁可實際連線目標庫(測試連接狀態、瀏覽數據表),數據表管理頁可檢視欄位結構並新增 / 刪除欄位,數據管理頁可分頁瀏覽並編輯(以主鍵定位修改資料列)表內資料,管理介面其餘部分與查詢引擎仍在開發中。

專案簡介

alterdb 是一個使用 Rust 編寫的自定義數據庫引擎。它不是又一個 SQLite 管理工具,而是:

  • 自定義查詢引擎:對外提供自己的查詢介面與數據模型,查詢的解析、規劃與執行均由 alterdb 自行實作;
  • SQLite 作為底層存儲:數據最終落在單一 SQLite 檔案中,直接享受其成熟可靠的持久化與事務能力,而無需從零重造存儲層;
  • Web 管理介面:以本地伺服器方式運行,打開瀏覽器即可瀏覽數據、執行查詢、管理表結構。

核心特性(規劃中)

  • 自定義查詢引擎:查詢的解析與執行由本項目實作,不直接透傳 SQL
  • SQLite 存儲後端:單檔案持久化,支援事務
  • Web 管理介面:
    • 數據庫 / 表 / 結構瀏覽
    • 線上執行查詢,結果以表格呈現
    • 數據新增、編輯、刪除
  • 單一可執行檔部署:編譯為單一 binary,內嵌前端資源,無外部運行時依賴

架構規劃

┌─────────────┐      HTTP       ┌───────────────────┐
│   瀏覽器     │ ◄────────────► │   HTTP API 伺服器   │
│   (Web UI)  │                 │      (Rust)       │
└─────────────┘                 └─────────┬─────────┘
                                          │
                                ┌─────────▼─────────┐
                                │     查詢引擎        │
                                │  解析 / 規劃 / 執行  │
                                └─────────┬─────────┘
                                          │
                                ┌─────────▼─────────┐
                                │      存儲層         │
                                │   (SQLite 檔案)    │
                                └───────────────────┘

技術選型:

項目 選擇 備註
語言 Rust(edition 2024) 需 Rust 1.85+
Web 框架 axum 0.8 + tokio REST API;中間件透過 tower-http
模板引擎 Askama 0.16 編譯期模板,HTML 內嵌於執行檔
存儲 SQLite(rusqlite bundled) deadpool-sqlite 連接池;WAL 模式
外部庫連線 mysql_async / tokio-postgres 連接測試與數據表瀏覽;暫未啟用 TLS
密碼雜湊 argon2 0.5 帳戶密碼以 Argon2id + 隨機鹽雜湊存儲,永不落明文

快速開始

環境需求:Rust 1.85 或以上版本(本項目使用 edition 2024)。

# 編譯並運行
cargo run

# 伺服器啟動後:
# - 管理介面首頁:http://127.0.0.1:8080/(未登入會被導向 /login)
# - 數據庫連接管理頁:http://127.0.0.1:8080/databases(需登入)
# - 變更密碼頁:http://127.0.0.1:8080/password(需登入)
# - 登入頁:http://127.0.0.1:8080/login(默認帳號 admin / admin)
# - 健康檢查 API:http://127.0.0.1:8080/api/health
# - 帳戶 API:http://127.0.0.1:8080/api/accounts
# - 數據庫連接 API:http://127.0.0.1:8080/api/databases

資料庫檔案默認為工作目錄下的 alterdb.db,可透過環境變數 ALTERDB_DB_PATH 指定其他路徑。

SQLite 數據檔(*.db 等)已被 .gitignore 排除,不會被誤提交到版本庫。

登入與會話

Web 介面頁面(目前為首頁 /)需登入後才能訪問,未登入的請求會被重定向到 /login:

方法 路徑 說明
GET /login 登入頁(已登入者直接回到首頁);帶 ?changed=1 時顯示密碼變更成功提示
POST /login 提交用戶名 / 密碼;成功建立會話並下發 Cookie
POST /logout 銷毀會話並清除 Cookie
GET /password 變更密碼頁(需登入)
POST /password 校驗原密碼後更新密碼;成功即銷毀該帳戶全部會話,導向 /login?changed=1

實作要點:

  • 密碼以 Argon2id 校驗;帳號不存在時仍執行一次雜湊運算,避免以回應時間差列舉用戶名;已停用(is_active = false)的帳戶無法登入
  • 會話保存在進程內存,以 256 位隨機 token 為鑰匙,透過 HttpOnly + SameSite=Lax Cookie 傳遞,有效期 24 小時;伺服器重啟後會話失效(重新登入即可)
  • 變更密碼需驗證原密碼;新密碼長度 8–128 字符、兩次輸入需一致且不得與原密碼相同;成功後以新 Argon2 雜湊覆寫,該帳戶全部會話(含當前)即刻失效,需以新密碼重新登入
  • /api/* 端點暫不要求會話,後續將統一接入認證

Web 管理介面

登入後的頁面共用側邊導航欄(_shell.html 應用外殼):左側為品牌、主導航(首頁 / 數據庫,當前頁以 aria-current="page" 高亮)與底部的登入者資訊、變更密碼入口及登出;窄屏(≤ 48rem)自動折疊為頂欄。登入頁與 404 頁保持無側欄的獨立版式。頁面外殼由 Askama 渲染,數據操作由內嵌 vanilla JS 調用 /api/* 完成(無任何外部前端依賴):

全站支援深淺色主題:預設跟隨系統偏好(color-scheme: light dark),點擊側邊欄底部(獨立版式頁為頁首右側)的「深色 / 淺色模式」按鈕可手動固定,選擇保存在 localStorage(鍵 theme),並由 <head> 內的同步內聯腳本在首幀繪製前重新套用,避免刷新閃爍。配色完全由 color-scheme 驅動(currentColor 混色與 light-dark()),無任何主題相關的服務端狀態。

路徑 說明
/ 首頁:服務資訊與各管理入口
/databases 數據庫連接列表(名稱連到詳情頁;含刪除)
/databases/new 新增連接(獨立表單頁)
/databases/{id} 連接詳情頁:服務端渲染連接資訊,頁面載入即自動測試連接(狀態徽章 + 伺服器版本 + 延遲)並列出目標庫當前 schema 的數據表(名稱連到表管理頁);可「重新測試」
/databases/{id}/edit 編輯連接(獨立表單頁,服務端預填現值;連接不存在返回 404)
/databases/{id}/tables/{table} 數據表管理頁:欄位結構(名稱 / 類型 / 可空 / 默認值,支援新增與刪除欄位),由內嵌 JS 即時連線目標庫取得;頁面不請求表內資料
/databases/{id}/tables/{table}/data 數據管理頁:表內資料分頁瀏覽(每頁 25–200 列、顯示總列數)與編輯資料列(以主鍵定位,逐欄輸入、可空欄位可設 NULL),由內嵌 JS 即時連線目標庫取得
/password 變更密碼(表單:原密碼 / 新密碼 / 確認新密碼;成功後全部會話失效並導向登入頁)

/databases 相關頁面要點:

  • 埠留空按類型自動補默認(MySQL 3306 / PostgreSQL 5432)
  • 編輯頁由服務端預填現值,但密碼永不回填(API 與頁面皆不輸出密碼);編輯時密碼欄留空表示保留原密碼(對應 PUT 語義:password 鍵缺省=保留、空字串=清空、有值=覆寫)
  • 表單提交由 JS 調用 /api/databases,成功後跳回列表頁;操作錯誤(驗證失敗、名稱衝突等)以頁面錯誤條展示 API 返回的錯誤訊息
  • 詳情頁(/databases/{id})載入時自動並行發起連接測試與數據表查詢,狀態以徽章呈現(已連接 / 無法連接 / 測試中),數據表類型映射為「表 / 視圖」
  • 表管理頁(/databases/{id}/tables/{table})載入時僅載入欄位結構,不請求表內資料;工具列「數據管理」連到對應的數據管理頁
  • 數據管理頁(/databases/{id}/tables/{table}/data)載入時載入第一頁資料;支援換頁、調整每頁列數與手動刷新,NULL 儲存格以斜體 NULL 呈現,過長內容截斷並以懸浮提示顯示全文;頁面對視圖(VIEW)同樣適用
  • 數據管理頁可編輯資料列:有主鍵的表每列附「編輯」按鈕,對話框逐欄輸入(可空欄位附 NULL 勾選),只送出有變更的欄位,以編輯前的主鍵原值定位資料列;沒有主鍵的表(含視圖)僅能瀏覽並顯示提示;目標列已被刪除或主鍵已變更時返回衝突錯誤並保留表單內容
  • 表管理頁的欄位結構區可新增欄位(內嵌表單:名稱 / 類型(含常用建議)/ 可空(默認允許)/ 默認值)與刪除欄位(紅色按鈕,彈出確認對話框後才執行);結構變更成功後自動刷新欄位結構
  • 所有動態內容經 textContent 寫入,不以字串拼接 HTML

帳戶 API

管理介面用戶帳戶的 CRUD 端點(後續將由自定義查詢引擎統一驅動):

方法 路徑 說明
GET /api/accounts 列出所有帳戶
POST /api/accounts 建立帳戶;username、email 必填且全表唯一
GET /api/accounts/{id} 取得單一帳戶,不存在時返回 404
PUT /api/accounts/{id} 覆寫帳戶可編輯欄位(updated_at 自動刷新)
DELETE /api/accounts/{id} 刪除帳戶

輸入會先被正規化(去除首尾空白、display_name 缺省時同 username、is_active 缺省為啟用);驗證失敗返回 400,唯一欄位衝突返回 409。

默認管理員帳號

資料庫初始化時會自動建立默認管理員帳號(僅在不存在時建立,重啟不會覆蓋日後的變更):

  • 用戶名:admin
  • 密碼:admin

密碼以 Argon2 雜湊存儲,password_hash 欄位永不透過 API 序列化輸出;既有舊版資料庫啟動時會自動遷移(補上 password_hash 欄位)。初始密碼即帳號名,建議儘早變更。

# 建立帳戶
curl -X POST http://127.0.0.1:8080/api/accounts \
  -H 'Content-Type: application/json' \
  -d '{"username": "alice", "email": "alice@example.com", "display_name": "Alice"}'

# 列出所有帳戶
curl http://127.0.0.1:8080/api/accounts

數據庫連接 API

管理外部數據庫連接(MySQL / PostgreSQL)的 CRUD 端點,以及對目標庫的實際連線操作(測試連接、列出數據表、檢視欄位結構、分頁瀏覽與修改資料,由 mysql_async / tokio-postgres 驅動):

方法 路徑 說明
GET /api/databases 列出所有連接
POST /api/databases 建立連接;name 必填且全表唯一
GET /api/databases/{id} 取得單一連接,不存在時返回 404
PUT /api/databases/{id} 覆寫連接可編輯欄位(updated_at 自動刷新)
DELETE /api/databases/{id} 刪除連接
POST /api/databases/{id}/test 實際連線目標庫並查詢伺服器版本;成功與失敗都是 200(結果在 ok 欄位,附 server_version / error 與 elapsed_ms),連接記錄不存在返回 404
GET /api/databases/{id}/tables 列出目標庫當前 schema 的數據表(name + kind,按名稱排序);連不上目標時返回 502 並附錯誤訊息
GET /api/databases/{id}/tables/{table}/columns 列出資料表欄位結構(name / type / nullable / default / primary_key,按表定義順序);資料表不存在返回 404
POST /api/databases/{id}/tables/{table}/columns 新增欄位(ALTER TABLE … ADD COLUMN):name(≤64 字符)、type(原生 SQL 片段,如 varchar(255))、nullable(缺省允許)、default(可選,原生 SQL 片段);欄位名衝突返回 409,成功返回 204
DELETE /api/databases/{id}/tables/{table}/columns/{column} 刪除欄位(ALTER TABLE … DROP COLUMN,資料一併丟失且無法復原);欄位不存在返回 404,成功返回 204
GET /api/databases/{id}/tables/{table}/rows 分頁瀏覽資料:?limit=&offset=(limit 1–200、缺省 50;offset ≥ 0),回應含 columns、字串化的 rows(NULL 為 null)、total(COUNT(*))與生效的分頁參數
PATCH /api/databases/{id}/tables/{table}/rows 修改資料列:body 為 {"where": {主鍵欄位: 原值}, "set": {欄位: 新值}}(值為字串或 null=設為 NULL,按欄位型別隱式轉換);where 須恰好覆蓋主鍵欄位,set 欄位須存在且值 ≤65536 字符;目標列不存在(已被刪除或主鍵已變更)返回 409,成功返回 204

連線操作要點:

  • 每次操作建立全新連線、用完即斷(不做連接複用);整體逾時 5 秒,目標不可達時不會長時間佔住請求
  • 錯誤訊息展平驅動庫的錯誤鏈,可直接區分連線被拒、認證失敗等原因(如 db error: FATAL: password authentication failed for user "app")
  • 表名 / 欄位名拼入 SQL 前先經 information_schema 白名單核對(不存在的表或欄位直接 404,不進入 SQL),再以驅動各自的引號規則轉義(MySQL 反引號、PostgreSQL 雙引號),杜絕經標識符注入
  • 新增欄位的 type / default 是原生 SQL 片段(管理工具的固有語義):驗證攔截分號、註解起始符(--、/*)與控制字元,配合兩驅動皆走的預備語句協議(不啟用多語句),即使片段被惡意構造也無法執行第二條語句;目標端語義錯誤(如對有資料的表加 NOT NULL 無默認值欄位)由驅動錯誤原樣回報(502)
  • 刪除欄位屬不可復原的破壞性操作;/api/* 端點暫不要求會話(接入認證前,對外暴露埠時須自行做好網路隔離)
  • 瀏覽資料時儲存格一律轉為字串表示(PostgreSQL 逐欄 ::text 轉換),NULL 保留為 null 由前端呈現
  • 修改資料列以主鍵定位(欄位是否為主鍵經 TABLE_CONSTRAINTS 連接 KEY_COLUMN_USAGE 判定):MySQL 的值以預備語句參數綁定(並啟用 CLIENT_FOUND_ROWS,使 affected rows 為匹配列數);PostgreSQL 的值以引號加倍轉義的字面值拼入(標準遵循字串下反斜線無特殊含義),未知型別字面值由伺服器隱式轉換為欄位型別;型別無法轉換、違反約束等錯誤由目標庫原樣回報(502)
  • 暫未啟用 TLS:對目標庫的連線為明文,僅適合配合受信任網路使用(MySQL 的 caching_sha2_password 認證在無 TLS 下經伺服器 RSA 金鑰完成)

輸入欄位:

欄位 必填 說明
name 是 連接顯示名稱,全表唯一,≤ 128 字符
type 是 數據庫類型,目前僅支援 "mysql" / "postgres"
host 是 主機名或 IP,≤ 255 字符
port 否 埠,1–65535;缺省按類型補 3306(mysql)/ 5432(postgres)
username 是 連接用戶名,≤ 64 字符
password 否 連接密碼,默認空字串;永不透過 API 序列化輸出(連線需明文可用,故不做單向雜湊)
database_name 是 目標資料庫名,≤ 64 字符

輸入會先被正規化(去除首尾空白,密碼除外;port 缺省補默認);驗證失敗返回 400,name 衝突返回 409,type 為不支援的類型時由反序列化直接拒絕。

# 建立連接(port 自動補 3306,回應不含 password)
curl -X POST http://127.0.0.1:8080/api/databases \
  -H 'Content-Type: application/json' \
  -d '{"name": "shop-prod", "type": "mysql", "host": "10.0.0.5",
       "username": "app", "password": "secret", "database_name": "shop"}'

# 列出所有連接
curl http://127.0.0.1:8080/api/databases

開發路線圖

  • 階段一:存儲層 MVP(進行中) — 連接池(deadpool-sqlite)與 WAL 初始化已完成;Account 模型的 CRUD 與 REST API(/api/accounts)已就緒,通用表管理開發中
  • 階段二:自定義查詢引擎 — 查詢語法定義、解析與執行
  • 階段三:HTTP API(進行中) — axum 伺服器骨架、健康檢查與帳戶 CRUD 端點已完成,登入 / 登出 / 會話已就緒(頁面已受保護,API 端點接入認證開發中),其餘 API 端點開發中
  • 階段四:Web 管理介面(進行中) — 登入頁、會話、變更密碼頁、數據庫連接管理頁(/databases)、連接詳情頁(實際連線測試 + 數據表瀏覽)、數據表管理頁(欄位結構 + 新增 / 刪除欄位)與數據管理頁(表內資料分頁瀏覽 + 以主鍵定位編輯資料列)已完成;瀏覽器端 UI:查詢執行、新增 / 刪除資料列開發中
  • 進階功能 — 匯入 / 匯出、備份、多用戶與權限等

專案現狀與結構

目前已完成 HTTP API 骨架、存儲層接線、首頁渲染與登入 / 會話(axum + rusqlite + askama),並以 Account(帳戶)打通「模型 → 存儲 → REST API」全鏈路;Database(外部數據庫連接,mysql / postgres)為第二個模型,同構接入同一鏈路,且已具備實際連線能力(connect 模組:測試連接、列出數據表、檢視欄位結構、分頁瀏覽資料):

alterdb/
├── Cargo.toml        # 項目清單(axum / tokio / rusqlite / askama / mysql_async / tokio-postgres 等)
├── .gitignore
├── README.md
├── templates/            # Askama 模板(編譯期內嵌進執行檔)
│   ├── base.html         # 頁面骨架(基礎樣式與布局塊)
│   ├── _shell.html       # 應用外殼:側邊導航欄(品牌 / 導航 / 登入者與登出)+ 主內容區
│   ├── index.html        # 首頁(登入後可見)
│   ├── databases.html    # 數據庫連接列表頁(外殼 + 內嵌 JS 調用 /api/databases)
│   ├── database_detail.html # 連接詳情頁(連接資訊 + 連接狀態 + 數據表,內嵌 JS 即時連線)
│   ├── table_detail.html # 數據表管理頁(欄位結構管理,內嵌 JS 即時連線)
│   ├── table_data.html   # 數據管理頁(表內資料分頁瀏覽 + 編輯資料列,內嵌 JS 即時連線)
│   ├── database_form.html # 新增 / 編輯連接共用表單頁(服務端預填 + 提交 JS)
│   ├── password.html      # 變更密碼頁(原密碼 + 新密碼兩次確認)
│   ├── login.html        # 登入頁(無側欄)
│   └── 404.html          # 404 頁(無側欄)
└── src/
    ├── main.rs           # 程序入口:初始化存儲層並啟動 HTTP 伺服器
    ├── state.rs          # 應用共享狀態(資料庫連接池 + 會話存儲)
    ├── session.rs        # 會話管理:進程內存存儲、Cookie 解析與 CurrentUser 提取器
    ├── connect.rs        # 實際連線能力:測試連接(版本 / 延遲)、列出數據表、檢視欄位結構與分頁瀏覽資料(mysql_async / tokio-postgres)
    ├── models/           # 模型層:數據結構與輸入驗證(不含存儲細節)
    │   ├── account.rs    # Account 模型(帳戶)與輸入正規化、密碼雜湊
    │   └── database.rs   # Database 模型(外部數據庫連接)與 DatabaseType 枚舉
    ├── storage/          # 存儲層:各模型對應表的 SQL 與 CRUD
    │   ├── mod.rs        # 連接池建立、資料庫初始化與共用錯誤類型
    │   ├── account.rs    # accounts 表結構與 CRUD
    │   └── database.rs   # databases 表結構與 CRUD
    └── controllers/      # 控制器層(HTTP API + 頁面)
        ├── mod.rs        # 路由匯總
        ├── accounts.rs   # 帳戶 CRUD API(/api/accounts)
        ├── auth.rs       # 登入 / 登出 / 變更密碼(/login、/logout、/password)
        ├── database.rs   # 數據庫連接 CRUD API 與連線操作(/api/databases)
        ├── health.rs     # 健康檢查 API(GET /api/health,含資料庫狀態)
        └── pages.rs      # 頁面渲染(GET /、/databases 等,需登入)

開發

cargo build    # 編譯
cargo test     # 執行測試
cargo run      # 運行

授權

尚未決定(TBD)。

S
Description
No description provided
Readme
148 KiB
Languages
Rust 74.8%
HTML 24.4%
Dockerfile 0.8%