透過個人開發學習 App Store 上架流程 ― 以 UIKit + FastAPI + K8s 製作的 Minecraft 伺服器監控應用「MineWatch」

你是不是也有這種經驗:自己在家裡或和朋友一起架設的 Minecraft Java Edition 伺服器,偏偏每次最想玩的時候就當機了。等你打開客戶端、準備登入的那一刻才發現,「啊,又掛了」。

為了解決這個問題,我用 iOS(UIKit)+ FastAPI + PostgreSQL + Kubernetes 個人開發了Minecraft 伺服器監控應用「MineWatch」。我已於 2026-07-19 將 1.0.0 提交至 App Store,目前正在等待審查。

這篇文章比起「介紹應用功能」,更著重在個人開發到這個規模時,為什麼會採用這樣的架構,以及為了學習 App Store 上架流程而特別意識到的設計取捨

TL;DR

  • 最主要的目的是學習 App Store 的上架流程。我和實作是否正確一樣重視「為什麼這樣做」的紀錄。
  • 監控採用Server List Ping(SLP)——也就是 Minecraft 用戶端在伺服器列表畫面所使用的公開協定——因此被監控的伺服器端完全不需要修改
  • API 程序與監控執行分離為 worker / beat。即使 API/Web 掛掉,已經排進佇列的監控任務仍會繼續執行。
  • 推播通知不經由 FCM,而是直接送到 APNs。如此一來,iOS 端就不需要整合 Firebase 的推播 SDK。
  • 分支管理採用 git-flow。PR 會由 GitHub Copilot CLI 自動審查,並以 VERDICT 為觸發條件,由 GitHub Actions 自動決定合併或轉人工審查。

應用解決的問題

MineWatch 透過 SLP 從外部取得伺服器狀態。SLP 是 Minecraft 用戶端在多人遊戲畫面中使用的相同公開協定,因此對監控對象的伺服器不需要安裝外掛或修改設定。可取得的資訊如下:

  • 在線 / 離線狀態
  • 目前連線人數 / 最大人數
  • 回應時間(延遲)、版本、MOTD
  • 人數趨勢圖、運作率
  • 伺服器當機時的推播通知(直接送 APNs)

整體架構

minewatch-arch.png

重點有兩個:

  1. 監控執行與 API 程序分離beat 會把週期性任務丟到 Redis,worker 負責執行,偵測到當機後直接送 APNs 推播。即使 API 掛掉,已在佇列中的任務仍會繼續執行。
  2. 通知不經由 FCM。直接送 APNs 可讓 iOS 端不必攜帶 Firebase 推播 SDK。

技術堆疊

層級 技術
iOS UIKit(無 Storyboard / XIB)、MVVM、@Observable、Swift 6 strict concurrency、XcodeGen + SPM
API FastAPI(Python 3.13 / uv)、SQLModel + Alembic
非同步處理 Celery(worker / beat)+ Redis
資料庫 PostgreSQL 17
驗證 Firebase Authentication(Apple / Google 登入。後端驗證 ID token)
推播通知 直接送 APNs(.p8 金鑰。不使用 FCM)
容器 / 編排 Docker、Kubernetes(F5 NGINX Ingress Controller)
GitOps Argo CD
CI/CD Jenkins(Mac 節點 + k8s Pod agent)、GitHub Actions
邊緣 / 網路 Cloudflare(DNS / Tunnel / Access)

會在個人開發中組到這樣的基礎架構,一部分確實是出於興趣;但更大的原因是我意識到,如果要穩定通過 App Store 審查,就需要一套能持續維持可運作後端的機制。我不希望在審查期間 API 掛掉、導致無法重現問題這類事故發生,所以至少把監控執行系統的可用性獨立切開了。

API 設計方針

行動應用不可能期待所有使用者都同時更新,因此後端 API 的設計重點放在「如何維持向下相容」。

  • 目錄=版本:採用 app/api/<version>/<domain>/router.py 的結構,啟動時由 discover_routers() 自動註冊。只要切出目錄並放入 router.py,就能新增路由。
  • 破壞性變更時凍結 v1 並新增 v2。邏輯集中在 services/,router 只保留各版本的薄型適配層,以避免重複實作。
  • 資料庫型別變更採用 Expand-Contract(新增欄位 → 遷移 → 切換 → 刪除)。由於舊程式與新程式一定會有一段時間同時操作同一份資料庫,因此設計上必須能同時支援兩邊。
  • 所有回應都以物件包裝。不直接回傳陣列(例如採用 {"servers": [...], "total": n} 的形式),以保留日後加入中繼資料的彈性。

servers.host 會使用 Fernet 金鑰加密後儲存。若要輪替金鑰,可以用逗號分隔的方式同時放入新舊金鑰,達成無停機切換。

分支運用與 CI/CD

配合 1.0.0 的發布,我改用了 git-flow。

  • develop:功能 PR 的 base。Argo CD 會自動同步到 staging。
  • release/x.y.z:從 develop 分支切出,只負責發布資源的穩定化與本番 overlay 標籤確認。
  • main:正式環境。只接受 release / hotfix 的合併,合併後會打上標籤。

PR 審查由 GitHub Copilot CLI 自動執行。Jenkins 會對每個 PR 跑 pytest 與 build 驗證,並以 Copilot 的 VERDICT 註解為起點,由 GitHub Actions 分流處理。

  • VERDICT: APPROVE 且 base ≠ main → squash merge + 刪除分支
  • VERDICT: APPROVE 且 base = main → 通知人工合併流程(git-flow 的發布由人來操作)
  • VERDICT: REQUEST_CHANGES → 加上 needs-human-review 標籤

這裡我刻意設計成不讓 Claude 自動修正。目的是避免形成 AI 審查 → AI 修正 → AI 審查……的循環。

安全性與隱私

  • 驗證:由 Firebase 發行的 ID token 由後端進行驗證。
  • 加密儲存:伺服器連線目的地 host 會以 Fernet 加密。金鑰只存在於 k8s Secret 中。
  • 封鎖內部 API/internal/* 會由 nginx 擋下外部公開路徑,只允許從叢集內部存取。
  • App 隱私:收集的資料僅包含電子郵件地址、使用者 ID(帳號管理)、使用狀況與當機資料。不使用追蹤(IDFA)。

結語

在開發 MineWatch 的過程中,我學到最多的,其實不是「做出一個應用程式」本身,而是「建立一個能持續發布應用程式的環境」這件事的重要性。像 git-flow、Expand-Contract、API 版本控管這些決策,都是從「面對無法一次更新所有行動裝置的情況,要如何安全地持續發布」這個核心問題反推而來。

等審查通過後,我也會另外整理審查時被指出的事項,以及實際提交流程的細節。


:sparkles:即使沒有經驗也能學會!一起挑戰吧!:sparkles:

我也有在寫 note↓



原文出處:https://qiita.com/jqit-yukiono/items/d33908602fd060f5e1d1


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝7   💬2   ❤️4
225
🥈
我愛JS
📝2   💬6   ❤️3
110
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登