浪花科技 Roamer Tech浪花科技 Roamer Tech
聯絡我們 ↗
浪花科技/通知轟炸不是用戶要...CUSTOM SYSTEMS + AI
Laravel 與後端開發 ·

通知轟炸不是用戶要的:Laravel Notifications 節流與批次進階架構

Eric,浪花科技創辦人 / AI 架構師
浪花科技創辦人 · AI 架構師
通知轟炸不是用戶要的:Laravel Notifications 節流與批次進階架構
☰ 目錄
字體大小

通知系統大概是 Laravel 裡最讓開發者「愛恨交織」的功能:用戶要的是剛剛好的提醒,許多系統給的卻是連環轟炸。這篇不教基礎寄信,我們直接處理批次摘要(Digest)、頻率節流(Throttling)與使用者偏好管理的進階架構,讓通知從騷擾變回服務。

為什麼說愛恨交織?因為 Laravel 的 Notification 系統設計得太優雅了,優雅到你寫一行 $user->notify(new OrderShipped($order)); 就能發送郵件、簡訊、甚至 Slack 通知。但這也是災難的開始。

想像一下,你的電商平台正在舉辦「雙11」活動,當用戶在短時間內觸發了 50 次狀態更新,你的系統是不是就真的乖乖寄了 50 封 Email 給他?如果是一萬個用戶同時在操作,你的 SMTP Server 是不是已經開始冒煙,或者被 Gmail 判定為垃圾郵件發送者了?

這不是危言聳聽,這是我在重構無數個「義大利麵專案」時最常看到的慘況。很多開發者只學會了「怎麼發送」,卻忽略了「怎麼管理發送」。

今天這篇文章,不教你基礎的怎麼寄信(官方文件都有),我們要深入探討企業級的通知架構:如何實作通知摘要(Digest)、頻率限制(Throttling)以及使用者偏好管理,讓你的 App 從「騷擾狂」變成貼心的「私人秘書」。

為什麼標準的 Laravel Notification 還不夠?

Laravel 預設的 Notification 其實是一個「觸發即發送(Fire and Forget)」的機制。雖然我們可以透過 ShouldQueue 介面將發送工作丟到隊列(Queue)中,解決了網頁卡頓的問題,但並沒有解決「過度通知(Notification Fatigue)」的問題。

在使用者體驗(UX)中,過多的通知是導致用戶關閉權限、甚至刪除 App 的元兇之一。作為工程師,我們需要思考以下三個層面的架構優化:

  • 聚合(Aggregation):將短時間內的多條通知合併為一條(例如:「Eric 和其他 5 人按了讚」)。
  • 控頻(Throttling):限制特定時間內的發送上限。
  • 路由控制(Routing Control):讓用戶自己決定「訂單通知要寄信,但行銷訊息只要站內通知」。

實戰一:實作「通知摘要(Digest)」模式

這是最常見的需求。假設你是一個專案管理工具,當有人在任務中留言時,你希望通知相關人員。如果一分鐘內有 10 則留言,你應該寄送一封包含 10 則留言的「摘要郵件」,而不是 10 封信。

在 Laravel 中,我們可以利用 Cache 或 Database 先暫存通知,再透過 Scheduled Job 統一發送。

步驟 1: 建立一個暫存機制

我們不直接發送 Notification,而是先將事件「推入」一個暫存區。這裡我喜歡用 Redis List,因為速度快且支援原子操作。


// 在你的 Service 或 Event Listener 中
use Illuminate\Support\Facades\Redis;

public function handleNewComment($comment)
{
    $userId = $comment->post->author_id;
    $key = "notifications:digest:{$userId}";
    
    // 將留言 ID 或內容推入 Redis List
    Redis::rpush($key, json_encode([
        'id' => $comment->id,
        'content' => substr($comment->content, 0, 50),
        'time' => now()->toIso8601String(),
    ]));
    
    // 設定過期時間,避免累積太多垃圾資料
    Redis::expire($key, 3600 * 24);
}

步驟 2: 排程發送摘要

接下來,我們寫一個 Command 或 Job,每 15 分鐘或 30 分鐘執行一次,檢查有哪些用戶有未發送的摘要。


// Console/Commands/SendNotificationDigests.php

public function handle()
{
    // 這裡為了簡化,假設我們遍歷活躍用戶,實際場景可用 Redis SCAN 或 Set 來記錄有哪些用戶需要通知
    $users = User::where('is_active', true)->get();

    foreach ($users as $user) {
        $key = "notifications:digest:{$user->id}";
        $pendingNotifications = Redis::lrange($key, 0, -1);

        if (empty($pendingNotifications)) {
            continue;
        }

        // 解碼資料
        $comments = array_map(fn($item) => json_decode($item, true), $pendingNotifications);

        // 發送真正的 Laravel Notification
        $user->notify(new CommentDigestNotification($comments));

        // 發送成功後,清除 Redis
        Redis::del($key);
    }
}

這樣一來,無論對方留言多快,用戶每段時間最多只會收到一封整理好的郵件。這才是有「儀式感」的通知系統。

實戰二:精細的「使用者偏好」路由控制

Laravel 的 Notification 類別中有一個 via() 方法,這就是我們控制「發送管道」的靈魂所在。很多新手只會寫死 return ['mail', 'database'];,這是不及格的。

我們應該讓資料庫中的使用者設定來決定回傳什麼陣列。

資料庫設計思路

建議建立一張 notification_settings 表,或在 users 表中有一個 JSON 欄位 preferences。


// user->preferences 範例
{
    "new_comment": {
        "mail": true,
        "database": true,
        "slack": false
    },
    "order_update": {
        "mail": true,
        "database": true,
        "sms": true
    }
}

動態的 via() 方法

在 Notification 類別中,我們可以這樣寫:


namespace App\Notifications;

use Illuminate\Notifications\Notification;

class OrderUpdated extends Notification
{
    public function via(object $notifiable): array
    {
        $channels = [];
        $preferences = $notifiable->preferences['order_update'] ?? [];

        // 預設開啟資料庫通知
        if ($preferences['database'] ?? true) {
            $channels[] = 'database';
        }

        // 只有用戶明確開啟 Email 才發送
        if ($preferences['mail'] ?? false) {
            $channels[] = 'mail';
        }

        // 簡訊成本高,預設關閉
        if ($preferences['sms'] ?? false) {
            $channels[] = TwilioChannel::class;
        }

        return $channels;
    }
}

這段程式碼的價值在於,它將控制權交還給了用戶,同時也幫公司節省了不必要的 SMS 或 Email 費用(這些 API 可都是要錢的啊!)。

實戰三:防止意外的無限迴圈(Circuit Breaker)

工程師的噩夢之一:系統發生錯誤 -> 發送錯誤通知 -> 通知發送失敗 -> 觸發錯誤處理 -> 再次發送錯誤通知。這就是傳說中的「通知風暴」。

在 Laravel 11 中,我們可以利用 Redis 的 Rate Limiter 來做最後一道防線。即使前面的邏輯都失效了,我們也要確保不會在一分鐘內對同一個用戶轟炸 100 封信。


use Illuminate\Support\Facades\RateLimiter;

public function via(object $notifiable): array
{
    $key = "notification:throttle:{$notifiable->id}";

    // 限制每分鐘最多 5 則通知
    if (RateLimiter::tooManyAttempts($key, 5)) {
        // 可以在這裡記錄 Log,或者只回傳 database channel (不干擾用戶)
        return ['database'];
    }

    RateLimiter::hit($key, 60);

    return ['mail', 'database'];
}

這個簡單的 RateLimiter 就像是家裡的保險絲,雖然平時沒感覺,但在關鍵時刻能救你的 SMTP Server 一命。

2025 新趨勢:Laravel Reverb 與即時通知

隨著 Laravel 11 推出的 Laravel Reverb,WebSocket 變得前所未有的好用。現在的通知系統不僅僅是「發送」,更強調「即時互動」。

當你使用 BroadcastMessage 時,配合前端的 Laravel Echo,可以做到像 Facebook 一樣,不用重新整理頁面,鈴鐺圖示就會自動亮起紅點。這對於提升使用者黏著度非常有幫助。

要在 Notification 中使用廣播,只需要實作 toBroadcast 方法:


use Illuminate\Notifications\Messages\BroadcastMessage;

public function toBroadcast(object $notifiable): BroadcastMessage
{
    return new BroadcastMessage([
        'title' => '訂單已出貨',
        'body' => '您的訂單 #1234 已經在運送途中,點擊查看詳情。',
        'action_url' => url("/orders/{$this->order->id}"),
    ]);
}

Eric 的碎碎念總結

寫了這麼多 Code,其實核心觀念只有一個:「把通知當成一種珍貴的資源」。每一次的 $user->notify() 都在消耗用戶的注意力(和你的伺服器資源)。

一個優秀的後端工程師,不會只滿足於「功能做出來了」,而是會去思考系統在極端狀況下的表現,以及對使用者的干擾程度。從今天開始,試著把 Middleware、Queue 和 Redis 整合進你的通知系統,你會發現,原來寫 Notification 也可以這麼有架構感。

延伸閱讀:讓你的 Laravel 架構更上一層樓

如果你對今天提到的技術感興趣,這裡有幾篇我精選的實戰文章,強烈建議搭配服用:

想為您的企業打造高併發、可擴展的系統架構嗎?

浪花科技擁有豐富的 Laravel 開發與效能優化經驗,協助企業解決技術債與效能瓶頸。

立即聯繫我們,預約技術諮詢

常見問題

Laravel 預設的 Notification 為什麼不足以應付大量通知?
因為它本質是「觸發即發送(Fire and Forget)」。雖然透過 ShouldQueue 介面可把發送丟進 Queue 解決網頁卡頓,但並沒有解決「過度通知」的問題。短時間大量觸發仍會轟炸使用者,甚至讓 SMTP 被判定為垃圾郵件發送者,因此需要聚合、控頻與路由控制等架構優化。
如何在 Laravel 實作通知摘要(Digest),避免一次寄出大量信件?
做法是不直接發送通知,而是先把事件推入暫存區(文中用 Redis List,以 rpush 推入並設過期時間),再寫一個 Command 或排程 Job 每隔一段時間(如 15 或 30 分鐘)執行,取出某使用者所有待發通知合併成一封 Digest 通知發送,成功後清除暫存。這樣使用者每段時間最多只收到一封整理好的信。
Laravel Notification 的 via() 方法可以依使用者偏好動態決定發送管道嗎?
可以,而且建議這麼做,而非寫死回傳固定陣列。可在 users 表存一個 JSON 偏好欄位,via() 方法讀取該使用者對特定通知類型的設定,動態組裝要回傳的管道陣列(例如資料庫預設開啟、Email 須明確開啟才加入、簡訊因成本高預設關閉),把控制權交還使用者並節省 API 費用。
怎麼防止通知系統發生「通知風暴」或無限迴圈?
可在 Laravel 11 中以 Redis 的 RateLimiter 做最後防線。在 via() 中針對使用者設定限制(例如每分鐘最多 5 則),若 tooManyAttempts 超過上限,就只回傳不干擾使用者的 database 管道,否則 hit 計數後正常發送。它像保險絲,在關鍵時刻能保護 SMTP Server。
本主題完整指南 · Laravel 與後端開發
→ Laravel 企業級客製開發完整指南:SaaS 撐不住之後,系統該怎麼做

這個主題的完整脈絡、選型比較與導入建議,都整理在指南裡。

訂閱免費電子報

把 AI 自動化、企業系統設計與 WordPress / Laravel 開發的真實案例和可直接照做的技巧,整理成電子報寄給你。只寄精選內容、不灌垃圾信,一鍵就能退訂。

⑂ main通知轟炸不是用戶要...Ln 1UTF-8