py程式設計(三)課程網站
第 10 週 · 單元 4-1 · 專案二

社團活動報名平台(上):多張資料表與登入

第一次做「不同的人能做不同的事」的網站。本週完成規劃、三張資料表、註冊登入,以及報名與名額控制。

W10報名平台登入+名額

本週結束時你會

今天的時間安排

0:00–0:20讀需求說明書;觀念:使用者、角色、權限
0:20–0:50【自己做】提案書、時程、PROJECT.md 前三段
0:50–1:20【跟著做】建立專案、三張資料表、示範資料
1:20–1:30休息
1:30–2:00功能 1【跟著做】註冊與登入
2:00–2:30功能 2【自己做】活動列表、報名與取消;週誌

三張資料表,兩種關聯

users    使用者   id · username(不可重複)· password_hash · role(member/officer)
events   活動     id · title · held_on · capacity
signups  報名紀錄 id · event_id → events.id · user_id → users.id · created_at
                  UNIQUE (event_id, user_id)   ← 同一人不能重複報名同一場

一個人可以報名很多場活動,一場活動也有很多人報名,這叫多對多。多對多一定要有一張「中間表」(signups)記錄誰報了哪一場。

常見錯誤

在 events 表放一個「報名者」欄位,存「阿明,小美,阿哲」這樣的文字。要算人數、要判斷重複、要刪除某一人,都會變得很麻煩又容易出錯。

正確做法

每一筆報名是 signups 表的一列。人數用 COUNT,重複由 UNIQUE 擋下,取消就是 DELETE 一列。

學生的 PROJECT.md 若沒有中間表,請他用「阿明報了兩場、工作坊有三個人」畫出資料,自己會發現一個欄位裝不下。參考解答在 practice/s3-4-1_社團報名平台/參考解答/club-signup/。

建立專案【跟著做】

提示詞 1 Antigravity
請先讀 PROJECT.md 與需求說明書。建立 Flask 專案骨架:
1. 沿用記帳網站的做法:db.py 依 DATABASE_URL 切換 SQLite/Postgres,schema_sqlite.sql 與 schema_postgres.sql 分開
2. 三張資料表 users、events、signups;signups 用 UNIQUE (event_id, user_id) 防止重複報名;SQLite 要開啟 PRAGMA foreign_keys
3. flask --app app init-db:建表並放入需求說明書的示範帳號與活動。密碼必須用 werkzeug 的 generate_password_hash 雜湊後再存
4. app.secret_key 從環境變數 SECRET_KEY 讀取;本機沒有設定時才使用開發用的預設值
完成後用 SQL 告訴我:四場活動各有幾人報名,以及 users 表中阿明的 password_hash 長什麼樣子。
檢查正確結果
報名人數社員大會 2、工作坊 3、校外參訪 1、成果展 0
阿明的 password_hash一長串以演算法名稱開頭的文字(例如 scrypt:…),不是 member123

為什麼密碼要雜湊:雜湊是單向的,可以驗證「輸入的密碼對不對」,但無法從雜湊值反推出密碼。就算資料庫外洩,攻擊者也拿不到密碼本身。

功能 1:註冊與登入【跟著做】

提示詞 2 Antigravity
請先讀 PROJECT.md。現在只做「註冊、登入、登出」:
1. /register:暱稱 2–20 字且不可重複、密碼至少 8 字元、兩次輸入要相同;錯誤時顯示中文訊息
2. /login:成功後把 user_id 存進 session;失敗時只顯示「暱稱或密碼錯誤」,不透露是哪一個錯
3. 登出必須用 POST
4. 寫一個 login_required 裝飾器:未登入時導到登入頁,登入後回到原本的頁面(只接受站內網址,避免被導到外部網站)
5. 導覽列顯示目前登入的暱稱,幹部加註「(幹部)」
只修改 app.py 與 templates/。
操作正確結果
阿明/member123 登入導覽列顯示「阿明」
阿明/錯誤密碼「暱稱或密碼錯誤」
註冊暱稱「阿明」已經有人使用
註冊密碼 short至少 8 個字元
小派社長登入導覽列顯示「小派社長(幹部)」
巡堂重點

密碼存成明碼:打開 DB Browser 看 users 表最清楚。請學生要求 AI 改用雜湊,並重新 init-db。

登入後跳到外部網站:如果 next 參數沒有檢查,/login?next=https://壞網站 會把剛登入的使用者送去釣魚頁。這叫「開放式重新導向」。

存檔並推上 GitHub:「功能1:註冊與登入」。

功能 2:活動列表、報名與取消【自己做】

自己寫提示詞。需求:首頁列出活動與剩餘名額;活動頁依狀態顯示「登入後才能報名」「我要報名」「已額滿」或「取消報名」;報名與取消都用 POST 並要求登入;寫入前在伺服器再檢查一次名額;重複報名由資料庫的 UNIQUE 擋下並顯示友善訊息。

操作正確結果
首頁社員大會剩 28 位、工作坊「額滿」、校外參訪剩 1 位、成果展剩 50 位
未登入按報名導到登入頁,登入後回到該活動
豆豆報名工作坊按鈕停用;就算用開發者工具送出,也顯示已額滿
豆豆報名校外參訪成功,已報名 2 人,變成額滿
豆豆再送出一次報名「你已經報名過了」,仍是 2 人
豆豆取消報名變回 1 人

兩個人同時搶最後一個名額:如果兩人在同一瞬間按下報名,兩邊都先查到「還有 1 位」,就會超收。參考解答在 Postgres 上用 SELECT … FOR UPDATE 鎖住那場活動,讓第二個人等第一個人寫完再檢查。這是真實系統常見的問題,可以請 AI 解釋給你聽。

存檔並推上 GitHub:「功能2:報名與取消」。

本週檢核