--- title: 軟體設計文件(SDD) --- # 軟體設計文件(SDD) ## 專案資訊 - 專案名稱:海大教室租借系統 - 撰寫日期:2025/11/23 - 發展者:吳致緯、黃星智、陳宥嘉、薛翔安、高翊誠 --- ## 版次變更記錄 | 版次 | 變更項目 | 變更日期 | | :---: | :--------: | :--------: | | 0.1 | 初版 | 2025/11/23 | | 0.2 | Iteration_2 後的修改 | 2025/12/01 | | 0.3 | Iteration_3 前的修改 | 2025/12/19 | | 0.4 | | | | 0.5 | | | | 1.0 | | | --- ## 目錄 1. [系統模型與架構 (System Model / System Architecture)](#1.-系統模型與架構-(System-Model-/-System-Architecture)) 2. [介面需求與設計 (Interface Requirement and Design)](#2.-介面需求與設計-(Interface-Requirement-and-Design)) 3. [流程設計 (Process Design)](#3.-流程設計-(Process-Design)) 4. [使用者畫面設計 (User Interface Design)](#4.-使用者畫面設計-(User-Interface-Design)) 5. [資料設計 (Data Design)](#5.-資料設計-(Data-Design)) 6. [類別圖設計 (Class Diagram)](#6.-類別圖設計-(Class-Diagram)) 7. [實作方案 (Implementation Languages and Platforms)](#7.-實作方案-(Implementation-Languages-and-Platforms)) 8. [設計議題 (Design Issue)](#8.-設計議題-(Design-Issue)) --- ## 1. 系統模型與架構 (System Model / System Architecture) 本系統前端語言/框架是以React(Vite + Tailwind CSS)進行畫面呈現、租借表單、租借查詢。 後端語言/框架則是以 Python + Django 進行帳號登入、租借邏輯、資料處理與寄信通知,Redis 用來發送驗證碼,Aiven 遠端資料庫儲存資料,並部署在 Zeabur。 - System Context Diagram ![SystemContext.drawio](https://hackmd.io/_uploads/HkVFl4d-Zl.png) - Containers Diagram ![Containers.drawio](https://hackmd.io/_uploads/BkAAwhwmbg.png) - Component Diagram ![Component.drawio](https://hackmd.io/_uploads/B1QaLnPm-e.png) --- ## 2. 介面需求與設計 (Interface Requirement and Design) - Login.jsx | 介面名稱 | 介面提供者 | 介面使用者 | 連結方式 | 輸入資料 | 輸出資料 | 介面描述 | | :-----: | :-----: | :-----: | :-----: | :-----: | :-----: | :-----: | | 使用者登入 API | Django 後端(Auth 模組) | 前端 Login.jsx | POST /api/auth/login/ | { account, password } | 成功:{ access, refresh, user };失敗:錯誤訊息 | 供前端提交帳密,後端驗證後回傳 JWT Token 與使用者基本資訊。 | | 使用者註冊 API | Django 後端(Auth 模組) | 前端 Login.jsx | POST /api/auth/register/ | { name, account, password } | 成功訊息或錯誤訊息 JSON | 前端傳送註冊資料給後端,後端建立新使用者,成功後前端切回登入頁面。 | | 忘記密碼 API | Django 後端(Auth 模組) | 前端 Login.jsx | POST /api/auth/forgot/ | { account, verify_code, new_password, confirm_password } | 成功:{ message: "Password reset success" };失敗:錯誤訊息 | 用於重設密碼。後端驗證帳號與驗證碼是否正確,若通過則更新密碼。前端收到成功訊息後返回登入頁面。 | - ClassroomBooking.jsx | 介面名稱 | 介面提供者 | 介面使用者 | 連結方式 | 輸入資料 | 輸出資料 | 介面描述 | | :-----: | :------: | :------: | :----: | :-----: | :-----: | :----: | | 取得大樓與教室清單 API | Django 後端(教室管理模組) | 前端 ClassroomBooking.jsx | **GET** `/api/buildings/` | 無 | `[ { code, name, rooms[] }, ... ]` | 提供前端載入所有可借用的大樓與底下的教室,用於左側樹狀清單顯示。 | | 取得教室週曆時段 API | Django 後端(預約模組) | 前端 WeekCalendar 元件 | **GET** `/api/rooms/{room_code}/schedule/?week=YYYY-WW` | 路徑:`room_code`;查詢參數:`week`(週別) | `[ { day, start, end }, ... ]` | 查詢某間教室在指定週的被占用時段,供前端標示「已被預約 / 可預約」格子。 | | 建立教室預約申請 API | Django 後端(預約模組) | 前端 ClassroomBooking.jsx(handleReserve) | **POST** `/api/reservations/` | `{ room_code, building_code, day_of_week, start_hour, end_hour }`(使用者從 JWT 判定) | 建立後的預約資料(含 `status: "待確認"`) | 使用者在週曆點選可預約時段後,送出預約申請,等待管理員審核。 | | 取得我的預約歷史 API | Django 後端(預約模組) | 前端 HistoryPanel | **GET** `/api/reservations/my/` | JWT 使用者身分 | `[ { id, room_code, building_code, day, start, end, status, created_at }, ... ]` | 回傳登入使用者的所有預約紀錄,用於「我的教室預約歷史」頁面。 | | 取消教室預約 API | Django 後端(預約模組) | 前端 HistoryPanel(取消預約按鈕) | **PATCH** `/api/reservations/{id}/cancel/` | 路徑參數:id(預約編號);JWT 使用者身分 | 更新後的預約資料`(status: "已取消")` | 使用者可取消「尚未開始」的預約;前端需先顯示確認訊息,成功後更新狀態並釋放該時段。 | | 管理員取得待審核預約清單 API | Django 後端(預約模組,管理端) | 前端 RequestPanel | **GET** `/api/reservations/pending/` | 管理員身分(JWT) | `[ { id, room_code, building_code, day, start, end, status, created_at, user }, ... ]` | 管理員查看所有 `status = "待確認"` 的預約申請,用於審核。 | | 管理員批准預約 API | Django 後端(預約模組) | 前端 RequestPanel(批准按鈕) | **PATCH** `/api/reservations/{id}/approve/` | 路徑參數:`id` | 更新後預約資料(`status: "已批准"`) | 管理員批准某筆預約,並將該時段加入占用清單。 | | 管理員拒絕預約 API | Django 後端(預約模組) | 前端 RequestPanel(拒絕按鈕) | **PATCH** `/api/reservations/{id}/reject/` | 路徑參數:`id`;可選:`reason` | 更新後預約資料(`status: "已拒絕"`) | 管理員拒絕某筆預約,可附上拒絕理由。 | - EditingClassroom.jsx | 介面名稱 | 介面提供者 | 介面使用者 | 連結方式 | 輸入資料 | 輸出資料 | 介面描述 | | :-----: | :------: | :------: | :-----: | :----: | :-----: | :----: | | 取得教室清單 API | Django 後端(教室管理模組) | 前端 EditingClassroom.jsx | **GET** `/api/classrooms/?page_size=200` | JWT 管理員身分 | `{ results: [ { building, room_code, name, capacity, has_projector, has_whiteboard, has_network, has_mic }, ... ] }` | 載入系統中所有教室資料,供管理員編輯教室資訊與設備設定。 | | 新增教室 API | Django 後端(教室管理模組) | 前端 EditingClassroom.jsx(handleCreate) | **POST** `/api/classrooms/` | `{ building, room_code, name, capacity, has_projector, has_whiteboard, has_network, has_mic }` | 新增後的教室資料 | 管理員新增一間教室,包含基本資訊與設備設定。 | | 更新教室設定 API | Django 後端(教室管理模組) | 前端 EditingClassroom.jsx(handleSaveClassroom) | **PATCH** `/api/classrooms/{room_code}/` | `{ capacity, has_projector, has_whiteboard, has_network, has_mic }` | 更新後的教室資料 | 管理員修改既有教室的可容納人數與設備配置。 | | 刪除教室 API | Django 後端(教室管理模組) | 前端 EditingClassroom.jsx(handleDelete) | **DELETE** `/api/classrooms/{room_code}/` | 路徑參數:`room_code` | 無(204 No Content) | 管理員刪除指定教室資料,刪除前需再次確認。 | - BlacklistPage.jsx | 介面名稱 | 介面提供者 | 介面使用者 | 連結方式 | 輸入資料 | 輸出資料 | 介面描述 | | :-----: | :------: | :------: | :-----: | :----: | :-----: | :----: | | 取得使用者清單與黑名單清單 API | Django 後端(黑名單模組,管理端) | 前端 Blacklist.jsx(fetchUsers) | **GET** `/api/blacklist/users/` | 管理員身分(JWT) | `{ normal_users: [ { id, username, email, first_name, last_name }, ... ], blacklisted_users: [ ... ] }` | 回傳所有使用者並分成正常使用者與黑名單使用者,供前端左右兩欄顯示並提供停權/恢復操作。 | | 將使用者加入黑名單 API(停權) | Django 後端(黑名單模組) | 前端 Blacklist.jsx(handleBlockUser) | **POST** `/api/blacklist/ban/` | `{ user_id, reason }`(reason 可選) | `{ detail: "...", user_id, status }` 或更新後黑名單資料 | 管理員將指定使用者加入黑名單。前端會先跳出確認視窗,並可選填停權原因。成功後重新載入清單。 | | 將使用者移出黑名單 API(恢復) | Django 後端(黑名單模組) | 前端 Blacklist.jsx(handleRestoreUser) | **POST** `/api/blacklist/unban/` | `{ user_id }` | `{ detail: "...", user_id, status }` 或更新後黑名單資料 | 管理員解除指定使用者的黑名單狀態。前端先跳出確認視窗,成功後重新載入清單。 | - ProfilePage.jsx | 介面名稱 | 介面提供者 | 介面使用者 | 連結方式 | 輸入資料 | 輸出資料 | 介面描述 | | :-----: | :------: | :------: | :----: | :-----: | :-----: | :----: | | 更新使用者名稱 API | Django 後端(Auth/個人資料模組) | 前端 ProfilePage.jsx(saveDisplayName) | **PATCH** `/api/auth/profile/`(或 `API_ENDPOINTS.change_name()`) | `{ name }`;JWT 使用者身分 | 更新後的使用者資料(或 `{ detail: "..." }`) | 使用者修改「使用者名稱」。成功後更新畫面並寫入 localStorage(name),讓其他頁面可同步顯示新名稱。 | | 修改密碼 API | Django 後端(Auth/密碼模組) | 前端 ProfilePage.jsx(savePassword) | **POST** `/api/auth/change-password/`(或 `API_ENDPOINTS.change_password()`) | `{ current_password, new_password }`;JWT 使用者身分 | `{ detail: "..." }` 或狀態訊息 | 使用者修改密碼。需驗證舊密碼且新密碼需符合規範(前端至少 6 碼)。成功後要求重新登入。 | --- ## 3. 流程設計 (Process Design) - 3.1:註冊流程 ![註冊流程.drawio](https://hackmd.io/_uploads/rJuYTuv-Zg.png) - 3.2:登入流程 ![登入流程.drawio](https://hackmd.io/_uploads/B1jxJnzXZx.png) - 3.3:忘記密碼流程 ![忘記密碼流程.drawio](https://hackmd.io/_uploads/Syx06uwWZg.png) - 3.4:查詢教室流程 ![查詢教室流程.drawio](https://hackmd.io/_uploads/Skr1AdPb-x.png) - 3.5:申請租借教室流程 ![申請租借教室流程.drawio](https://hackmd.io/_uploads/SJ-gRdvbWg.png) - 3.6:使用者取消租借教室 ![取消租借.drawio](https://hackmd.io/_uploads/rkuTQpMX-g.png) - 3.7:使用者修改使用者名稱 ![修改使用者名稱.drawio](https://hackmd.io/_uploads/HkK0mpMXbe.png) - 3.8:使用者修改密碼 ![修改密碼.drawio](https://hackmd.io/_uploads/ByU14aG7We.png) - 3.9:管理員新增教室流程 ![管理員新增教室流程.drawio](https://hackmd.io/_uploads/ryqZAODZZg.png) - 3.10:管理員刪除教室流程 ![管理員刪除教室流程.drawio](https://hackmd.io/_uploads/SySGAOwbWx.png) - 3.11:管理員編輯教室流程 ![管理員編輯教室流程.drawio](https://hackmd.io/_uploads/rJaMRdPZZg.png) - 3.12:管理員將一般使用者加入黑名單 ![加入黑名單.drawio](https://hackmd.io/_uploads/rkreETfmZx.png) - 3.13:管理員將黑名單裡的使用者移回一般使用者 ![移出黑名單.drawio](https://hackmd.io/_uploads/H12lVTfmbx.png) - 3.14:教室狀態流程 ![教室狀態.drawio](https://hackmd.io/_uploads/SywWNaGQWe.png) --- ## 4. 使用者畫面設計 (User Interface Design) - 主畫面 ![主畫面](https://hackmd.io/_uploads/S1pxijf7-x.png) - 註冊帳號 ![註冊帳號](https://hackmd.io/_uploads/S1kNojfX-l.png) - 登入畫面 ![登入](https://hackmd.io/_uploads/rJ78isfQbg.png) - 忘記密碼 ![忘記密碼](https://hackmd.io/_uploads/BJVOooMX-g.png) - 搜尋和租借教室 ![搜尋和租借教室](https://hackmd.io/_uploads/Sk50jjzX-e.png) - 預約歷史 ![預約歷史](https://hackmd.io/_uploads/rku-nifQ-l.png) - 修改個人資料 ![個人資料](https://hackmd.io/_uploads/BJ1ahozmZl.png) - 租借請求 ![租借請求](https://hackmd.io/_uploads/SyRE3szXZe.png) - 管理教室 ![管理教室](https://hackmd.io/_uploads/HJzZowv-We.png) - 黑名單管理 ![黑名單](https://hackmd.io/_uploads/SkZ16sfX-x.png) --- ## 5. 資料設計 (Data Design) ### 5.1 資料庫 Schema 及XML / JSON 結構 #### user | 欄位代碼 | 欄位名稱 | 說明 | 型態 | | ------------- | ----- | --------- | ----------------- | | `id` | 使用者編號 | id | Integer | | `username` | 帳號 | 使用者登入帳號 | String | | `email` | 電子郵件 | 使用者 Email | String | | `is_staff` | 管理員旗標 | 是否為管理員 | Boolean | | `is_active` | 啟用狀態 | 帳號是否啟用 | Boolean | | `date_joined` | 建立時間 | 帳號建立時間 | String | #### Validator ```json { "jsonSchema": { "bsonType": "object", "required": ["username", "email"], "properties": { "id": { "bsonType": "int" }, "username": { "bsonType": "string" }, "email": { "bsonType": "string" }, "is_staff": { "bsonType": "bool" }, "is_active": { "bsonType": "bool" }, "date_joined": { "bsonType": "string" } } } } ``` __________________________ #### rooms | 欄位代碼 | 欄位名稱 | 說明 | 型態 | | ------------------------ | ------ | ---------------------------------------- | ----------------- | | `id` | 教室編號 | id | Integer | | `building` | 大樓代碼 | CS / E1 / E2 / M / S | String | | `room_code` | 教室代碼 | 如 CS201、E1-204 | String | | `name` | 教室名稱 | 顯示名稱 | String | | `capacity` | 容納人數 | 可容納之座位數 | Integer | | `room_type` | 教室類型 | NORMAL / LAB / MEETING / LECTURE / OTHER | String | | `has_projector` | 投影機 | 是否有投影機 | Boolean | | `has_whiteboard` | 白板 | 是否有白板 | Boolean | | `has_mic` | 麥克風 | 是否有麥克風 | Boolean | | `has_internet` | 網路 | 是否有網路 | Boolean | | `is_active` | 是否啟用 | 是否啟用此教室 | Boolean | | `created_at` | 建立時間 | 建立記錄時自動填入 | String | | `updated_at` | 最後更新時間 | 每次修改時自動更新 | String | #### Validator ```json { "jsonSchema": { "bsonType": "object", "required": [ "building", "room_code", "capacity", "room_type" ], "properties": { "id": { "bsonType": "int" }, "building": { "bsonType": "string" }, "room_code": { "bsonType": "string" }, "name": { "bsonType": "string" }, "capacity": { "bsonType": "int" }, "room_type": { "bsonType": "string" }, "has_projector": { "bsonType": "bool" }, "has_whiteboard": { "bsonType": "bool" }, "has_mic": { "bsonType": "bool" }, "has_internet": { "bsonType": "bool" }, "is_active": { "bsonType": "bool" }, "created_at": { "bsonType": "string" }, "updated_at": { "bsonType": "string" } } } } ``` _____________________________ #### reservation | 欄位代碼 | 欄位名稱 | 說明 | 型態 | | ------------ | ------ | ----------------------------------------- | ----------------- | | `id` | 預約編號 | 主鍵,自動遞增 | Integer | | `classroom` | 教室 ID | 外鍵,對應 `Classroom.id` | Integer | | `user` | 使用者 ID | 外鍵,對應 `auth_user.id` | Integer | | `date` | 預約日期 | 預約使用日期(yyyy-mm-dd) | Date / String | | `time_slot` | 預約時段 | 例如 `"10:00-11:00"` | String | | `reason` | 預約原因 | 使用教室的目的說明(可留空) | String | | `status` | 審核狀態 | pending / approved / rejected / cancelled | String | | `created_at` | 建立時間 | 建立預約的時間 | String | #### Validator ```json { "jsonSchema": { "bsonType": "object", "required": [ "classroom", "user", "date", "time_slot" ], "properties": { "id": { "bsonType": "int" }, "classroom": { "bsonType": "int" }, "user": { "bsonType": "int" }, "date": { "bsonType": "string" }, "time_slot": { "bsonType": "string" }, "reason": { "bsonType": "string" }, "status": { "bsonType": "string" }, "created_at": { "bsonType": "string" } } } } ``` ___ ### 5.2 檔案結構 ```text ntou-classroom-reservation/ │ ├─ frontend/ │ │ │ ├─ App.jsx │ ├─ ClassroomBooking.jsx │ ├─ Login.jsx │ ├─ App.css │ ├─ ClassroomBooking.css │ └─ Login.css │ ├─ backend/ │ │ │ ├─ djangosetting/ │ │ ├─ init.py │ │ ├─ asgi.py │ │ ├─ settings.py │ │ ├─ urls.py │ │ └─ wsgi.py │ │ │ ├─ accounts/ │ │ ├─ init.py │ │ ├─ apps.py │ │ ├─ models.py │ │ ├─ serializers.py │ │ ├─ services.py │ │ ├─ views.py │ │ ├─ urls.py │ │ ├─ admin.py │ │ ├─ tests.py │ │ │ ├─ rooms/ │ │ ├─ init.py │ │ ├─ apps.py │ │ ├─ models.py │ │ ├─ serializers.py │ │ ├─ views.py │ │ ├─ urls.py │ │ ├─ admin.py │ │ └─ tests.py │ │ │ ├─ reservations/ │ │ ├─init.py │ │ ├─ apps.py │ │ ├─ models.py │ │ ├─ serializers.py │ │ ├─ views.py │ │ ├─ urls.py │ │ ├─ admin.py │ │ └─ tests.py │ │ │ └─ venv/ ``` --- ## 6. 類別圖設計 (Class Diagram) ![image](https://hackmd.io/_uploads/H1qnBy_7Wx.png) #### AuthController:負責登入、註冊與取得目前登入使用者資訊。 #### ClassroomController:提供教室查詢、單一教室資訊、列出所有教室。 #### ReservationController:負責新增、更新預約狀況並查詢預約紀錄。 --- ## 7. 實作方案 (Implementation Languages and Platforms) - 平台:網頁 - 前端技術與框架:JavaScript + React - 後端技術與框架:Python + Django ,資料庫則是使用 Aiven,驗證碼使用 Redis - 主要函式庫:React / DRF / SimpleJWT / cors-headers - 服務:Vite Dev Server、Django API、JWT Auth - 部署方式:在 GitHub Action 進行 CI / CD,並預計部署在 Zeabur --- ## 8. 設計議題 (Design Issue) ### 議題 1:不同作業系統(Windows / macOS)之間的開發環境問題 - 議題內容: 使用不同作業系統(Windows 與 macOS)進行開發時,導致開發環境設定不一致。 例如:Python venv 建立方式不同、Django 套件安裝版本差異、Node.js / npm 在 Mac 與 Windows 的安裝方式不同、MySQL / phpMyAdmin 在兩個平台上的配置不一致。 造成專案無法在不同組員之間執行、版本不一致、啟動錯誤(import module error、環境變數問題等)。 - 可能解決方案: 1. 每位組員自行建立本機環境(Local Setup),視 OS 調整設定 2. 使用虛擬環境與 requirements.txt / package.json 統一版本 - 最後解決方案與理由: 解決方案:使用虛擬環境與 requirements.txt / package.json 統一版本 理由:較簡單的方式來跨平台合作 ### 議題 2:使用 GitHub 時發生程式碼衝突(Merge Conflict)問題 - 議題內容: 使用 GitHub 協作專案時,多位組員同時修改相同檔案,導致 Merge Conflict,造成版本混亂、功能被覆蓋。 - 可能解決方案: 1. 每個組員負責不同區域/檔案,避免同時修改同一檔案 2. 發生衝突時手動比對與解決(使用 VS Code / GitHub Desktop) - 最後解決方案與理由: 解決方案:發生衝突時手動比對與解決(使用 VS Code / GitHub Desktop) 理由:每周開會時解決衝突就好了 ### 議題 3:部署專案至 Zeabur 時發生環境與設定錯誤問題 - 議題內容: 在將專案部署到 Zeabur 時,因為本地端與雲端環境設定不同(如環境變數未設定、API 位址錯誤、Build 失敗等),導致專案無法正常啟動或功能異常,影響系統對外服務。 - 可能解決方案: 1. 部署前先統一並確認本地與雲端的環境設定(如 .env、API Base URL、Port 設定) 2. 發生錯誤時,透過 Zeabur 提供的 Log 與錯誤訊息進行除錯,逐一修正設定問題 - 最後解決方案與理由: 解決方案:發生問題時,透過 Zeabur 的部署紀錄與系統 Log 手動檢查並修正環境變數與設定錯誤 理由:部署問題多半與環境設定相關,透過實際查看 Log 能快速定位錯誤原因,並在每次部署時即時修正,確保系統能順利上線 ---