iT邦幫忙

2026 iThome 鐵人賽

DAY 3
0
Build on Google AI

將考國際證照的應用程式變成開源系列 第 3

02-SDD-SystemOverview-Architecture-DataModels

  • 分享至 

  • xImage
  •  

01-system-overview

01. 系統總覽 (System Overview)

雙層架構說明 (Dual-Layer Context)

  • AI 代理規範 (AI Agent Specification):本文檔提供機器可解析的架構限制、依賴樹狀圖與技術堆疊定義。
  • 人類開發者背景 (Human Context):為新進開發者提供應用程式生態系、必要建置環境與模組配置的高階視角。

1. 技術堆疊總覽 (Technology Stack Overview)

本認證考試應用程式採用現代化、離線優先 (Offline-first) 的行動開發技術堆疊。(例如,在網路認證考試的參考實作中,此架構可確保高可用性與安全性)。

類別 技術 用途
前端 Flutter 3.x / Dart 3.x 跨平台使用者介面 (UI) 開發。
資料庫 Firebase RTDB 儲存即時使用者狀態與系統設定。
Supabase PostgreSQL 關聯式資料與支援語意搜尋的 pgvector
MongoDB Atlas 非結構化日誌 (Logs) 與效能指標分析。
PowerSync (SQLCipher) 加密本地 SQLite,實現離線優先同步功能。
身分驗證 Firebase Auth 支援 Google、電子郵件/密碼、匿名登入。
AI 引擎 Gemini 2.5 Flash (Cloud) 雲端進階大型語言模型 (LLM),提供家教功能。
Gemma 4 LiteRT-LM (Local) 裝置端備援模型 (2B 參數),支援離線 AI。
金流與變現 RevenueCat + Google Play 訂閱管理與原生 v8+ 應用程式內購買 (IAP)。
Google AdMob 廣告投放 (橫幅、插頁、獎勵、原生、開屏)。
診斷與監控 Firebase Crashlytics 原生與 Dart 層級的崩潰 (Crash) 報告。
Shake SDK 應用程式內部的錯誤回報與使用者回饋。
國際化 easy_localization 多國語系支援 (EN, JA, zh-TW, zh-CN)。
系統安全 safe_device, local_auth 越獄 (Jailbreak)/Root 偵測與生物辨識。
check_vpn_connection 網路安全與 VPN 連線偵測。
screen_protector 防截圖與螢幕共享偵測。
媒體與機器學習 flutter_tts, speech_to_text 語音模式的文字轉語音 (TTS) / 語音轉文字 (STT)。
google_mlkit_text_recognition 使用者上傳圖片的光學字元辨識 (OCR)。

2. 系統上下文圖 (System Context Diagram)

graph TD
    User["終端使用者 (End User)"] --> App["認證考試應用程式 (Exam App)"]
    
    subgraph "外部雲端服務 (External Cloud Services)"
        App --> Firebase["Firebase (Auth, RTDB, Crashlytics)"]
        App --> Supabase["Supabase (PostgreSQL + pgvector)"]
        App --> Mongo["MongoDB Atlas"]
        App --> GooglePlay["Google Play Billing"]
        App --> RevenueCat["RevenueCat SDK"]
        App --> Gemini["Google Gemini API"]
        App --> AdMob["Google AdMob"]
    end
    
    subgraph "裝置端服務 (On-Device Services)"
        App --> PowerSync["PowerSync + SQLCipher (加密本地資料庫)"]
        App --> Gemma["Gemma 4 LiteRT-LM (邊緣 AI)"]
    end

3. 模組依賴圖 (Module Dependency Graph)

graph TD
    Presentation["lib/screens/ & lib/widgets/ (表現層)"] --> Controllers["lib/controllers/ (狀態管理)"]
    Controllers --> Services["lib/services/ (單例服務)"]
    Controllers --> Repositories["lib/repositories/ (資料存取)"]
    Services --> Utils["lib/utils/ (工具模組)"]
    Repositories --> Models["lib/models/ (資料模型)"]
    Models --> DataSources["Data Source Layer (資料來源層)"]

4. 目錄結構規範 (Directory Structure Convention)

應用程式嚴格遵守以下 lib/ 目錄佈局:

lib/
├── main.dart             # 應用程式進入點
├── constants/            # 全域常數、主題設定、列舉 (Enums)
├── controllers/          # 狀態管理 (ChangeNotifier providers)
├── models/               # 資料結構 (Question, AppUser, Exam 等)
├── repositories/         # 資料存取介面與工廠模式 (Factory)
├── screens/              # 26 個 UI 畫面 (例:DashboardScreen)
├── services/             # 26 個單例服務 (例:AuthService)
├── utils/                # 輔助函式、擴充功能、格式化工具
└── widgets/              # 可共用的 UI 元件 (11 個核心, 13 個考試專用)

5. 建置環境需求 (Build Environment Requirements)

為確保建置的一致性,必須符合以下環境規格:

  • Flutter SDK: 3.x 版本 (最新穩定版)
  • Dart SDK: 3.x 版本
  • Java Development Kit (JDK): JDK 17
  • Android SDK:
    • minSdk: 30 (Android 11)
    • compileSdk: 最新穩定版
  • Android NDK: 版本 28.2.13676358 (裝置端 ML/LiteRT 編譯必要需求)
  • Gradle/AGP: 與 Flutter 3.x 預設相容的版本 (AGP 8.x+)

02-architecture.md

02. 架構設計 (Architecture Design)

雙層架構說明 (Dual-Layer Context)

  • AI 代理規範 (AI Agent Specification):本文檔定義了設計模式、流程順序、路由閘道以及嚴格的應用程式分層。任何程式碼修改都必須符合這些架構約束。
  • 人類開發者背景 (Human Context):了解不同元件之間的通訊方式、導航安全的執行機制,以及如何最佳化初始化流程以避免 UI 阻塞。

1. 分層架構 (Layered Architecture)

本應用程式採用嚴格的五層式架構以確保關注點分離 (Separation of Concerns)。(在現代化的認證考試題庫應用程式中,這是處理複雜狀態與離線/連線資料的標準做法)。

graph TD
    subgraph "五層式架構 (5-Layer Architecture)"
        UI["表現層 (Presentation Layer: screens/, widgets/)"]
        Controller["控制器層 (Controller Layer: controllers/ - ChangeNotifier)"]
        Service["服務層 (Service Layer: services/ - 單例模式)"]
        Repository["儲存庫層 (Repository Layer: repositories/ - 工廠模式)"]
        DataSource["資料來源層 (Data Source Layer: Firebase, Supabase, PowerSync)"]

        UI --> Controller
        Controller --> Service
        Controller --> Repository
        Repository --> DataSource
        Service --> DataSource
    end

2. 設計模式 (Design Patterns)

架構大量採用成熟的設計模式以維持系統的擴充性:

  1. 儲存庫工廠模式 (Repository Factory Pattern):由 RepositoryFactory 類別處理。它負責根據系統設定或網路連線狀態動態切換資料提供者 (Provider)。
  2. 單例模式 (Singleton Pattern):所有 26 個核心應用程式服務 (例如 PaymentService, DeviceSecurityService) 皆實作為單例。存取方式嚴格限制為 ServiceName.instance
  3. Provider / ChangeNotifier 模式:狀態管理是透過 main.dart 中的 MultiProvider 設定進行。控制器 (Controllers) 繼承 ChangeNotifier,負責將狀態更新廣播至表現層 (UI)。
  4. 策略模式 (Strategy Pattern):應用於儲存庫層內部,根據 examId 的前綴與標準 systemConfig 動態切換資料來源 (例如,從 Supabase 獲取進階考題,從 Firebase 獲取使用者設定)。

3. 導航與路由 (安全閘道)

所有的導航流程都必須通過一個中央安全閘道:AuthWrapper。此元件負責在呈現應用程式儀表板前,確保系統安全性未受到損害。

該閘道強制執行嚴格的 七步驟驗證鏈 (7-step verification chain)

graph TD
    Start["AuthWrapper (安全閘道)"] --> Step1{"1. 裝置安全 (Device Security)"}
    Step1 -- "通過" --> Step2{"2. NTP 時間驗證 (NTP Time)"}
    Step2 -- "通過" --> Step3{"3. 身分驗證狀態 (Auth State)"}
    Step3 -- "已登入" --> Step4{"4. 單一裝置登入 (Single Device)"}
    Step4 -- "通過" --> Step5{"5. 帳戶到期日 (Account Expiry)"}
    Step5 -- "有效" --> Step6{"6. 平台限制 (Platform Restriction)"}
    Step6 -- "通過" --> Dashboard["DashboardScreen (儀表板)"]

    Step1 -- "失敗 (Root/模擬器/分享)" --> Reject1["DeviceSecurityScreen"]
    Step2 -- "失敗 (時間回滾)" --> Reject2["UnauthorizedScreen"]
    Step3 -- "未登入" --> Reject3["LoginScreen (登入畫面)"]
    Step4 -- "裝置 ID 不符" --> Reject4["UnauthorizedScreen"]
    Step5 -- "Viewer 過期" --> Downgrade["自動降級為 Pending 權限"]
    Step6 -- "平台不允許" --> Reject6["UnauthorizedScreen"]

4. 錯誤處理 (Error Handling)

系統在多個層級優雅地攔截錯誤:

  • GlobalErrorBoundary:包裝整個應用程式的 Widget 樹,用於攔截 Flutter 的渲染錯誤。
  • FlutterError.onError:捕捉框架層級 (Framework-level) 的錯誤。
  • PlatformDispatcher.instance.onError:捕捉非同步的 Dart 執行錯誤。
  • ErrorReportService:將攔截到的堆疊追蹤 (Stack traces) 原生推送至 Firebase Crashlytics,並透過 Shake SDK 記錄使用者體驗上下文。

5. 應用程式生命週期與初始化順序

由於應用程式包含較重的依賴項 (AI 模型、SQLite 加密、金流 SDK),main.dart 中的初始化流程採用延遲渲染 (Delayed Rendering) 技術,以防止作業系統層級的 ANR (應用程式無回應)。

sequenceDiagram
    participant Main as main.dart
    participant Services as 核心服務 (Core Services)
    participant UI as 根視圖 (Root Widget / MultiProvider)
    participant Post as PostFrameCallback (延遲執行)

    Main->>Services: EasyLocalization.ensureInitialized()
    Main->>Services: 初始化 FlutterGemma + LiteRtLmEngine (非 Web 環境)
    Main->>Services: Firebase.initializeApp() & Supabase.initialize()
    Main->>Services: 註冊全域錯誤處理常式 (Global Error Handlers)
    Main->>Services: DeviceSecurityService 安全檢查 (2 秒超時限制)
    Main->>UI: runApp(EasyLocalization -> MultiProvider -> ErrorBoundary -> ObscuredBlockerScreen)
    
    UI->>Post: WidgetsBinding.instance.addPostFrameCallback
    
    Note over Post: 延遲初始化非關鍵性 SDK
    Post->>Post: 初始化 Shake SDK
    Post->>Post: 初始化 Crashlytics
    Post->>Post: 初始化 AdMob
    Post->>Post: 初始化 RevenueCat
    Post->>Post: 初始化 Screen Protector (防截圖)
    Post->>Post: 初始化 TTS & Speech-to-Text

延遲初始化策略 (Delayed Initialization Strategy)

使用 addPostFrameCallback 可將非關鍵的 SDK (如 AdMob, RevenueCat, Shake) 的初始化延後至初始 Flutter 渲染幀 (Frame) 之後。此策略能夠打破啟動期間同步佔用 CPU 的狀況,完全避免低階裝置上發生 ANR 死結,同時確保 ObscuredBlockerScreen (啟動畫面 UI) 能立即被渲染。

03-data-models.md

資料模型規格 (Data Models Specification)

本文檔詳細說明了通用考試題庫應用程式中使用的核心資料模型。此文件同時作為人類開發者與 AI Agent 的權威綱要參考。

1. 考題模型 (Question Model)

Question 模型代表考試題庫中的單一測驗題目。

欄位定義

  • id: int - 唯一識別碼 (例如從 'q100' 格式解析而來)。
  • examId: String - 認證考試識別碼 (預設:參考實作中為 'CCNA',但可為任何考試代碼)。
  • type: QuestionType (列舉) - 題目類型:SINGLE_CHOICE (單選題)、MULTIPLE_CHOICE (複選題)、DRAG_AND_DROP (拖曳題)、SIMULATION (模擬題)。
  • title: String - 題目的主要文字或敘述。
  • questionImage: String? - 題目附帶圖片的選用檔案名稱/路徑。
  • options: List<String>? - 單選/複選題的可用選項。
  • dragOptions: List<DragAndDropOption>? - 可供拖曳的選項 (id, content)。
  • dropTargets: List<DropTarget>? - 選項可以放置的目標位置 (id, label)。
  • correctAnswer: dynamic - 正確答案。結構依類型而異:
    • int 適用於 SINGLE_CHOICE
    • List<int> 適用於 MULTIPLE_CHOICE
    • Map<String,String> 適用於 DRAG_AND_DROP
    • String 適用於 SIMULATION
  • explanation: String - 正確答案的詳細解析。
  • discussion: List<Comment> - 與該考題相關的使用者評論與討論。
  • topic: int - 考試領域或主題分類 (例如 Domain 1-6)。
  • communityVotes: Map<String, int>? - 該考題的社群投票資料。
  • isApproved: bool - 審核狀態,預設為 false
  • questionNumber: int? - 特定考卷中考題的流水號。
  • images: List<String>? - 與考題相關的額外圖片。
  • explanationTexts: Map<String, String>? - 支援多國語言的解析文字。
  • learnEnglish: String? - AI 生成的考題文字英文學習分析。
  • videoUrl: String? - AI 生成的解說影片連結。
  • videoStatus: String? - 影片生成狀態 (null, generating, completed, failed)。

序列化 (Serialization)

  • toMap(): 將物件序列化為 Map 以供資料庫儲存。列舉會轉換為字串,而巢狀物件 (如評論) 會映射為其對應的 Map 表示。
  • fromMap(Map<String, dynamic> map): 工廠建構子,用於從資料庫輸出實例化 Question。

類別圖 (Class Diagram)

classDiagram
    class Question {
        +int id
        +String examId
        +QuestionType type
        +String title
        +String? questionImage
        +List~String~? options
        +List~DragAndDropOption~? dragOptions
        +List~DropTarget~? dropTargets
        +dynamic correctAnswer
        +String explanation
        +List~Comment~ discussion
        +int topic
        +Map~String, int~? communityVotes
        +bool isApproved
        +int? questionNumber
        +List~String~? images
        +Map~String, String~? explanationTexts
        +String? learnEnglish
        +String? videoUrl
        +String? videoStatus
        +toMap() Map
        +fromMap(Map) Question
    }

2. 使用者模型 (AppUser Model)

AppUser 模型處理使用者個人檔案、角色及應用程式層級的偏好設定。

UserRole 列舉

  • admin: 完整管理員權限。
  • viewer: 僅具備進階/內部功能的檢視權限。
  • pending: 帳號等待審核中。
  • internalTester: 可存取發布前功能與未審核考題。
  • publicTester: 具有特定限制的 Beta 測試人員。
  • guest: 未驗證或具最低存取權限的訪客。

欄位定義

  • firebaseUser: User - 底層 Firebase Authentication 的使用者物件。
  • username: String - 顯示名稱。
  • role: UserRole - 指派的使用者角色。
  • picture: String? - 個人資料圖片 URL。
  • roleDescription: String - 本地化的使用者角色描述。
  • expiryDate: DateTime? - 進階會員或存取權限到期日。
  • activeDeviceId: String? - 綁定 ID 以強制單一裝置使用。
  • allowedPlatforms: List<String> - 允許平台的白名單 (例如 ["ios", "android"])。
  • allowedDatabases: List<String> - 使用者可連線的資料庫白名單。
  • requireBioAuth: bool - 強制生物辨識驗證的旗標 (預設:true)。
  • isGoogleUser: bool - 若透過 Google 進行驗證則為 true。
  • isAnonymous: bool - 若以訪客身分登入則為 true。
  • premiumProductId: String? - 生效中的進階訂閱產品識別碼。
  • premiumPurchaseDate: DateTime? - 進階方案購買日期。
  • showTestAds: bool - 顯示測試廣告的旗標 (預設:true)。
  • email: String? - 衍生自 firebaseUser.email 的 getter。

角色權限矩陣 (Role Permissions Matrix)

功能 / 能力 admin viewer pending internalTester publicTester guest
登入 / 基本驗證
檢視已審核考題 限制
檢視未審核考題
編輯考題
發布評論
進行購買 不適用 不適用
存取管理員面板

類別圖 (Class Diagram)

classDiagram
    class AppUser {
        +User firebaseUser
        +String username
        +UserRole role
        +String? picture
        +String roleDescription
        +DateTime? expiryDate
        +String? activeDeviceId
        +List~String~ allowedPlatforms
        +List~String~ allowedDatabases
        +bool requireBioAuth
        +bool isGoogleUser
        +bool isAnonymous
        +String? premiumProductId
        +DateTime? premiumPurchaseDate
        +bool showTestAds
        +String? email
    }

3. 考試與考試中介資料模型 (Exam & ExamMetadata Models)

Exam 模型

代表完整的考試集合。

  • id: String - 唯一識別碼。
  • vendor: String - 認證廠商 (例如 Cisco, AWS)。
  • title: String - 考試標題。
  • questions: List<Question> - 相關考題的完整清單。
  • lastUpdated: DateTime - 最後修改時間戳記。

ExamMetadata 模型

用於清單與導覽的輕量級模型。

  • id: String
  • vendor: String
  • title: String
  • lastUpdated: DateTime
  • questionCount: int
  • isApproved: bool

4. 交易紀錄模型 (TransactionRecord Model)

處理購買紀錄與進階功能解鎖。

TransactionStatus 列舉

  • pending, completed, failed, refunded

欄位定義

  • id: String - 唯一交易 ID。
  • uid: String - 進行購買的使用者 ID。
  • email: String? - 使用者電子郵件。
  • productId: String - 來自商店的產品識別碼。
  • productName: String - 易於閱讀的產品名稱。
  • amount: double - 購買金額。
  • currency: String - ISO 貨幣代碼。
  • status: TransactionStatus
  • timestamp: DateTime - 交易時間。
  • purchaseToken: String - 來自 Google Play / App Store 的驗證權杖。

序列化 (Serialization)

時間戳記使用 millisecondsSinceEpoch 轉換為整數與還原,以確保跨 NoSQL 與關聯式資料庫 (SQL) 儲存的相容性。

5. 評論模型 (Comment Model)

  • id: String - 唯一識別碼。
  • user: String - 評論者的使用者名稱。
  • comment: String - 評論本文。
  • timestamp: int - 建立時間 (自 epoch 以來的毫秒數)。
  • upvotes: int - 獲得的推文(讚)數。
  • uid: String - 評論者的使用者 ID。
  • copyWith(): 建立該實例修改後複本的方法。

6. 跨資料庫欄位映射表 (Cross-Database Field Mapping Table)

在 Firebase RTDB、Supabase PostgreSQL、MongoDB Atlas 與 PowerSync 之間同步時,映射必須嚴格遵守下表。

Dart 模型 (camelCase) SQL/PowerSync (snake_case) Firebase RTDB (camelCase)
id id id
examId exam_id examId
questionImage question_image questionImage
correctAnswer correct_answer correctAnswer
isApproved is_approved isApproved
questionNumber question_number questionNumber
explanationTexts explanation_texts explanationTexts
learnEnglish learn_english learnEnglish
videoUrl video_url videoUrl
videoStatus video_status videoStatus
activeDeviceId active_device_id activeDeviceId
allowedPlatforms allowed_platforms allowedPlatforms
requireBioAuth require_bio_auth requireBioAuth
premiumProductId premium_product_id premiumProductId

注意:Firebase RTDB 依賴與 Dart 模型完全一致的 camelCase 命名,而關聯式資料庫 (SQL) 則需要在 Repository 層中翻譯為 snake_case。


上一篇
第2天 ( 2026年8月3日 星期一 ) 產品需求文件 (PRD) - MockExam
下一篇
第4天 sdd , database-design , security-architecture , ai-engine
系列文
將考國際證照的應用程式變成開源4
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言