laravel
5. 深入探讨
通知

介绍

除了支持发送电子邮件外,Laravel 还支持通过多种交付渠道发送通知,包括电子邮件、短信(通过Vonage (opens in a new tab),以前称为 Nexmo)和Slack (opens in a new tab)。此外,已经创建了各种社区构建的通知渠道 (opens in a new tab),以便通过数十种不同的渠道发送通知!通知也可以存储在数据库中,以便在您的 Web 界面中显示。

通常,通知应该是简短的信息性消息,通知用户您的应用程序中发生的事情。例如,如果您正在编写计费应用程序,您可以通过电子邮件和短信渠道向用户发送“发票已支付”通知。

生成通知

在 Laravel 中,每个通知都由一个单独的类表示,该类通常存储在app/Notifications目录中。如果您在应用程序中没有看到此目录,不用担心 - 当您运行make:notification Artisan 命令时,将为您创建:

php artisan make:notification InvoicePaid

此命令将在您的app/Notifications目录中放置一个新的通知类。每个通知类都包含一个via方法和多个消息构建方法,例如toMailtoDatabase,这些方法将通知转换为针对该特定渠道定制的消息。

发送通知

使用可通知特征

通知可以通过两种方式发送:使用Notifiable特征的notify方法或使用Notification外观Notifiable特征默认包含在您的应用程序的App\Models\User模型中:

<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;
}

此特征提供的notify方法期望接收一个通知实例:

use App\Notifications\InvoicePaid;

$user->notify(new InvoicePaid($invoice));

[!注意]
请记住,您可以在任何模型上使用Notifiable特征。您不仅限于将其包含在User模型中。

使用通知外观

或者,您可以通过Notification外观发送通知。当您需要向多个可通知实体(例如用户集合)发送通知时,此方法很有用。要使用外观发送通知,请将所有可通知实体和通知实例传递给send方法:

use Illuminate\Support\Facades\Notification;

Notification::send($users, new InvoicePaid($invoice));

您还可以使用sendNow方法立即发送通知。即使通知实现了ShouldQueue接口,此方法也会立即发送通知:

Notification::sendNow($developers, new DeploymentCompleted($deployment));

指定交付渠道

每个通知类都有一个via方法,用于确定通知将通过哪些渠道交付。通知可以通过maildatabasebroadcastvonageslack渠道发送。

[!注意]
如果您想使用其他交付渠道,如 Telegram 或 Pusher,请查看社区驱动的Laravel 通知渠道网站 (opens in a new tab)

via方法接收一个$notifiable实例,该实例将是要向其发送通知的类的实例。您可以使用$notifiable来确定应将通知交付到哪些渠道:

/**
 * 获取通知的交付渠道。
 *
 * @return array<int, string>
 */
public function via(object $notifiable): array
{
    return $notifiable->prefers_sms? ['vonage'] : ['mail', 'database'];
}

排队通知

[!警告]
在排队通知之前,您应该配置您的队列并启动一个工作进程

发送通知可能需要时间,特别是如果渠道需要进行外部 API 调用来交付通知。为了加快应用程序的响应时间,可以通过将ShouldQueue接口和Queueable特征添加到您的类中,让您的通知排队。使用make:notification命令生成的所有通知都已经导入了接口和特征,因此您可以立即将它们添加到您的通知类中:

<?php

namespace App\Notifications;

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

class InvoicePaid extends Notification implements ShouldQueue
{
    use Queueable;

    //...
}

一旦将ShouldQueue接口添加到您的通知中,您就可以像往常一样发送通知。Laravel 将检测到类上的ShouldQueue接口,并自动将通知的交付排队:

$user->notify(new InvoicePaid($invoice));

当排队通知时,将为每个收件人和渠道组合创建一个排队作业。例如,如果您的通知有三个收件人和两个渠道,则将向队列分派六个作业。

延迟通知

如果您想延迟通知的交付,可以将delay方法链接到您的通知实例化上:

$delay = now()->addMinutes(10);

$user->notify((new InvoicePaid($invoice))->delay($delay));

您可以将一个数组传递给delay方法,以指定特定渠道的延迟量:

$user->notify((new InvoicePaid($invoice))->delay([
    'mail' => now()->addMinutes(5),
    'sms' => now()->addMinutes(10),
]));

或者,您可以在通知类本身定义一个withDelay方法。withDelay方法应该返回一个包含渠道名称和延迟值的数组:

/**
 * 确定通知的交付延迟。
 *
 * @return array<string, \Illuminate\Support\Carbon>
 */
public function withDelay(object $notifiable): array
{
    return [
        'mail' => now()->addMinutes(5),
        'sms' => now()->addMinutes(10),
    ];
}

自定义通知队列连接

默认情况下,排队通知将使用您的应用程序的默认队列连接进行排队。如果您想为特定通知指定应使用的不同连接,可以从通知的构造函数中调用onConnection方法:

<?php

namespace App\Notifications;

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

class InvoicePaid extends Notification implements ShouldQueue
{
    use Queueable;

    /**
     * 创建一个新的通知实例。
     */
    public function __construct()
    {
        $this->onConnection('redis');
    }
}

或者,如果您想为通知支持的每个通知渠道指定应使用的特定队列连接,可以在您的通知上定义一个viaConnections方法。此方法应返回一个包含渠道名称/队列连接名称对的数组:

/**
 * 确定应为每个通知渠道使用哪些连接。
 *
 * @return array<string, string>
 */
public function viaConnections(): array
{
    return [
        'mail' => 'redis',
        'database' => 'sync',
    ];
}

自定义通知渠道队列

如果您想为通知支持的每个通知渠道指定应使用的特定队列,可以在您的通知上定义一个viaQueues方法。此方法应返回一个包含渠道名称/队列名称对的数组:

/**
 * 确定应为每个通知渠道使用哪些队列。
 *
 * @return array<string, string>
 */
public function viaQueues(): array
{
    return [
        'mail' => 'mail-queue',
        'slack' => 'slack-queue',
    ];
}

排队通知中间件

排队通知可以定义中间件就像排队作业一样。要开始使用,在您的通知类上定义一个middleware方法。middleware方法将接收$notifiable$channel变量,这使您可以根据通知的目的地自定义返回的中间件:

use Illuminate\Queue\Middleware\RateLimited;

/**
 * 获取通知作业应通过的中间件。
 *
 * @return array<int, object>
 */
public function middleware(object $notifiable, string $channel)
{
    return match ($channel) {
        'email' => [new RateLimited('postmark')],
        'slack' => [new RateLimited('slack')],
        default => [],
    };
}

排队通知和数据库事务

当在数据库事务内分派排队通知时,它们可能会在数据库事务提交之前由队列处理。当发生这种情况时,在数据库事务期间对模型或数据库记录所做的任何更新可能尚未反映在数据库中。此外,在事务中创建的任何模型或数据库记录可能不存在于数据库中。如果您的通知依赖于这些模型,则在处理发送排队通知的作业时可能会发生意外错误。

如果您的队列连接的after_commit配置选项设置为false,您仍然可以在发送通知时调用afterCommit方法来指示应在所有打开的数据库事务提交后分派特定的排队通知:

use App\Notifications\InvoicePaid;

$user->notify((new InvoicePaid($invoice))->afterCommit());

或者,您可以从通知的构造函数中调用afterCommit方法:

<?php

namespace App\Notifications;

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

class InvoicePaid extends Notification implements ShouldQueue
{
    use Queueable;

    /**
     * 创建一个新的通知实例。
     */
    public function __construct()
    {
        $this->afterCommit();
    }
}

[!注意]
要了解有关解决这些问题的更多信息,请查看有关排队作业和数据库事务的文档。

确定是否应发送排队通知

在将排队通知分派到队列进行后台处理后,通常会由队列工作者接受并发送到其预期收件人。

但是,如果您想在队列工作者处理排队通知后最终确定是否应发送该通知,可以在通知类上定义一个shouldSend方法。如果此方法返回false,则不会发送通知:

/**
 * 确定是否应发送通知。
 */
public function shouldSend(object $notifiable, string $channel): bool
{
    return $this->invoice->isPaid();
}

按需通知

有时您可能需要向未作为您的应用程序的“用户”存储的人发送通知。使用Notification外观的route方法,您可以在发送通知之前指定临时通知路由信息:

use Illuminate\Broadcasting\Channel;
use Illuminate\Support\Facades\Notification;

Notification::route('mail', 'taylor@example.com')
            ->route('vonage', '5555555555')
            ->route('slack', '#slack-channel')
            ->route('broadcast', [new Channel('channel-name')])
            ->notify(new InvoicePaid($invoice));

如果您想在向mail路由发送按需通知时提供收件人的姓名,可以提供一个数组,该数组将电子邮件地址作为键,将姓名作为数组第一个元素的值:

Notification::route('mail', [
    'barrett@example.com' => 'Barrett Blair',
])->notify(new InvoicePaid($invoice));

使用routes方法,您可以一次为多个通知渠道提供临时路由信息:

Notification::routes([
    'mail' => ['barrett@example.com' => 'Barrett Blair'],
    'vonage' => '5555555555',
])->notify(new InvoicePaid($invoice));

邮件通知

格式化邮件消息

如果通知支持作为电子邮件发送,则应在通知类上定义一个toMail方法。此方法将接收一个$notifiable实体,并应返回一个Illuminate\Notifications\Messages\MailMessage实例。

MailMessage类包含一些简单的方法,可帮助您构建事务性电子邮件消息。邮件消息可以包含文本行以及“操作调用”。让我们看一个示例toMail方法:

/**
 * 获取通知的邮件表示形式。
 */
public function toMail(object $notifiable): MailMessage
{
    $url = url('/invoice/'.$this->invoice->id);

    return (new MailMessage)
                ->greeting('Hello!')
                ->line('One of your invoices has been paid!')
                ->lineIf($this->amount > 0, "Amount paid: {$this->amount}")
                ->action('View Invoice', $url)
                ->line('Thank you for using our application!');
}

[!注意]
请注意,我们在toMail方法中使用了$this->invoice->id。您可以将通知生成消息所需的任何数据传递到通知的构造函数中。

在此示例中,我们注册了一个问候语、一行文本、一个操作调用,然后是另一行文本。MailMessage对象提供的这些方法使格式化小型事务性电子邮件变得简单快捷。然后,邮件渠道会将消息组件转换为一个美观、响应式的 HTML 电子邮件模板以及一个纯文本对应项。以下是由mail渠道生成的电子邮件的示例:

[!注意]
在发送邮件通知时,请确保在您的config/app.php配置文件中设置name配置选项。此值将用于您的邮件通知消息的页眉和页脚。

错误消息

一些通知会通知用户错误,例如发票付款失败。在构建消息时,您可以通过调用error方法来指示邮件消息是关于错误的。当在邮件消息上使用error方法时,操作调用按钮将是红色而不是黑色:

/**
 * 获取通知的邮件表示形式。
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
                ->error()
                ->subject('Invoice Payment Failed')
                ->line('...');
}

其他邮件通知格式化选项

您可以使用view方法指定应用于呈现通知电子邮件的自定义模板,而不是在通知类中定义文本“行”:

/**
 * 获取通知的邮件表示形式。
 */
public function toMail(object $notifiable): MailMessage
{
   

附件

若要向电子邮件通知添加附件,在构建邮件时使用 attach 方法。attach 方法的第一个参数为文件的绝对路径:

/**
 * 获取通知的邮件表示形式。
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
                ->greeting('Hello!')
                ->attach('/path/to/file');
}

[!NOTE]
通知邮件消息提供的 attach 方法也接受可附加对象。请查阅全面的可附加对象文档以了解更多信息。

当向消息附加文件时,您还可以通过将一个 array 作为 attach 方法的第二个参数来指定显示名称和/或 MIME 类型:

/**
 * 获取通知的邮件表示形式。
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
                ->greeting('Hello!')
                ->attach('/path/to/file', [
                    'as' => 'name.pdf',
                    'mime' => 'application/pdf',
                ]);
}

与在可邮寄对象中附加文件不同,您不能使用 attachFromStorage 直接从存储磁盘附加文件。您应该使用 attach 方法并提供存储磁盘上文件的绝对路径。或者,您可以从 toMail 方法中返回一个可邮寄对象

use App\Mail\InvoicePaid as InvoicePaidMailable;

/**
 * 获取通知的邮件表示形式。
 */
public function toMail(object $notifiable): Mailable
{
    return (new InvoicePaidMailable($this->invoice))
                ->to($notifiable->email)
                ->attachFromStorage('/path/to/file');
}

必要时,可以使用 attachMany 方法将多个文件附加到消息中:

/**
 * 获取通知的邮件表示形式。
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
                ->greeting('Hello!')
                ->attachMany([
                    '/path/to/forge.svg',
                    '/path/to/vapor.svg' => [
                        'as' => 'Logo.svg',
                        'mime' => 'image/svg+xml',
                    ],
                ]);
}

原始数据附件

可以使用 attachData 方法将原始字节字符串作为附件附加。调用 attachData 方法时,您应该提供应分配给附件的文件名:

/**
 * 获取通知的邮件表示形式。
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
                ->greeting('Hello!')
                ->attachData($this->pdf, 'name.pdf', [
                    'mime' => 'application/pdf',
                ]);
}

添加标签和元数据

一些第三方电子邮件提供商,如 Mailgun 和 Postmark,支持邮件“标签”和“元数据”,可用于对您的应用程序发送的电子邮件进行分组和跟踪。您可以通过 tagmetadata 方法向电子邮件消息添加标签和元数据:

/**
 * 获取通知的邮件表示形式。
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
                ->greeting('Comment Upvoted!')
                ->tag('upvote')
                ->metadata('comment_id', $this->comment->id);
}

如果您的应用程序使用 Mailgun 驱动程序,您可以查阅 Mailgun 的文档以获取有关标签 (opens in a new tab)元数据 (opens in a new tab)的更多信息。同样,您也可以查阅 Postmark 文档以获取有关其对标签 (opens in a new tab)元数据 (opens in a new tab)支持的更多信息。

如果您的应用程序使用 Amazon SES 发送电子邮件,您应该使用 metadata 方法将SES“标签” (opens in a new tab)附加到消息中。

自定义 Symfony 消息

MailMessage 类的 withSymfonyMessage 方法允许您注册一个闭包,在发送消息之前,该闭包将与 Symfony 消息实例一起被调用。这使您有机会在消息传递之前对其进行深度自定义:

use Symfony\Component\Mime\Email;

/**
 * 获取通知的邮件表示形式。
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
                ->withSymfonyMessage(function (Email $message) {
                    $message->getHeaders()->addTextHeader(
                        'Custom-Header', 'Header Value'
                    );
                });
}

使用可邮寄对象

如果需要,您可以从通知的 toMail 方法中返回一个完整的可邮寄对象。当返回 Mailable 而不是 MailMessage 时,您需要使用可邮寄对象的 to 方法指定消息接收者:

use App\Mail\InvoicePaid as InvoicePaidMailable;
use Illuminate\Mail\Mailable;

/**
 * 获取通知的邮件表示形式。
 */
public function toMail(object $notifiable): Mailable
{
    return (new InvoicePaidMailable($this->invoice))
                ->to($notifiable->email);
}

可邮寄对象和按需通知

如果您正在发送按需通知,则传递给 toMail 方法的 $notifiable 实例将是 Illuminate\Notifications\AnonymousNotifiable 的一个实例,该实例提供了一个 routeNotificationFor 方法,可用于检索按需通知应发送到的电子邮件地址:

use App\Mail\InvoicePaid as InvoicePaidMailable;
use Illuminate\Notifications\AnonymousNotifiable;
use Illuminate\Mail\Mailable;

/**
 * 获取通知的邮件表示形式。
 */
public function toMail(object $notifiable): Mailable
{
    $address = $notifiable instanceof AnonymousNotifiable
           ? $notifiable->routeNotificationFor('mail')
            : $notifiable->email;

    return (new InvoicePaidMailable($this->invoice))
                ->to($address);
}

预览邮件通知

在设计邮件通知模板时,像典型的 Blade 模板一样在浏览器中快速预览呈现的邮件消息是很方便的。因此,Laravel 允许您从路由闭包或控制器中直接返回由邮件通知生成的任何邮件消息。当返回 MailMessage 时,它将在浏览器中呈现并显示,使您能够快速预览其设计,而无需将其发送到实际的电子邮件地址:

use App\Models\Invoice;
use App\Notifications\InvoicePaid;

Route::get('/notification', function () {
    $invoice = Invoice::find(1);

    return (new InvoicePaid($invoice))
                ->toMail($invoice->user);
});

Markdown 邮件通知

Markdown 邮件通知允许您利用邮件通知的预构建模板,同时为您提供更多编写更长、自定义消息的自由。由于消息是用 Markdown 编写的,Laravel 能够为消息呈现美观、响应式的 HTML 模板,同时还会自动生成纯文本对应版本。

生成消息

要生成具有相应 Markdown 模板的通知,您可以使用 make:notification Artisan 命令的 --markdown 选项:

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

与所有其他邮件通知一样,使用 Markdown 模板的通知应在其通知类上定义一个 toMail 方法。但是,不是使用 lineaction 方法来构建通知,而是使用 markdown 方法指定应使用的 Markdown 模板的名称。您可以将希望提供给模板的一个数据数组作为该方法的第二个参数传递:

/**
 * 获取通知的邮件表示形式。
 */
public function toMail(object $notifiable): MailMessage
{
    $url = url('/invoice/'.$this->invoice->id);

    return (new MailMessage)
                ->subject('Invoice Paid')
                ->markdown('mail.invoice.paid', ['url' => $url]);
}

编写消息

Markdown 邮件通知结合了 Blade 组件和 Markdown 语法,使您能够轻松构建通知,同时利用 Laravel 预先制作的通知组件:

<x-mail::message>
# Invoice Paid
 
Your invoice has been paid!
 
<x-mail::button :url="$url">
View Invoice
</x-mail::button>
 
Thanks,<br>
{{ config('app.name') }}
</x-mail::message>

按钮组件

按钮组件呈现一个居中的按钮链接。该组件接受两个参数,一个 url 和一个可选的 color。支持的颜色是 primarygreenred。您可以根据需要向通知中添加任意数量的按钮组件:

<x-mail::button :url="$url" color="green">
View Invoice
</x-mail::button>

面板组件

面板组件将给定的文本块呈现在一个面板中,该面板的背景颜色与通知的其他部分略有不同。这可以使您将注意力吸引到给定的文本块上:

<x-mail::panel>
This is the panel content.
</x-mail::panel>

表格组件

表格组件允许您将 Markdown 表格转换为 HTML 表格。该组件将 Markdown 表格作为其内容。使用默认的 Markdown 表格对齐语法支持表格列对齐:

<x-mail::table>
| Laravel       | Table         | Example       |
| ------------- | :-----------: | ------------: |
| Col 2 is      | Centered      | $10           |
| Col 3 is      | Right-Aligned | $20           |
</x-mail::table>

自定义组件

您可以将所有 Markdown 通知组件导出到您自己的应用程序中进行自定义。要导出组件,使用 vendor:publish Artisan 命令发布 laravel-mail 资产标签:

php artisan vendor:publish --tag=laravel-mail

此命令将把 Markdown 邮件组件发布到 resources/views/vendor/mail 目录。mail 目录将包含一个 html 和一个 text 目录,每个目录都包含每个可用组件的各自表示形式。您可以自由地根据自己的喜好自定义这些组件。

自定义 CSS

导出组件后,resources/views/vendor/mail/html/themes 目录将包含一个 default.css 文件。您可以自定义此文件中的 CSS,您的样式将自动内联到 Markdown 通知的 HTML 表示中。

如果您想为 Laravel 的 Markdown 组件构建一个全新的主题,您可以在 html/themes 目录中放置一个 CSS 文件。命名并保存您的 CSS 文件后,更新 mail 配置文件的 theme 选项以匹配您的新主题的名称。

要为单个通知自定义主题,您可以在构建通知的邮件消息时调用 theme 方法。theme 方法接受发送通知时应使用的主题的名称:

/**
 * 获取通知的邮件表示形式。
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
                ->theme('invoice')
                ->subject('Invoice Paid')
                ->markdown('mail.invoice.paid', ['url' => $url]);
}

数据库通知

先决条件

database 通知通道将通知信息存储在数据库表中。此表将包含诸如通知类型以及描述通知的 JSON 数据结构等信息。

您可以查询该表以在您的应用程序的用户界面中显示通知。但是,在您可以这样做之前,您需要创建一个数据库表来保存您的通知。您可以使用 make:notifications-table 命令生成一个具有正确表模式的迁移

php artisan make:notifications-table
 
php artisan migrate

[!NOTE]
如果您的可通知模型正在使用UUID 或 ULID 主键,则应在通知表迁移中将 morphs 方法替换为 uuidMorphsulidMorphs

格式化数据库通知

如果通知支持存储在数据库表中,则应在通知类上定义 toDatabasetoArray 方法。此方法将接收一个 $notifiable 实体,并应返回一个普通的 PHP 数组。返回的数组将被编码为 JSON 并存储在您的 notifications 表的 data 列中。让我们看一个 toArray 方法的示例:

/**
 * 获取通知的数组表示形式。
 *
 * @return array<string, mixed>
 */
public function toArray(object $notifiable): array
{
    return [
        'invoice_id' => $this->invoice->id,
        'amount' => $this->invoice->amount,
    ];
}

当通知存储在您的应用程序的数据库中时,type 列将填充通知的类名。但是,您可以通过在通知类上定义一个 databaseType 方法来自定义此行为:

/**
 * 获取通知的数据库类型。
 *
 * @return string
 */
public function databaseType(object $notifiable): string
{
    return 'invoice-paid';
}

toDatabasetoArray

toArray 方法也被 broadcast 通道用于确定要广播到您的 JavaScript 驱动的前端的数据。如果您希望为 databasebroadcast 通道提供两个不同的数组表示形式,则应定义一个 toDatabase 方法而不是 toArray 方法。

访问通知

一旦通知存储在数据库中,您需要一种方便的方法从您的可通知实体中访问它们。包含在 Laravel 默认的 App\Models\User 模型中的 Illuminate\Notifications\Notifiable 特征,包含一个 notifications Eloquent 关系,该关系返回该实体的通知。要获取通知,您可以像访问任何其他 Eloquent 关系一样访问此方法。默认情况下,通知将按 created_at 时间戳进行排序,最新的通知在集合的开头:

$user = App\Models\User::find(1);

foreach ($user->notifications as $notification) {
    echo $notification->type;
}

如果您只想检索“未读”通知,则可以使用 unreadNotifications 关系。同样,这些通知将按 created_at 时间戳进行排序,最新的通知在集合的开头:

$user = App\Models\User::find(1);

foreach ($user->unreadNotifications as $notification) {
    echo $notification->type;
}

[!NOTE]
要从您的 JavaScript 客户端访问您的通知,您应该为您的应用程序定义一个通知控制器,该控制器返回可通知实体(例如当前用户)的通知。然后,您可以从您的 JavaScript 客户端向该控制器的 URL 发出 HTTP 请求。

将通知标记为已读

通常,当用户查看通知时,您会希望将其标记为“已读”。Illuminate\Notifications\Notifiable 特征提供了一个 markAsRead 方法,该方法会更新通知的数据库记录中的 read_at 列:

$user = App\Models\User::find(1);

foreach ($user->unreadNotifications as $notification) {
    $notification->markAsRead();
}

但是,您不必遍历每个通知,而是可以直接在通知集合上使用 markAsRead 方法:

$user->unreadNotifications->markAsRead();

您还可以使用批量更新查询将所有通知标记为已读,而无需从数据库中检索它们:

$user = App\Models\User::find(1);

$user->unreadNotifications()->update(['read_at' => now()]);

您可以 delete 通知以将其从表中完全删除:

$user->notifications()->delete();

广播通知

先决条件

在广播通知之前,您应该配置并熟悉 Laravel 的事件广播服务。事件广播为您的 JavaScript 驱动的前端提供了一种对服务器端 Laravel 事件做出反应的方式。

格式化广播通知

broadcast

Unicode 内容

如果您的 SMS 消息将包含 Unicode 字符,在构建 VonageMessage 实例时,您应该调用 unicode 方法:

use Illuminate\Notifications\Messages\VonageMessage;

/**
 * 获取通知的 Vonage / SMS 表示形式。
 */
public function toVonage(object $notifiable): VonageMessage
{
    return (new VonageMessage)
                ->content('您的 Unicode 消息')
                ->unicode();
}

自定义“发件人”号码

如果您希望从与您的 VONAGE_SMS_FROM 环境变量指定的电话号码不同的电话号码发送一些通知,您可以在 VonageMessage 实例上调用 from 方法:

use Illuminate\Notifications\Messages\VonageMessage;

/**
 * 获取通知的 Vonage / SMS 表示形式。
 */
public function toVonage(object $notifiable): VonageMessage
{
    return (new VonageMessage)
                ->content('您的 SMS 消息内容')
                ->from('15554443333');
}

添加客户参考

如果您想跟踪每个用户、团队或客户的成本,您可以向通知添加一个“客户参考”。Vonage 将允许您使用此客户参考生成报告,以便您更好地了解特定客户的 SMS 使用情况。客户参考可以是任何最多 40 个字符的字符串:

use Illuminate\Notifications\Messages\VonageMessage;

/**
 * 获取通知的 Vonage / SMS 表示形式。
 */
public function toVonage(object $notifiable): VonageMessage
{
    return (new VonageMessage)
                ->clientReference((string) $notifiable->id)
                ->content('您的 SMS 消息内容');
}

路由 SMS 通知

要将 Vonage 通知路由到正确的电话号码,在您的可通知实体上定义一个 routeNotificationForVonage 方法:

<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Notifications\Notification;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * 为 Vonage 通道路由通知。
     */
    public function routeNotificationForVonage(Notification $notification): string
    {
        return $this->phone_number;
    }
}

Slack 通知

先决条件

在发送 Slack 通知之前,您应该通过 Composer 安装 Slack 通知通道:

composer require laravel/slack-notification-channel

此外,您必须为您的 Slack 工作区创建一个Slack 应用 (opens in a new tab)

如果您只需要将通知发送到创建应用的同一个 Slack 工作区,您应该确保您的应用具有 chat:writechat:write.publicchat:write.customize 范围。这些范围可以从 Slack 中的“OAuth & Permissions”应用管理选项卡中添加。

接下来,将应用的“Bot User OAuth Token”复制并放置在您应用的 services.php 配置文件中的 slack 配置数组中。此令牌可以在 Slack 中的“OAuth & Permissions”选项卡中找到:

'slack' => [
    'notifications' => [
        'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),
        'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
    ],
],

应用分发

如果您的应用将向您的应用用户拥有的外部 Slack 工作区发送通知,您将需要通过 Slack 来“分发”您的应用。应用分发可以从 Slack 中的您的应用的“Manage Distribution”选项卡中进行管理。一旦您的应用已分发,您可以使用Socialite代表您的应用用户获取 Slack Bot 令牌

格式化 Slack 通知

如果通知支持作为 Slack 消息发送,您应该在通知类上定义一个 toSlack 方法。此方法将接收一个 $notifiable 实体,并应返回一个 Illuminate\Notifications\Slack\SlackMessage 实例。您可以使用Slack 的 Block Kit API (opens in a new tab)构建丰富的通知。以下示例可以在Slack 的 Block Kit 构建器 (opens in a new tab)中进行预览:

use Illuminate\Notifications\Slack\BlockKit\Blocks\ContextBlock;
use Illuminate\Notifications\Slack\BlockKit\Blocks\SectionBlock;
use Illuminate\Notifications\Slack\BlockKit\Composites\ConfirmObject;
use Illuminate\Notifications\Slack\SlackMessage;

/**
 * 获取通知的 Slack 表示形式。
 */
public function toSlack(object $notifiable): SlackMessage
{
    return (new SlackMessage)
            ->text('您的一张发票已支付!')
            ->headerBlock('发票已支付')
            ->contextBlock(function (ContextBlock $block) {
                $block->text('客户 #1234');
            })
            ->sectionBlock(function (SectionBlock $block) {
                $block->text('一张发票已支付。');
                $block->field("*发票号:*\n1000")->markdown();
                $block->field("*发票收件人:*\ntaylor@laravel.com")->markdown();
            })
            ->dividerBlock()
            ->sectionBlock(function (SectionBlock $block) {
                $block->text('恭喜!');
            });
}

Slack 交互性

Slack 的 Block Kit 通知系统提供了强大的功能来处理用户交互 (opens in a new tab)。要利用这些功能,您的 Slack 应用应该启用“交互性”并配置一个指向您的应用所服务的 URL 的“请求 URL”。这些设置可以从 Slack 中的“Interactivity & Shortcuts”应用管理选项卡中进行管理。

在以下示例中,使用了 actionsBlock 方法,Slack 将向您的“请求 URL”发送一个 POST 请求,请求体包含点击按钮的 Slack 用户、点击的按钮的 ID 等。您的应用可以根据请求体确定要采取的操作。您还应该验证请求 (opens in a new tab)是由 Slack 发出的:

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

/**
 * 获取通知的 Slack 表示形式。
 */
public function toSlack(object $notifiable): SlackMessage
{
    return (new SlackMessage)
            ->text('您的一张发票已支付!')
            ->headerBlock('发票已支付')
            ->contextBlock(function (ContextBlock $block) {
                $block->text('客户 #1234');
            })
            ->sectionBlock(function (SectionBlock $block) {
                $block->text('一张发票已支付。');
            })
            ->actionsBlock(function (ActionsBlock $block) {
                 // ID 默认值为 "button_acknowledge_invoice"...
                $block->button('确认发票')->primary();

                // 手动配置 ID...
                $block->button('拒绝')->danger()->id('deny_invoice');
            });
}

确认模态框

如果您希望用户在执行操作之前确认操作,您可以在定义按钮时调用 confirm 方法。confirm 方法接受一个消息和一个闭包,闭包接收一个 ConfirmObject 实例:

use Illuminate\Notifications\Slack\BlockKit\Blocks\ActionsBlock;
use Illuminate\Notifications\Slack\BlockKit\Blocks\ContextBlock;
use Illuminate\Notifications\Slack\BlockKit\Blocks\SectionBlock;
use Illuminate\Notifications\Slack\BlockKit\Composites\ConfirmObject;
use Illuminate\Notifications\Slack\SlackMessage;

/**
 * 获取通知的 Slack 表示形式。
 */
public function toSlack(object $notifiable): SlackMessage
{
    return (new SlackMessage)
            ->text('您的一张发票已支付!')
            ->headerBlock('发票已支付')
            ->contextBlock(function (ContextBlock $block) {
                $block->text('客户 #1234');
            })
            ->sectionBlock(function (SectionBlock $block) {
                $block->text('一张发票已支付。');
            })
            ->actionsBlock(function (ActionsBlock $block) {
                $block->button('确认发票')
                    ->primary()
                    ->confirm(
                        '确认付款并发送感谢电子邮件?',
                        function (ConfirmObject $dialog) {
                            $dialog->confirm('是');
                            $dialog->deny('否');
                        }
                    );
            });
}

检查 Slack 块

如果您想快速检查您正在构建的块,您可以在 SlackMessage 实例上调用 dd 方法。dd 方法将生成并转储一个到 Slack 的Block Kit 构建器 (opens in a new tab)的 URL,该 URL 在您的浏览器中显示有效负载和通知的预览。您可以将 true 传递给 dd 方法以转储原始有效负载:

return (new SlackMessage)
        ->text('您的一张发票已支付!')
        ->headerBlock('发票已支付')
        ->dd();

路由 Slack 通知

要将 Slack 通知定向到适当的 Slack 团队和频道,在您的可通知模型上定义一个 routeNotificationForSlack 方法。此方法可以返回以下三个值之一:

  • null - 将路由推迟到通知本身配置的频道。您可以在构建 SlackMessage 时使用 to 方法在通知内配置频道。
  • 指定要将通知发送到的 Slack 频道的字符串,例如 #support-channel
  • SlackRoute 实例,它允许您指定 OAuth 令牌和频道名称,例如 SlackRoute::make($this->slack_channel, $this->slack_token)。此方法应用于向外部工作区发送通知。

例如,从 routeNotificationForSlack 方法返回 #support-channel 将把通知发送到与位于您的应用的 services.php 配置文件中的 Bot User OAuth 令牌相关联的工作区中的 #support-channel 频道:

<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Notifications\Notification;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * 为 Slack 通道路由通知。
     */
    public function routeNotificationForSlack(Notification $notification): mixed
    {
        return '#support-channel';
    }
}

通知外部 Slack 工作区

[!NOTE]
在向外部 Slack 工作区发送通知之前,您的 Slack 应用必须分发

当然,您通常会希望向您的应用用户拥有的 Slack 工作区发送通知。为此,您首先需要为用户获取一个 Slack OAuth 令牌。值得庆幸的是,Laravel Socialite包含一个 Slack 驱动程序,它将允许您轻松地将您的应用用户与 Slack 进行身份验证并获取一个 bot 令牌

一旦您获得了 bot 令牌并将其存储在您的应用数据库中,您可以使用 SlackRoute::make 方法将通知路由到用户的工作区。此外,您的应用可能需要为用户提供一个机会来指定通知应发送到哪个频道:

<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Slack\SlackRoute;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * 为 Slack 通道路由通知。
     */
    public function routeNotificationForSlack(Notification $notification): mixed
    {
        return SlackRoute::make($this->slack_channel, $this->slack_token);
    }
}

本地化通知

Laravel 允许您以除 HTTP 请求的当前区域设置以外的区域设置发送通知,并且如果通知排队,甚至会记住此区域设置。

要实现此目的,Illuminate\Notifications\Notification 类提供了一个 locale 方法来设置所需的语言。当评估通知时,应用程序将切换到此区域设置,然后在评估完成后恢复到以前的区域设置:

$user->notify((new InvoicePaid($invoice))->locale('es'));

也可以通过 Notification 外观对多个可通知条目进行本地化:

Notification::locale('es')->send(
    $users, new InvoicePaid($invoice)
);

用户首选区域设置

有时,应用程序会存储每个用户的首选区域设置。通过在您的可通知模型上实现 HasLocalePreference 契约,您可以指示 Laravel 在发送通知时使用此存储的区域设置:

use Illuminate\Contracts\Translation\HasLocalePreference;

class User extends Model implements HasLocalePreference
{
    /**
     * 获取用户的首选区域设置。
     */
    public function preferredLocale(): string
    {
        return $this->locale;
    }
}

一旦您实现了该接口,Laravel 在向模型发送通知和可邮寄内容时将自动使用首选区域设置。因此,在使用此接口时无需调用 locale 方法:

$user->notify(new InvoicePaid($invoice));

测试

您可以使用 Notification 外观的 fake 方法来阻止通知被发送。通常,发送通知与您实际正在测试的代码无关。很可能,仅仅断言 Laravel 被指示发送给定的通知就足够了。

在调用 Notification 外观的 fake 方法后,您可以断言已指示将通知发送给用户,甚至可以检查通知收到的数据:

<?php
 
use App\Notifications\OrderShipped;
use Illuminate\Support\Facades\Notification;
 
test('订单可以发货', function () {
    Notification::fake();
 
    // 执行订单发货...
 
    // 断言没有通知被发送...
    Notification::assertNothingSent();
 
    // 断言向给定用户发送了一个通知...
    Notification::assertSentTo(
        [$user], OrderShipped::class
    );
 
    // 断言没有发送一个通知...
    Notification::assertNotSentTo(
        [$user], AnotherNotification::class
    );
 
    // 断言发送了给定数量的通知...
    Notification::assertCount(3);
});
<?php
 
namespace Tests\Feature;
 
use