SDD
本文件詳細說明 MockExam 開源專案的資料庫架構。其設計目的在於同時服務 AI 代理(作為可解析的架構定義)與人類工程師(提供架構脈絡)。
[!NOTE]
此通用架構支援任何認證考試。例如,在 CCNA 題庫應用中,資料庫結構可以輕鬆容納網路拓撲、子網路問題、路由器設定等考題,但整體設計是完全無關特定廠商的 (vendor-agnostic)。
應用程式採用多資料庫架構,以滿足不同的營運需求:
approvedKeys),以降低記憶體佔用。pgvector 儲存 AI 嵌入向量(支援語意搜尋),並強制執行嚴格的資料列層級安全 (RLS)。作為繁重運算查詢的記錄系統。| 情境 | 主要資料庫 | 理由 |
|---|---|---|
| 使用者個人檔案與角色 | Firebase RTDB | 與 Firebase Auth 無縫整合,具備即時在線狀態。 |
| AI 語意搜尋 | Supabase (pgvector) | 原生向量儲存與相似度搜尋功能。 |
| 離線優先學習 | PowerSync (SQLite) | SQLCipher 加密與透過 Supabase 的差異化同步。 |
| Web 彈性 API | MongoDB (via Cloudflare) | 無結構描述 (Schema-less) 設計適應多變題目格式,並避免 Web 端的直連問題。 |
即時資料庫的結構旨在避免深層資料巢狀,最佳化查詢效能。
users/{uid}
username (String): 使用者顯示名稱。role (String): 列舉值 admin | viewer | pending | internalTester | publicTester | guest。picture (String): 頭像 URL。expiryDate (String/Timestamp): 帳號或訂閱到期日。activeDeviceId (String): 目前綁定的裝置識別碼。allowedPlatforms (List): 例如 ["android", "ios", "web"]。allowedDatabases (List): 使用者可切換的資料庫。requireBioAuth (Boolean): 生物辨識強制標記。premiumProductId (String): 目前啟用的訂閱層級。premiumPurchaseDate (Timestamp): 購買日期。showTestAds (Boolean): 內部測試人員是否顯示測試廣告。examsMetadata/{examId}
id (String): 測驗識別碼。vendor (String): 例如 "Cisco", "AWS"。title (String): 測驗標題。lastUpdated (Timestamp): 最後修改日期。questionCount (Number): 題庫總題數。isApproved (Boolean): 顯示開關。{examId}/{questionKey}
id, examId, type (SINGLE_CHOICE, MULTIPLE_CHOICE, DRAG_AND_DROP, SIMULATION), title, options, correctAnswer, explanation, topic, isApproved, images, learnEnglish, videoUrl, videoStatus。在網路考題中,主題 (topic) 可能涵蓋路由器、交換器、IP 位址與預設閘道等。approvedKeys/{examId}/{questionId}
boolean: 輕量級索引。原因:防止低階 Android 裝置在獲取大量考題清單時發生 JVM 記憶體不足 (OOM)。客戶端先獲取這些鍵值,再進行分頁獲取完整物件。
discussions/{examId}/{questionId}
restrictedDefaults
{"watermarkOpacity": {"admin": 0.0, "guest": 0.8}}。systemConfig
defaultProvider 與全域維護模式。transactions/{uid} 與 systemTransactions/{transactionId}
testAdDeviceIds
database.rules.json)安全架構強制執行零信任資料存取。
users 節點:
auth.token.admin == true),限制對 role, expiryDate, allowedPlatforms 的修改。$uid === auth.uid),管理員則具備全域讀取權限。{examId} 節點:
.indexOn": "isApproved"。admin / viewer / internalTester: 讀取全部。publicTester / pending: 僅在 data.child('isApproved').val() == true 時讀取。guest: 僅在 isApproved == true 且 questionNumber <= 10 時讀取。discussions 節點:
guest: 完全排除(無讀寫權限)。pending: 存取權限取決於待定使用者的 restrictedDefaults 設定旗標。restrictedDefaults / approvedKeys / systemConfig:
Supabase 負責關聯式映射與向量嵌入。
questions
embedding vector(768) 用於相似度搜尋。learn_english (Boolean)video_url (Text)video_status (Enum)isApproved = true。tr_profile_update_protection 防止使用者在 auth.users 資料表中直接修改其 JWT 角色或敏感元資料。generate-exam-video(在參考實作中為 generate-ccna-video)get-db-secret
get-question / get-question-list
manage-discussion / manage-question
migrate-from-mongodb
資料庫利用 8 個遷移步驟,從最初的資料表建立、啟用 pgvector,一直發展到最終的影片元資料支援。
MongoDB 的架構設計為文件導向的備用方案與 Web API 後端。
Questions 集合: 直接對應至 Dart Question 模型欄位。Discussions 集合: 巢狀評論子文件。PowerSync 透過加密的 SQLite 資料庫提供離線優先功能。
powersync_rules.yaml 詳細資訊questions_by_role
token_parameters.role 決定同步範圍。admin, viewer, internalTester): 同步 SELECT * FROM questions。pending, publicTester): 同步 SELECT * FROM questions WHERE isApproved = true。SELECT * FROM questions WHERE isApproved = true AND questionNumber <= 10。discussions
guest 角色同步討論,以節省本機儲存空間並強制執行限制。應用程式使用工廠模式 (Factory Pattern) 動態路由資料請求。
flowchart TD
A["請求資料 (examId)"] --> B{"是否為 Mock Repository?"}
B -- 是 --> C["回傳 Mock Repository"]
B -- 否 --> D{"usePowerSync 或\nexamId 包含 'powersync'?"}
D -- 是 --> E["回傳 PowerSync Repository"]
D -- 否 --> F{"examId 開頭為 'mongodb-'?"}
F -- 是 --> G["回傳 MongoDB Repository"]
F -- 否 --> H{"examId 開頭為 'supabase-'?"}
H -- 是 --> I["回傳 Supabase Repository"]
H -- 否 --> J{"檢查 _defaultProvider\n(systemConfig)"}
J -- "firebase" --> K["回傳 Firebase Repository"]
J -- "supabase" --> I
J -- "mongodb" --> G
J -- "powersync" --> E
透過管理員面板控制動態提供者切換,經由 systemConfig 廣播更新,無需更新應用程式即可將使用者無縫轉移至不同資料庫。
為了維持 4 個資料儲存區之間的一致性,我們採用了持續同步策略。
flowchart LR
Admin["管理員面板"] -->|寫入| Supabase["Supabase (主節點)"]
Supabase -->|Python 指令碼\nsync_supabase_to_firebase.py| Firebase["Firebase RTDB"]
Supabase -->|同步指令碼| MongoDB["MongoDB Atlas"]
Supabase <-->|雙向同步\n(背景)| PowerSyncService["PowerSync 服務"]
PowerSyncService <-->|SQLCipher 加密| LocalSQLite["本機 SQLite (客戶端)"]
Client["行動裝置客戶端"] -->|即時事件| Firebase
sync_supabase_to_firebase.py) 將核准的題目與元資料推播至 Firebase 以提供快速讀取存取。本文件詳細說明 MockExam 專案的極致細節安全架構。它詳述了保護優質內容(例如:認證考試題庫)免於智慧財產權盜竊與未授權存取的縱深防禦 (Defense-in-depth) 機制。
[!NOTE]
此安全架構可保護任何認證考試內容的智慧財產權,無論考題是關於雲端架構、程式設計或網路技術(例如,在 CCNA 題庫應用中關於路由器設定或封包分析的考題)。
本應用程式旨在減輕考試與教育平台常見的數個關鍵攻擊向量:
guest 升級為 admin 或 premium。縱深防禦策略建立在 10 層以上的堆疊架構之上。
flowchart TD
subgraph "應用程式安全堆疊"
A["驗證綁定 (單一裝置 ID)"] --> B["權限驗證 (JWT 與 RTDB 規則)"]
B --> C["時間服務 (NTP 驗證)"]
C --> D["API 金鑰服務 (加密儲存)"]
D --> E["資料庫層級安全 (RLS 與 SQLCipher)"]
subgraph "客戶端環境保護"
F["Root 與越獄偵測"]
G["模擬器與 VPN 偵測"]
H["FLAG_SECURE (防截圖)"]
I["防螢幕投放 (輪詢偵測)"]
J["動態浮水印系統"]
K["Web 專屬 (禁右鍵、防開發者工具)"]
end
E --> Client["客戶端應用程式"]
Client -.-> F
Client -.-> G
Client -.-> H
Client -.-> I
Client -.-> J
Client -.-> K
end
DeviceSecurityService)DeviceSecurityService 主動對執行階段環境進行特徵分析。
SafeDevice.isJailBroken): 立即封鎖所有角色的存取。已 Root 的裝置被視為不可信任。SafeDevice.isRealDevice): 立即封鎖所有角色的存取,以防止自動化爬蟲擷取。SafeDevice.isDevelopmentModeEnable): 封鎖非管理員使用者。(例外:internal_testing 測試通道免除此檢查)。SafeDevice.isMockLocation): 封鎖非管理員使用者。CheckVpnConnection.isVpnActive()): 封鎖非管理員使用者,以防止地理位置欺騙與網路封包檢查。環境免除條件:
SecurityResult 模型。SecureScreenService)防止資料被視覺化擷取。
FLAG_SECURE,防止作業系統在截圖或最近使用應用程式切換器預覽中渲染該 App。FLAG_WINDOW_IS_OBSCURED 以防止惡意覆疊層。EnhancedSecurityWatermark)針對作業系統層級防截圖較弱的平台(例如 Web、Desktop),會在整個應用程式上覆蓋動態浮水印。
UID 與 Email。restricted_defaults_screen 設定。
0.0 (隱形)。0.8 (高度可見)。MaterialApp builder 層級注入,確保其涵蓋所有路由、對話方塊與底部選單 (bottom sheets)。TimeService)防止使用者透過手動更改裝置時鐘來繞過訂閱限制。
NTP.getNtpOffset() 從網路時間伺服器獲取真實時間。FlutterSecureStorage 儲存 secure_time_last_known 時間戳記。TimeValidationStatus (valid, rollbackDetected, error)。若偵測到時間回溯,則立即鎖定帳號。防止帳號共用。
activeDeviceId 記錄於 Firebase users/{uid} 中。AuthWrapper 會檢查裝置的硬體 ID 是否與資料庫相符。UnauthorizedScreen (未授權畫面)。針對使用者權限的零信任架構。
role, expiryDate 與 allowedPlatforms 欄位透過後端規則鎖定。即使客戶端被入侵,也無法寫入這些欄位。pending 或 guest。tr_profile_update_protection 會在使用者嘗試直接在資料庫中更新自己的 JWT 角色聲明或訂閱日期時,明確引發 SQL 例外。| 安全功能 | 管理員 (Admin) | 檢視者 (Viewer) | 內部測試員 | 公開測試員 | 待定 (Pending) | 訪客 (Guest) |
|---|---|---|---|---|---|---|
| Root/越獄封鎖 | 強制 | 強制 | 強制 | 強制 | 強制 | 強制 |
| 模擬器封鎖 | 強制 | 強制 | 強制 | 強制 | 強制 | 強制 |
| 開發模式封鎖 | 免除 | 強制 | 免除 | 強制 | 強制 | 強制 |
| VPN 封鎖 | 免除 | 強制 | 免除 | 強制 | 強制 | 強制 |
| 防截圖 | 免除 | 強制 | 強制 | 強制 | 強制 | 強制 |
| 浮水印 | 可設定 | 可設定 | 可設定 | 可設定 | 可設定 | 可設定 |
| NTP 驗證 | 強制 | 強制 | 強制 | 強制 | 強制 | 強制 |
WebSecurityWrapper)由於瀏覽器的開放性,Web 需要特定的 DOM 層級干預。
Ctrl+S (儲存)、Ctrl+P (列印) 以及 F12 (開發者工具)。保護關鍵基礎設施憑證免受逆向工程攻擊。
FlutterSecureStorage(Android 上為 AES 加密,iOS 上為 Keychain)。systemConfig 動態輪替。--dart-define 注入,完全避免在版本控制中出現硬編碼字串。| 平台 | 核心威脅偵測 | 螢幕保護 | Web 干預 | 浮水印 |
|---|---|---|---|---|
| Android | 完整 (Root, 模擬器, VPN) | 完整 (FLAG_SECURE) |
不適用 | 完整 |
| iOS | 完整 (越獄, VPN) | 完整 (模糊處理) | 不適用 | 完整 |
| Web | 不適用 (沙盒環境) | 部分 (依賴 DOM) | 完整 (快速鍵, DevTools) | 完整 |
| Windows/macOS | 僅偵測 | 無 | 不適用 | 完整 |
06-ai-engine.md
AI 代理程式提醒 (AI Agent Note): 本文件描述
AiGeneratorService與GemmaLocalService類別,此為認證考試平台雙引擎 AI 架構的核心。修改提示詞生成、LLM 整合或本地端推論設定時請參考此文件。
本應用程式採用雙引擎 AI 架構,提供高精確度的解析與離線使用的便利性,確保準備任何認證考試(如練習題庫測驗)的使用者,在任何網路狀態下皆能取得 AI 導師的協助。
graph TD
A["使用者請求 (User Request)"] --> B{"模式選擇 (Mode Selection)"}
B -- "'cloud' / 'auto'" --> C["AiGeneratorService (雲端 API)"]
B -- "'local'" --> D["GemmaLocalService (本地端設備)"]
C -- "網路錯誤 (Network Error)" --> E{"允許降級備援? (Fallback Allowed?)"}
E -- "是 ('auto')" --> D
E -- "否 ('cloud')" --> F["拋出例外 (Throw Exception)"]
C -- "成功 (Success)" --> G["顯示結果 (Display Result)"]
D -- "成功 (Success)" --> G
AiGeneratorService): 運用雲端運算(如 Gemini)處理複雜邏輯、動態專家角色建立以及影片腳本生成。GemmaLocalService): 提供離線、低延遲且具隱私保護的推論服務,採用輕量級的 LiteRT-LM 模型。AiGeneratorService)雲端服務採用 REST API 整合模式,不綁定特定 SDK,可靈活適配任何 LLM 供應商。
getDynamicExpertPersona)根據 examId、topic 與 title 關鍵字動態建構專家角色。
generateLearnEnglishForQuestion)設計用於協助非母語人士理解考試題目。
grammar_analysis (文法解析)、speedrun_tips (速解技巧)、vocabulary_analysis (單字解析)。tts://speak?text=URL_ENCODED_TEXT 呼叫應用程式內建語音。[📖] 圖示,Google 翻譯使用 [🌐] 圖示。[]()|" 以防 Markdown 解析錯誤,強制替換為全形符號(【】()|「」)。生成 768 維度的嵌入表示 (如 Gemini Embeddings) 以提供語意搜尋功能。這些資料儲存於 Supabase 的 pgvector 欄位中,讓使用者能依概念搜尋練習題庫,而非僅限於精確的關鍵字比對。
GemmaLocalService)提供全離線推論功能。
gemma-4-E2B-it.litertlm (約 1.7GB)。StreamController<double> 廣播進度。.tmp),成功完成後才重新命名為最終檔名。// Initialize LiteRT-LM for Gemma
FlutterGemma.installModel(
modelType: ModelType.gemmaIt,
modelFileType: ModelFileType.litertlm,
preferredBackend: PreferredBackend.gpu, // Hardware acceleration
maxTokens: 1024,
);
為避免行動裝置上的 JVM OutOfMemory (OOM) 錯誤,嚴格的上下文管理是必要的。
InternetAddress.lookup('google.com') 判斷是否需要觸發備援邏輯。flowchart TD
Start["呼叫 getExplanationTutor()"] --> ModeCheck{"檢查請求模式"}
ModeCheck -- "'cloud'" --> CloudOnly["執行 AiGeneratorService"]
ModeCheck -- "'local'" --> LocalOnly["執行 GemmaLocalService"]
ModeCheck -- "'auto' (預設)" --> AutoCloud["執行 AiGeneratorService"]
AutoCloud --> IsSuccess{"是否成功?"}
IsSuccess -- "是" --> Return["回傳結果"]
IsSuccess -- "否 (網路/API錯誤)" --> AutoLocal["執行 GemmaLocalService"]
AutoLocal --> Return
核心原則: 絕對不要在輕量級(如 2B 參數)模型的系統提示詞 (System Instruction) 中混合多種語言。否則會嚴重降低效能,導致模型產生幻覺或以錯誤語言回應。
context.locale 取得以設定 _langCode。en: 嚴格的英文專家角色。zh_TW: 台灣繁體中文。針對網路/IT領域,嚴格使用台灣在地術語(例如:路由器、交換器、封包、子網路、連接埠、IP 位址、預設閘道)。zh_CN: 簡體中文術語。ja: 日文 IT 術語。videoStatus): 狀態變換流程為 null -> generating -> completed (或 failed)。translateText() 動態翻譯使用者查詢或特定的練習測驗內容。