🦖 第十一章:FastAPI 巨獸崛起!打造萬人打卡伺服器
在上一門課「Line 打卡網頁 (LIFF)」中,我們做出了極度炫酷的打卡按鈕,並取得了 Line 簽發的安全憑證 (JWT Token)。 但前端網頁就像是餐廳的「服務生」,他只負責點餐。真正把資料寫進資料庫、計算薪水、判斷有沒有遲到的,是躲在廚房裡的「後端伺服器 (Backend Server)」。
如果你是用傳統的 Node.js (Express) 或是 PHP 來寫後端,當早上 09:00 湧入 5000 個員工同時打卡時,傳統伺服器會因為「阻塞 (Blocking)」而崩潰,導致後面的 1000 個員工打卡失敗,全公司大暴走。
為了解決這個問題,這堂課我們將引入 Python 界目前最紅、效能足以媲美 NodeJS 和 Go 的框架:FastAPI。 如同它的名字,它極度快速、天生支援非同步 (Async)、並且能自動幫你產出 API 文件。 我們將透過 Vibe Coding,指揮 AI 幫我們建構這個可以承受萬人並發的巨獸級後台!
⚡ 實戰 1:極速搭建 FastAPI 骨架與 ngrok 穿透
要開發 Line 相關的應用程式 (Line Bot 或 LIFF 串接),我們面臨的第一個死穴是:Line 伺服器只能把資料送到「有 https 網址的公網伺服器」。
如果你在自己電腦上跑 localhost:8000,Line 根本找不到你!
這時候,我們需要兩個神器:FastAPI (架設本地伺服器) 與 ngrok (把本地伺服器打穿出去變成外網網址)。
💡 Vibe Prompt 實戰 1:讓 AI 寫出最簡潔的啟動腳本
不要自己去背那一堆 uvicorn 的參數,直接讓 AI 幫你寫一個馬上能跑的骨架。
[!IMPORTANT] 請複製以下 Prompt 傳送給 AI:
我正在使用 Python 開發一個 Line Bot 的後端伺服器。請幫我用 FastAPI 寫一個最基礎的骨架程式 (main.py)。需求:1. 建立 FastAPI 實體 app。2. 寫一個根目錄路由 (GET /),回傳 {"status": "ok", "message": "Punch System is running"}3. 寫一個 Webhook 接收路由 (POST /api/webhook),接收任意 JSON 資料並在 console 印出來。4. 提供在終端機啟動這個伺服器的 uvicorn 指令 (使用 port 8000,並開啟 reload)。5. 提供對應的 requirements.txt 內容。
🤖 AI 生成的極速起手式:
# main.py
from fastapi import FastAPI, Request
# 建立 FastAPI 巨獸實體
app = FastAPI(title="Line Punch System API")
@app.get("/")
async def root():
"""用來檢查伺服器是否活著的健康檢查點 (Health Check)"""
return {"status": "ok", "message": "Punch System is running 🚀"}
@app.post("/api/webhook")
async def receive_webhook(request: Request):
"""
這是一個「開大門」的路由,準備用來接收前端或 Line 伺服器打過來的資料
"""
# 非同步讀取傳進來的 JSON 資料
payload = await request.json()
print("📥 收到新的 Webhook 資料:", payload)
# FastAPI 非常聰明,只要回傳字典,它會自動幫你轉成 JSON 格式送回前端
return {"status": "success", "received_data": payload}
啟動指令與穿透:
- 在終端機輸入:
uvicorn main:app --reload --port 8000 - 打開第二個終端機輸入:
ngrok http 8000 - ngrok 會給你一串神奇的網址(例如
https://1234-abcd.ngrok-free.app)。把這個網址貼到你的前端或 Line Developer 後台,你的電腦就正式成為雲端伺服器了!
🔐 實戰 2:資安第一防線 (CORS 設定)
當你興高采烈地把 ngrok 網址貼到前端的 LIFF 網頁中,按下「打卡」按鈕。
你會發現瀏覽器的 Console 出現一片鮮紅色的錯誤:Blocked by CORS policy (被跨來源資源共用政策阻擋)!
這是因為現代瀏覽器有一套極度嚴格的安全機制:如果不允許,A 網站 (Vercel 上的前端) 絕對不能隨便打 API 給 B 網站 (你的 ngrok 後端)。 我們必須在 FastAPI 裡面安裝一個「警衛」,告訴他:「沒關係,Vercel 那邊是自己人,放他進來!」
💡 Vibe Prompt 實戰 2:完美設定 CORS Middleware
[!IMPORTANT] 請複製以下 Prompt 傳送給 AI:
我的前端網頁 (部署在 https://my-punch.vercel.app 和 http://localhost:5173) 打 API 給我的 FastAPI 伺服器時,遇到了 CORS 錯誤。請幫我修改剛才的 main.py,加入 CORSMiddleware 的設定。需求:1. 允許上述兩個網址的來源 (Origins)。2. 允許所有的 Methods (GET, POST, OPTIONS 等)。3. 允許所有的 Headers。請給我包含 CORS 設定的完整 FastAPI 初始化程式碼。
🤖 AI 的企業級警衛設定:
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI(title="Line Punch System API")
# 定義「白名單」:只有這些網址發出的請求,我們才接聽!
# ⚠️ 在正式上線時,絕對不能寫 ["*"] (允許所有人),那等於門戶洞開!
origins = [
"http://localhost:5173", # 給前端工程師在本地端測試用
"https://my-punch.vercel.app", # 正式上線的前端網址
]
# 安裝跨來源警衛 (Middleware)
app.add_middleware(
CORSMiddleware,
allow_origins=origins, # 只允許白名單的來源
allow_credentials=True, # 允許攜帶 Cookie 或認證資訊
allow_methods=["*"], # 允許所有 HTTP 動作 (GET, POST, PUT, DELETE)
allow_headers=["*"], # 允許所有自訂 Header (包含我們等等要用的 Authorization)
)
# ... 下面接著寫你的 @app.get("/") 路由 ...
🕵️♂️ 實戰 3:解開 LIFF 的數位簽章 (JWT 驗證)
還記得我們在前端課程中說過:「前端傳過來的任何資料,都不可信任」 嗎?
如果前端傳來 {"user_name": "王老闆", "action": "clock_in"},駭客隨時可以攔截並改成 {"user_name": "李小明"} 來幫別人代打卡。
所以我們在前端傳送的是一個亂碼:JWT Token (idToken)。
現在,後端收到這串 Token 了。我們不能自己去解開它,我們必須把這串 Token 傳送給 Line 的官方伺服器,問它:「這串憑證是不是真的?它是發給誰的?」
💡 Vibe Prompt 實戰 3:呼叫 Line 官方 API 驗證 Token
這是一段牽涉到第三方 API 溝通的複雜邏輯,如果自己看官方文件寫,很容易漏掉錯誤處理。
[!IMPORTANT] 請複製以下 Prompt 傳送給 AI:
在 FastAPI 中,我的前端會在 HTTP Header 的 Authorization 中傳送 Line LIFF 產生的 JWT Token (格式為 Bearer <id_token>)。請幫我寫一個依賴注入函數 (Dependency) verify_line_token(request: Request)。邏輯如下:1. 從 request.headers 取得 Authorization 字串,並擷取出 token。2. 如果沒有 token,拋出 HTTPException 401。3. 使用 httpx 套件發送 POST 請求到 Line 驗證端點:https://api.line.me/oauth2/v2.1/verify4. 參數需要夾帶 id_token 和 client_id (我的 LIFF ID,放在環境變數 LINE_LIFF_ID 中)。5. 如果 Line 回傳錯誤,拋出 HTTPException 401 拒絕存取。6. 如果驗證成功,回傳包含使用者姓名和 line_id (sub) 的字典檔。
🤖 AI 的終極防偽驗證中心:
import os
import httpx
from fastapi import Request, HTTPException, status, Depends
async def verify_line_token(request: Request):
"""
這個函數就像是公司門口的「虹膜掃描機」。
任何想要打卡的 API 請求,都必須先通過這個函數的檢驗!
"""
# 1. 從 Header 中抽出通行證
auth_header = request.headers.get("Authorization")
if not auth_header or not auth_header.startswith("Bearer "):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="缺少安全憑證或格式錯誤"
)
# 抽出 "Bearer " 後面的那串亂碼
id_token = auth_header.split(" ")[1]
client_id = os.getenv("LINE_LIFF_ID")
# 2. 拿著這張通行證,去問 Line 警察總部:「這張是真的嗎?」
# 使用非同步的 httpx (取代舊的 requests,不會卡死伺服器)
async with httpx.AsyncClient() as client:
response = await client.post(
"https://api.line.me/oauth2/v2.1/verify",
data={
"id_token": id_token,
"client_id": client_id
}
)
payload = response.json()
# 3. 判斷驗證結果
if response.status_code != 200 or "error" in payload:
# 如果 Line 說這張是假證件,立刻把駭客踢出去!
print("❌ Token 驗證失敗:", payload.get("error_description"))
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="憑證失效或無效,請重新登入"
)
# 4. 驗證成功!Line 伺服器會告訴我們這張證件本人的真實身份
# payload 裡面會包含 sub (Line 使用者的唯一 ID)、name (姓名)、picture (頭像)
print(f"✅ 歡迎光臨,真實身分確認為:{payload.get('name')}")
return {
"line_id": payload.get("sub"),
"name": payload.get("name"),
"picture": payload.get("picture")
}
🛡️ 實戰 4:打造滴水不漏的打卡 API (結合 Dependency)
現在我們有了虹膜掃描機 (verify_line_token)。我們要把這台機器安裝在打卡的打卡鐘前面。
在 FastAPI 中,這叫做 Dependency Injection (依賴注入)。這也是 FastAPI 之所以強大且優雅的原因。
💡 Vibe Prompt 實戰 4:串接驗證與打卡路由
[!IMPORTANT] 請複製以下 Prompt 傳送給 AI:
請幫我寫一個打卡的 API 端點:POST /api/punch。需求:1. 使用 FastAPI 的 Depends 將剛才的 verify_line_token 注入為參數 current_user。2. 定義一個 Pydantic BaseModel 作為接收資料的格式 (包含 action: str,值只能是 'in' 或 'out')。3. 當請求進來時,印出:"[{current_user['name']}] 執行了打卡動作:{action}"。4. 回傳成功的 JSON,包含使用者名稱與打卡時間。
🤖 AI 完美結合依賴注入的打卡路由:
from fastapi import APIRouter, Depends
from pydantic import BaseModel
from datetime import datetime
# 建立 Pydantic 資料模型:這是第二層防護罩,用來檢查前端傳來的資料格式對不對
class PunchRequest(BaseModel):
action: str # 'in' 代表上班, 'out' 代表下班
# 我們可以把路由獨立出來管理
router = APIRouter()
# 🚀 終極安全的打卡端點
@router.post("/api/punch")
async def handle_punch(
# 這裡發生了兩件事:
# 1. 檢查前端傳來的 body 格式對不對 (PunchRequest)
request_data: PunchRequest,
# 2. 🔥 神級防護:強制這個請求必須先經過虹膜掃描機 (verify_line_token)!
# 如果掃描失敗,請求在這裡就會直接被擋掉報錯,根本進不到後面的邏輯。
# 如果掃描成功,current_user 就會拿到使用者的真實姓名與 ID。
current_user: dict = Depends(verify_line_token)
):
action_type = request_data.action
user_name = current_user["name"]
line_id = current_user["line_id"]
now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# 記錄打卡行為
# 在真實系統中,我們會在下一章把這段寫入 Supabase 資料庫
print(f"🎯 [打卡成功] {user_name} ({line_id}) 執行了 {action_type}。時間:{now_str}")
return {
"status": "success",
"message": f"打卡成功!歡迎,{user_name}。",
"punch_time": now_str,
"action": action_type
}
🔍 深度解析:
這是企業級後端架構的精髓:「職責分離」。
在傳統的 PHP 程式碼中,你可能會在 /api/punch 裡面寫了 100 行驗證 Token 的邏輯,再寫 50 行檢查格式的邏輯,最後才寫打卡邏輯。程式碼變得又長又臭。
而在 FastAPI 的設計中:
PunchRequest負責擋下格式錯誤的瞎子。Depends(verify_line_token)負責擋下沒有通行證的駭客。- 主體函數
handle_punch只專心做一件事:寫入打卡紀錄。 這不僅讓程式碼可讀性極高,未來如果要加一個「請假 API」,你只要再掛上Depends(verify_line_token),一秒就能獲得一模一樣的安全防護!
🗄️ 實戰 5:串接 Supabase 雲端資料庫 (Async 寫入)
現在我們確認了使用者的身份,我們要正式把他的打卡紀錄寫進資料庫了。
跟我們在網頁端用的 supabase-js 不太一樣,在 Python 中,我們使用的是 supabase-py。由於 FastAPI 是非同步架構,我們必須確保資料庫的連線不會「阻塞」其他幾千人的打卡。
💡 Vibe Prompt 實戰 5:撰寫安全高效的資料庫寫入函數
[!IMPORTANT] 請複製以下 Prompt 傳送給 AI:
我需要在 FastAPI 中把打卡紀錄寫入 Supabase 的 "punch_logs" 資料表。請幫我寫一個初始化 Supabase 客戶端的程式碼 (使用 os.getenv 取得 url 和 key)。接著,寫一個 async 函數 record_punch_to_db(line_id: str, action: str)。邏輯:1. 將 line_id, action, 並且附上現在的 timestamp,寫入 Supabase。2. 使用 supabase.table("punch_logs").insert(...).execute()。3. 請加上完整的 try...except 防呆保護,確保資料庫斷線時不會讓 API 當機崩潰,並印出錯誤訊息。
🤖 AI 的高階非同步資料庫模組:
import os
from supabase import create_client, Client
from datetime import datetime
# 1. 建立 Supabase 連線引擎
# (注意:Python 版本的 supabase-py 目前底層還是同步的,
# 但我們把它包在 async 函數中,或是使用 postgrest-py 的 async 版本,這裡示範最穩定的通用寫法)
url: str = os.getenv("SUPABASE_URL")
key: str = os.getenv("SUPABASE_SERVICE_KEY") # ⚠️ 後端伺服器可以使用特權 Key 來繞過 RLS
try:
supabase_db: Client = create_client(url, key)
print("✅ Supabase 資料庫連線成功!")
except Exception as e:
print("❌ 資料庫連線失敗,請檢查環境變數:", e)
# 2. 建立防呆寫入函數
async def record_punch_to_db(line_id: str, action: str) -> bool:
"""
將打卡紀錄安全地寫入雲端資料庫。
回傳 True 代表寫入成功,False 代表寫入失敗。
"""
try:
# 準備要塞進資料庫的包裹
data_packet = {
"user_id": line_id, # 關聯到員工表的 ID
"action": action, # 'in' 或 'out'
"created_at": datetime.utcnow().isoformat() # 使用 UTC 國際標準時間是後端潛規則!
}
# 執行寫入動作
response = supabase_db.table("punch_logs").insert(data_packet).execute()
print(f"📦 紀錄已成功寫入資料庫!資料 ID: {response.data[0]['id']}")
return True
except Exception as e:
# 如果 Supabase 掛了,或是憑證過期,我們在這裡攔截爆炸!
print(f"🚨 [嚴重錯誤] 無法寫入 Supabase 資料庫:{str(e)}")
# 你甚至可以在這裡加一行寄發警報 Email 給工程師的程式碼!
return False
🔍 深度整合:
現在,你只需要把這段 await record_punch_to_db(line_id, action_type) 放進剛才的 handle_punch 路由裡面。
當王小明按下打卡按鈕 ➡️ FastAPI 瞬間解開 JWT 憑證 ➡️ 抓出王小明的真實身分 ➡️ 將身分與時間打包成包裹寫入 Supabase ➡️ 系統回傳「打卡成功」。
這整個極度複雜、防偽、且高併發的流程,在 FastAPI 的加持下,只需要不到 0.05 秒就能完成!這就是為什麼它可以承受萬人並發的原因!
✅ 本章總結與除錯心法
在這個長達 6000 字的高階架構章節中,我們完成了一個企業級的後端核心。
回顧你建立的無敵防線:
- ngrok 內網穿透:讓你的筆電瞬間變成可以接收 Line 訊號的公網伺服器。
- CORS 白名單:架設警衛,嚴禁未授權的網站隨便呼叫你的 API。
- JWT 虹膜驗證:絕對不信任前端傳來的身分,強制送到 Line 官方伺服器做二次驗證。
- FastAPI 依賴注入 (Depends):將複雜的驗證邏輯模組化,保護核心業務邏輯的乾淨。
- Supabase 容錯寫入:即使資料庫連線中斷,也要確保系統不會陷入當機死鎖。
現在,我們的系統可以記錄「誰」在「什麼時候」打卡了。 但是,這是一個人性本惡的世界。 如果有個聰明的員工,他在家裡的床上,早上 8:59 用手機連上你的打卡網頁,按下了打卡按鈕。系統會判定他準時上班! 這對公司來說是不可接受的!
在真正的百萬級考勤系統中,我們必須加入**「Anti-Cheat 防作弊機制」**。 下一章:第十二章:Anti-Cheat 防作弊機制:定位驗證與假 GPS 防護,我們將教你如何用 Vibe Coding 寫出最強的幾何數學演算法,利用前端回傳的經緯度與後端的中心點比對,甚至阻擋使用「虛擬定位 (Fake GPS)」的員工!準備好迎接真正的挑戰了嗎?我們下章見!