一個為楓之谷玩家設計的 Boss 副本排團管理系統,整合 Discord OAuth2 登入、自動分配引擎、隊伍編輯併發控制(悲觀鎖 + 樂觀鎖)與 Bot 通知,從零到部署完整實作。
- 自製 SqlBuilder:不依賴 EF Core,以 Lambda 表達式(
Expression<Func<T, bool>>)解析欄位與條件建構 SQL,支援 CTE、條件群組(AND/OR)、NULL 比較;欄位參照走屬性、編譯期就能抓到打錯,避免字串拼接。 - CQRS-Lite 讀寫分離:寫入路徑走 Service + Repository(逐筆操作、語意清晰),讀取路徑走獨立 Query 介面(不受寫入模型約束,可自由使用 JOIN 等最佳化 SQL 一次查回所需資料)。
- 重複提交防護:POST/PUT/DELETE 強制帶合法 UUID 的
X-Idempotency-Key(否則 400),同一 key 60 秒內重送回 409,防止連點 / 網路重試造成重複寫入。去重狀態存 Redis(跨 pod 共享),Redis 不可用時 fail-open 放行。(擋重送,非重播原結果的完整冪等) - 雙軌身分驗證:一般玩家使用自定義 JWT,管理員使用 Session(DB 為真實來源,讀取路徑走 Redis 快取),兩者在同一 Middleware 中統一驗證,依 Discord 身分組自動分流。管理員撤銷(登出/拔身分組/踢人)靠共用 Redis 一次刪除即在所有 pod 立即生效。
- 按身分限流:以已驗證的
discordId(session/JWT claim)為 key 做 per-user 限流(換 IP 也繞不掉),計數存 Redis(跨 pod 共用上限),Middleware 掛在驗證之後、交易之前,被擋的請求不白開 DB 交易。 - 自動分配引擎:玩家報名後即時觸發,根據可用時段比對現有隊伍空位,無匹配則建立新隊(來源標記
auto)。 - 併發控制:自動分配的「讀隊 → 開新隊」是 read-then-write,兩人同時報名同一 period 會重複開隊;以交易級 advisory lock(
pg_advisory_xact_lock)按 period 序列化,不同 period 並行、鎖在 DB 故多 pod 安全。 - 隊伍編輯併發控制:管理員排團存檔與玩家補位共用同一寫入路徑,同一隊伍被兩個請求同時編輯會超編或撞外鍵違反;先以交易級 advisory lock 序列化同隊編輯,取鎖後用重新讀到的當下狀態做樂觀鎖(Postgres
xmin)版本比對——隊伍已消失或版本衝突統一收進衝突清單、不中斷其他隊伍的處理,前端原地標紅、不重新排序。用Task.WhenAll真並發(非序列)測試驗證鎖確實擋住超編。 - 批次組隊預覽:管理員以職業範本手動觸發,從所有報名者批次生成完整隊伍預覽(來源標記
admin、以未存檔的隊伍呈現),存檔後才寫入。 - 補位保護機制:手動補位 / 微調的成員標記
IsManual = true;批次重排時,含IsManual成員的隊伍(與管理員隊)整隊保留、只自動補滿空位,防止人工調整被覆蓋。 - Schema 版本管理:以 golang-migrate 管理資料庫 migration,up/down 分開維護,Docker Compose 與 Kubernetes 皆整合 migrate 服務,確保各環境 schema 一致。
| 層級 | 技術 |
|---|---|
| 後端 | .NET 9 (C# 13)、ASP.NET Core Web API |
| 前端 | Next.js 15 (App Router)、Tailwind CSS、Shadcn/UI |
| 資料庫 | PostgreSQL 18(手寫 SQL + Dapper 對映,無 EF Core) |
| 快取 / 跨 pod 共享狀態 | Redis 7(session 快取、重複提交去重、限流計數) |
| Schema 管理 | golang-migrate(up/down SQL,版本追蹤) |
| 身分驗證 | Discord OAuth2、自定義 JWT、DB Session |
| 通知 | DSharpPlus Discord Bot |
| 日誌 | Serilog + Seq(結構化可查詢日誌) |
| 容器化 | Docker Compose、Kubernetes |
| 測試 | xUnit + Moq(單元)、Testcontainers(整合)、Playwright(E2E) |
採用分層架構,依賴方向由外向內單向流動:
Presentation.WebApi → Application → Domain
↓
Infrastructure
- Domain:純 C# 實體與介面,零外部依賴,可獨立測試。
- Application:DTOs、服務介面、查詢介面,定義業務邊界。
- Infrastructure:Dapper Repository、Discord 整合、背景作業,實作所有外部依賴。
- Presentation.WebApi:Controller + Middleware 管線(例外處理 / 冪等 / 驗證 / 限流 / 交易)。
詳細設計請見 架構設計文件;其餘文件(業務規則、部署、E2E 測試)見 文件索引。
| 專案 | 職責 |
|---|---|
Domain/ |
核心實體、Repository 介面、業務邏輯,無外部依賴 |
Application/ |
DTOs、服務介面 (Interface/)、查詢介面 (Queries/) |
Infrastructure/ |
Dapper Repository 實作、Discord 整合、背景作業 |
Presentation.WebApi/ |
ASP.NET Core 控制器、Middleware 管線 |
Presentation/ |
Discord Bot 主控台應用程式 (DSharpPlus) |
web/ |
Next.js 15 前端 (App Router, Tailwind, Shadcn/UI) |
Test/ |
xUnit + Moq 單元測試 |
Test.Integration/ |
整合測試(Testcontainers 真 Postgres、WebApplicationFactory 限流測試) |
Utils/ |
自製 SqlBuilder、JSON 轉換器 |
db/migrations/ |
golang-migrate SQL 檔案(up/down) |
k8s/ |
Kubernetes 部署設定 |
# 複製 secrets 範本並填入實際值
cp secrets/example/* secrets/
docker compose up -d啟動順序由 Docker Compose 自動管理:database → migrate(schema 初始化)→ backend / bot → frontend → cloudflared
| 服務 | Port | 說明 |
|---|---|---|
database PostgreSQL 18 |
5432 | — |
migrate golang-migrate |
— | 執行 schema migration 後自動退出 |
redis Redis 7 |
6379 | session 快取 / 重複提交去重 / 限流計數;fail-open,掛了不擋主流程 |
backend ASP.NET Core Web API |
5230 | — |
frontend Next.js |
3000 | — |
bot Discord Bot |
— | — |
seq 日誌收集 |
8080 | Web UI |
cloudflared Cloudflare Tunnel |
— | — |
kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/secrets.yaml # 先填入真實值
kubectl apply -f k8s/database.yaml
kubectl apply -f k8s/seq.yaml
kubectl apply -f k8s/migrate-job.yaml # 等 Job 完成後再繼續
kubectl apply -f k8s/backend.yaml
kubectl apply -f k8s/frontend.yaml
kubectl apply -f k8s/bot.yaml
kubectl apply -f k8s/cloudflared.yaml# 1. 修改 Infrastructure/Entities/*DbModel.cs
# 2. 執行草稿產生器(自動跑 ef diff → 輸出 SQL → 清理 .cs,不需要額外的 DB/State File)
bash db/create-migration.sh <MigrationName>
# 3. 審閱並補齊產出的 SQL 草稿
# db/migrations/000002_<name>.up.sql ← 補 FK constraints / indexes
# db/migrations/000002_<name>.down.sql ← 確認 rollback 正確性
# 4. 本機套用
docker compose run --rm migrate詳細原理(為何用 EF Core 產 diff、golang-migrate 只負責套用)見 架構設計文件 §6。
後端
cd Presentation.WebApi
dotnet run前端
cd web
npm install
npm run devMIT License — 詳見 LICENSE。