消息通知

未匹配的标注
本文档最新版为 10.x,旧版本可能放弃维护,推荐阅读最新版!

Notifications

介绍

除了支持 发送电子邮件 之外,Laravel 还支持通过各种渠道发送通知的功能,包括电子邮件、SMS(通过 Vonage ,以前称为 Nexmo )和 Slack 。此外,已经创建了多种 社区构建的通知渠道 ,可以通过数十个不同的渠道发送通知!通知也可能存储在数据库中,以便它们可以显示在你的 Web 界面中。

通常,通知应该是简短的信息性消息,用于通知用户应用程序中发生的事情。例如,如果你正在编写一个账单应用,你可以通过电子邮件和 SMS 渠道向用户发送「支付凭证」通知。

生成通知

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

php artisan make:notification InvoicePaid

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

发送通知

使用 Notifiable Trait

通知可以通过两种方式发送:使用 Notificate 特性的 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 门面发送通知。 当您需要向多个可通知实体(例如一组用户)发送通知时,此方法非常有用。要使用 Facade 发送通知,请将所有可通知实体和通知实例传递给方法 send :

use Illuminate\Support\Facades\Notification;

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

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

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

制定通知渠道

每个通知类都有一个 via 方法来确定通知将在哪些渠道上传递。通知可以在 maildatabasebroadcast, vonage, 和 slack 渠道上发送、

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

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

/**
 * Get the notification's delivery channels.
 *
 * @return array<int, string>
 */
public function via(object $notifiable): array
{
    return $notifiable->prefers_sms ? ['vonage'] : ['mail', 'database'];
}

队列通知

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

发送通知可能需要一些时间,尤其时当渠道需要进行外部API调用来传递通知时。为了加快应用程序的响应时间,请将接口 ShouldQueueQueueable特征增加到类中。以使通知排队。使用该命令生成的所有通知都已导入接口和特征 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 方法应返回一个通道名称和延迟值的数组:

/**
 * Determine the notification's delivery delay.
 *
 * @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;

    /**
     * Create a new notification instance.
     */
    public function __construct()
    {
        $this->onConnection('redis');
    }
}

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

/**
 * Determine which connections should be used for each notification channel.
 *
 * @return array<string, string>
 */
public function viaConnections(): array
{
    return [
        'mail' => 'redis',
        'database' => 'sync',
    ];
}

自定义通知渠道队列

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

/**
 * Determine which queues should be used for each notification channel.
 *
 * @return array<string, string>
 */
public function viaQueues(): array
{
    return [
        'mail' => 'mail-queue',
        'slack' => 'slack-queue',
    ];
}

队列通知中间件

队列通知可以像 队列作业 一样定义中间件。 首先, middleware 在通知类上定义一个方法。该 middleware 方法将接收 $notifiable$channel 变量, 这些变量允许您根据通知的目的地自定义返回的中间件:

use Illuminate\Queue\Middleware\RateLimited;

/**
 * Get the middleware the notification job should pass through.
 *
 * @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;

    /**
     * Create a new notification instance.
     */
    public function __construct()
    {
        $this->afterCommit();
    }
}

[!注意]
要了解有关解决这些问题的更多信息,请参阅 消息队列和数据事务的文档。

确定是否应发送队列通知

在将队列通知发送到队列后惊醒后台处理之后,它通常会被队列工作者接收并发送给其预期的收件人。

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

/**
 * Determine if the notification should be sent.
 */
public function shouldSend(object $notifiable, string $channel): bool
{
    return $this->invoice->isPaid();
}

按需通知

有时您可能需要向未储存为应该程序“用户”的人发送通知。使用 Notification facade 的 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 方法示例:

/**
 * Get the mail representation of the notification.
 */
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 生成的电子邮件示例:

notification-example-2.png

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

错误消息

某些通知恢告知用户错误,例如发票付款失败。您可以使用 error 在构建邮件时调用该方法来表明邮件消息与错误有关。在邮件消息使用该 error 方法时,点击按钮将为红色而不是黑色:

/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
                ->error()
                ->subject('Invoice Payment Failed')
                ->line('...');
}

其他邮件通知格式选项

您无需在通知类中定义文本行,而是可以使用 view 方法指定用户呈现电子邮件通知的自定义模版:

/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)->view(
        'mail.invoice.paid', ['invoice' => $this->invoice]
    );
}

您可以通过 view 将视图名称作为提供给该方法的数组的第二个元素来制定邮件消息的纯文本视图:

/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)->view(
        ['mail.invoice.paid', 'mail.invoice.paid-text'],
        ['invoice' => $this->invoice]
    );
}

或者,如果您的消息只有纯文本视图,你可以使用 text 方法:

/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)->text(
        'mail.invoice.paid-text', ['invoice' => $this->invoice]
    );
}

自定义发件人

默认情况下,电子邮件的发送人/发件人地址在配置文件 config/mail.php 中定义。但是,您可以使用 from 方法为特定通知指定发件人地址:

/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
                ->from('barrett@example.com', 'Barrett Blair')
                ->line('...');
}

自定义收件人

通过 mail 渠道发送通知时,通知系统将自动查找可通知实体上的属性。您可以通过在 email 中定义 routeNotificationForMail 方法来自定义用户发送通知的电子邮件地址:

<?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;

    /**
     * Route notifications for the mail channel.
     *
     * @return  array<string, string>|string
     */
    public function routeNotificationForMail(Notification $notification): array|string
    {
        // Return email address only...
        return $this->email_address;

        // Return email address and name...
        return [$this->email_address => $this->name];
    }
}

自定义主题

默认情况下,电子邮件的主题是通知的类名,格式为标题大小写。因此,如果您的通知类名为 InvoicePaid,则电子邮件的主题将是 Invoice Paid。如果您想为消息指定不同的主题,你可以使用 subject 在构建消息时调用该方法:

/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
                ->subject('Notification Subject')
                ->line('...');
}

自定义邮件程序

默认情况下,电子邮件通知将使用 config/mail.php 中定义的默认邮件程序发送。但是,您可以在构建时调用 mailer 方法指定其他邮件程序:

/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
                ->mailer('postmark')
                ->line('...');
}

自定义模版

您可以通过发布通知包的资源来修改邮件通知使用的HTML和纯文本模版。运行此命令后。邮件通知模版将位于以下 resources/views/vendor/notifications 目录中:

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

附件

要将附件增加到电子邮件中,请在构建时使用 attach 方法。 该 attach 方法接收文件的绝对路径作为其第一个参数:

/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
                ->greeting('Hello!')
                ->attach('/path/to/file');
}

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

将文件附加到消息时,您还可以通过将 array 第二个参数传递给 attach 方法来指定显示名称或MIME类型:

/**
 * Get the mail representation of the notification.
 */
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;

/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): Mailable
{
    return (new InvoicePaidMailable($this->invoice))
                ->to($notifiable->email)
                ->attachFromStorage('/path/to/file');
}

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

/**
 * Get the mail representation of the notification.
 */
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 方法时,应提供分配给附件的文件名:

/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
                ->greeting('Hello!')
                ->attachData($this->pdf, 'name.pdf', [
                    'mime' => 'application/pdf',
                ]);
}

增加标签和元数据

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

/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
                ->greeting('Comment Upvoted!')
                ->tag('upvote')
                ->metadata('comment_id', $this->comment->id);
}

如果您的运用程序正在使用 Mailgun 驱动程序,您可以查阅 Mialgun 的文档以获取有关 标签元数据 的更多信息。 同样,您可以查阅 Postmark 文档以获取有关 标签元数据 的更多信息。

如果您的应用程序使用 Amazon SES 发送电子邮件,您应该使用 metadata 方法将 SES "标签" 附加到消息。

自定义 Symfony 消息

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

use Symfony\Component\Mime\Email;

/**
 * Get the mail representation of the notification.
 */
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;

/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): Mailable
{
    return (new InvoicePaidMailable($this->invoice))
                ->to($notifiable->email);
}

邮件资料和按需通知

如果您要发送 按需通知,则 Illuminate\Notifications\AnonymousNotifiable 实例赋予 toMail 方法 $notifiable ,它提供一种可用于检索应将按需通知发送到电子邮件的 routeNotificationFor 方法:

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

/**
 * Get the mail representation of the notification.
 */
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 模版生成通知,您可以使用 Artisan 命令 make:notification 的选项 --markdown

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

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

/**
 * Get the mail representation of the notification.
 */
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 参数。支持的颜色有 primarygreen, 和 red。您可以根据需要向通知增加任意数量的按钮组件:

<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 通知组件导出到您自己的运用程序进行自定义。要导出组件,请使用 Artisan 命令的 vendor:publish 方法发布 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 文件后,更新配置文件 mailtheme 选项以匹配新主题名称。

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

/**
 * Get the mail representation of the notification.
 */
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

[!注意]
如果您的通知模型使用 UUID or ULID 主键,则应在通知表迁移中用 uuidMorphsulidMorphs 替换 morphs 方法。

格式化数据库通知

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

/**
 * Get the array representation of the notification.
 *
 * @return array<string, mixed>
 */
public function toArray(object $notifiable): array
{
    return [
        'invoice_id' => $this->invoice->id,
        'amount' => $this->invoice->amount,
    ];
}

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

/**
 * Get the notification's database type.
 *
 * @return string
 */
public function databaseType(object $notifiable): string
{
    return 'invoice-paid';
}

toDatabase 对比 toArray

toArray 方法还被通道用来 broadcast 决定要向 JavaScript 驱动的前段广播哪些数据。如果您希望为 databasebroadcast 通道提供两种不同数组来表示, 则应该定义一个 toDatabase 方法而不是一个 toArray 方法。

访问通知

一旦通知存储在数据库中,您需要一种方便的方法从通知实体访问它们。 Illuminate\Notifications\Notifiable 特征包含在 Laravel 默认的 App\Models\User 模型中,包括一个 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;
}

[!注意]
要从 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 渠道使用 Laravel 的 事件广播 服务。 允许您的 JavaScript 前段实时捕获通知。如果通知支持广播,您可以在通知类上定义一个 toBroadcast方法。该方法将接收一个 $notifiable 实体返回一个 BroadcastMessage 实例。如果 toArray 方法不存在,则该方法将用于收集应广播的数据。返回的数据将被编码为 JSON 并广播到您的 JavaScript 前端。让我们看一个 toBroadcast 方法示例:

use Illuminate\Notifications\Messages\BroadcastMessage;

/**
 * Get the broadcastable representation of the notification.
 */
public function toBroadcast(object $notifiable): BroadcastMessage
{
    return new BroadcastMessage([
        'invoice_id' => $this->invoice->id,
        'amount' => $this->invoice->amount,
    ]);
}

广播队列配置

所有广播通知都排队等待广播。如果您想要配置用于排队广播操作的队列连接或者队列名称,您可以使用 BroadcastMessageonConnectiononQueue 方法:

return (new BroadcastMessage($data))
                ->onConnection('sqs')
                ->onQueue('broadcasts');

自定义通知类型

除了您指定的数据外,所有广播通知还具有一个 包含通知完整类名的 type 字段,如果你想自定义通知 type ,您可以在通知类定义一个 broadcastType 方法:

/**
 * Get the type of the notification being broadcast.
 */
public function broadcastType(): string
{
    return 'broadcast.message';
}

监听通知

通知将在使用约定格式 {notifiable}.{id} 的私有频道上广播。因此,如果您向 ID 为1的 App\Models\User 实例发送通知,则通知将在私有频道上广播。使用 Laravel Echo,您可以使用 notification 方法轻松地在频道上监听通知:

Echo.private('App.Models.User.' + userId)
    .notification((notification) => {
        console.log(notification.type);
    });

自定义通知渠道

如果您想自定义实体的广播通知在哪个渠道上广播,您可以在可通知实体上定义一个 receivesBroadcastNotificationsOn 方法:

<?php

namespace App\Models;

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

class User extends Authenticatable
{
    use Notifiable;

    /**
     * The channels the user receives notification broadcasts on.
     */
    public function receivesBroadcastNotificationsOn(): string
    {
        return 'users.'.$this->id;
    }
}

短信通知

先决条件

Laravel 中发送短信通知由 Vonage (以前称为 Nexmo)提供支持。在通过 Vonage 发送通知前,您需要安装 laravel/vonage-notification-channelguzzlehttp/guzzle 扩展包:

composer require laravel/vonage-notification-channel guzzlehttp/guzzle

这个扩展包包含一个 配置文件。但是,您不需要将此配置文件导出到您自己的应用程序。您只需要使用 VONAGE_KEYVONAGE_SECRET 环境变量来定义您的 Vonage 公钥和密钥即可。

定义好密钥后,你可以设置一个 VONAGE_SMS_FROM 环境变量,该变量定义了你发送 SMS 消息的默认电话号码。你可以在 Vonage 控制面板中生成此电话号码:

VONAGE_SMS_FROM=15556666666

格式化短信通知

如果通知支持作为 SMS 发送,你应该在通知类上定义一个 toVonage 方法。此方法将接收一个 $notifiable 实体并应返回一个 Illuminate\Notifications\Messages\VonageMessage 实例:

use Illuminate\Notifications\Messages\VonageMessage;

/**
 * 获取通知的 Vonage / SMS 表达式。
 */
public function toVonage(object $notifiable): VonageMessage
{
    return (new VonageMessage)
                ->content('Your SMS message content');
}

Unicode 内容

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

use Illuminate\Notifications\Messages\VonageMessage;

/**
 * 获取通知的 Vonage / SMS 表达式。
 */
public function toVonage(object $notifiable): VonageMessage
{
    return (new VonageMessage)
                ->content('Your unicode message')
                ->unicode();
}

自定义「来源」号码

如果你想从一个不同于 VONAGE_SMS_FROM 环境变量所指定的电话号码发送通知,你可以在 VonageMessage 实例上调用 from 方法:

use Illuminate\Notifications\Messages\VonageMessage;

/**
 * 获取通知的 Vonage / SMS 表达式。
 */
public function toVonage(object $notifiable): VonageMessage
{
    return (new VonageMessage)
                ->content('Your SMS message content')
                ->from('15554443333');
}

添加客户关联

如果你想跟踪每个用户、团队或客户的消费,你可以在通知中添加「客户关联」。Vonage 将允许你使用这个客户关联生成报告,以便你能更好地了解特定客户的短信使用情况。客户关联可以是任何字符串,最多 40 个字符。

use Illuminate\Notifications\Messages\VonageMessage;

/**
 * 获取通知的 Vonage / SMS 表达式。
 */
public function toVonage(object $notifiable): VonageMessage
{
    return (new VonageMessage)
                ->clientReference((string) $notifiable->id)
                ->content('Your SMS message content');
}

路由短信通知

要将 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 App

如果你只需要向同一个 Slack 工作空间发送通知,该工作空间是 App 创建的,你应该确保你的 App 具有 chat:writechat:write.public 和 chat:write.customize 范围。这些范围可以从 Slack 的 「OAuth & Permissions」 App 管理标签页中添加。

接下来,复制 App 的 「Bot User OAuth Token」 并将其放置在应用程序的 services.php 配置文件中的 slack 配置数组内。这个 Token 可以在 Slack 的 「OAuth & Permissions」 标签页中找到:

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

应用分发

如果你的应用程序需要向应用程序用户的外部 Slack 工作空间发送通知,你需要通过 Slack 「分发」你的 App 。App 分发可以从 Slack 中你的 App 的 「管理分发」标签页进行管理。一旦你的 App 被分发,你可以使用 Socialite 来代表你的应用程序的用户 获取 Slack Bot 令牌

格式化 Slack 通知

你需要在你的通知类中定义一个 toSlack 方法。这个方法会接收一个 $notifiable 实体,并且应该返回一个 Illuminate\Notifications\Slack\SlackMessage 实例。通过这个实例,你可以使用 Slack 的 Block Kit API 来构建丰富的通知内容。以下示例可以在 Slack 的 Block Kit 构建器中预览:

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('One of your invoices has been paid!')
            ->headerBlock('Invoice Paid')
            ->contextBlock(function (ContextBlock $block) {
                $block->text('Customer #1234');
            })
            ->sectionBlock(function (SectionBlock $block) {
                $block->text('An invoice has been paid.');
                $block->field("*Invoice No:*\n1000")->markdown();
                $block->field("*Invoice Recipient:*\ntaylor@laravel.com")->markdown();
            })
            ->dividerBlock()
            ->sectionBlock(function (SectionBlock $block) {
                $block->text('Congratulations!');
            });
}

Slack 交互性

Slack 的 Block Kit 通知系统提供了强大的功能来 处理用户交互。为了利用这些功能,你的 Slack 应用应该启用「交互性」并配置一个指向你的应用程序提供的URL的「请求URL」。这些设置可以在 Slack 内的「交互性与快捷方式」应用管理标签页中进行管理。

在以下示例中,使用了 actionsBlock 方法后,Slack 将向你的「请求 URL 」发送一个包含点击按钮的 Slack 用户、点击按钮的 ID 等信息的 POST 请求负载。然后,你的应用程序可以根据负载确定要采取的行动。你还应该 验证请求 确实是由 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('One of your invoices has been paid!')
            ->headerBlock('Invoice Paid')
            ->contextBlock(function (ContextBlock $block) {
                $block->text('Customer #1234');
            })
            ->sectionBlock(function (SectionBlock $block) {
                $block->text('An invoice has been paid.');
            })
            ->actionsBlock(function (ActionsBlock $block) {
                 // ID默认为 「button_acknowledgement _invoice」...
                $block->button('Acknowledge Invoice')->primary();

                // 手动配置ID...
                $block->button('Deny')->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('One of your invoices has been paid!')
            ->headerBlock('Invoice Paid')
            ->contextBlock(function (ContextBlock $block) {
                $block->text('Customer #1234');
            })
            ->sectionBlock(function (SectionBlock $block) {
                $block->text('An invoice has been paid.');
            })
            ->actionsBlock(function (ActionsBlock $block) {
                $block->button('Acknowledge Invoice')
                    ->primary()
                    ->confirm(
                        'Acknowledge the payment and send a thank you email?',
                        function (ConfirmObject $dialog) {
                            $dialog->confirm('Yes');
                            $dialog->deny('No');
                        }
                    );
            });
}

检查 Slack 块

如果你想快速检查你构建的块,可以在 SlackMessage 实例上调用 dd 方法。dd 方法将生成并转储一个 URL 到 Slack 的 Block Kit 构建器, 在浏览器中显示负载和通知的预览。你可以向 dd 方法传递 true 来转储原始负载:

return (new SlackMessage)
        ->text('One of your invoices has been paid!')
        ->headerBlock('Invoice Paid')
        ->dd();

路由 Slack 通知

要将Slack通知直接发送到适当的Slack团队和渠道,请在可通知模型上定义一个“routenotifationforslack”方法。这个方法可以返回以下三个值之一:

为了将 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 工作空间

注意
在向外部 Slack 工作空间发送通知之前,您的 Slack 应用必须进行分发

当然,你通常会希望将通知发送到你应用程序用户拥有的 Slack 工作空间。为此,首先你需要为用户获取一个 Slack OAuth 令牌。幸运的是,Laravel Socialite 包括一个 Slack 驱动,它将允许你轻松地使用 Slack 对你的应用程序用户进行认证,并获得一个 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 允许你在除了当前请求语言环境之外的其他语言环境中发送通知,甚至在通知被队列化的情况下也能记住此语言环境。

为了实现这一功能,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('orders can be shipped', function () {
    Notification::fake();

    // 执行订单发货...

    // 断言没有发送通知...
    Notification::assertNothingSent();

    // 断言通知已发送给给定用户...
    Notification::assertSentTo(
        [$user], OrderShipped::class
    );

    // 断言未发送通知...
    Notification::assertNotSentTo(
        [$user], AnotherNotification::class
    );

    // 断言已发送给定数量的通知...
    Notification::assertCount(3);
});
<?php

namespace Tests\Feature;

use App\Notifications\OrderShipped;
use Illuminate\Support\Facades\Notification;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_orders_can_be_shipped(): void
    {
        Notification::fake();

       // 执行订单发货...

        // 断言没有发送通知...
        Notification::assertNothingSent();

        // 断言通知已发送给给定用户...
        Notification::assertSentTo(
            [$user], OrderShipped::class
        );

        // 断言未发送通知...
        Notification::assertNotSentTo(
            [$user], AnotherNotification::class
        );

        // 断言已发送给定数量的通知...
        Notification::assertCount(3);
    }
}

你可以通过向 assertSentTo 或 assertNotSentTo 方法传递一个闭包来断言发送了符合给定「真实性测试」的通知。如果发送了至少一个符合给定真实性测试的通知,则断言将成功:

Notification::assertSentTo(
    $user,
    function (OrderShipped $notification, array $channels) use ($order) {
        return $notification->order->id === $order->id;
    }
);

按需通知

如果你正在测试的代码发送 即时通知,你可以使用 assertSentOnDemand 方法测试是否发送了即时通知:

Notification::assertSentOnDemand(OrderShipped::class);

通过将闭包作为 assertSentOnDemand 方法的第二个参数传递,你可以确定是否将即时通知发送到了正确的 「route」 地址:

Notification::assertSentOnDemand(
    OrderShipped::class,
    function (OrderShipped $notification, array $channels, object $notifiable) use ($user) {
        return $notifiable->routes['mail'] === $user->email;
    }
);

通知事件

通知发送事件

发送通知时,通知系统会调度 Illuminate\Notifications\Events\NotificationSending 事件。 这包含「可通知」实体和通知实例本身。 你可以在应用程序的 EventServiceProvider 中为该事件注册 监听器

use Illuminate\Notifications\Events\NotificationSending;

class CheckNotificationStatus
{
    /**
     * 处理给定的事件。
     */
    public function handle(NotificationSending $event): void
    {
        // ...
    }
}

如果 NotificationSending 事件的事件监听器从其 handle 方法返回 false ,则不会发送通知:

/**
 * 处理给定的事件。
 */
public function handle(NotificationSending $event): bool
{
    return false;
}

"在事件监听器中,你可以访问事件的 notifiablenotificationchannel 属性,以获取更多关于通知接收者或通知本身的信息:

/**
 * 处理给定的事件。
 */
public function handle(NotificationSending $event): void
{
    // $event->channel
    // $event->notifiable
    // $event->notification
}

发送通知事件

发送通知时,通知系统会调度 Illuminate\Notifications\Events\NotificationSent 事件。该事件包含 “被通知” 实体和通知实例本身。你可以在应用程序中为该事件创建 事件侦听器

use Illuminate\Notifications\Events\NotificationSent;

class LogNotification
{
    /**
     * 处理给定事件。
     */
    public function handle(NotificationSent $event): void
    {
        // ...
    }
}

在事件监听器中,您可以访问事件的notifiable, notification, channel, 和response 属性,以了解有关通知接收者或通知本身的更多信息:

/**
 * 处理给定事件。
 */
public function handle(NotificationSent $event): void
{
    // $event->channel
    // $event->notifiable
    // $event->notification
    // $event->response
}

自定义渠道

Laravel 提供了一些通知渠道,也可以编写自己的驱动程序,通过其他渠道发送通知。
首先,定义一个包含 send 方法的类。方法应该接收两个参数:$notifiable$notification

send 方法中, 您可以调用通知上的方法来获取渠道可理解的消息对象,然后将通知发送到 $notifiable 实例:

<?php

namespace App\Notifications;

use Illuminate\Notifications\Notification;

class VoiceChannel
{
    /**
     * 发送指定通知。
     */
    public function send(object $notifiable, Notification $notification): void
    {
        $message = $notification->toVoice($notifiable);

        // 向 $notifiable 实例发送通知...
    }
}

一旦定义了通知通道类,就可以从任何通知的 via 方法中返回类名。在这个例子中,通知的 toVoice 方法可以返回任何你选择用来表示语音信息的对象。例如,您可以定义自己的 VoiceMessage 类来表示这些信息:

<?php

namespace App\Notifications;

use App\Notifications\Messages\VoiceMessage;
use App\Notifications\VoiceChannel;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;

class InvoicePaid extends Notification
{
    use Queueable;

    /**
     * 获取通知渠道。
     */
    public function via(object $notifiable): string
    {
        return VoiceChannel::class;
    }

    /**
     * 获取通知语音。
     */
    public function toVoice(object $notifiable): VoiceMessage
    {
        // ...
    }
}

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/laravel/11.x/no...

译文地址:https://learnku.com/docs/laravel/11.x/no...

上一篇 下一篇
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
贡献者:5
讨论数量: 0
发起讨论 只看当前版本


暂无话题~