那時候找不到完全符合需求的可以直接用或改,所以最後自己寫了一個,供大家參考。
根據我爬文,要用 Laravel 實作 Webhook 的方法應該不只一種,也有已經寫好的套件可以使用,可以花點時間看,再根據自己需求選擇用做好的或自開發。
本次只會分享「發送通知」和「觸發發送通知」的寫法
實作上會用到這兩樣,我的理解如下
又因為需要控制送到哪、與是否啟用,新增 subscribes table,去記錄。
schema 設計如下,一個分銷商(client)可以訂閱多個事件(event),每個訂閱可以設置一個endpoint(url)、且可以控制是否啟用(active)
Subscribes |
---|
id |
event_name |
client_id |
active |
url |
created_at |
updated_at |
<?php
namespace App\Notifications;
use App\Channels\WebhookChannel;
use App\Models\Subscribe;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class WebhookNotification extends Notification implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
use SerializesModels;
public $tries = 10; // 可以設定最多重試幾次
public $timeout = 45; // 等待分銷商幾秒後沒有回覆算失敗
public $retryAfter = 100; // 若失敗後幾秒重試
/**
* Where the webhook notification will send to
*
* @var string
*/
public $url;
/**
* Webhook event
*
* @var string
*/
public $event;
/**
* Gds Client instance
*
* @var App\Models\Client
*/
public $client;
/**
* Create a new notification instance.
*
* @return void
*/
public function __construct(Subscribe $subscribe)
{
$this->url = $subscribe->url;
$this->event = $subscribe->event;
$this->client = $subscribe->client;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return [WebhookChannel::class];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
*/
public function toWebhook($notifiable)
{
// 根據不同事件組訊息
switch ($this->event) {
case 'product_status_updated':
return [
'event' => $this->event,
'product_id' => $notifiable->prod_id,
'product_name' => $notifiable->prod_name,
'new_status' => $notifiable->prod_status,
// 時間資訊會想送「寄送」時間,所以在 Channel 再做
];
case 'order_status_updated':
return [
'event' => $this->event,
'order_id' => $notifiable->order_id,
'order_voucher' => $notifiable->order_voucher,
'new_status' => $notifiable->order_status,
// 時間資訊會想送「寄送」時間,所以在 Channel 再做
];
// ...之後有新事件可以加在下面
}
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
<?php
namespace App\Channels;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Psr7\Request;
use Illuminate\Notifications\Notifiable;
use Illuminate\Notifications\Notification;
class WebhookChannel
{
public function __construct()
{
$this->client = new Client();
}
/**
* @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();
$body['timestamp'] = $timestamp->format('Y-m-d H:i:s');
$headers = [
'Content-Type' => 'application/json'
];
$url = $notification->url;
$request = new Request('POST', $url, $headers, json_encode($body));
try {
$response = $this->client->send(
$request,
['timeout' => 45.0]
);
// 這邊看你覺得定義收到什麼回覆算成功,
// 可以是收到 code 2XX、200、或特定訊息
// 以下範例是只有收到 200 系統才是為成功
if ($response->getStatusCode() == 200) {
// Success
} else {
// Get a non 200 respones
$notification->release(100); // 100秒後重新再跑一次
}
} catch (Throwable $th) {
// handle exception
}
}
}
以產品狀態異動為例,可以利用 model 在 saving 的時候觸發發送
class Product extends Model
{
use Notifiable; // 要加這個
protected static function boot()
{
parent::boot();
// 在儲存時觸發
static::saving(function ($product) {
// 檢查狀態是否有異動
$prev_status = $product->getOriginal('prod_status');
if ($prev_status != $product->prod_status) {
// 對有該產品下所有的訂閱發送通知
$subscribes = $product->getSubscribes("product_status_updated");
foreach ($subscribes as $subscribe) {
$product->notify(new WebhookNotification($subscribe));
}
}
});
}
public function getSubscribes($event)
{
return Subscribe::where('active', 1)
->where('event_name', $event)
->get();
}
}