关于 laravel-notification-patterns
面向 Laravel 开发者的通知模式参考,涵盖邮件、数据库、广播和 Slack 等渠道,以及队列、条件发送与测试实践。
主要功能
- 01邮件通知与 Markdown 模板
- 02数据库通知的存储和读取
- 03广播及 Slack 通知
- 04队列、延迟与失败处理
- 05条件发送和自定义渠道
- 06通知发送与测试模式
适用场景
- 01实现订单邮件和附件通知
- 02存储并管理未读通知
- 03在事务提交后异步发送通知
- 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