Laravel 13 通知系统详解

Laravel 的通知系统提供了一种优雅的方式来发送各种类型的通知,支持邮件、短信、数据库、Slack 等多种渠道。

创建通知

生成通知类

1
2
php artisan make:notification OrderShipped
php artisan make:notification InvoicePaid --markdown=mail.invoice.paid

通知类结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\DatabaseMessage;
use App\Models\Order;

class OrderShipped extends Notification
{
use Queueable;

public function __construct(
public Order $order
) {}

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

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

public function toDatabase(object $notifiable): array
{
return [
'order_id' => $this->order->id,
'message' => 'Your order has been shipped.',
];
}
}

发送通知

使用 Notifiable trait

1
2
3
4
5
6
7
8
use Illuminate\Notifications\Notifiable;

class User extends Model
{
use Notifiable;
}

$user->notify(new OrderShipped($order));

使用 Notification 门面

1
2
3
4
5
use Illuminate\Support\Facades\Notification;
use App\Notifications\OrderShipped;

Notification::send($users, new OrderShipped($order));
Notification::sendNow($users, new OrderShipped($order));

通知渠道

邮件渠道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)
->subject('Order Shipped')
->from('orders@example.com', 'Store')
->greeting('Hello ' . $notifiable->name . '!')
->line('Your order #' . $this->order->id . ' has been shipped.')
->action('Track Order', $this->order->trackingUrl())
->line('Estimated delivery: ' . $this->order->estimatedDelivery())
->line('Thank you for shopping with us!');
}

// Markdown 邮件
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)
->markdown('mail.order.shipped', [
'order' => $this->order,
]);
}

数据库渠道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public function toDatabase(object $notifiable): array
{
return [
'order_id' => $this->order->id,
'tracking_number' => $this->order->tracking_number,
'message' => 'Your order has been shipped.',
];
}

// 或使用自定义格式
public function toDatabase(object $notifiable): DatabaseMessage
{
return new DatabaseMessage([
'title' => 'Order Shipped',
'body' => 'Your order has been shipped.',
'action_url' => route('orders.track', $this->order->id),
]);
}

广播渠道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use Illuminate\Notifications\Messages\BroadcastMessage;

public function toBroadcast(object $notifiable): BroadcastMessage
{
return new BroadcastMessage([
'order_id' => $this->order->id,
'message' => 'Your order has been shipped.',
]);
}

public function broadcastOn()
{
return new PrivateChannel('user.'.$this->notifiable->id);
}

SMS 渠道

1
2
3
4
5
6
7
8
use Illuminate\Notifications\Messages\VonageMessage;

public function toVonage(object $notifiable): VonageMessage
{
return (new VonageMessage)
->content('Your order has been shipped!')
->from('15554443333');
}

Slack 渠道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
use Illuminate\Notifications\Messages\SlackMessage;

public function toSlack(object $notifiable): SlackMessage
{
return (new SlackMessage)
->from('Order Bot', ':package:')
->to('#orders')
->content('Order #' . $this->order->id . ' has been shipped!')
->attachment(function ($attachment) {
$attachment->title('Order Details', route('orders.show', $this->order->id))
->fields([
'Customer' => $this->order->user->name,
'Total' => '$' . number_format($this->order->total, 2),
'Status' => 'Shipped',
]);
});
}

自定义渠道

创建自定义渠道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

namespace App\Channels;

use Illuminate\Notifications\Notification;

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

// 发送短信
app('sms')->send($phone, $message);
}
}

使用自定义渠道

1
2
3
4
5
6
7
8
9
public function via(object $notifiable): array
{
return ['mail', \App\Channels\SmsChannel::class];
}

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

通知路由

自定义邮件路由

1
2
3
4
5
6
7
class User extends Model
{
public function routeNotificationForMail(): string
{
return $this->email;
}
}

自定义 SMS 路由

1
2
3
4
5
6
7
class User extends Model
{
public function routeNotificationForVonage(): string
{
return $this->phone;
}
}

自定义 Slack 路由

1
2
3
4
5
6
7
class User extends Model
{
public function routeNotificationForSlack(): string
{
return $this->slack_webhook_url;
}
}

队列通知

队列发送

1
2
3
4
5
6
7
8
9
class OrderShipped extends Notification
{
use Queueable;

public function __construct()
{
$this->onQueue('notifications');
}
}

延迟发送

1
$user->notify((new OrderShipped($order))->delay(now()->addMinutes(5)));

数据库通知

创建通知表

1
2
php artisan notifications:table
php artisan migrate

访问通知

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$user = User::find(1);

foreach ($user->notifications as $notification) {
echo $notification->type;
echo $notification->data['message'];
echo $notification->created_at;
}

// 未读通知
$unread = $user->unreadNotifications;

// 标记为已读
$notification->markAsRead();
$user->unreadNotifications->markAsRead();

// 删除通知
$notification->delete();
$user->notifications()->delete();

通知事件

监听事件

1
2
3
4
5
6
7
8
9
10
11
12
13
use Illuminate\Support\Facades\Event;

Event::listen(function (\Illuminate\Notifications\Events\NotificationSent $event) {
// 通知发送后
$event->channel;
$event->notifiable;
$event->notification;
$event->response;
});

Event::listen(function (\Illuminate\Notifications\Events\NotificationFailed $event) {
// 通知发送失败
});

测试通知

通知伪造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use Illuminate\Support\Facades\Notification;
use App\Notifications\OrderShipped;

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

$order = Order::factory()->create();
$user = User::factory()->create();

$user->notify(new OrderShipped($order));

Notification::assertSentTo($user, OrderShipped::class);
Notification::assertSentTo(
$user,
OrderShipped::class,
function ($notification) use ($order) {
return $notification->order->id === $order->id;
}
);
Notification::assertNotSentTo($user, InvoicePaid::class);
Notification::assertNothingSent();
}

最佳实践

1. 使用队列

1
2
3
4
5
6
7
8
// 好的做法
class OrderShipped extends Notification
{
use Queueable;
}

// 发送时使用队列
$user->notify(new OrderShipped($order));

2. 条件渠道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public function via(object $notifiable): array
{
$channels = ['database'];

if ($notifiable->email_notifications) {
$channels[] = 'mail';
}

if ($notifiable->sms_notifications) {
$channels[] = 'vonage';
}

return $channels;
}

3. 分离通知逻辑

1
2
3
4
5
6
7
8
9
// 好的做法:每个通知类只处理一种通知
class OrderShipped extends Notification {}
class OrderDelivered extends Notification {}
class OrderCancelled extends Notification {}

// 不好的做法:一个通知类处理多种情况
class OrderNotification extends Notification {
public function __construct($type) {}
}

总结

Laravel 13 的通知系统提供了强大而灵活的通知发送能力。通过合理使用多种通知渠道、队列发送和数据库通知,可以构建出完善的通知系统。记住使用队列发送通知以避免阻塞请求、根据用户偏好选择通知渠道、并充分利用通知事件来跟踪通知状态。通知是用户沟通的重要渠道,良好的通知体验可以提升用户满意度。