--- title: 軟體設計文件(SDD) --- # 軟體設計文件(SDD) ## 專案資訊 - **專案名稱**:早一點 - **撰寫日期**:2025/11/24 - **發展者**: 01157128 蔡翊宣 01257116 康慈軒 01257126 呂亭儀 01257153 鄭尹宣 01257161 李永聖 --- ## 版次變更記錄 | 版次 | 變更項目 | 變更日期 | |------|----------|----------| | 0.1 |初版 |2025/11/24| | 0.2 |使用者畫面設計 |2025/11/25| | 0.3 |新增C4 Model圖|2025/12/23| | 1.0 |最終版 |2026/01/03| --- ## 目錄 1. [系統模型與架構 (System Model / System Architecture)](#section1) 2. [介面需求與設計 (Interface Requirement and Design)](#section2) 3. [流程設計 (Process Design)](#section3) 4. [使用者畫面設計 (User Interface Design)](#section4) 5. [資料設計 (Data Design)](#section5) 6. [類別圖設計 (Class Diagram)](#section6) 7. [實作方案 (Implementation Languages and Platforms)](#section7) 8. [設計議題 (Design Issue)](#section8) --- ## <span id="section1">1. 系統模型與架構 (System Model / System Architecture)</span> ![image](https://hackmd.io/_uploads/B1BcPzfW-e.png) ![image](https://hackmd.io/_uploads/Hy4HKMf-be.png) ![image](https://hackmd.io/_uploads/H1v_KNumbl.png) --- ## <span id="section2">2. 介面需求與設計 (Interface Requirement and Design)</span> - Store | 介面名稱 | 介面提供者 | 介面使用者 | 連結方式 | 輸入資料 | 輸出資料 | 介面描述 | |---------|------------|------------|----------|----------|----------|-----------| | 創建新店家 | Backend - StoreController | Frontend - StoreSettingView | HTTP POST /api/stores | Header (Authorization), JSON (Store 物件) | JSON (Store 物件) | 驗證使用者 Token,將新的店家資訊寫入資料庫,並將當前使用者設為擁有者 Owner。 | | 獲取所有店家 | Backend - StoreController | Frontend - StoreListView | HTTP GET /api/stores | 無 | JSON (List<Store>) | 從資料庫中檢索並回傳所有已註冊且狀態為 Active 的店家列表。 | | 獲取店家詳情 | Backend - StoreController | Frontend - StoreDetailView | HTTP GET /api/stores/{storeId} | Path (storeId) | JSON (Store 物件) | 根據提供的 Store ID 查詢並回傳該店家的詳細資訊(包含菜單)。 | | 更新店家菜單 | Backend - StoreController | Frontend - StoreManagementView | HTTP PUT /api/stores/{id}/menu | Path (id), Header (Authorization), JSON (List<MenuItem>) | JSON (Store 物件) 或 錯誤訊息 | 驗證店家擁有者權限,更新指定店家的菜單項目(若項目無 ID 則自動產生 ObjectId)。 | | 搜尋店內商品 | Backend - StoreController | Frontend - StoreDetailView | HTTP GET /api/stores/{storeId}/search-product | Path (storeId), Query Param (keyword) | JSON (List<MenuItem>) | 在特定店家根據關鍵字搜尋菜單項目,回傳名稱包含關鍵字的商品列表。 | --- - User | 介面名稱 | 介面提供者 | 介面使用者 | 連結方式 | 輸入資料 | 輸出資料 | 介面描述| | ------- | -------- | --------- | ---- | ----- | ---- | ----- | | 使用者註冊 | Backend - UserController | Frontend - RegisterView | HTTP POST | JSON (account, password, role, email) | JSON (User 物件) | 接收使用者資訊,檢查帳號是否重複,若無重複就寫入資料庫 | | 使用者登入 | Backend - UserController | Frontend - LoginView | HTTP POST | JSON (account, password) | JSON (User 物件) | 接收使用者帳號密碼,若與資料庫資料相同就登入 | | 回傳使用者資訊 | Backend - UserController | Frontend - HomeView | HTTP GET | URL Path Variable: userId | JSON (User 物件) | 根據網址中的 userId 回傳該使用者的資訊 | | 修改使用者資訊 | Backend - UserController | Frontend - HomeView | HTTP PATCH | URL Path Variable: userId + JSON(password, nickname, photo, phone, email) | JSON (User 物件) | 根據網址中的 userId 和 傳入的 JSON 資料來修改該使用者資訊 | | 刪除使用者 | Backend - UserController | Frontend - HomeView | HTTP DELETE | URL Path Variable: userId | String(使用者 ID: userId 已成功刪除) | 根據網址中的 userId 來刪除該使用者 | | 將店家加入收藏 | Backend - UserController | Frontend - ShopView or FavoriteView | HTTP POST | URL Path Variable: userId, storeId | JSON (User 物件) | 根據網址中的 userId 和 storeId ,將店家加入該使用者的收藏 | | 將店家移除收藏 | Backend - UserController | Frontend - ShopView or FavoriteView | HTTP DELETE | URL Path Variable: userId, storeId | JSON (User 物件) | 根據網址中的 userId 和 storeId ,將店家移除該使用者的收藏 | | 回傳收藏店家列表 | Backend - UserController | Frontend - FavoriteView | HTTP GET | URL Path Variable: userId | JSON (List<String\>) | 根據網址中的 userId ,回傳該使用者的收藏店家之 storeId | | 將商品加入收藏 | Backend - UserController | Frontend - ShopView or FavoriteView | HTTP POST | URL Path Variable: userId, storeId, itemId | JSON (User 物件) | 根據網址中的 userId 、 storeId 和 itemId ,將商品加入該使用者的收藏 | | 將商品移除收藏 | Backend - UserController | Frontend - ShopView or FavoriteView | HTTP DELETE | URL Path Variable: userId, storeId, itemId | JSON (User 物件) | 根據網址中的 userId 、 storeId 和 itemId ,將商品移除該使用者的收藏 | | 回傳收藏商品列表 | Backend - UserController | Frontend - ShopView or FavoriteView | HTTP GET | URL Path Variable: userId, storeId, itemId | JSON (List<FavorItem 物件>) | 根據網址中的 userId ,回傳該使用者的收藏商品(FavorItem 物件) | | 新增自訂義組合 | Backend - UserController | Frontend - ShopView or FavoriteView | HTTP POST | URL Path Variable: userId + JSON(comboName) | JSON (User 物件) | 根據網址中的 userId ,為該使用者的新增名稱為 comboName 的自訂義組合(內容為空) | | 修改自訂義組合名稱 | Backend - UserController | Frontend - ShopView or FavoriteView | HTTP PATCH | URL Path Variable: userId, comboId + JSON(comboName) | JSON (User 物件) | 根據網址中的 userId ,為該使用者修改 ID 為 comboId 的自訂義組合名稱,修改為 comboName | | 刪除自訂義組合 | Backend - UserController | Frontend - ShopView or FavoriteView | HTTP DELETE | URL Path Variable: userId, comboId | JSON (User 物件) | 根據網址中的 userId ,為該使用者刪除 ID 為 comboId 的自訂義組合 | | 將商品加入自訂義組合 | Backend - UserController | Frontend - ShopView or FavoriteView | HTTP POST | URL Path Variable: userId, comboId, storeId, itemId | JSON (User 物件) | 根據網址中的 userId ,為該使用者將商品加入 ID 為 comboId 的自訂義組合(同一個自訂義組合內的商品不能跨店家) | | 將商品移除自訂義組合 | Backend - UserController | Frontend - ShopView or FavoriteView | HTTP DELETE | URL Path Variable: userId, comboId, storeId, itemId | JSON (User 物件) | 根據網址中的 userId ,為該使用者將商品移除 ID 為 comboId 的自訂義組合 | | 回傳自訂義組合列表 | Backend - UserController | Frontend - ShopView or FavoriteView | HTTP GET | URL Path Variable: userId | JSON (List<CustomCombo 物件>) | 根據網址中的 userId ,回傳該使用者的自訂義組合列表 | | 回傳特定自訂義組合內容 | Backend - UserController | Frontend - ShopView or FavoriteView | HTTP GET | URL Path Variable: userId, comboId | JSON (CustomCombo 物件) | 根據網址中的 userId 和 comboId ,回傳該使用者的 ID 為 comboId 的自訂義組合內容 | --- - Cart | 介面名稱 | 介面提供者 | 介面使用者 | 連結方式 | 輸入資料 | 輸出資料 | 介面描述| | ------- | -------- | --------- | ---- | ----- | ---- | ----- | | 取得購物車內容 | Backend - CartController | Frontend - CartView, ShopView | HTTP GET | URL Path Variable: userId | JSON (Cart 物件) | 根據 User ID 取得該使用者的購物車內容,包含店家 ID、商品列表與總金額。若無購物車則回傳空車 | | 加入商品至購物車 | Backend - CartController | Frontend - CartView, ShopView | HTTP POST | URL Path Variable: userId + JSON (CartItem 物件) + Request Param: storeId | JSON (Cart 物件) | 將商品加入購物車。若車內已有不同店家的商品,則回傳錯誤(禁止跨店點餐),若商品已存在則增加數量 | | 移除購物車商品 | Backend - CartController | Frontend - CartView, ShopView | HTTP DELETE | URL Path Variable: userId, itemId | JSON (Cart 物件) | 根據 Item ID 將特定商品從購物車中移除,並重新計算總金額。若購物車清空,則重置店家鎖定 | | 清空購物車 | Backend - CartController | Frontend - CartView, ShopView | HTTP DELETE | URL Path Variable: userId | String(使用者 ID 為:userId的購物車已清空) | 移除購物車內所有商品,並重置 storeId 為 null | --- - Order | 介面名稱 | 介面提供者 | 介面使用者 | 連結方式 | 輸入資料 | 輸出資料 | 介面描述| | ------- | -------- | --------- | ---- | ----- | ---- | ----- | | 建立新訂單 | Backend - OrderController | Frontend - CheckoutView | HTTP POST | JSON (storeId, customerId, items, totalAmount, orderType, dineInDetail/takeoutDetail, paymentMethod) | JSON (Order 物件) | 接收前端傳來的訂單詳細資訊,將資料寫入 MongoDB 資料庫,並回傳包含自動生成 `_id` 與 `createAt` 的完整訂單物件。 | | 查詢顧客歷史訂單 | Backend - OrderController | Frontend - OrderHistoryView | HTTP GET | URL Path Variable: customerId | JSON (List<Order>) | 根據網址中的 `customerId`,回傳該名顧客過去所有的訂單列表(通常按時間倒序排列)。 | | 查詢單筆訂單詳情 | Backend - OrderController | Frontend - OrderDetailView | HTTP GET | URL Path Variable: orderId | JSON (Order 物件) | 根據網址中的 `orderId`,回傳該筆訂單的完整資訊(包含訂單項目、金額、狀態等)。 | | 查詢店家所有訂單 | Backend - OrderController | Frontend - MerchantOrderView | HTTP GET | URL Path Variable: storeId | JSON (List<Order>) | 根據網址中的 `storeId`,回傳該店家收到的所有訂單列表。 | | 更新訂單狀態 | Backend - OrderController | Frontend - MerchantOrderView | HTTP PATCH | URL Path Variable: orderId + JSON (state) | JSON (Order 物件) | 商家操作接單或完成餐點時呼叫。接收新的狀態字串(如 '已接單', '已完成'),更新資料庫並回傳更新後的訂單。 | | 取消訂單 | Backend - OrderController | Frontend - OrderHistoryView | HTTP PATCH | URL Path Variable: orderId | JSON (Order 物件) | 顧客或商家取消訂單。後端需驗證訂單狀態是否允許取消(例如:只有 '已送出' 狀態可取消),將狀態更新為 '已取消'。 | --- ## <span id="section3">3. 流程設計 (Process Design)</span> <!-- <span style="color:red">可用 UML 之 Activity diagram / State Transition diagram 或一般的程式流程圖描述所開發的系統流程,目的是運用流程圖或狀態圖設計整個系統的完整運作。 此部分應是操作概念的細部設計。</span> (於此處插入主要流程之 Activity diagram、State diagram 或流程圖) --> ### 3.1 Store - 首頁顯示流程 ![截圖 2025-11-25 晚上11.45.24](https://hackmd.io/_uploads/rkHQy8Q-bx.png) - 查看商家流程 ![截圖 2025-11-26 凌晨12.40.21](https://hackmd.io/_uploads/rJBb3LmZbl.png) - 商家建立與設定流程 ![截圖 2025-11-26 凌晨12.10.25](https://hackmd.io/_uploads/H1y-SLX-be.png) - 商家的菜單管理流程 ![截圖 2025-11-26 凌晨12.27.02](https://hackmd.io/_uploads/rJvJKL7b-e.png) ### 3.2 User - 註冊 ![image](https://hackmd.io/_uploads/ByKB1D7bWx.png) - 登入 ![image](https://hackmd.io/_uploads/Bkt1bPQbbg.png) - 加入收藏 ![image](https://hackmd.io/_uploads/S1TR1PXbWe.png) ### 3.3 Cart - 查詢購物車 ![image](https://hackmd.io/_uploads/H1FhSD7-Ze.png) - 加入購物車 ![image](https://hackmd.io/_uploads/By06Sv7Wbx.png) - 移除購物車 ![image](https://hackmd.io/_uploads/B1b18v7Zbg.png) ### 3.4 Order - 建立訂單流程圖 ![image](https://hackmd.io/_uploads/B1lvvImbbe.png) --- ## <span id="section4">4. 使用者畫面設計 (User Interface Design)</span> 1.登入頁面 ![image](https://hackmd.io/_uploads/B19ygHQbZg.png) 2.註冊頁面 ![image](https://hackmd.io/_uploads/B1TgeBmb-e.png) 3.顧客主頁面 ![image](https://hackmd.io/_uploads/H1_Xgr7WWx.png) 4.使用者資訊 ![image](https://hackmd.io/_uploads/S1grlS7W-e.png) 5.商家頁面 ![image](https://hackmd.io/_uploads/rJVIxB7Z-e.png) 6.商品頁面 ![image](https://hackmd.io/_uploads/r1LDgH7b-x.png) 7.購物車頁面 ![image](https://hackmd.io/_uploads/BJbtxHQbbl.png) 8.訂單管理頁面 ![image](https://hackmd.io/_uploads/Bk8MS7fZbx.png) 9.收藏頁面 ![image](https://hackmd.io/_uploads/ryNieBQZbg.png) 10.商家設定頁面 ![image](https://hackmd.io/_uploads/Skp16zMbZe.png) --- ## <span id="section5">5. 資料設計 (Data Design)</span> ### 5.1 資料庫 Schema <table style="width: 100%; border-collapse: collapse;"> <tr style="background-color: #4472C4; color: white; text-align: center; font-weight: bold;"> <td colspan="2" style="border: 1px solid black; padding: 8px;">名稱</td> <td colspan="2" style="border: 1px solid black; padding: 8px;">描述</td> </tr> <tr style="text-align: center;"> <td colspan="2" style="border: 1px solid black; padding: 8px;">store</td> <td colspan="2" style="border: 1px solid black; padding: 8px;">店家的設定(包括菜單、資訊等)</td> </tr> <tr style="background-color: #4472C4; color: white; text-align: center; font-weight: bold;"> <td style="border: 1px solid black; padding: 8px; width: 20%;">屬性描述</td> <td style="border: 1px solid black; padding: 8px; width: 20%;">屬性名稱</td> <td style="border: 1px solid black; padding: 8px; width: 20%;">屬性型態</td> <td style="border: 1px solid black; padding: 8px; width: 40%;">屬性設定</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">店家 ID</td> <td style="border: 1px solid black; padding: 8px;">_id</td> <td style="border: 1px solid black; padding: 8px;">ObjectId</td> <td style="border: 1px solid black; padding: 8px;">required: true, PK<br>unique: true</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">店家名稱</td> <td style="border: 1px solid black; padding: 8px;">name</td> <td style="border: 1px solid black; padding: 8px;">String</td> <td style="border: 1px solid black; padding: 8px;">required: true</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">店家分類</td> <td style="border: 1px solid black; padding: 8px;">category</td> <td style="border: 1px solid black; padding: 8px;">String</td> <td style="border: 1px solid black; padding: 8px;">required: true</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">店家簡介</td> <td style="border: 1px solid black; padding: 8px;">description</td> <td style="border: 1px solid black; padding: 8px;">String</td> <td style="border: 1px solid black; padding: 8px;">default: ""</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">擁有者 ID</td> <td style="border: 1px solid black; padding: 8px;">ownerId</td> <td style="border: 1px solid black; padding: 8px;">ObjectId</td> <td style="border: 1px solid black; padding: 8px;">required: true</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">聯絡電話</td> <td style="border: 1px solid black; padding: 8px;">phone</td> <td style="border: 1px solid black; padding: 8px;">String</td> <td style="border: 1px solid black; padding: 8px;">default: ""</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">聯絡信箱</td> <td style="border: 1px solid black; padding: 8px;">email</td> <td style="border: 1px solid black; padding: 8px;">String</td> <td style="border: 1px solid black; padding: 8px;">default: ""</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">店家地址</td> <td style="border: 1px solid black; padding: 8px;">address</td> <td style="border: 1px solid black; padding: 8px;">String</td> <td style="border: 1px solid black; padding: 8px;">required: true</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">經緯度座標</td> <td style="border: 1px solid black; padding: 8px;">coordinates</td> <td style="border: 1px solid black; padding: 8px;">Array<Number></td> <td style="border: 1px solid black; padding: 8px;">required: true(格式:[經度, 緯度])</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">營業時間</td> <td style="border: 1px solid black; padding: 8px;">businessHours</td> <td style="border: 1px solid black; padding: 8px;">{ day: String, start: String, close: String, note: String }[] </td> <td style="border: 1px solid black; padding: 8px;">default: []</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">菜單</td> <td style="border: 1px solid black; padding: 8px;">menu</td> <td style="border: 1px solid black; padding: 8px;">{ _id: ObjectId, itemName: String, category: String, price: Number, description: String, imgUrl: String, isAvailable: Boolean, tag: String }[] </td> <td style="border: 1px solid black; padding: 8px;">default: []</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">店家狀態</td> <td style="border: 1px solid black; padding: 8px;">isActive</td> <td style="border: 1px solid black; padding: 8px;">Boolean</td> <td style="border: 1px solid black; padding: 8px;">default: true</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">更新時間</td> <td style="border: 1px solid black; padding: 8px;">updateAt</td> <td style="border: 1px solid black; padding: 8px;">Date</td> <td style="border: 1px solid black; padding: 8px;">default: 現在時間</td> </tr> </table> --- <table style="width: 100%; border-collapse: collapse;"> <tr style="background-color: #4472C4; color: white; text-align: center; font-weight: bold;"> <td colspan="2" style="border: 1px solid black; padding: 8px;">名稱</td> <td colspan="2" style="border: 1px solid black; padding: 8px;">描述</td> </tr> <tr style="text-align: center;"> <td colspan="2" style="border: 1px solid black; padding: 8px;">user</td> <td colspan="2" style="border: 1px solid black; padding: 8px;">使用者的設定(包括帳密、偏好等)</td> </tr> <tr style="background-color: #4472C4; color: white; text-align: center; font-weight: bold;"> <td style="border: 1px solid black; padding: 8px; width: 20%;">屬性描述</td> <td style="border: 1px solid black; padding: 8px; width: 20%;">屬性名稱</td> <td style="border: 1px solid black; padding: 8px; width: 20%;">屬性型態</td> <td style="border: 1px solid black; padding: 8px; width: 40%;">屬性設定</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">使用者 ID</td> <td style="border: 1px solid black; padding: 8px;">_id</td> <td style="border: 1px solid black; padding: 8px;">ObjectId</td> <td style="border: 1px solid black; padding: 8px;">required: true, PK<br>unique: true</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">使用者帳號</td> <td style="border: 1px solid black; padding: 8px;">account</td> <td style="border: 1px solid black; padding: 8px;">String</td> <td style="border: 1px solid black; padding: 8px;">required: true, <br>unique: true</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">使用者密碼</td> <td style="border: 1px solid black; padding: 8px;">password</td> <td style="border: 1px solid black; padding: 8px;">String</td> <td style="border: 1px solid black; padding: 8px;">required: true</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">角色</td> <td style="border: 1px solid black; padding: 8px;">role</td> <td style="border: 1px solid black; padding: 8px;">String</td> <td style="border: 1px solid black; padding: 8px;">required: true, <br>enum: [owner, buyer, admin]</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">大頭貼</td> <td style="border: 1px solid black; padding: 8px;">photo</td> <td style="border: 1px solid black; padding: 8px;">String</td> <td style="border: 1px solid black; padding: 8px;">default: ""</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">電話</td> <td style="border: 1px solid black; padding: 8px;">phone</td> <td style="border: 1px solid black; padding: 8px;">String</td> <td style="border: 1px solid black; padding: 8px;">default: ""</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">電子郵件</td> <td style="border: 1px solid black; padding: 8px;">email</td> <td style="border: 1px solid black; padding: 8px;">String</td> <td style="border: 1px solid black; padding: 8px;">required: true, <br>unique: true</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">收藏店家</td> <td style="border: 1px solid black; padding: 8px;">favorStores</td> <td style="border: 1px solid black; padding: 8px;">Array<ObjectId></td> <td style="border: 1px solid black; padding: 8px;">default: [], <br>ref: 'store'</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">收藏商品</td> <td style="border: 1px solid black; padding: 8px;">favorItems</td> <td style="border: 1px solid black; padding: 8px;">{ storeId: ObjectId, itemId: ObjectId }[]</td> <td style="border: 1px solid black; padding: 8px;">default: [], <br>ref: 'store'</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">自訂義組合</td> <td style="border: 1px solid black; padding: 8px;">customCombos</td> <td style="border: 1px solid black; padding: 8px;">{ comboId: ObjectId, comboName: String, storeId: ObjectId, { storeId: ObjectId, itemId: ObjectId }[] }[]</td> <td style="border: 1px solid black; padding: 8px;">default: [], <br>ref: 'store'</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">更新時間</td> <td style="border: 1px solid black; padding: 8px;">updateAt</td> <td style="border: 1px solid black; padding: 8px;">Date</td> <td style="border: 1px solid black; padding: 8px;">default: 現在時間</td> </tr> </table> --- <table style="width: 100%; border-collapse: collapse;"> <tr style="background-color: #4472C4; color: white; text-align: center; font-weight: bold;"> <td colspan="2" style="border: 1px solid black; padding: 8px;">名稱</td> <td colspan="2" style="border: 1px solid black; padding: 8px;">描述</td> </tr> <tr style="text-align: center;"> <td colspan="2" style="border: 1px solid black; padding: 8px;">cart</td> <td colspan="2" style="border: 1px solid black; padding: 8px;">顧客在某店的購物車資訊(包含商品、價錢等)</td> </tr> <tr style="background-color: #4472C4; color: white; text-align: center; font-weight: bold;"> <td style="border: 1px solid black; padding: 8px; width: 20%;">屬性描述</td> <td style="border: 1px solid black; padding: 8px; width: 20%;">屬性名稱</td> <td style="border: 1px solid black; padding: 8px; width: 20%;">屬性型態</td> <td style="border: 1px solid black; padding: 8px; width: 40%;">屬性設定</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">購物車 ID</td> <td style="border: 1px solid black; padding: 8px;">_id</td> <td style="border: 1px solid black; padding: 8px;">ObjectId</td> <td style="border: 1px solid black; padding: 8px;">required: true, PK, <br>unique: true</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">使用者 ID</td> <td style="border: 1px solid black; padding: 8px;">userId</td> <td style="border: 1px solid black; padding: 8px;">ObjectId</td> <td style="border: 1px solid black; padding: 8px;">required: true, <br>Indexed</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">店家 ID</td> <td style="border: 1px solid black; padding: 8px;">storeId</td> <td style="border: 1px solid black; padding: 8px;">ObjectId</td> <td style="border: 1px solid black; padding: 8px;">required: true</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">購物車內容</td> <td style="border: 1px solid black; padding: 8px;">items</td> <td style="border: 1px solid black; padding: 8px;">{ itemId: ObjectId, itemName: String, price: Number, quantity: Number, subtotal: Number, description: String }[]</td> <td style="border: 1px solid black; padding: 8px;">default: []</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">總金額</td> <td style="border: 1px solid black; padding: 8px;">totalPrice</td> <td style="border: 1px solid black; padding: 8px;">Number</td> <td style="border: 1px solid black; padding: 8px;">default: 0</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">更新時間</td> <td style="border: 1px solid black; padding: 8px;">updateAt</td> <td style="border: 1px solid black; padding: 8px;">Date</td> <td style="border: 1px solid black; padding: 8px;">default: 現在時間</td> </tr> </table> --- <table style="width: 100%; border-collapse: collapse;"> <tr style="background-color: #4472C4; color: white; text-align: center; font-weight: bold;"> <td colspan="2" style="border: 1px solid black; padding: 8px;">名稱</td> <td colspan="2" style="border: 1px solid black; padding: 8px;">描述</td> </tr> <tr style="text-align: center;"> <td colspan="2" style="border: 1px solid black; padding: 8px;">order</td> <td colspan="2" style="border: 1px solid black; padding: 8px;">顧客提交的訂單(訂單詳情、訂單狀態等)</td> </tr> <tr style="background-color: #4472C4; color: white; text-align: center; font-weight: bold;"> <td style="border: 1px solid black; padding: 8px; width: 20%;">屬性描述</td> <td style="border: 1px solid black; padding: 8px; width: 20%;">屬性名稱</td> <td style="border: 1px solid black; padding: 8px; width: 20%;">屬性型態</td> <td style="border: 1px solid black; padding: 8px; width: 40%;">屬性設定</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">訂單 ID</td> <td style="border: 1px solid black; padding: 8px;">_id</td> <td style="border: 1px solid black; padding: 8px;">ObjectId</td> <td style="border: 1px solid black; padding: 8px;">required: true, PK <br>unique: true </td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">店家 ID</td> <td style="border: 1px solid black; padding: 8px;">storeId</td> <td style="border: 1px solid black; padding: 8px;">ObjectId</td> <td style="border: 1px solid black; padding: 8px;">required: true, <br>ref: 'store'</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">顧客 ID</td> <td style="border: 1px solid black; padding: 8px;">customerId</td> <td style="border: 1px solid black; padding: 8px;">ObjectId</td> <td style="border: 1px solid black; padding: 8px;">ref: 'user'</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">顧客電話號碼</td> <td style="border: 1px solid black; padding: 8px;">customerPhone</td> <td style="border: 1px solid black; padding: 8px;">String</td> <td style="border: 1px solid black; padding: 8px;">(未登入顧客必填)</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">訂單類型</td> <td style="border: 1px solid black; padding: 8px;">orderType</td> <td style="border: 1px solid black; padding: 8px;">String</td> <td style="border: 1px solid black; padding: 8px;">required: true, <br>enum: ['內用', '外帶']</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">內用詳情</td> <td style="border: 1px solid black; padding: 8px;">dineInDetail</td> <td style="border: 1px solid black; padding: 8px;">{ tableNumber: String }</td> <td style="border: 1px solid black; padding: 8px;">(當 orderType 為'內用'時需要)</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">外帶詳情</td> <td style="border: 1px solid black; padding: 8px;">takeoutDetail</td> <td style="border: 1px solid black; padding: 8px;">{ takeoutTime: Date }</td> <td style="border: 1px solid black; padding: 8px;">(當 orderType 為'外帶'時需要)</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">訂單項目</td> <td style="border: 1px solid black; padding: 8px;">items</td> <td style="border: 1px solid black; padding: 8px;">{ menuItemId: ObjectId, itemName: String, quantity: Number, unitPrice: Number, customization: [String], itemSubTotal: Number }[]</td> <td style="border: 1px solid black; padding: 8px;">default = []</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">訂單總金額</td> <td style="border: 1px solid black; padding: 8px;">totalAmount</td> <td style="border: 1px solid black; padding: 8px;">Number</td> <td style="border: 1px solid black; padding: 8px;">requied: true</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">顧客備註</td> <td style="border: 1px solid black; padding: 8px;">remarks</td> <td style="border: 1px solid black; padding: 8px;">String</td> <td style="border: 1px solid black; padding: 8px;">default: ''</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">支付方式</td> <td style="border: 1px solid black; padding: 8px;">paymentMethod</td> <td style="border: 1px solid black; padding: 8px;">String</td> <td style="border: 1px solid black; padding: 8px;">requied: true, <br>enum: ['店內付款']</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">訂單狀態</td> <td style="border: 1px solid black; padding: 8px;">state</td> <td style="border: 1px solid black; padding: 8px;">String</td> <td style="border: 1px solid black; padding: 8px;">required: true, <br>default: '已送出訂單', <br>enum: ['已送出訂單', '已接單', '準備中', '已完成', '顧客已取餐']</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">訂單成立時間</td> <td style="border: 1px solid black; padding: 8px;">createAt</td> <td style="border: 1px solid black; padding: 8px;">Date</td> <td style="border: 1px solid black; padding: 8px;">default: 現在時間</td> </tr> <tr> <td style="border: 1px solid black; padding: 8px;">訂單更新時間</td> <td style="border: 1px solid black; padding: 8px;">updateAt</td> <td style="border: 1px solid black; padding: 8px;">Date</td> <td style="border: 1px solid black; padding: 8px;">default: 現在時間</td> </tr> </table> --- ### 5.2 檔案結構 ``` backend └── src └── main ├── java │ └── Team5 │ └── example │ └── breakfast_ordering │ │ │ ├── config │ │ └── WebSocketConfig.java // 建立後端與前端之間的通訊通道,推播通知使用 │ │ │ ├── controller // 負責接收前端 API 請求 │ │ ├── UserController.java // 處理使用者註冊、登入、收藏、自定義組合 │ │ ├── StoreController.java // 處理店家瀏覽、菜單、搜尋商品 │ │ ├── CartController.java // 處理購物車邏輯 │ │ ├── OrderController.java // 處理訂單邏輯 │ │ └── WebConfig.java // 處理 CORS 跨域設定 │ │ │ ├── model // 定義資料庫結構 │ │ ├── User.java // 使用者資料 │ │ ├── Store.java // 店家與菜單資料 │ │ ├── Cart.java // 購物車資料 │ │ └── Order.java // 訂單資料 │ │ │ ├── repository // 負責與 MongoDB 溝通 │ │ ├── UserRepository.java // User 資料庫操作介面 │ │ ├── StoreRepository.java // Store 資料庫操作介面 │ │ ├── CartRepository.java // Cart 資料庫操作介面 │ │ └── OrderRepository.java // Order 資料庫操作介面 │ │ │ ├── security │ │ ├── CustomOAuth2UserService.java // 負責處理 Google 登入後的資料同步邏輯 │ │ └── OrderRepository.java // 整個後端的安全防護網,規定誰可以進入哪裡 │ │ │ └── BackendApplication.java // Spring Boot 程式入口 │ └── resources └── application.properties // MongoDB 連線字串、Port 設定 frontend ├── src │ ├── assets // 需編譯的靜態資源 │ │ └── logo.png │ │ │ ├── components // 可重複使用的 UI 元件 (非完整頁面) │ │ └── MenuItem.vue // 例如:顯示單一菜單項目的卡片 │ │ │ ├── data // 存放測試用的 JSON 資料 │ │ ├── menu.json │ │ └── StoreData.json │ │ │ ├── models // 前端資料結構定義 (Class/Interface) │ │ └── store.model.js // 定義店家資料的格式 │ │ │ ├── router // 設定網址 (URL) 與頁面 (View) 的對應關係 │ │ └── index.js // Vue Router 設定檔 │ │ │ ├── store // 全域資料狀態 (Vuex),負責與後端 API 溝通 │ │ ├── index.js // Vuex 主設定檔 │ │ ├── cart.js // 購物車狀態 (對應後端 CartController) │ │ ├── shops.js // 店家列表狀態 (對應後端 StoreController) │ │ └── user.js // 使用者登入狀態 (對應後端 UserController) │ │ │ ├── viewmodels // 處理視圖邏輯 (MVVM 模式),分離 UI 與資料處理 │ │ └── storeSettingViewModel.js │ │ │ ├── views // 完整的網頁頁面 │ │ ├── CartView.vue // 購物車頁面 │ │ ├── FavoriteView.vue // 收藏頁面 │ │ ├── HomeView.vue // 顧客首頁 │ │ ├── LoginView.vue // 登入頁面 │ │ ├── RegisterView.vue // 註冊頁面 │ │ ├── ShopView.vue // 店家頁面 │ │ └── StoreSetting.vue // 店家設定頁面 │ │ │ ├── App.vue // 根組件 (Root Component) │ └── main.js // 程式啟動點,掛載 Vue 實例 │ ├── .browserslistrc // 瀏覽器相容性設定 ├── .eslintrc.js // 程式碼風格檢查設定 ├── babel.config.js // ES6+ 語法轉譯設定 └── package.json // 專案依賴套件管理 ``` <!-- ### 5.3 XML / JSON 結構 (於此處描述重要的 XML / JSON 結構範例) --> --- ## <span id="section6">6. 類別圖設計 (Class Diagram)</span> <!-- <span style="color:red">發展 UML 之 class diagram(可對應 C4 model 中的 Code diagram),明確設計系統中包含哪些類別,以及這些類別之間的關係。 若遇到非一般物件類別或特殊物件類別,如 HTML、JavaScript、JSP、Servlet 等,可用 stereotype 表達,如 <<JavaScript>>、<<Servlet>>。 可把重點放在定義程式結構:訂出所有類別名稱、拉出所有類別關係,再加入重要的 attribute/operation 即可。而特殊的類別職責設計可加上文字說明。</span> (於此處插入 UML 類別圖,並可補充文字說明) --> ```mermaid classDiagram %% ========================== %% 1. 資料模型 (Models) %% ========================== class User { -String id -String account -String password -String email -String nickname -String photo -String phone -List~String~ favorStores -List~FavorItem~ favorItems -List~CustomCombo~ customCombos } class FavorItem { -String storeId -String itemId } class CustomCombo { -String comboId -String comboName -List~FavorItem~ items } class Cart { -String userId -String storeId -List~CartItem~ items -Integer totalAmount } class CartItem { -String itemId -String itemName -Integer quantity -Integer Price -Integer subtotal -String description } class Store { -String id -String ownerId -String name -String address -String description -Boolean isActive -List~MenuItem~ menu } class MenuItem { -String id -String itemName -Integer price -Boolean isAvailable } class Order { -String id -String storeId -String customerId -String orderType -Integer totalAmount -String state -Date createAt -List~OrderItem~ items -DineInDetail dineInDetail -TakeoutDetail takeoutDetail -String paymentMethod } class OrderItem { -String menuItemId -String itemName -Integer quantity -Integer unitPrice -List~String~ customization } %% ========================== %% 2. 控制層 (Controllers) %% ========================== class UserController { +register(userJson) +login(account, password) +getUser(userId) +updateUser(userId, updateJson) +deleteUser(userId) +addFavoriteStore(userId, storeId) +removeFavoriteStore(userId, storeId) +getFavoriteStores(userId) +addFavoriteItem(userId, storeId, itemId) +removeFavoriteItem(userId, storeId, itemId) +getFavoriteItems(userId, storeId, itemId) +addCustomCombo(userId, comboJson) +updateCustomComboName(userId, comboId, comboJson) +deleteCustomCombo(userId, comboId) +addItemToCustomCombo(userId, comboId, storeId, itemId) +removeItemFromCustomCombo(userId, comboId, storeId, itemId) +getCustomCombos(userId) +getCustomCombo(userId, comboId) } class CartController { +getCart(userId) +addItemToCart(userId, cartItem) +removeItemFromCart(userId, itemId) +clearCart(userId) } class StoreController { +createStore(storeJson) +getAllStores() +getStoreDetail(storeId) +updateStoreMenu(id, menuItems) +searchStoreProducts(storeId, keyword) } class OrderController { +createOrder(orderJson) +getCustomerHistory(customerId) +getOrderDetail(orderId) +getStoreOrders(storeId) +updateOrderState(orderId, state) +cancelOrder(orderId) } %% ========================== %% 3. 服務層 (Services) %% ========================== class StoreService { +createNewStore(store) +findAllActiveStores() +findStoreById(storeId) +updateMenu(storeId, menu) +searchProducts(storeId, keyword) } class OrderService { +createOrder(order) +getOrdersByCustomer(customerId) +getOrdersByStore(storeId) +getOrderById(orderId) +updateStatus(orderId, status) } %% ========================== %% 4. 儲存庫 (Repositories) %% ========================== class UserRepository { <<interface>> +findByAccount(account) +existsByAccount(account) } class CartRepository { <<interface>> +findByUserId(userId) } class StoreRepository { <<interface>> +findByIsActiveTrue() +findByNameContaining(keyword) } class OrderRepository { <<interface>> +findByCustomerId(customerId) +findByStoreId(storeId) } %% ========================== %% 5. 關係連線 (Relationships) %% ========================== %% Controller 依賴 Service StoreController ..> StoreService OrderController ..> OrderService %% Service 依賴 Repository UserController ..> UserRepository CartController ..> CartRepository StoreService ..> StoreRepository OrderService ..> OrderRepository %% Repository 操作 Model UserRepository ..> User CartRepository ..> Cart StoreRepository ..> Store OrderRepository ..> Order %% Model 內部的組合關係 (Composition) User *-- FavorItem User *-- CustomCombo Cart *-- CartItem Store *-- MenuItem Order *-- OrderItem %% 模組間的關聯 (Association via IDs) Cart --> User : "userId" Cart --> Store : "storeId" Order --> User : "customerId" Order --> Store : "storeId" FavorItem --> Store : "storeId" ``` --- ## <span id="section7">7. 實作方案 (Implementation Languages and Platforms)</span> - 平台: - 網站(可支援電腦與手機) - 前端: - 程式語言:HTML5 + CSS3 + JavaScript - 框架:Vue 3 - 後端: - 程式語言:Java - 框架:Spring Boot - 資料庫操作:MongoDB - 主要函式庫與服務: - 前端: - Fetch API:向後端發送 HTTP 請求(登入、註冊、更新資料等) - Vuex:管理前端狀態 - 後端: - Spring Boot Web:提供 RESTful API,負責前後端通訊 - Spring Data MongoDB:與 MongoDB 連接,負責資料存取 - Spring Validation:處理資料驗證(例如 電子信箱格式、電話格式) - Spring Security(若需要 JWT):管理登入驗證、授權 - bcrypt(Spring Security BCryptPasswordEncoder): 用於使用者密碼加密 - 資料庫: - MongoDB:儲存使用者、店家、商品、訂單等資料 - MongoRepository:簡化 CRUD 操作 - 第三方服務:Cloud Storage、Email API、OAuth 認證(Google、Facebook 登入) - 部署方式: - 雲端 PaaS --- ## <span id="section8">8. 設計議題 (Design Issue)</span> ### 議題 1:後端 Controller 的職責劃分與 API 設計風格 - 議題內容: - 在設計後端 API 架構時,我們面臨了如何劃分 Controller 職責的選擇。這個決策將影響到程式碼的可維護性以及前後端對接的模式。 - 可能解決方案: - 以「功能」為中心:一個主要頁面或功能模組對應一個 Controller - 以「資料庫 Model」 為中心:一個資料庫 Collection 對應一個 Controller - 最後解決方案與理由: - 最後選擇採用以「資料庫 Model」 為中心,此方案優點如下: - 方便 debug:以 Model 為中心的寫法會寫出很多基本函式,如果有問題會比較好追(錯誤來源單純) - 彈性較大:如上所述,會有很多基本函式拼湊成前端可用的功能 - 降低邏輯耦合:避免多個功能寫出來的程式碼中會有重複、相近的程式碼 <!-- ### 議題 2:________ - 議題內容: - 可能解決方案: - 最後解決方案與理由: -->