ActiFlow:活動建立、報名與審核的全端平台
2026年9月15日
實踐專案:ActiFlow 活動報名平台

為什麼做這個專案
辦活動最麻煩的往往不是活動本身,而是前後的行政:建立報名表、控制名額、截止後關閉報名、整理報名名單。ActiFlow 是一個活動建立、公開招募、線上報名與主辦方審核的平台,把這些流程放在同一個地方完成。
主要功能

| 區域 | 功能 |
|---|---|
| 公開活動 | 首頁近期活動、活動分類、活動列表、歷史活動、活動詳情 |
| 活動報名 | 依活動設定的動態報名欄位、截止與名額檢查、報名成功頁 |
| 會員中心 | 個人資料、頭像上傳、我的報名與單筆報名詳情 |
| 主辦後台 | 建立與編輯活動、設定報名欄位、審核並匯出報名資料(CSV) |
| 系統後台 | 會員與主辦單位管理、主辦申請審核、活動強制下架、管理操作紀錄 |
一場活動從建立到結束的流程:
- 主辦單位建立活動,設定報名欄位、名額、日期與報名截止時間
- 活動發布後出現在公開列表;已結束的活動由 API 在查詢階段分到「歷史活動」
- 會員登入後送出報名,後端檢查活動狀態、截止時間、名額與重複報名
- 主辦單位在後台審核、匯出報名資料,會員可在「我的報名」查看狀態
權限設計:網站層級 × 活動單位層級
這個專案花最多心思的是權限。ActiFlow 不用單一的角色階級,而是拆成兩層:
| 層級 | 角色 |
|---|---|
| 網站層級 | 網站系統管理員、網站會員、訪客 |
| 活動單位層級 | owner、admin(對外都顯示為「活動單位管理員」)、member |
同一個人可以是網站會員,同時是「河濱路跑協會」的 owner、「攝影學會」的 member。所以權限必須依「目前操作的是哪個資源」來判斷:
- 進入活動單位後台時,用網址中的單位 ID 查詢這個人在該單位的身分
- 敏感操作不信任前端傳來的角色或 ID,一律回資料庫查詢最新的成員資格
- owner 與 admin 都能管理活動,但只有 owner 能指派管理員、轉移擁有權或解散單位
- 系統管理員不會自動變成每個單位的 owner;平台介入(例如強制下架活動)走獨立的管理 API,必須填寫原因並寫入操作紀錄
前端隱藏按鈕只是操作體驗,真正的權限邊界在後端。例如直接打開已截止活動的報名網址,後端仍會再次檢查截止時間與名額。
圖片上傳:金鑰不離開後端
頭像、主辦單位 Logo 和活動封面都存放在 Cloudflare R2:
- 前端告訴後端檔案類型、大小與用途
- 後端驗證後,產生 5 分鐘有效的預簽章上傳網址(presigned URL)
- 瀏覽器直接把檔案上傳到 R2,R2 金鑰從頭到尾都留在後端
- 資料庫只存圖片的公開網址
上傳限制依用途區分(頭像與 Logo 5 MiB、活動封面 8 MiB),只接受 JPEG、PNG、WebP、GIF;因為有 XSS 風險,刻意不接受 SVG。
部署:Vercel + Cloud Run + Neon
| 服務 | 平台 |
|---|---|
| 前端 | Vercel |
| 後端 API | Google Cloud Run(新加坡) |
| 資料庫 | Neon PostgreSQL(新加坡) |
| 圖片 | Cloudflare R2 |
| Resend(註冊驗證信與通知信) |
部署時有幾個安全設定:
- 資料庫連線字串與 JWT 金鑰放在 GCP Secret Manager,執行服務的帳號只有讀取這些機密的權限
- Cloud Run 閒置時縮到 0 個實例、上限 1 個,並設定費用警示
- 後端在 staging/production 環境會拒絕不安全的設定直接啟動失敗,例如過短的 JWT 金鑰、非 HTTPS 的前端網址、萬用 CORS,或開啟除錯路由
- 登入使用 HttpOnly Cookie 中的 JWT,前端程式碼讀不到 token
技術架構
| 項目 | 使用技術 |
|---|---|
| 前端 | Next.js 16(App Router)+ React 19 + TypeScript |
| 狀態管理 | Redux Toolkit、Axios |
| 樣式 | Tailwind CSS |
| 後端 | Python 3.11 + FastAPI + Pydantic 2 |
| 資料庫 | PostgreSQL(Neon)+ SQLAlchemy 2 + Alembic |
| 驗證 | HttpOnly Cookie + JWT、RBAC |
| 測試 | Pytest(使用獨立的測試資料庫,避免動到正式資料) |
🌐 線上體驗