init
This commit is contained in:
34
.gitignore
vendored
Normal file
34
.gitignore
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# dependencies (bun install)
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# output
|
||||||
|
out
|
||||||
|
dist
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# code coverage
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# logs
|
||||||
|
logs
|
||||||
|
_.log
|
||||||
|
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# caches
|
||||||
|
.eslintcache
|
||||||
|
.cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# IntelliJ based IDEs
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Finder (MacOS) folder config
|
||||||
|
.DS_Store
|
||||||
193
api.md
Normal file
193
api.md
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
# API 接口文檔
|
||||||
|
|
||||||
|
## 訂單管理系統 API 接口文檔
|
||||||
|
|
||||||
|
### 1. 添加訂單
|
||||||
|
|
||||||
|
#### 接口信息
|
||||||
|
- **接口路徑**: `POST /api/orders`
|
||||||
|
- **功能描述**: 添加新的訂單記錄
|
||||||
|
- **請求格式**: JSON
|
||||||
|
- **響應格式**: JSON
|
||||||
|
|
||||||
|
#### 請求參數
|
||||||
|
| 參數名 | 類型 | 必填 | 說明 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| customer_name | String | 是 | 客戶姓名 |
|
||||||
|
| product_name | String | 是 | 商品名稱 |
|
||||||
|
| quantity | Number | 是 | 商品數量,必須大於0 |
|
||||||
|
| price | Number | 是 | 商品單價,必須大於0 |
|
||||||
|
| status | String | 否 | 訂單狀態,默認為"pending" |
|
||||||
|
|
||||||
|
#### 響應參數
|
||||||
|
| 參數名 | 類型 | 說明 |
|
||||||
|
|--------|------|------|
|
||||||
|
| success | Boolean | 請求是否成功 |
|
||||||
|
| message | String | 響應消息 |
|
||||||
|
| data | Object | 返回的數據對象 |
|
||||||
|
| data.id | Number | 新增訂單的ID |
|
||||||
|
| data.customer_name | String | 客戶姓名 |
|
||||||
|
| data.product_name | String | 商品名稱 |
|
||||||
|
| data.quantity | Number | 商品數量 |
|
||||||
|
| data.price | Number | 商品單價 |
|
||||||
|
| data.status | String | 訂單狀態 |
|
||||||
|
| data.created_at | String | 創建時間 |
|
||||||
|
|
||||||
|
#### 成功響應示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "訂單創建成功",
|
||||||
|
"data": {
|
||||||
|
"id": 6,
|
||||||
|
"customer_name": "陳八",
|
||||||
|
"product_name": "HomePod Mini",
|
||||||
|
"quantity": 1,
|
||||||
|
"price": 99.99,
|
||||||
|
"status": "pending",
|
||||||
|
"created_at": "2023-12-01 10:30:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 失敗響應示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"message": "缺少必需字段或字段值無效"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Curl 請求示例
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:3000/api/orders \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Accept: application/json" \
|
||||||
|
-d '{
|
||||||
|
"customer_name": "陳八",
|
||||||
|
"product_name": "HomePod Mini",
|
||||||
|
"quantity": 1,
|
||||||
|
"price": 99.99,
|
||||||
|
"status": "pending"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 查詢訂單列表
|
||||||
|
|
||||||
|
#### 接口信息
|
||||||
|
- **接口路徑**: `GET /api/orders`
|
||||||
|
- **功能描述**: 查詢訂單列表,支持條件篩選和分頁
|
||||||
|
- **請求格式**: Query String
|
||||||
|
- **響應格式**: JSON
|
||||||
|
|
||||||
|
#### 查詢參數
|
||||||
|
| 參數名 | 類型 | 必填 | 默認值 | 說明 |
|
||||||
|
|--------|------|------|--------|------|
|
||||||
|
| page | Number | 否 | 1 | 頁碼 |
|
||||||
|
| pageSize | Number | 否 | 10 | 每頁顯示數量 |
|
||||||
|
| customerName | String | 否 | - | 客戶姓名模糊匹配 |
|
||||||
|
| productName | String | 否 | - | 商品名稱模糊匹配 |
|
||||||
|
| status | String | 否 | - | 訂單狀態精確匹配 |
|
||||||
|
|
||||||
|
#### 響應參數
|
||||||
|
| 參數名 | 類型 | 說明 |
|
||||||
|
|--------|------|------|
|
||||||
|
| success | Boolean | 請求是否成功 |
|
||||||
|
| data | Object | 返回的數據對象 |
|
||||||
|
| data.orders | Array | 訂單列表 |
|
||||||
|
| data.pagination | Object | 分頁信息 |
|
||||||
|
| data.pagination.page | Number | 當前頁碼 |
|
||||||
|
| data.pagination.pageSize | Number | 每頁顯示數量 |
|
||||||
|
| data.pagination.total | Number | 總記錄數 |
|
||||||
|
| data.pagination.totalPages | Number | 總頁數 |
|
||||||
|
|
||||||
|
#### 成功響應示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"orders": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"customer_name": "張三",
|
||||||
|
"product_name": "iPhone 15",
|
||||||
|
"quantity": 1,
|
||||||
|
"price": 999.99,
|
||||||
|
"status": "completed",
|
||||||
|
"created_at": "2023-12-01 10:30:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"customer_name": "李四",
|
||||||
|
"product_name": "MacBook Pro",
|
||||||
|
"quantity": 1,
|
||||||
|
"price": 1999.99,
|
||||||
|
"status": "pending",
|
||||||
|
"created_at": "2023-12-01 10:25:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pagination": {
|
||||||
|
"page": 1,
|
||||||
|
"pageSize": 10,
|
||||||
|
"total": 2,
|
||||||
|
"totalPages": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Curl 請求示例
|
||||||
|
|
||||||
|
##### 查詢所有訂單(默認分頁)
|
||||||
|
```bash
|
||||||
|
curl "http://localhost:3000/api/orders"
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 分頁查詢
|
||||||
|
```bash
|
||||||
|
curl "http://localhost:3000/api/orders?page=1&pageSize=5"
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 按客戶姓名查詢
|
||||||
|
```bash
|
||||||
|
curl "http://localhost:3000/api/orders?customerName=張"
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 按商品名稱查詢
|
||||||
|
```bash
|
||||||
|
curl "http://localhost:3000/api/orders?productName=iPhone"
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 按狀態查詢
|
||||||
|
```bash
|
||||||
|
curl "http://localhost:3000/api/orders?status=pending"
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 組合條件查詢
|
||||||
|
```bash
|
||||||
|
curl "http://localhost:3000/api/orders?page=1&pageSize=10&customerName=張&status=completed"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 錯誤碼說明
|
||||||
|
|
||||||
|
| 錯誤碼 | 狀態碼 | 說明 |
|
||||||
|
|--------|--------|------|
|
||||||
|
| 400 | 400 Bad Request | 請求參數錯誤 |
|
||||||
|
| 404 | 404 Not Found | 路由不存在 |
|
||||||
|
| 500 | 500 Internal Server Error | 服務器內部錯誤 |
|
||||||
|
|
||||||
|
### 4. 公共響應格式
|
||||||
|
|
||||||
|
所有接口響應都遵循以下格式:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true/false,
|
||||||
|
"message": "響應消息",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `success`: 請求是否成功的布爾值
|
||||||
|
- `message`: 響應消息,成功時可選,失敗時必填
|
||||||
|
- `data`: 返回的業務數據,根據接口不同而異
|
||||||
26
bun.lock
Normal file
26
bun.lock
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"configVersion": 1,
|
||||||
|
"workspaces": {
|
||||||
|
"": {
|
||||||
|
"name": "bun_lite",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest",
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"packages": {
|
||||||
|
"@types/bun": ["@types/bun@1.3.8", "", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="],
|
||||||
|
|
||||||
|
"@types/node": ["@types/node@25.2.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w=="],
|
||||||
|
|
||||||
|
"bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="],
|
||||||
|
|
||||||
|
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||||
|
|
||||||
|
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||||
|
}
|
||||||
|
}
|
||||||
181
index.ts
Normal file
181
index.ts
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
import { Database } from "bun:sqlite";
|
||||||
|
|
||||||
|
// 初始化 SQLite 數據庫
|
||||||
|
const db = new Database("orders.db");
|
||||||
|
|
||||||
|
// 創建 orders 表
|
||||||
|
db.run(`
|
||||||
|
CREATE TABLE IF NOT EXISTS orders (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
customer_name TEXT NOT NULL,
|
||||||
|
product_name TEXT NOT NULL,
|
||||||
|
quantity INTEGER NOT NULL,
|
||||||
|
price REAL NOT NULL,
|
||||||
|
status TEXT DEFAULT 'pending',
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
// 定義類型
|
||||||
|
interface Order {
|
||||||
|
id?: number;
|
||||||
|
customer_name: string;
|
||||||
|
product_name: string;
|
||||||
|
quantity: number;
|
||||||
|
price: number;
|
||||||
|
status?: string;
|
||||||
|
created_at?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 準備 SQL 語句
|
||||||
|
const insertOrderStmt = db.prepare(`
|
||||||
|
INSERT INTO orders (customer_name, product_name, quantity, price, status)
|
||||||
|
VALUES ($customerName, $productName, $quantity, $price, $status)
|
||||||
|
`);
|
||||||
|
|
||||||
|
const getOrdersStmt = db.prepare(`
|
||||||
|
SELECT * FROM orders
|
||||||
|
WHERE ($customerName IS NULL OR customer_name LIKE $customerName)
|
||||||
|
AND ($productName IS NULL OR product_name LIKE $productName)
|
||||||
|
AND ($status IS NULL OR status = $status)
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT $limit OFFSET $offset
|
||||||
|
`);
|
||||||
|
|
||||||
|
const getOrderCountStmt = db.prepare(`
|
||||||
|
SELECT COUNT(*) as count FROM orders
|
||||||
|
WHERE ($customerName IS NULL OR customer_name LIKE $customerName)
|
||||||
|
AND ($productName IS NULL OR product_name LIKE $productName)
|
||||||
|
AND ($status IS NULL OR status = $status)
|
||||||
|
`);
|
||||||
|
|
||||||
|
// 創建服務器
|
||||||
|
const server = Bun.serve({
|
||||||
|
port: 3000,
|
||||||
|
async fetch(req) {
|
||||||
|
const url = new URL(req.url);
|
||||||
|
const path = url.pathname;
|
||||||
|
const method = req.method;
|
||||||
|
|
||||||
|
// CORS headers
|
||||||
|
const headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Access-Control-Allow-Origin": "*",
|
||||||
|
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
||||||
|
"Access-Control-Allow-Headers": "Content-Type",
|
||||||
|
};
|
||||||
|
|
||||||
|
// 處理預檢請求
|
||||||
|
if (method === "OPTIONS") {
|
||||||
|
return new Response(null, { status: 204, headers });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (path === "/api/orders" && method === "GET") {
|
||||||
|
// 解析查詢參數
|
||||||
|
const page = parseInt(url.searchParams.get("page") || "1");
|
||||||
|
const pageSize = parseInt(url.searchParams.get("pageSize") || "10");
|
||||||
|
const customerName = url.searchParams.get("customerName") || null;
|
||||||
|
const productName = url.searchParams.get("productName") || null;
|
||||||
|
const status = url.searchParams.get("status") || null;
|
||||||
|
|
||||||
|
// 計算偏移量
|
||||||
|
const offset = (page - 1) * pageSize;
|
||||||
|
|
||||||
|
// 準備查詢參數
|
||||||
|
const params: any = {
|
||||||
|
$customerName: customerName ? `%${customerName}%` : null,
|
||||||
|
$productName: productName ? `%${productName}%` : null,
|
||||||
|
$status: status,
|
||||||
|
$limit: pageSize,
|
||||||
|
$offset: offset
|
||||||
|
};
|
||||||
|
|
||||||
|
// 查詢訂單列表
|
||||||
|
const orders = getOrdersStmt.all(params) as Order[];
|
||||||
|
|
||||||
|
// 獲取總數
|
||||||
|
const countResult: any = getOrderCountStmt.get({
|
||||||
|
$customerName: customerName ? `%${customerName}%` : null,
|
||||||
|
$productName: productName ? `%${productName}%` : null,
|
||||||
|
$status: status
|
||||||
|
});
|
||||||
|
|
||||||
|
const total = countResult.count;
|
||||||
|
|
||||||
|
return Response.json({
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
orders,
|
||||||
|
pagination: {
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
total,
|
||||||
|
totalPages: Math.ceil(total / pageSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, { headers });
|
||||||
|
} else if (path === "/api/orders" && method === "POST") {
|
||||||
|
// 添加新訂單
|
||||||
|
const body: any = await req.json();
|
||||||
|
|
||||||
|
const order: Order = {
|
||||||
|
customer_name: body.customer_name,
|
||||||
|
product_name: body.product_name,
|
||||||
|
quantity: body.quantity,
|
||||||
|
price: body.price,
|
||||||
|
status: body.status || 'pending'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 驗證必需字段
|
||||||
|
if (!order.customer_name || !order.product_name || order.quantity <= 0 || order.price <= 0) {
|
||||||
|
return Response.json({
|
||||||
|
success: false,
|
||||||
|
message: "缺少必需字段或字段值無效"
|
||||||
|
}, {
|
||||||
|
status: 400,
|
||||||
|
headers
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 插入數據
|
||||||
|
const result = insertOrderStmt.run({
|
||||||
|
$customerName: order.customer_name,
|
||||||
|
$productName: order.product_name,
|
||||||
|
$quantity: order.quantity,
|
||||||
|
$price: order.price,
|
||||||
|
$status: order.status || 'pending'
|
||||||
|
});
|
||||||
|
|
||||||
|
return Response.json({
|
||||||
|
success: true,
|
||||||
|
message: "訂單創建成功",
|
||||||
|
data: {
|
||||||
|
id: result.lastInsertRowid as number,
|
||||||
|
...order
|
||||||
|
}
|
||||||
|
}, { headers });
|
||||||
|
} else {
|
||||||
|
return Response.json({
|
||||||
|
success: false,
|
||||||
|
message: "路由不存在"
|
||||||
|
}, {
|
||||||
|
status: 404,
|
||||||
|
headers
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("服務器錯誤:", error);
|
||||||
|
|
||||||
|
return Response.json({
|
||||||
|
success: false,
|
||||||
|
message: error.message || "服務器內部錯誤"
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Server running at http://localhost:${server.port}`);
|
||||||
26
install.sql
Normal file
26
install.sql
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
-- SQLite 數據庫初始化腳本
|
||||||
|
-- 創建 orders 表
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS orders (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
customer_name TEXT NOT NULL,
|
||||||
|
product_name TEXT NOT NULL,
|
||||||
|
quantity INTEGER NOT NULL,
|
||||||
|
price REAL NOT NULL,
|
||||||
|
status TEXT DEFAULT 'pending',
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 插入示例數據
|
||||||
|
INSERT INTO orders (customer_name, product_name, quantity, price, status) VALUES
|
||||||
|
('張三', 'iPhone 15', 1, 999.99, 'completed'),
|
||||||
|
('李四', 'MacBook Pro', 1, 1999.99, 'pending'),
|
||||||
|
('王五', 'iPad Air', 2, 599.99, 'shipped'),
|
||||||
|
('趙六', 'Apple Watch', 1, 399.99, 'delivered'),
|
||||||
|
('錢七', 'AirPods Pro', 1, 249.99, 'processing');
|
||||||
|
|
||||||
|
-- 創建索引以提高查詢性能
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_customer_name ON orders(customer_name);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_product_name ON orders(product_name);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_status ON orders(status);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_created_at ON orders(created_at);
|
||||||
16
package.json
Normal file
16
package.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "bun_lite",
|
||||||
|
"module": "index.ts",
|
||||||
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "bun run index.ts",
|
||||||
|
"start": "bun run index.ts"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5"
|
||||||
|
}
|
||||||
|
}
|
||||||
29
tsconfig.json
Normal file
29
tsconfig.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
// Environment setup & latest features
|
||||||
|
"lib": ["ESNext"],
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "Preserve",
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"allowJs": true,
|
||||||
|
|
||||||
|
// Bundler mode
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
// Best practices
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
|
||||||
|
// Some stricter flags (disabled by default)
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"noPropertyAccessFromIndexSignature": false
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user