在你的 Laravel 应用中是实现 Webhook 功能(通过 Notification 系统)

翻译 CismonX ⋅ 于 2个月前 ⋅ 744 阅读 ⋅ 原文地址

站点的翻译文章创建时,您将第一时间收到通知。

这是一篇社区协同翻译的文章,已完成翻译,更多信息请点击 协同翻译介绍

file

当我们整合第三方服务时,通常需要实现 Webhooks 。Webhooks 是一个通过 HTTP 请求向其它应用程序发送数据或者事件的 Web 应用程序。例如,大多数邮件服务像 Mailgun 和 Spark Post ,会给你的应用邮箱发送一些邮件被拒收或者垃圾邮件通知相关的消息 。

接收方,一般我们简单地创建一个控制器就可以很容易的接收 Webhook 消息 。发送方这边,在 Laravel 消息通知框架 的帮助下,发送 Webhook 消息也是同样简单,我们只需要创建一个 自定义通知通道 即可。

Explorer 翻译于 2个月前

安装我们依赖项

首先我们需要安装一个用于发生 HTTP 请求的库。我们在这里要用到的是 Guzzle,这在大多数 PHP Web 应用程序中都很常见。

我们可以通过使用 Composer 来安装它。

composer require guzzlehttp/guzzle:~6.0

这样做以后,我们可以创建新的 Webhook 通知通道。

Explorer 翻译于 2个月前

通知通道

Laravel 中的一个通知通道为用户、团队提供了一个发送结构化消息的机制,在任何一个“通道”中都可以找到适合的内容,这可以是电子邮件、 Slack 通道或 SMS 消息。这些通道自身通常会发送 HTTP 请求,所以这不是我们在这里做的新东西。

首先,我们创建一个  app/Channels 目录并添加一个 WebhookChannel 类。这个类将实现一个发送方法,该方法需要一个 Notifiable 对象和一个 Notification 对象。

鉴于框架中通道的使用方式,我们还需要在该类的构造函数中引入一个 Guzzle 客户端和一个日志实例,将我们的新类中的某些依赖关系置于其中。日志实例的引入主要是为了更容易的确认消息的送达情况。

Honvid 翻译于 1个月前

所以现在我们的 WebhookChannel 应该是这样子的: 

<?php

namespace App\Channels;

use GuzzleHttp\Client;
use Illuminate\Log\Logger;
use Illuminate\Notifications\Notifiable;
use Illuminate\Notifications\Notification;

class WebhookChannel
{
    /**
     * @var Client
     */
    private $client;
    /**
     * @var Logger
     */
    private $logger;

    public function __construct(Client $client, Logger $logger)
    {
        $this->client = $client;
        $this->logger = $logger;
    }

    /**
     * @param Notifiable $notifiable
     * @param Notification $notification
     * @throws WebHookFailedException
     */
    public function send($notifiable, Notification $notification)
    {

    }
}

我们稍后将实现具体的方法,但是现在我们应该使我们自己的可通知特性标准化以及如何通过 Webhook 来接收消息。

Explorer 翻译于 2个月前

一个 Webhook 类型的 trait 通知

我们将用 Webhook 为我们想要通知的对象创建一个 Trait,在这一点上很像 NotifiableTrait。这不是完全需要的,但它是一种很好的方式——我们可以很容易地为任何想要的类添加其他方法。

现在,我们在app文件夹下创建一个名为WebhookNotifiable的新 Trait,它有两个方法: getSigningKey()和 getWebhookUrl()。这两个方法是必需的并将作为 Webhook 流程的一部分。

这两个方法通过 Trait 方式访问对象属性,在本例中也就是webhook_urlapi_key属性。前者是通知的目标 URL,而后者api_key用来授予终端用户一种验证方式,验证我们发送给他们的 HTTP 请求。

<?php

namespace App;

trait WebhookNotifiable
{
    /**
     * @return string
     */
    public function getSigningKey()
    {
        return $this->api_key;
    }

    /**
     * @return string
     */
    public function getWebhookUrl()
    {
        return $this->webhook_url;
    }
}
mingc 翻译于 1个月前

创建可以接收 Webhook的用户

下面我们在用户模块使用这个 Trait 。只需要花几秒钟修改用户的迁移表,并创建 api_key 和 webhook_url 两个字段进行存储。同时我们将 Trait 添加到 app/User.php 的 User 类中。这里已经又一个 Notifiable Trait ,我们只需要在额外添加刚才我们创建的这个即可。

<?php

namespace App;

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

class User extends Authenticatable
{
    use Notifiable, WebhookNotifiable;

    /**
     * 可填充的属性
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password', 'api_key', 'webhook_url',
    ];

    /**
     * 隐藏不展示的属性
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token', 'api_key',
    ];
}
Honvid 翻译于 1个月前

完成我们的 Webhook 通道

现在我们有一个带通知特性的 Webhook ,我们可以编写我们的 Send 方法通过我们的 WebhookChannel 来发送通知。我们将编辑 Send 方法,首先检查通知中是否有  toWebHook() 方法,如果没有它将会调用  toArray() 方法来取代它。Laravel 默认使用  toArray()  方法来进行通知,但是很高兴的是我们有可选择的多个通道来调用这个方法。她允许我们可以从被通知的 User 用户的通知中获取内容。这将成为我们 Webhook 请求的 HTTP 主体。
然后我们将为我们的 Webhook 请求生成一些头部信息。我们这样做的目的是为了确保接受通知方能够确保通知来自于我们而不是其他人。我们通过哈希随机字串来生成用户的签名键来实现这一点。提供时间戳是用来确保来自 Hooks 的请求不会过期的好方法。

Explorer 翻译于 1个月前

然后,我们用 Guzzle 客户端来发送消息。如果没有返回一个 HTTP 200 的状态码,我们将抛出异常。在这种情况下,我创建来一个简单的 WebhookFailedException 类来标准化处理异常。 同时,我们确保如果捕获 Guzzle 抛出的异常并将其重新抛出为 WebhookFailedException 。我们使用异常,是因为如果一个通知在已在排队且在后台进程中处理,异常将导致通知重新排队并等待进一步尝试。需要说明的是,我们也使用日志实例把请求写入日志是为了清楚的记录请求的成功或者失败。

<?php

namespace App\Channels;

use App\Exceptions\WebHookFailedException;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Psr7\Request;
use Illuminate\Log\Logger;
use Illuminate\Notifications\Notifiable;
use Illuminate\Notifications\Notification;

class WebhookChannel
{
    /**
     * @var Client
     */
    private $client;
    /**
     * @var Logger
     */
    private $logger;

    public function __construct(Client $client, Logger $logger)
    {
        $this->client = $client;
        $this->logger = $logger;
    }

    /**
     * @param Notifiable $notifiable
     * @param Notification $notification
     * @throws WebHookFailedException
     */
    public function send($notifiable, Notification $notification)
    {
        if (method_exists($notification, 'toWebhook')) {
            $body = (array) $notification->toWebhook($notifiable);
        } else {
            $body = $notification->toArray($notifiable);
        }
        $timestamp = now()->timestamp;
        $token = str_random(16);

        $headers = [
            'timestamp' => $timestamp,
            'token' => $token,
            'signature' => hash_hmac(
                'sha256',
                $token . $timestamp,
                $notifiable->getSigningKey()
            ),
        ];

        $request = new Request('POST', $notifiable->getWebhookUrl(), $headers, json_encode($body));

        try {
            $response = $this->client->send($request);

            if ($response->getStatusCode() !== 200) {
                throw new WebHookFailedException('Webhook received a non 200 response');
            }

            $this->logger->debug('Webhook successfully posted to '. $notifiable->getWebhookUrl());

            return;

        } catch (ClientException $exception) {
            if ($exception->getResponse()->getStatusCode() !== 410) {
                throw new WebHookFailedException($exception->getMessage(), $exception->getCode(), $exception);
            }
        } catch (GuzzleException $exception) {
            throw new WebHookFailedException($exception->getMessage(), $exception->getCode(), $exception);
        }

        $this->logger->error('Webhook failed in posting to '. $notifiable->getWebhookUrl());
    }
}
Honvid 翻译于 1个月前

通知示例

现在已经完成了建立通过 Webhook 发送数据的机制的过程,我们来实现一个通知的简单示例吧。

我们可以通过 Artisan 命令 php artisan make:notification SomethingHappenedNotification 生成 app/Notifications/SomethingHappenedNotification.php 类文件。

给该通知类添加 $message 参数,这是为了展示可以传递给通知类的数据类型。

接下里移除我们不会使用的 toMail() 方法,添加一个返回数组类型的方法 toWebhook($notification) 。此时,我们只是为了测试它而放置一些值。

Honvid 翻译于 1个月前

为了实现上面的事情需要修改 via() 方法,返回一个包含 WebhookChannel 类的数组。这将告诉 Laravel 该通知需要通过 WebhookChannel 来传递。 我们还需要添加 ShouldQueue 接口来实现队列。这非常有用,意味着可以使用 队列 进行处理,因此,如果你实现了队列, Laravel 将把它作为一个后台进程来处理。这对于处理 HTTP 请求超时或阻断给用户发送的响应时非常有用。

<?php

namespace App\Notifications;

use App\Channels\WebhookChannel;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;

class SomethingHappenedNotification extends Notification implements ShouldQueue
{
    use Queueable;
    /**
     * @var string
     */
    private $message;

    /**
     * Create a new notification instance.
     *
     * @param string $message
     */
    public function __construct($message)
    {
        //
        $this->message = $message;
    }

    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return [WebhookChannel::class];
    }

    public function toWebhook($notifiable)
    {
        return [
            'message' => $this->message,
        ];
    }

    /**
     * Get the array representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function toArray($notifiable)
    {
        return [];
    }
}
Honvid 翻译于 1个月前

集中测试

显然,从实现到构建一个工作示例有点困难。所以我已经在 GitHub 上创建了一个小项目,你可以尽情的使用它。自述中提供了一些基本的使用说明供你参照。

若相惜 翻译于 2个月前

总结

好吧,没什么好多说的了。Laravel 的通知自 5.3 版本开始就已经存在了,它是真正帮助框架脱颖而出的独特特性之一。它的伟大之处在于,它是可扩展的,可以用来简化那些需要发生消息给每个收件人的流程。
更棒的是,它还支持消息队列机制,这在你项目刚刚启动的时候也许不重要,但是随着用户基数的增长,它能够帮助你加快应用程序的请求速度。总之,如果你到目前为止还没有了解过 Laravel 的通知,那么我建议你去了解一下。

Explorer 翻译于 2个月前

原文地址:https://medium.com/@SlyFireFox/laravel-i...

译文地址:https://laravel-china.org/topics/13195/i...


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

回复数量: 0
暂无回复~
您需要登陆以后才能留下评论!

Composer 中国全量镜像

Top 250 扩展包

Lumen 中文文档

Laravel 速查表

Laravel 中文文档

Laravel 项目开发规范

Laravel 开发环境部署

Composer 中文文档

Elasticsearch-PHP 中文文档

Lumen 中文文档

GraphQL PHP 中文文档

社区文档撰写指南

TDD 构建 Laravel 论坛笔记

PHP PSR 标准规范

PHP 设计模式全集

Dingo API 中文文档