mcpField
L
laravel-notification-patterns

laravel-notification-patterns

github.com/iserter开发4885 次安装27 周前更新
代码仓库

关于 laravel-notification-patterns

面向 Laravel 开发者的通知模式参考,涵盖邮件、数据库、广播和 Slack 等渠道,以及队列、条件发送与测试实践。

主要功能

  1. 01邮件通知与 Markdown 模板
  2. 02数据库通知的存储和读取
  3. 03广播及 Slack 通知
  4. 04队列、延迟与失败处理
  5. 05条件发送和自定义渠道
  6. 06通知发送与测试模式

适用场景

  1. 01实现订单邮件和附件通知
  2. 02存储并管理未读通知
  3. 03在事务提交后异步发送通知
  4. 04测试通知渠道与邮件内容

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/iserter/laravel-claude-agents --skill laravel-notification-patterns

安装后在 Claude Code 中输入/laravel-notification-patterns确认已加载。

详细介绍

通知模式

通知类结构

<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class OrderShipped extends Notification implements ShouldQueue
{
    use Queueable;

    public function __construct(
        public readonly Order $order,
    ) {}

    public function via(object $notifiable): array
    {
        return ['mail', 'database', 'broadcast'];
    }

    public function toMail(object $notifiable): MailMessage
    {
        return (new MailMessage)
            ->subject('Order Shipped')
            ->greeting("Hello {$notifiable->name}!")
            ->line("Your order #{$this->order->number} has been shipped.")
            ->action('Track Order', url("/orders/{$this->order->id}/track"))
            ->line('Thank you for your purchase!');
    }

    public function toArray(object $notifiable): array
    {
        return [
            'order_id' => $this->order->id,
            'order_number' => $this->order->number,
            'message' => "Order #{$this->order->number} has been shipped.",
        ];
    }
}

邮件通知

MailMessage 构建器
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->from('[email protected]', 'App Name')
        ->subject('Invoice Paid')
        ->greeting('Hello!')
        ->line('One of your invoices has been paid.')
        ->lineIf($this->amount > 100, 'This was a large payment.')
        ->action('View Invoice', $this->invoiceUrl)
        ->line('Thank you for using our application!')
        ->salutation('Regards, The Team');
}
Markdown 邮件模板
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->subject('Order Confirmation')
        ->markdown('mail.order.confirmed', [
            'order' => $this->order,
            'url' => route('orders.show', $this->order),
        ]);
}
{{-- resources/views/mail/order/confirmed.blade.php --}}
<x-mail::message>
# Order Confirmed

Your order **#{{ $order->number }}** has been confirmed.

<x-mail::table>
| Item       | Quantity | Price   |
|:-----------|:---------|:--------|
@foreach ($order->items as $item)
| {{ $item->name }} | {{ $item->quantity }} | ${{ $item->price }} |
@endforeach
</x-mail::table>

<x-mail::button :url="$url">
View Order
</x-mail::button>

Thanks,<br>
{{ config('app.name') }}
</x-mail::message>
附件
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->subject('Monthly Report')
        ->line('Please find your monthly report attached.')
        ->attach($this->reportPath, [
            'as' => 'report.pdf',
            'mime' => 'application/pdf',
        ])
        ->attachData($this->csvContent, 'data.csv', [
            'mime' => 'text/csv',
        ]);
}

数据库通知

设置
php artisan notifications:table
php artisan migrate
// Model must use Notifiable trait
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;
}
存储通知
public function toArray(object $notifiable): array
{
    return [
        'invoice_id' => $this->invoice->id,
        'amount' => $this->invoice->amount,
        'message' => "Invoice #{$this->invoice->number} paid.",
    ];
}
读取和管理通知
// Get all notifications
$notifications = $user->notifications;

// Get unread notifications
$unread = $user->unreadNotifications;

// Mark as read
$user->unreadNotifications->markAsRead();

// Mark a single notification as read
$notification->markAsRead();

// Mark as unread
$notification->markAsUnread();

// Delete old notifications
$user->notifications()->where('created_at', '<', now()->subMonths(3))->delete();

广播通知

use Illuminate\Notifications\Messages\BroadcastMessage;

public function toBroadcast(object $notifiable): BroadcastMessage
{
    return new BroadcastMessage([
        'invoice_id' => $this->invoice->id,
        'amount' => $this->invoice->amount,
    ]);
}

// Custom channel name (optional)
public function broadcastType(): string
{
    return 'invoice.paid';
}
// Listening with Echo
Echo.private(`App.Models.User.${userId}`)
    .notification((notification) => {
        console.log(notification.type);
        console.log(notification.invoice_id);
    });

Slack 通知

use Illuminate\Notifications\Slack\BlockKit\Blocks\SectionBlock;
use Illuminate\Notifications\Slack\SlackMessage;

public function toSlack(object $notifiable): SlackMessage
{
    return (new SlackMessage)
        ->text("Order #{$this->order->number} shipped")
        ->headerBlock("Order Shipped")
        ->sectionBlock(function (SectionBlock $block) {
            $block->text("Order *#{$this->order->number}* has been shipped.");
            $block->field("*Customer:*\n{$this->order->customer_name}")->markdown();
            $block->field("*Tracking:*\n{$this->order->tracking_number}")->markdown();
        });
}

将通知加入队列

基本队列处理
// ✅ Implement ShouldQueue
class OrderShipped extends Notification implements ShouldQueue
{
    use Queueable;

    // Per-channel queue configuration
    public function viaQueues(): array
    {
        return [
            'mail' => 'mail-queue',
            'database' => 'default',
            'slack' => 'slack-queue',
        ];
    }
}
提交后处理
// ✅ Only dispatch after database transaction commits
class OrderShipped extends Notification implements ShouldQueue
{
    use Queueable;

    public $afterCommit = true;
}
延迟通知
$user->notify(
    (new OrderShipped($order))->delay([
        'mail' => now()->addMinutes(5),
        'database' => now(),
    ])
);
重试和失败处理
class OrderShipped extends Notification implements ShouldQueue
{
    use Queueable;

    public $tries = 3;
    public $backoff = [30, 60, 120];

    public function failed(\Throwable $exception): void
    {
        // Handle failure (log, alert, etc.)
        Log::error('OrderShipped notification failed', [
            'order_id' => $this->order->id,
            'error' => $exception->getMessage(),
        ]);
    }
}

条件发送

public function shouldSend(object $notifiable, string $channel): bool
{
    // Don't send mail if user disabled email notifications
    if ($channel === 'mail') {
        return $notifiable->prefers_email_notifications;
    }

    // Don't notify about small amounts
    return $this->invoice->amount > 10;
}

按需通知

use Illuminate\Support\Facades\Notification;

// ✅ Send to an email address without a user model
Notification::route('mail', '[email protected]')
    ->route('slack', '#alerts')
    ->notify(new ServerHealthReport($server));

// ✅ With recipient name
Notification::route('mail', ['[email protected]' => 'Admin User'])
    ->notify(new WeeklyDigest());

发送通知

use Illuminate\Support\Facades\Notification;

// Via the Notifiable trait
$user->notify(new OrderShipped($order));

// Via the Notification facade (multiple recipients)
Notification::send($users, new OrderShipped($order));

// ❌ Don't loop to send individually
foreach ($users as $user) {
    $user->notify(new OrderShipped($order)); // Inefficient
}

// ✅ Send to a collection
Notification::send(User::all(), new SystemAnnouncement($message));

自定义通知渠道

class SmsChannel
{
    public function send(object $notifiable, Notification $notification): void
    {
        $message = $notification->toSms($notifiable);
        $phone = $notifiable->routeNotificationFor('sms', $notification);

        // Send SMS via your provider
        SmsProvider::send($phone, $message);
    }
}

// In the notification
public function via(object $notifiable): array
{
    return [SmsChannel::class, 'database'];
}

public function toSms(object $notifiable): string
{
    return "Your order #{$this->order->number} has been shipped.";
}

// On the notifiable model
public function routeNotificationForSms(Notification $notification): string
{
    return $this->phone_number;
}

测试通知

use Illuminate\Support\Facades\Notification;

public function test_order_shipped_notification_is_sent(): void
{
    Notification::fake();

    // Perform action that triggers notification
    $order = Order::factory()->create();
    $order->ship();

    // Assert notification was sent
    Notification::assertSentTo(
        $order->user,
        OrderShipped::class,
        function (OrderShipped $notification, array $channels) use ($order) {
            return $notification->order->id === $order->id
                && in_array('mail', $channels)
                && in_array('database', $channels);
        }
    );

    // Assert not sent to other users
    Notification::assertNotSentTo(
        User::factory()->create(),
        OrderShipped::class,
    );

    // Assert count
    Notification::assertSentToTimes($order->user, OrderShipped::class, 1);

    // Assert nothing sent
    Notification::assertNothingSent();
}
测试邮件内容
public function test_order_shipped_mail_content(): void
{
    $order = Order::factory()->create();
    $notification = new OrderShipped($order);

    $mail = $notification->toMail($order->user);

    $this->assertEquals('Order Shipped', $mail->subject);
    $this->assertStringContainsString($order->number, $mail->render());
}

检查清单

  • 通知类通过 via() 使用适当的渠道
  • 邮件通知使用 MailMessage 构建器或 Markdown 模板
  • 已迁移数据库通知表
  • toArray() 仅返回可序列化的数据,以便存储到数据库
  • 长时间运行的通知实现 ShouldQueue
  • 通过 viaQueues() 按渠道配置队列名称
  • 在数据库事务中发送通知时设置 afterCommit
  • 使用 shouldSend() 实现条件投递逻辑
  • 对非模型接收者使用按需通知
  • 批量发送使用 Notification::send(),而不是循环
  • 通知测试使用 Notification::fake()
  • 为排队通知配置重试和失败处理

评论

支持 Markdown