mido-learning:一個 Firebase + .NET 的學習平台架構解析
技術棧:Next.js 14 + .NET 8 Minimal API + Firebase (Auth/Firestore/Storage) + Cloud Run
這個專案做什麼
mido-learning 是一個學習組件平台。使用者可以上傳 ZIP 格式的互動式教材(HTML/CSS/JS),平台會自動解壓、版本控制、並提供嵌入式預覽。
從 codebase 看,核心功能包括:
- 教材上傳與版本管理(MaterialEndpoints.cs:750 行)
- 學習組件的 CRUD(ComponentEndpoints.cs:725 行)
- 評分與願望清單系統
- 動態分類管理
- 三種角色:admin / teacher / member [此處可加入:你為什麼想做這個專案?解決什麼問題?]
架構選擇
為什麼是 Firebase + .NET?
這個架構有點特別——前端用 Firebase Auth SDK 直接處理登入,後端用 .NET 8 Minimal API 驗證 Token。
從 FirebaseAuthMiddleware.cs 可以看到流程:
// 1. 從 header 拿 token
var token = context.Request.Headers[“Authorization”]
.FirstOrDefault()?.Replace(“Bearer “, “”);
// 2. Firebase Admin SDK 驗證
var decodedToken = await firebaseService.VerifyIdTokenAsync(token);
// 3. 解析 custom claims(特別是 admin)
var isAdmin = decodedToken.Claims.TryGetValue(“admin”, out var adminClaim)
&& adminClaim is bool b && b;
這種「前端認證、後端驗證」的模式,讓認證邏輯分散在兩端。
[此處可加入:你選擇這個架構的原因是什麼?踩過什麼坑?]
檔案上傳的設計
MaterialEndpoints.cs 處理教材上傳,邏輯如下:
- 前端上傳 ZIP 檔(限制 50MB)
- 後端驗證 ZIP 內容必須包含 HTML 檔
- 自動偵測進入點(優先找 index.html)
- 解壓到 Cloud Storage:materials/{componentId}/v{version}/
- 在 Firestore 記錄 manifest POST /api/components/{componentId}/materials
Content-Type: multipart/form-data
Header: X-Expected-Size: {bytes} // 早期驗證用 版本控制是自動的——每次上傳就是新版本。舊版本不會被刪除。
前端結構
Next.js 14 App Router,路由群組清楚:
app/
├── (public)/ # 公開頁面
├── (auth)/ # 登入/註冊
├── (member)/ # 會員專區
├── (teacher)/ # 教師管理
├── (admin)/ # 管理後台
└── (fullscreen)/ # 全螢幕教材檢視
API 呼叫封裝在 lib/api/*,統一注入 Firebase ID Token。從 components.ts 可以看到:
const response = await fetch(${API_URL}/api/components, {
headers: {
‘Authorization’: Bearer ${await getIdToken()}
}
});
部署
GitHub Actions + Cloud Run,兩條 pipeline:
後端 (deploy-backend.yml):
- 觸發:push 到 main 且 backend/** 有變更
- 多階段 Docker build(.NET SDK → .NET Runtime)
- 部署到 asia-east1,512Mi memory,0-10 instances 前端 (deploy-frontend.yml):
- Firebase 環境變數從 GitHub Secrets 注入
- Node 20 Alpine 映像
- 同樣部署到 Cloud Run 自訂網域:https://learn.paulfun.net
程式碼統計
從 codebase 實際計算:
┌──────────────────────┬───────────┐
│ 區塊 │ 行數 │
├──────────────────────┼───────────┤
│ Endpoints (9 個檔案) │ 2,661 行 │
├──────────────────────┼───────────┤
│ Services │ 932 行 │
├──────────────────────┼───────────┤
│ Models │ 597 行 │
├──────────────────────┼───────────┤
│ 後端總計 │ ~4,644 行 │
└──────────────────────┴───────────┘
前端估計 3,000+ 行(未精確計算)。
Firestore 資料結構
從 firestore.rules 和 Models 推導:
components/{componentId}
– title, theme, description
– visibility: “published” | “login” | “private”
– materials: [{id, name, url, type}]
– questions: [{question, answer}]
– ratingAverage, ratingCount
– createdBy: {uid, displayName}
materials/{materialId}
– componentId, version, entryPoint
– uploadedAt, uploadedBy
本地開發
docker-compose.yml 一鍵啟動:
docker-compose up
- 前端:http://localhost:3000
- 後端:http://localhost:5000
- Firebase Emulator UI:http://localhost:4000 不需要真的 GCP 帳號就能跑完整環境。