2025-07-04 Line A 階段:LINE webhook + Node.js + MySQL(最小可行版本)

2025-07-04 Line A 階段:LINE webhook + Node.js + MySQL(最小可行版本)

A 階段(最小可行原型) 立刻開始:目標是建立可接收 LINE Webhook 的 Node.js 伺服器,並把來自使用者的訊息寫入 MySQL(後端跑在你的 Lenovo)。整個流程會包含:資料庫初始化、後端程式碼、環境變數設定、用 ngrok 曝露 webhook、以及實測步驟與檢查點。
下面是一步一步的操作手冊——可直接複製貼上執行。


A 階段:LINE webhook + Node.js + MySQL(最小可行版本)

目標成果

  • Node.js 後端能接收 LINE Webhook,回覆訊息並把訊息寫入 MySQL messages_log
  • 能用 ngrok 把本機 http://localhost:3000/line/webhook 暴露給 LINE 並測試。
  • 程式碼存到 GitHub(不含 .env)。

前置條件(請先完成)

  • Lenovo(開發主機)已安裝 Node.js(建議 v18+)與 npm。若沒安裝,請用 nvm-windows 或官網安裝器。
  • Lenovo 已安裝 MySQL Server,知道 root 或其他使用者密碼。或已註冊 PlanetScale(若使用雲端 DB,請準備好連線字串)。
  • 已有 GitHub 帳號(會推 code 到 repo)。
  • 已安裝 ngrok 並完成驗證(ngrok authtoken)或準備安裝。
  • 已註冊 LINE Developers 帳號(稍後要建立 channel)。

檔案結構(最終會長這樣)

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

Step 0 — 建 GitHub repo(若尚未建立)

  1. 在 GitHub 建 repo:line-order-system(Private 或 Public 都可)。
  2. clone 到 Lenovo:
1
2
3
4
cd D:\Projects
git clone https://github.com/<你的帳號>/line-order-system.git
cd line-order-system
mkdir backend

Step 1 — 建資料庫與初始化 SQL

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
-- 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
);

執行 SQL(若在本機 MySQL)

  1. 用 MySQL Workbench 或命令列匯入:
1
2
3
mysql -u root -p
-- 輸入密碼,然後在 mysql> 提示符輸入:
SOURCE /path/to/line-order-system/backend/db_init.sql;
  1. 確認:
1
2
3
USE line_order_db;
SHOW TABLES;
-- 應會看到 users、messages_log

如果你用 PlanetScale,使用其 Console 的 「Run schema」 或者透過 CLI 執行相同 SQL。


Step 2 — 建立後端專案 & 安裝套件(在 backend 目錄)

line-order-system/backend

1
2
3
4
cd backend
npm init -y
# 設為 ES module
# 編輯 package.json,加入 "type": "module" (或用下面指令)

編輯 package.json(加入 scripts 與 type):

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"
}
}

安裝套件:

1
2
npm install express cors dotenv mysql2 @line/bot-sdk
npm install -D nodemon

Step 3 — 建置 .env.example 與 .gitignore

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/.gitignore(或 repo 根目錄)加入:

1
2
3
node_modules/
.env
.DS_Store

注意:不要把實際 .env 推到 GitHub。使用 .env.example 讓其他開發者知道要填何種變數。


Step 4 — 寫程式碼(貼好檔案)

backend/db.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// backend/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
});

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
48
49
50
51
52
53
54
55
56
57
// backend/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);

// 使用 SDK middleware 驗證 signature
router.post('/webhook', middleware(config), async (req, res) => {
try {
const events = req.body.events || [];
// 處理每個 event
for (const event of events) {
// 範例:只處理文字訊息並回覆
if (event.type === 'message' && event.message.type === 'text') {
const userId = event.source?.userId || null;
const text = event.message.text;

// 1) 儲存到 MySQL messages_log
try {
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)]
);
} catch (dbErr) {
console.error('DB insert error', dbErr);
}

// 2) 回覆使用者
const replyText = `收到:${text}\n(這是測試回覆)`;
await client.replyMessage(event.replyToken, {
type: 'text',
text: replyText
});
} else {
// 處理非文字訊息或其他事件時,僅回 200
console.log('Unhandled event type:', event.type);
}
}

res.status(200).send('OK');
} catch (err) {
console.error('Webhook processing 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
17
18
19
20
21
22
23
24
// backend/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'));

// 註冊 route
app.use('/line', lineRouter);

// optional: health check
app.get('/health', (req, res) => res.json({ status: 'ok' }));

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

// Export app for testing (if needed)
export default app;

Step 5 — 建立本機 .env(不要上傳)

backend .env,填入你的真實值(不要推上 GitHub):

1
2
3
4
5
6
7
LINE_CHANNEL_SECRET=xxxx
LINE_CHANNEL_ACCESS_TOKEN=yyyy
DB_HOST=localhost
DB_USER=root
DB_PASS=your_mysql_password
DB_NAME=line_order_db
PORT=3000

Step 6 — 啟動伺服器並測試本機

在 backend 目錄啟動:

1
2
3
npm run dev
# 或
node index.js

在瀏覽器開 http://localhost:3000/ 應顯示 Line Order Backend - OK
http://localhost:3000/health 看到 {status: "ok"}


Step 7 — 用 ngrok 暴露 webhook(在 Lenovo)

  1. 啟動 ngrok(假設你已安裝且驗證過):
1
ngrok http 3000
  1. ngrok 會顯示一個公開 URL,例如:
    https://a1b2c3d4.ngrok.io
  2. 你的 LINE webhook 完整 URL 路徑會是:
    https://a1b2c3d4.ngrok.io/line/webhook

Step 8 — 在 LINE Developers 設定 Webhook(建立 Channel)

  1. 登入 LINE Developers → 建立 Provider → 建立 Channel (Messaging API)。

  2. 在 Channel 設定頁填入 Channel name、選類別、上傳 Icon(非必要)。

  3. 取得 Channel secretChannel access token(存進你的 backend/.env)。

  4. 在 Channel 的 Messaging API 設定區塊:

    • Webhook URL:貼上 https://<your-ngrok>/line/webhook
    • 點「Verify」應該顯示成功(若顯示失敗,檢查伺服器有沒有運行與 ngrok URL 是否正確)。
    • 啟用「Use webhook」。

Step 9 — 實機測試(手機 LINE)

  1. 在 Channel 裡的「Messaging API」頁面找到「QR Code」或「Add as friend」方式,把你的 LINE Official Account 加為好友(或把 Developer Channel 的測試號加入)。
  2. 用手機對帳號傳文字(比如 測試Hello)。
  3. 預期:Bot 回覆 收到:Hello(這是測試回覆),而且 MySQL messages_log 會多一筆紀錄。
  4. 確認 DB:
1
SELECT id, line_user_id, message_text, created_at FROM messages_log ORDER BY id DESC LIMIT 5;

Step 10 — 推到 GitHub(不包含 .env)

在 repo 根目錄:

1
2
3
4
cd ../   # 回到 line-order-system 根
git add backend
git commit -m "A-stage: backend webhook + save messages to MySQL"
git push origin main

確認 .gitignore.env 並且沒有把 .env 推上去。


Checkpoints(驗收)

  • http://localhost:3000/ 顯示 OK。
  • ngrok 提供的公開 URL 可以 POST/line/webhook(LINE Verify 成功)。
  • 手機傳文字給 LINE 帳號,Bot 會回覆。
  • MySQL messages_log 能看到該訊息的紀錄(raw_event 欄位有 JSON)。
  • 程式碼已 push 到 GitHub(但 .env 未上傳)。

常見問題與排錯建議

  • ngrok 驗證失敗 → 確認本機 server 有運行且能在 http://localhost:3000/line/webhook 回 200。用 curl 測試:
1
curl -X POST http://localhost:3000/line/webhook -H "Content-Type: application/json" -d '{"events":[]}'
  • LINE Verify 顯示「Invalid signature」→ 檢查 .envLINE_CHANNEL_SECRET 是否正確;@line/bot-sdk middleware 會驗證簽章。
  • MySQL 寫入失敗 → 檢查 backend/db.js 的連線參數與 DB 是否已建立 line_order_db、table 是否存在。
  • 若使用 PlanetScale → 連線字串與普通 MySQL 會有差異(需使用 TLS 與特殊用戶名),請參考 PlanetScale 的連線說明並把值放到 .env

安全提示(開發階段就應遵守)

  • 不要在任何公開地方(Slack、聊天室、GitHub)貼 LINE_CHANNEL_ACCESS_TOKEN.env 內容。
  • 在 GitHub 設置 Branch 保護、PR 流程(未來)。
  • 若要多人協作,把 .env.example 提供給其他人,讓他們在本地建立各自的 .env

下一步建議(A 階段完成後)

如果你通過 A 階段檢查點,我建議接下來做:

  1. 在後端新增「簡單指令解析」:我要點餐 → 回傳商品清單(從 MySQL 讀取)。
  2. 新增 ordersorder_items SQL schema(我們的 full 手冊 B 階段內容)。
  3. 在 frontend 開始做管理後台的最小 UI(顯示 messages_log 與商品列表)。

如果你要,我可以:

  • 立刻幫你把 backend 的這一套檔案 打包成可以直接 git clone 並執行 的 starter repo(包含 READMEdb_init.sql 與所有檔案),或
  • 以你現在的環境給出逐步的命令行輸入腳本(PowerShell / CMD)來在 Lenovo 一鍵建立這個後端與 DB(包含範例 seed 資料)。

你要先哪一個?(我會直接給出 repo 的檔案清單或一鍵腳本,供你拷貝執行)