iT邦幫忙

2025 iThome 鐵人賽

DAY 23
0
Modern Web

用 LINE OA 打造中小企業訂單系統:從零開始的 30 天實作紀錄系列 第 23

讓 Bot 自己說話:使用 Messaging API 自動通知顧客

  • 分享至 

  • xImage
  •  

昨天我們讓系統具備錯誤處理與 Logging 功能,能更穩定地記錄與除錯。

今天要讓整個訂單流程「活起來」:當訂單完成後,店家不再需要手動通知顧客,而是讓系統自動透過 Messaging API 的 Push API 發送訊息給顧客。

這樣不但能延續 LINE Notify 的功能,還能支援更多互動式推播與自動化邏輯。


LINE Notify 結束服務公告

先跟各位抱歉...在昨日的預告中有告訴大家今天會整合 LINE Notify,在較之前的文章也曾經提及過 LINE Notify 的整合,當時我還在大學確實也是使用 LINE Notify 沒錯,但我沒有及時更新到資訊,非常抱歉!但這著實也代表,我真的是在這 30 天中跟大家一起每天開發一點再馬上分享出來 ~(>_<。)\

LINE 官方已宣布將於 2025 年 3 月 31 日終止 LINE Notify 服務
若要實現同樣的推播功能,官方建議改用 Messaging API
Messaging API 能以 Bot 的身分主動推播訊息給特定使用者或群組,功能更強大且長期支援。

https://ithelp.ithome.com.tw/upload/images/20251007/20178868Bna6e9j1Fn.png


目標

  • 當訂單狀態從 Pending → Completed 時,自動通知顧客。

  • 使用 Messaging API 的 Push API(取代 LINE Notify)。

  • 支援個人與群組推播,延伸性更高。


架構流程圖

https://ithelp.ithome.com.tw/upload/images/20251007/20178868WNJJ8bSQDM.png


建立 LINE SDK Client

// src/lib/lineClient.js
const line = require("@line/bot-sdk");

const client = new line.Client({
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN,
  channelSecret: process.env.CHANNEL_SECRET,
});

module.exports = client;

.env

CHANNEL_ACCESS_TOKEN=你的MessagingAPIToken
CHANNEL_SECRET=你的ChannelSecret

建立推播工具(Messaging API 版)

// src/utils/notifyUser.js
const client = require("../lib/lineClient");
const logger = require("../lib/logger");

async function notifyOrderCompleted(userId, orderId) {
  const message = {
    type: "text",
    text: `🎉 您的訂單已完成!\n訂單編號:${orderId}\n感謝您的購買 🙌`,
  };

  try {
    await client.pushMessage(userId, message);
    logger.info(`推播成功:通知用戶 ${userId} 訂單完成`);
  } catch (err) {
    logger.error(`推播失敗 (${userId}):${err.message}`);
  }
}

module.exports = { notifyOrderCompleted };

整合到 Redis Worker

// worker/notify-worker.js
require("dotenv").config();
const { ensureConnected } = require("../src/lib/redis");
const { QUEUE_KEY } = require("../src/queues/notify");
const { notifyOrderCompleted } = require("../src/utils/notifyUser");
const logger = require("../src/lib/logger");

(async () => {
  const redis = await ensureConnected();
  logger.info("📡 Messaging API Worker 已啟動...");

  while (true) {
    const res = await redis.blPop(QUEUE_KEY, 5);
    if (!res) continue;

    const [, job] = res;
    try {
      const payload = JSON.parse(job);
      if (payload.type === "order_completed") {
        await notifyOrderCompleted(payload.userId, payload.orderId);
      }
    } catch (err) {
      logger.error(`通知任務錯誤:${err.message}`);
    }
  }
})();

測試流程

前置作業:
記得要先將 worker 起起來
!https://ithelp.ithome.com.tw/upload/images/20251007/201788680NYTKLLKN6.png

  1. 讓使用者先與 Bot 聊天一次(取得 userId)。
    https://ithelp.ithome.com.tw/upload/images/20251007/20178868ksK4FjkKbw.png
    https://ithelp.ithome.com.tw/upload/images/20251007/201788687CeuUdiO1z.png

  2. 當訂單狀態更新為 Completed 時,推入佇列:

    enqueueNotify({
      type: "order_completed",
      userId: "Uxxxxxxxxxx",
      orderId: order._id,
    });
    

    https://ithelp.ithome.com.tw/upload/images/20251007/20178868OSTmxosDzs.png

3. Worker 取出任務後使用 Push API 傳送訊息。

目前有遇到通知失敗的問題,但礙於篇幅,明天會再向大家說明該如何解決這個問題,初步分析原因是 userId 沒有正確帶入!

4. 顧客會收到:

```
🎉 您的訂單已完成!
訂單編號:6654aef...
感謝您的購買 🙌
```

🧯 實際踩坑紀錄:Worker 沒動、顧客沒收到訊息?

在實作過程中,許多人會遇到:「佇列有資料,但 Worker 沒反應」或「顧客沒有收到推播」。
這通常不是 LINE API 壞掉,而是 Worker 沒正確啟動Redis 客戶端型別不符

❌ 問題 1:Worker 沒自動執行

Worker 是一支獨立的 Node 程式,不會自動啟動
你必須開一個新終端機、手動啟動它。

node worker/notify-worker.js

看到這行就代表啟動成功:

📡 Messaging API Worker 已啟動...

建議把指令寫入 package.json 方便啟動:

"scripts": {
  "worker:notify": "node worker/notify-worker.js"
}

之後只要輸入:

npm run worker:notify

❌ 問題 2:錯誤訊息 TypeError: res is not iterable

這是因為我們使用的 Redis 驅動是 node-redis v4
它的 blPop() 回傳型別是 物件 { key, element },而不是陣列。
因此舊版寫法:

const [, job] = res; // ❌ 在 node-redis v4 會報錯

要改成:

const jobStr = res.element; // ✅ 正確

✅ 修正版 Worker 程式

// worker/notify-worker.js
require("dotenv").config();
const { ensureConnected } = require("../src/lib/redis");
const { QUEUE_KEY } = require("../src/queues/notify");
const { notifyOrderCompleted } = require("../src/utils/notifyUser");
const logger = require("../src/lib/logger");

(async () => {
  const redis = await ensureConnected();
  console.log("Notify worker started.");

  while (true) {
    try {
      const res = await redis.blPop(QUEUE_KEY, 5);
      if (!res) continue; // timeout 繼續 loop

      const jobStr = res.element; // ✅ node-redis v4 正確寫法
      if (!jobStr) continue;

      let payload;
      try {
        payload = JSON.parse(jobStr);
      } catch (e) {
        logger.error(`Job JSON 解析失敗:${jobStr}`);
        continue;
      }

      if (payload.type === "order_completed") {
        await notifyOrderCompleted(payload.userId, payload.orderId);
      }
    } catch (err) {
      const detail =
        err?.originalError?.response?.data || err?.response?.data || err.message;
      logger.error(`通知任務錯誤:${JSON.stringify(detail)}`);
    }
  }
})();

與 Day 12 的差異

功能 Day 12 Day 23
對象 管理者群組 顧客(個人 userId)
通知觸發點 新訂單建立 訂單完成
實作位置 /orders API Redis Worker
API 類型 pushMessage(groupId) pushMessage(userId)

延伸方向

  • 改為 Flex Message 通知卡片(顯示商品明細、取貨方式)。

  • 訂單狀態不同可發送不同模板(如製作中、出貨中)。

  • 使用 Redis Pub/Sub 實現多 Worker 通知同步。


重點回顧

  • LINE Notify 終止服務後,建議全面改用 Messaging API。

  • Push API 可主動推播訊息給個人或群組,功能更彈性。

  • 搭配 Redis Worker,即可建立穩定、自動化的通知機制。

明天(Day 24)我們將探討「多取貨方式與通知邏輯優化」,讓系統更貼近實際營運場景,並修正通知失敗,未帶入 userId 的問題。


上一篇
讓錯誤看得見:Logging 與錯誤處理設計
系列文
用 LINE OA 打造中小企業訂單系統:從零開始的 30 天實作紀錄23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言