2025-07-05 Line 訂單系統-A階 A1 在 GitHub 上保存與同步專案版本;A2 在 Lenovo 上快速重建或重新安裝測試環境

2025-07-05 Line 訂單系統-A階 A1 在 GitHub 上保存與同步專案版本;A2 在 Lenovo 上快速重建或重新安裝測試環境

同時準備兩份東西:

1️⃣ GitHub-ready boilerplate — 可以直接放進 repo、clone 下來執行的完整目錄(含 README、db_init.sql、後端程式碼)。
2️⃣ Lenovo 一鍵建立腳本 — 幫你在 Windows(PowerShell)上從零建立 backend 專案、安裝套件、建 DB、啟動測試。

這兩份設計會完全一致,讓你可以:

  • 在 GitHub 上保存與同步專案版本;
  • 在 Lenovo 上快速重建或重新安裝測試環境。

🧱 Part 1:GitHub-ready Boilerplate(檔案總覽)

📁 line-order-system/

1
2
3
4
5
6
7
8
9
10
11
line-order-system/
├─ backend/
│ ├─ index.js
│ ├─ db.js
│ ├─ routes/
│ │ └─ line.js
│ ├─ db_init.sql
│ ├─ package.json
│ ├─ .env.example
│ └─ .gitignore
└─ README.md

backend/db_init.sql

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-- Initialize database for LINE order system
CREATE DATABASE IF NOT EXISTS line_order_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE line_order_db;

CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
line_user_id VARCHAR(50) UNIQUE,
display_name VARCHAR(100),
phone VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS messages_log (
id INT AUTO_INCREMENT PRIMARY KEY,
line_user_id VARCHAR(50),
event_type VARCHAR(50),
message_type VARCHAR(50),
message_text TEXT,
raw_event JSON,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

backend/db.js

1
2
3
4
5
6
7
8
9
10
11
12
13
import mysql from "mysql2/promise";
import dotenv from "dotenv";
dotenv.config();

export const pool = mysql.createPool({
host: process.env.DB_HOST || "localhost",
user: process.env.DB_USER || "root",
password: process.env.DB_PASS || "",
database: process.env.DB_NAME || "line_order_db",
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0,
});

backend/routes/line.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import express from "express";
import { middleware, Client } from "@line/bot-sdk";
import dotenv from "dotenv";
import { pool } from "../db.js";

dotenv.config();

const router = express.Router();

const config = {
channelAccessToken: process.env.LINE_CHANNEL_ACCESS_TOKEN,
channelSecret: process.env.LINE_CHANNEL_SECRET,
};

const client = new Client(config);

router.post("/webhook", middleware(config), async (req, res) => {
try {
const events = req.body.events || [];

for (const event of events) {
if (event.type === "message" && event.message.type === "text") {
const userId = event.source?.userId || null;
const text = event.message.text;

// 寫入資料庫
await pool.query(
"INSERT INTO messages_log (line_user_id, event_type, message_type, message_text, raw_event) VALUES (?, ?, ?, ?, ?)",
[userId, event.type, event.message.type, text, JSON.stringify(event)]
);

// 回覆使用者
await client.replyMessage(event.replyToken, {
type: "text",
text: `收到:「${text}」`,
});
}
}

res.status(200).send("OK");
} catch (err) {
console.error("Webhook error:", err);
res.status(500).end();
}
});

export default router;

backend/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import express from "express";
import cors from "cors";
import dotenv from "dotenv";
import lineRouter from "./routes/line.js";

dotenv.config();

const app = express();
app.use(cors());
app.use(express.json({ limit: "1mb" }));

app.get("/", (req, res) => res.send("Line Order Backend - OK"));
app.use("/line", lineRouter);

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`🚀 Server running on port ${PORT}`));

backend/.env.example

1
2
3
4
5
6
7
8
9
10
11
12
# LINE
LINE_CHANNEL_SECRET=your_channel_secret_here
LINE_CHANNEL_ACCESS_TOKEN=your_channel_access_token_here

# MySQL
DB_HOST=localhost
DB_USER=root
DB_PASS=your_db_password
DB_NAME=line_order_db

# Server
PORT=3000

backend/package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"name": "line-order-backend",
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
"dependencies": {
"@line/bot-sdk": "^7.6.0",
"cors": "^2.8.5",
"dotenv": "^16.0.0",
"express": "^4.18.2",
"mysql2": "^3.3.0"
},
"devDependencies": {
"nodemon": "^2.0.22"
}
}

.gitignore

1
2
3
node_modules/
.env
.DS_Store

README.md(簡化版)

1
2
3
4
5
6
7
8
9
10
# LINE Order System (A-Stage Prototype)

## 功能
- 透過 LINE Webhook 接收訊息並回覆
- 將訊息記錄到 MySQL 資料庫

## 啟動步驟
1. 建立資料庫
```bash
mysql -u root -p < backend/db_init.sql
  1. 複製 .env.example.env 並填入 LINE / DB 設定

  2. 安裝依賴與啟動伺服器

    1
    2
    3
    cd backend
    npm install
    npm run dev
  3. 啟動 ngrok(開啟 webhook)

    1
    ngrok http 3000
  4. 設定 LINE Developer 的 webhook URL 為 https://xxxxx.ngrok.io/line/webhook

  5. 用 LINE 傳訊息測試(會收到回覆、資料寫入 MySQL)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164

---

## ⚙️ Part 2:Lenovo 一鍵建立腳本(PowerShell 版)

你可以把以下內容存成 `setup_line_order.ps1`(放在任何目錄),
然後右鍵 → **以系統管理員身分執行**。

> 💡 假設你的 MySQL root 密碼是 `rootpass`(請自行改),GitHub 已登入過。

```powershell
# setup_line_order.ps1
Write-Host "=== 建立 LINE Order System Backend 專案 ===" -ForegroundColor Cyan

# 1️⃣ 建立資料夾結構
$projectDir = "D:\Projects\line-order-system"
$backendDir = "$projectDir\backend"
Write-Host "建立資料夾..."
New-Item -ItemType Directory -Force -Path $backendDir | Out-Null
New-Item -ItemType Directory -Force -Path "$backendDir\routes" | Out-Null

# 2️⃣ 初始化 npm 專案
Set-Location $backendDir
npm init -y | Out-Null

# 3️⃣ 安裝套件
Write-Host "安裝 Node.js 依賴..."
npm install express cors dotenv mysql2 @line/bot-sdk | Out-Null
npm install -D nodemon | Out-Null

# 4️⃣ 建立主要檔案
Write-Host "建立程式檔案..."

# index.js
@"
import express from "express";
import cors from "cors";
import dotenv from "dotenv";
import lineRouter from "./routes/line.js";

dotenv.config();

const app = express();
app.use(cors());
app.use(express.json({ limit: "1mb" }));

app.get("/", (req, res) => res.send("Line Order Backend - OK"));
app.use("/line", lineRouter);

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`🚀 Server running on port ${PORT}`));
"@ | Set-Content "$backendDir\index.js"

# db.js
@"
import mysql from "mysql2/promise";
import dotenv from "dotenv";
dotenv.config();

export const pool = mysql.createPool({
host: process.env.DB_HOST || "localhost",
user: process.env.DB_USER || "root",
password: process.env.DB_PASS || "",
database: process.env.DB_NAME || "line_order_db",
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0,
});
"@ | Set-Content "$backendDir\db.js"

# routes/line.js
@"
import express from "express";
import { middleware, Client } from "@line/bot-sdk";
import dotenv from "dotenv";
import { pool } from "../db.js";
dotenv.config();

const router = express.Router();

const config = {
channelAccessToken: process.env.LINE_CHANNEL_ACCESS_TOKEN,
channelSecret: process.env.LINE_CHANNEL_SECRET,
};

const client = new Client(config);

router.post("/webhook", middleware(config), async (req, res) => {
try {
const events = req.body.events || [];
for (const event of events) {
if (event.type === "message" && event.message.type === "text") {
const userId = event.source?.userId || null;
const text = event.message.text;
await pool.query(
"INSERT INTO messages_log (line_user_id, event_type, message_type, message_text, raw_event) VALUES (?, ?, ?, ?, ?)",
[userId, event.type, event.message.type, text, JSON.stringify(event)]
);
await client.replyMessage(event.replyToken, { type: "text", text: `收到:「${text}」` });
}
}
res.status(200).send("OK");
} catch (err) {
console.error("Webhook error:", err);
res.status(500).end();
}
});

export default router;
"@ | Set-Content "$backendDir\routes\line.js"

# db_init.sql
@"
CREATE DATABASE IF NOT EXISTS line_order_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE line_order_db;
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
line_user_id VARCHAR(50) UNIQUE,
display_name VARCHAR(100),
phone VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS messages_log (
id INT AUTO_INCREMENT PRIMARY KEY,
line_user_id VARCHAR(50),
event_type VARCHAR(50),
message_type VARCHAR(50),
message_text TEXT,
raw_event JSON,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
"@ | Set-Content "$backendDir\db_init.sql"

# .env.example
@"
LINE_CHANNEL_SECRET=your_channel_secret_here
LINE_CHANNEL_ACCESS_TOKEN=your_channel_access_token_here
DB_HOST=localhost
DB_USER=root
DB_PASS=rootpass
DB_NAME=line_order_db
PORT=3000
"@ | Set-Content "$backendDir\.env.example"

# package.json 追加 scripts/type
(Get-Content "$backendDir\package.json") -replace '"test": "echo.*"', '"start": "node index.js", "dev": "nodemon index.js"' | Set-Content "$backendDir\package.json"
Add-Content "$backendDir\package.json" "`n`"'"type": "module",'

# .gitignore
@"
node_modules/
.env
.DS_Store
"@ | Set-Content "$backendDir\.gitignore"

# 5️⃣ 建 DB
Write-Host "初始化 MySQL..."
& mysql -u root -prootpass -e "SOURCE $backendDir\db_init.sql"

# 6️⃣ 啟動測試
Write-Host "啟動伺服器測試..."
Start-Process powershell -ArgumentList "cd $backendDir; npm run dev"
Write-Host "伺服器啟動後請執行: ngrok http 3000" -ForegroundColor Yellow
Write-Host "再到 LINE Developers 設定 Webhook URL -> https://xxxxx.ngrok.io/line/webhook"

⚙️ 執行方式

  1. 儲存為 setup_line_order.ps1

  2. 以系統管理員身份打開 PowerShell,執行:

    1
    2
    Set-ExecutionPolicy Bypass -Scope Process
    .\setup_line_order.ps1
  3. 等待安裝完成(約 2–5 分鐘)

  4. 執行 ngrok:

    1
    ngrok http 3000
  5. 連結 webhook 測試即可。


是否要我幫你補上「📗 B 階段 — 商品與訂單管理 API 設計(含 Vue3 後台初版 UI)」的架構規劃與實作步驟?
(會接在這個 A 階段後直接可用,並且能查詢、管理訂單)