如何让Laravel队列仅重试失败的通知渠道而非整个通知?
解决Laravel通知重试仅发送失败渠道的问题
不需要创建新通知,通过记录已成功发送的渠道,让重试时自动排除已完成的渠道即可,具体实现分两种方案(缓存/数据库),推荐数据库方案更可靠:
方案一:用数据库记录发送状态(推荐)
1. 创建发送状态记录表
生成迁移文件:
php artisan make:migration create_notification_channel_logs_table
编辑迁移文件,添加记录字段:
public function up() { Schema::create('notification_channel_logs', function (Blueprint $table) { $table->id(); $table->string('notification_type'); // 通知类名 $table->unsignedBigInteger('notifiable_id'); // 接收者ID $table->string('notifiable_type'); // 接收者模型类名 $table->string('channel'); // 渠道标识 $table->timestamp('sent_at')->useCurrent(); // 唯一索引,避免重复记录 $table->unique(['notification_type', 'notifiable_id', 'notifiable_type', 'channel']); }); }
执行迁移:
php artisan migrate
2. 创建事件监听器记录发送状态
生成监听器:
php artisan make:listener MarkNotificationChannelAsSent
编辑监听器,在渠道发送成功时写入记录:
namespace App\Listeners; use Illuminate\Notifications\Events\NotificationSent; use App\Models\NotificationChannelLog; class MarkNotificationChannelAsSent { public function handle(NotificationSent $event) { // 写入或更新发送记录 NotificationChannelLog::updateOrCreate( [ 'notification_type' => get_class($event->notification), 'notifiable_id' => $event->notifiable->getKey(), 'notifiable_type' => get_class($event->notifiable), 'channel' => $event->channel, ], ['sent_at' => now()] ); } }
3. 注册事件监听
打开app/Providers/EventServiceProvider.php,添加事件与监听器的关联:
protected $listen = [ \Illuminate\Notifications\Events\NotificationSent::class => [ \App\Listeners\MarkNotificationChannelAsSent::class, ], ];
4. 修改通知类的via方法
在LineLoginNotification中,修改via方法过滤已发送渠道:
use App\Models\NotificationChannelLog; public function via($notifiable) { $allChannels = ['mail', MyChannel::class]; // 查询已成功发送的渠道 $sentChannels = NotificationChannelLog::where([ 'notification_type' => get_class($this), 'notifiable_id' => $notifiable->getKey(), 'notifiable_type' => get_class($notifiable), ])->pluck('channel')->toArray(); // 只返回未发送的渠道 return array_diff($allChannels, $sentChannels); }
5. 确保渠道发送失败时抛出异常
修改MyChannel的send方法,发送失败时抛出异常(让队列触发重试):
public function send($notifiable, $notification) { $line_id = $notifiable->line_id; $msg = $notification->toMyChannelMethod($notifiable); $messageService = new MyMessageService(); // 根据实际业务判断发送是否失败,这里假设sendSimpleMsg失败返回false if (!$messageService->sendSimpleMsg($msg, $line_id)) { throw new \RuntimeException('MyChannel消息发送失败'); } }
邮件渠道默认发送失败会抛出异常,无需额外修改。
方案二:用缓存临时记录(快速实现)
如果不需要持久化记录,可改用缓存:
1. 创建事件监听器记录缓存
生成监听器后修改为:
namespace App\Listeners; use Illuminate\Notifications\Events\NotificationSent; use Illuminate\Support\Facades\Cache; class MarkNotificationChannelAsSent { public function handle(NotificationSent $event) { $key = $this->getCacheKey($event); // 缓存7天,可根据需求调整 Cache::set($key, true, now()->addDays(7)); } protected function getCacheKey(NotificationSent $event): string { return sprintf( 'notification_sent:%s:%s:%s', get_class($event->notification), $event->notifiable->getKey(), $event->channel ); } }
2. 修改通知类的via方法
use Illuminate\Support\Facades\Cache; public function via($notifiable) { $allChannels = ['mail', MyChannel::class]; return array_filter($allChannels, function ($channel) use ($notifiable) { $key = sprintf( 'notification_sent:%s:%s:%s', get_class($this), $notifiable->getKey(), $channel ); // 过滤掉已缓存的已发送渠道 return !Cache::has($key); }); }
后续步骤同方案一的3、5。
原理说明
当通知首次执行时,via返回所有渠道,逐个发送:
- 发送成功的渠道会触发
NotificationSent事件,监听器记录已发送状态; - 若某渠道发送失败抛出异常,队列会重试该通知;
- 重试时
via方法会自动排除已记录的成功渠道,仅尝试发送失败的渠道。
内容的提问来源于stack exchange,提问作者David Ko
相关产品推荐
相关产品推荐

