跳至主要内容

商品 API

概述

商品 API 提供商品資訊的完整 CRUD 功能,支援商品管理、搜尋過濾、圖片上傳等操作。

相關 User Stories:


端點列表

方法路徑說明權限
GET/api/v1/products列出商品(支援搜尋、過濾、分頁)ROLE_SALES 或更高
GET/api/v1/products/:id取得商品詳情ROLE_SALES 或更高
POST/api/v1/products創建商品ROLE_OWNER 或 ROLE_MANAGER
PUT/api/v1/products/:id更新商品(完整替換)ROLE_OWNER 或 ROLE_MANAGER
PATCH/api/v1/products/:id更新商品(部分更新)ROLE_OWNER 或 ROLE_MANAGER
PATCH/api/v1/products/:id/status更新商品狀態(上架/下架)ROLE_OWNER 或 ROLE_MANAGER
DELETE/api/v1/products/:id刪除商品ROLE_OWNER 或 ROLE_ADMIN
POST/api/v1/products/upload-image上傳商品圖片ROLE_OWNER 或 ROLE_MANAGER

資料模型

Product 實體

interface Product {
id: string; // 商品 ID(UUID)
tenantId: string; // 租戶 ID
sku: string; // SKU 編號(自動生成)
name: string; // 商品名稱
category: ProductCategory; // 商品分類
description?: string; // 商品描述
basePrice: number; // 基礎價格(NT$)
costPrice?: number; // 成本價格(僅管理者可見)
stock: number; // 庫存數量
lowStockThreshold: number; // 低庫存警戒值(預設 5)
stockUnit: string; // 庫存單位(如「件」、「盆」)
status: ProductStatus; // 上架狀態
isFeatured: boolean; // 是否熱門商品
isNew: boolean; // 是否新品
mainImageUrl: string; // 主圖 URL
galleryImageUrls: string[]; // 副圖 URL 列表(最多 5 張)
createdAt: string; // 創建時間(ISO 8601)
updatedAt: string; // 更新時間(ISO 8601)
createdBy?: string; // 創建者 ID
updatedBy?: string; // 更新者 ID
}

商品分類 (ProductCategory)

代碼說明
bouquetBQT花束
potPOT盆花
standSTD高架花籃
basketBSK花籃
boxBOX盒花
giftGFT禮品
customCST客製化專案

商品狀態 (ProductStatus)

說明
active已上架(可販售)
inactive已下架(不可販售)

庫存狀態 (StockStatus) - 計算屬性

說明計算規則
in_stock有庫存stock > lowStockThreshold
low_stock低庫存0 < stock <= lowStockThreshold
out_of_stock缺貨stock = 0

SKU 生成規則

格式:{租戶代碼}-{分類代碼}-{流水號}

範例:FS01-BQT-0001(花店01 的第一個花束商品)


端點詳細規格

GET /api/v1/products

描述: 列出商品,支援搜尋、多條件過濾和分頁

權限: ROLE_SALES 或更高

Query Parameters:

參數類型必填說明預設值
searchstringNo搜尋關鍵字(模糊匹配名稱、SKU、描述)-
categorystring[]No商品分類(可多選)-
statusstring[]No商品狀態(可多選)-
stockStatusstring[]No庫存狀態(可多選)-
isFeaturedbooleanNo是否熱門商品-
isNewbooleanNo是否新品-
priceMinnumberNo最低價格-
priceMaxnumberNo最高價格-
pageintegerNo頁碼(從 1 開始)1
limitintegerNo每頁數量20
sortBystringNo排序欄位(name, sku, basePrice, stock, createdAt)createdAt
sortOrderstringNo排序方向(asc, desc)desc

請求範例:

GET /api/v1/products?category=bouquet&category=pot&status=active&priceMin=500&priceMax=2000&page=1&limit=20
Authorization: Bearer {access_token}

響應範例 (200 OK):

Headers:

X-Total-Count: 45
X-Page: 1
X-Per-Page: 20
Link: </api/v1/products?page=1&limit=20>; rel="first", </api/v1/products?page=2&limit=20>; rel="next", </api/v1/products?page=3&limit=20>; rel="last"

Body:

[
{
"id": "prod-001",
"tenantId": "tenant-001",
"sku": "FS01-BQT-0001",
"name": "經典紅玫瑰花束(12朵)",
"category": "bouquet",
"description": "嚴選進口紅玫瑰,象徵熱情與愛情",
"basePrice": 1200,
"costPrice": 600,
"stock": 50,
"lowStockThreshold": 5,
"stockUnit": "束",
"status": "active",
"isFeatured": true,
"isNew": false,
"mainImageUrl": "https://example.com/images/rose-bouquet-main.jpg",
"galleryImageUrls": [
"https://example.com/images/rose-bouquet-1.jpg",
"https://example.com/images/rose-bouquet-2.jpg"
],
"createdAt": "2025-10-01T08:00:00Z",
"updatedAt": "2025-11-15T10:30:00Z"
}
]

GET /api/v1/products/:id

描述: 取得單一商品詳情

權限: ROLE_SALES 或更高

Path Parameters:

參數類型說明
idstring商品 ID

請求範例:

GET /api/v1/products/prod-001
Authorization: Bearer {access_token}

響應範例 (200 OK):

{
"id": "prod-001",
"tenantId": "tenant-001",
"sku": "FS01-BQT-0001",
"name": "經典紅玫瑰花束(12朵)",
"category": "bouquet",
"description": "嚴選進口紅玫瑰,象徵熱情與愛情",
"basePrice": 1200,
"costPrice": 600,
"stock": 50,
"lowStockThreshold": 5,
"stockUnit": "束",
"status": "active",
"isFeatured": true,
"isNew": false,
"mainImageUrl": "https://example.com/images/rose-bouquet-main.jpg",
"galleryImageUrls": [
"https://example.com/images/rose-bouquet-1.jpg",
"https://example.com/images/rose-bouquet-2.jpg"
],
"createdAt": "2025-10-01T08:00:00Z",
"updatedAt": "2025-11-15T10:30:00Z",
"createdBy": "user-001",
"updatedBy": "user-002"
}

錯誤響應:

狀態碼錯誤訊息說明
404商品不存在找不到指定的商品

POST /api/v1/products

描述: 創建新商品

權限: ROLE_OWNERROLE_MANAGER

Content-Type:

  • application/json - 純 JSON 格式
  • multipart/form-data - 包含圖片上傳(binary-mixed 模式)

請求體 (CreateProductRequest):

欄位類型必填說明
categorystringYes商品分類
namestringYes商品名稱
descriptionstringNo商品描述
basePricenumberYes基礎價格(>= 0)
costPricenumberNo成本價格
stockintegerNo庫存數量(預設 0)
lowStockThresholdintegerNo低庫存警戒值(預設 5)
stockUnitstringNo庫存單位(預設「件」)
statusstringNo商品狀態(預設 active)
isFeaturedbooleanNo是否熱門商品(預設 false)
isNewbooleanNo是否新品(預設 true)
imagesarrayNo商品圖片(第一張為主圖,最多 6 張)

請求範例 (JSON):

POST /api/v1/products
Authorization: Bearer {access_token}
Content-Type: application/json

{
"category": "bouquet",
"name": "粉色康乃馨花束",
"description": "母親節限定款,粉嫩溫馨",
"basePrice": 980,
"costPrice": 450,
"stock": 30,
"lowStockThreshold": 5,
"stockUnit": "束",
"isFeatured": true,
"isNew": true,
"images": [
"https://example.com/images/carnation-main.jpg",
"https://example.com/images/carnation-1.jpg"
]
}

請求範例 (FormData - binary-mixed):

POST /api/v1/products
Authorization: Bearer {access_token}
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary

------WebKitFormBoundary
Content-Disposition: form-data; name="data"
Content-Type: application/json

{"category":"bouquet","name":"粉色康乃馨花束","basePrice":980,"images":["$$file:0","$$file:1"]}
------WebKitFormBoundary
Content-Disposition: form-data; name="file_0"; filename="main.jpg"
Content-Type: image/jpeg

[binary data]
------WebKitFormBoundary
Content-Disposition: form-data; name="file_1"; filename="detail.jpg"
Content-Type: image/jpeg

[binary data]
------WebKitFormBoundary--

響應範例 (201 Created):

{
"id": "prod-002",
"tenantId": "tenant-001",
"sku": "FS01-BQT-0002",
"name": "粉色康乃馨花束",
"category": "bouquet",
"description": "母親節限定款,粉嫩溫馨",
"basePrice": 980,
"costPrice": 450,
"stock": 30,
"lowStockThreshold": 5,
"stockUnit": "束",
"status": "active",
"isFeatured": true,
"isNew": true,
"mainImageUrl": "https://example.com/images/carnation-main.jpg",
"galleryImageUrls": ["https://example.com/images/carnation-1.jpg"],
"createdAt": "2025-12-22T10:00:00Z",
"updatedAt": "2025-12-22T10:00:00Z",
"createdBy": "user-001"
}

錯誤響應:

狀態碼錯誤訊息說明
400請填寫商品名稱名稱為空
400請選擇商品分類分類為空
400請填寫有效的售價價格無效或為負數
409商品名稱已存在同租戶內名稱重複

PUT /api/v1/products/:id

描述: 更新商品(完整替換)

權限: ROLE_OWNERROLE_MANAGER

注意: 建議使用 PATCH 進行部分更新,PUT 會替換整個資源

請求體: 同 POST,所有欄位都需提供

響應: 同 POST 的響應格式


PATCH /api/v1/products/:id

描述: 部分更新商品(僅更新提供的欄位)

權限: ROLE_OWNERROLE_MANAGER

Content-Type:

  • application/json
  • multipart/form-data(包含圖片時)

請求體 (UpdateProductRequest):

所有欄位皆為可選,僅更新提供的欄位:

欄位類型說明
namestring商品名稱
descriptionstring商品描述
basePricenumber基礎價格
costPricenumber成本價格
stockinteger庫存數量
lowStockThresholdinteger低庫存警戒值
stockUnitstring庫存單位
isFeaturedboolean是否熱門商品
isNewboolean是否新品
imagesarray商品圖片

請求範例:

PATCH /api/v1/products/prod-001
Authorization: Bearer {access_token}
Content-Type: application/json

{
"basePrice": 1350,
"stock": 45,
"isFeatured": false
}

響應範例 (200 OK):

返回更新後的完整商品資料

錯誤響應:

狀態碼錯誤訊息說明
404商品不存在找不到指定的商品
409商品名稱已存在名稱與其他商品重複

PATCH /api/v1/products/:id/status

描述: 更新商品上架狀態

權限: ROLE_OWNERROLE_MANAGER

請求體 (UpdateProductStatusRequest):

欄位類型必填說明
statusstringYes目標狀態(active 或 inactive)

請求範例:

PATCH /api/v1/products/prod-001/status
Authorization: Bearer {access_token}
Content-Type: application/json

{
"status": "inactive"
}

響應範例 (200 OK):

返回更新後的完整商品資料

錯誤響應:

狀態碼錯誤訊息說明
400無效的狀態值狀態值不是 active 或 inactive
404商品不存在找不到指定的商品

DELETE /api/v1/products/:id

描述: 刪除商品

權限: ROLE_OWNERROLE_ADMIN

業務規則:

  • 僅 OWNER 或 ADMIN 可刪除商品
  • 有相關訂單的商品無法刪除(需先處理訂單)

請求範例:

DELETE /api/v1/products/prod-001
Authorization: Bearer {access_token}

響應範例 (204 No Content):

無響應體

錯誤響應:

狀態碼錯誤訊息說明
403權限不足,僅店主可刪除商品非 OWNER/ADMIN 角色
404商品不存在找不到指定的商品
409此商品有相關訂單,無法刪除存在關聯訂單

POST /api/v1/products/upload-image

描述: 上傳商品圖片(獨立上傳端點)

權限: ROLE_OWNERROLE_MANAGER

Content-Type: multipart/form-data

請求體:

欄位類型必填說明
fileFileYes圖片檔案(支援 jpg, png, webp)

檔案限制:

  • 最大檔案大小:5MB
  • 支援格式:image/jpeg, image/png, image/webp

請求範例:

POST /api/v1/products/upload-image
Authorization: Bearer {access_token}
Content-Type: multipart/form-data

------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="product.jpg"
Content-Type: image/jpeg

[binary data]
------WebKitFormBoundary--

響應範例 (201 Created):

{
"id": "img-001",
"url": "https://storage.example.com/products/img-001.jpg"
}

業務規則

商品名稱唯一性

  • 同一租戶內,商品名稱必須唯一
  • 創建或更新時會檢查重複
  • 重複時返回 409 Conflict

SKU 自動生成

  • 創建商品時自動生成 SKU
  • 格式:{租戶代碼}-{分類代碼}-{流水號}
  • SKU 不可修改

刪除限制

  • 有相關訂單的商品無法刪除
  • 返回 409 Conflict 並包含相關訂單數量

多租戶隔離

  • 所有商品資料自動附加 tenantId
  • 查詢自動過濾為當前租戶的商品
  • 跨租戶訪問返回 404 或空列表

圖片處理

  • 主圖 (mainImageUrl):images 陣列的第一張
  • 副圖 (galleryImageUrls):images 陣列的其餘圖片
  • 最多 6 張圖片(1 張主圖 + 5 張副圖)

錯誤碼總覽

HTTP 狀態碼錯誤碼說明
400BAD_REQUEST請求格式錯誤或缺少必填欄位
401AUTH_TOKEN_INVALIDToken 無效或過期
403FORBIDDEN權限不足
404NOT_FOUND資源不存在
409CONFLICT資源衝突(名稱重複、有關聯訂單等)
500INTERNAL_ERROR伺服器內部錯誤

最後更新: 2025-12-22