iT邦幫忙

2025 iThome 鐵人賽

DAY 2
3
Modern Web

Line Bot × NestJS:30 天開發日記系列 第 2

Day 2:從 Webhook 到後端,打造你的專屬機器人

  • 分享至 

  • xImage
  •  

2025 鐵人賽背景圖

前言

上一篇,帶大家透過「Chat 模式」,讓自身變成客服機器人的方式。這篇會從「Webhook 模式」切入,並以 NestJS 作為後端框架,教你如何在休息時間,也能讓後端伺服器當你的替身幫你解惑使用者的問題。

本文將著重於實現以下核心功能:

  • 使用後端伺服器接收 LINE 伺服器推送的事件
  • 根據 LINE 伺服器訊息事件自動回覆使用者「Hello World」

本日程式碼的範例連結

Step 1:啟用 LINE Official Account webhook 功能

預設 webhook 功能是關閉,需要設置完 webhook URL 才可以啟用這個功能

LINE Official Account webhook 設定

這邊會讓你選擇 Provider,這邊我選擇使用 Day1 創建的Antonio(2025 IT 鐵人賽專用)

LINE Official Account Messaging API Provider 選擇

Step 2:設定你的 webhook URL 連結

Webhook URL 用於建立後端伺服器與 LINE 平台的連結關係

完成 webhook Provider 設定後,如果回到 LINE Response Setting 頁面,你會發現原本說明已經改變,這表示已經進入到 webhook 設定的最後一個步驟:設置 webhook URL。

接下來的所有設定操作都會改在 LINE Developers 平台進行。雖然 webhook URL 在LINE Developers LINE Official Account Manager 兩個平台都可以設定,但我們選擇在 LINE Developers 進行的主要原因是在 LINE developers 有驗證連線的按鈕可以確認連線是否成功。

LINE Official Account 啟用成功畫面

LINE Official Account Response settings 說明改變

Line Official Account webhook 選項文字


Webhook 與 LINE Bot 之間的關係

首先,這邊會帶著大家理解 LINE 平台與後端伺服器的關係,後端伺服器就像是一個 24hr 不休息的員工,我們可以讓他跟 LINE 平台連接,藉此達到即使不在電腦前面,當收到 LINE 官方帳號的相關事件(新朋友加入、封鎖、傳送訊息)伺服器也能代替我們處理。

不僅如此,伺服器能夠按照我們所想的邏輯或是搭配 AI 回覆,讓回覆使用者的內容不要那麼像是罐頭訊息,也可以根據不同的使用情境(例如:聖誕節)動態調整回覆訊息的風格。

以下是 LINE 平台跟後端伺服器搭配運作的關係圖:

LINE 官方帳號訊息處理採用「事件驅動」的架構,透過 Webhook 機制實現即時的雙向溝通。
(白話解釋: Webhook URL 就像是你的官方帳號頻道與 LINE 平台之間的專屬橋樑。當有使用者與官方帳號互動時,LINE 平台會主動透過這座橋樑將訊息推送到你的伺服器進行處理,而不需要你的伺服器不斷詢問「有沒有新訊息?」

LINE 平台與後端伺服器傳送訊息流程圖

LINE 傳送訊息流程解釋:

  1. 用戶發送訊息:使用者透過 LINE 應用程式發送一則訊息。
  2. LINE 平台事件轉發:當使用者與 LINE 官方帳號互動時,LINE 平台會根據綁定的 Channel ID(頻道識別碼),將事件資料以 HTTP POST 方式傳送至頻道設定的 Webhook URL。伺服器接收到這些事件後,可以進行相對應的處理與回應。
  3. 伺服器回應處理:當伺服器完成訊息處理後,透過 LINE Messaging API 將回應訊息傳送回 LINE 平台,最終傳達給使用者。

動手來做一個後端伺服器吧!

前期會搭配使用 ngrok 的方式,方便大家快速看到 Demo 成效

首先,透過 NestJS CLI 快速建立後端的環境,讓大家快速體會使用 Webhook 與 LINE Bot 交互的感覺。

使用的環境配置如下:

  • nodejs 版本:v20.18.1
  • nvm 版本:0.39.1

Step 2-1:輸入指令安裝 NestCli

選擇你喜歡的套件管理器:如果不知道用哪一個推薦使用pnpm

pnpm add -g @nestjs/cli

Step 2-2:輸入指令安裝 NestCli

nest new linebot-webhook-server // 創建一個 nest專案,名稱為 linebot-webhook-server

看到終端機寫著Successfully created代表已經創立好專案囉!
nest cli 完成畫面

Step 2-3:按照 nest cli 的指示執行以下兩個指令

cd linebot-webhook-server // 進入這個創建完的資料夾中
pnpm run start:dev // 啟動這個 nest 專案(支援熱重載功能)

看到終端機寫著Nest application successfully started代表伺服器成功啟動!
開啟 NestJS 專案伺服器

預設伺服器啟動的網址是:http://localhost:3000
(看到 hello world 就代表順利透過後端伺服器取回預設回傳值)

Step 2-4:安裝 LINE sdk(LINE Bot 相關套件)

記得先關掉伺服器(control + c),再繼續安裝

pnpm install @line/bot-sdk --save

Step 2-5:安裝處理環境變數的套件

白話解釋:私人秘密不想被別人看到的檔案

pnpm install @nestjs/config

Step 2-6:創建一個檔案命名.env並且在其中貼上以下兩行

  • LINE_CHANNEL_SECRET:驗證請求後端伺服器來源的是 LINE 平台
  • LINE_CHANNEL_ACCESS_TOKEN:驗證頻道授權者,可以透過 Bot 與使用者互動
LINE_CHANNEL_ACCESS_TOKEN=你的 LINE Bot 頻道存取權杖
LINE_CHANNEL_SECRET=你的 LINE Bot 頻道密鑰

1️⃣ LINE_CHANNEL_SECRET 放在 Basic settings 頁籤:
LINE_CHANNEL_SECRET

2️⃣ LINE_CHANNEL_ACCESS_TOKEN 放在 Messaging API 頁籤:
LINE_CHANNEL_ACCESS_TOKEN

Step 2-7:極簡化伺服器資料夾結構 + 註冊環境變數

將檔案結構調整成最簡單能運行的方式,在逐漸優化!

  • 刪除app.controller.specapp.service的部分
  • 創建 config 資料夾及 line.config.ts 檔案

初始化檔案結構

line.config.ts 內容

import { ClientConfig } from '@line/bot-sdk';
import { ConfigService } from '@nestjs/config';

// 註冊名稱
export const LINE_CONFIG = 'LINE_CONFIG';

// 註冊的常數(這邊讀取的是.env 環境變數裡面的內容)
const lineConfig = (configService: ConfigService): ClientConfig => ({
  channelAccessToken:
    configService.get<string>('LINE_CHANNEL_ACCESS_TOKEN') ?? '',
  channelSecret: configService.get<string>('LINE_CHANNEL_SECRET') ?? '',
});

// 匯出成 NestJS Provider 供依賴注入系統使用(讓 NestJS 可以透過 DI 容器管理此設定)
export const LineConfigProvider = {
  provide: LINE_CONFIG,
  useFactory: (configService: ConfigService) => lineConfig(configService),
  inject: [ConfigService],
};

Step 2-8:將 LINE Config Provider 註冊到根模組中使用

  • ConfigModule.forRoot({ isGlobal: true }):全域註冊環境變數,讓整個應用程式都能存取環境變數
  • LINE Config Provider 註冊:將 LineConfigProvider 加入到 providers 陣列中,讓它可以被依賴注入系統使用。

src/app.module.ts:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { ConfigModule } from '@nestjs/config';
import { LineConfigProvider } from '../config/line.config';

@Module({
  imports: [ConfigModule.forRoot({ isGlobal: true })],
  controllers: [AppController],
  providers: [LineConfigProvider],
})
export class AppModule {}

Step 2-9:設定 /webhook 路徑接收 LINE 平台傳遞的事件

這裡設定的路由前綴為 /webhook,這是專門用來接收 LINE Bot webhook 事件的端點路徑。當 LINE 平台有訊息或事件需要推送給我們的應用程式時,會透過這個特定的路由路徑進行回調通知

src/app.controller.ts:

import { Body, Controller, Inject, Post } from '@nestjs/common';
import {
  ClientConfig,
  WebhookRequestBody,
  messagingApi,
  MessageEvent,
  WebhookEvent,
} from '@line/bot-sdk';
import { LINE_CONFIG } from '../config/line.config';

@Controller()
export class AppController {
  private readonly lineClient: messagingApi.MessagingApiClient;

  // 根據配置檔案初始化 LINE Messaging API 客戶端
  constructor(@Inject(LINE_CONFIG) private readonly lineConfig: ClientConfig) {
    this.lineClient = new messagingApi.MessagingApiClient({
      channelAccessToken: this.lineConfig.channelAccessToken,
    });
  }

  @Post('/webhook')
  handleWebhook(@Body() body: WebhookRequestBody): Promise<string> {
    console.log('Webhook 從 line platform 接收到的資訊:', body);
    return 'Webhook processed successfully';
  }
}

Step 2-10:Nest 後端伺服器開啟的狀態下搭配使用 ngrok

LINE Webhook URL 必須是 HTTPS 網址,所以這邊需要搭配 ngrok 使用

ngrok是一個反向代理工具,能夠將本地端口轉發至公開的HTTPS 網址。它提供第三方代理服務,讓外部用戶可以直接透過公開網址存取您本地運行的應用程式

使用指令非常簡單,僅需要打開終端機輸入以下指令:

ngrok http 3000

看到以下顯示的樣子就代表已經成功建立公開網址
ngrok 運行畫面

這個https://d120-49-213-168-27.ngrok-free.app就是 webhook URL

這種方式屬於臨時伺服器的方式,連結有時候會斷開。如果重新開啟 ngrok 需要記得 https 連結位置會改變,不要忘記連帶改動設定的 Webhook URL

Step 3:在 LINE developers 設置 webhook URL 並測試

登入 LINE developers 後,在 Message API Webhook settings可以設定 Webhook URL:
在 LINE developers 設置 Webhook URL

特別注意要加上 /webhook 的路徑前綴,這是我們後端設置接收 LINE 事件的端點,這樣才可以正確打到架設的後端伺服器喔!

按下Verify 按鈕的同時,LINE 平台就會發送事件到你的後端伺服器,這時候只要後端伺服器回傳的請求狀態碼是 200,畫面上會顯示 Success。這時候就代表已經成功建立 LINE 平台與你架設的後端伺服器之間的連結。

Step 4:在 LINE developers 打開 Use webhook 按鈕

在剛才設置 Webhook URL 的下方,找到並開啟「Use webhook」開關。此設定會與先前在 Line Official Account Manager 中的配置進行連動。


開啟後,你可以試著在官方帳號輸入訊息,查看終端機印出的訊息:

後端伺服器終端機印出 LINE 平台送出的事件

印出收到的訊息,代表你已經能成功接收到使用者跟你的 LINE 官方帳號互動的事件囉!

Step 5:修改 app.controller.ts 回覆使用者 Hello World!

LINE Bot 與使用者的互動包含多種事件類型,這邊舉例三種最常見的事件:

  • Follow event:用戶加入 LINE 官方帳號
  • Unfollow event:用戶封鎖 LINE 官方帳號
  • Message event:用戶傳送訊息

本系列的文章將聚焦於最常見的使用情境 Message event

在下面程式碼中,我們會特別展示一個重要概念:每則訊息都帶有專屬的 Reply Token(可以理解為該訊息的「身分證」)。後端伺服器可以利用這個 Token 來回應特定的用戶訊息,這就是 LINE Bot 回覆訊息的第一種方式 Reply Message

src/app.controller.ts:

//...略

// 限制型別先收斂成部分事件,搭配 eventHandler 使用
type HandlerMap = {
  [K in WebhookEvent['type']]: (
    event: Extract<WebhookEvent, { type: K }>,
  ) => Promise<void>;
};

@Controller()
export class AppController {
  private readonly lineClient: messagingApi.MessagingApiClient;

  // 根據配置檔案初始化 LINE Messaging API 客戶端
  constructor(@Inject(LINE_CONFIG) private readonly lineConfig: ClientConfig) {
    this.lineClient = new messagingApi.MessagingApiClient({
      channelAccessToken: this.lineConfig.channelAccessToken,
    });
  }

  /**
   * Webhook 端點處理器
   * 接收來自 LINE Platform 的事件通知
   * @param body LINE Platform 傳送的 Webhook 請求本體
   * @returns 處理完成的回應訊息
   */
  @Post('/webhook')
  async handleWebhook(@Body() body: WebhookRequestBody): Promise<string> {
    console.log('Webhook 從 LINE 平台接收到的資訊:', body);
    const { events } = body;

    // 事件處理器映射表
    const eventHandler = {
      message: (event) => this.handleMessageEvent(event),
    } satisfies Partial<HandlerMap>;

    // 逐項處理每一個事件
    for (const event of events) {
      const { type } = event;
      if (type === 'message') {
        await eventHandler[type](event);
      }
    }

    return 'Webhook processed successfully';
  }

  /**
   * 處理訊息事件
   * @param event 訊息事件物件,包含用戶訊息內容(text)和回覆憑證(replyToken)
   */
  private async handleMessageEvent(event: MessageEvent): Promise<void> {
    const { replyToken } = event;
    console.log('收到訊息事件', event);
    console.log('訊息憑證(身分證):', replyToken);
    await this.lineClient.replyMessage({
      replyToken,
      messages: [
        {
          type: 'text',
          text: 'hello world',
        },
      ],
    });
  }
}

輸入完訊息,就會看到你的專屬代理人回覆 hello world!的訊息 ⭐⭐⭐

這邊眼尖的你可能會發現我這邊沒使用到 LINE_CHANNEL_SECRET 也可以使用 LINE Bot Webhook 的功能,那為什麼還需要這個呢?明天的文章帶你揭曉

後端伺服器傳遞 Hello world 成功圖片


上一篇
Day 1:Hello World LINE Bot
下一篇
Day 3:Webhook 簽章驗證與訊息回覆策略(Push & Reply)
系列文
Line Bot × NestJS:30 天開發日記22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言