Laravel 即時應用的一種實現方式

Dearmadman發表於2016-08-12

即時互動的應用

在現代的 Web 應用中很多場景都需要運用到即時通訊,比如說最常見的支付回撥,與三方登入。這些業務場景都基本需要遵循以下流程:

  • 客戶端觸發相關業務,併產生第三方應用的操作(比如支付)

  • 客戶端等待服務端響應結果(使用者完成第三方應用的操作)

  • 第三方應用通知服務端處理結果(支付完成)

  • 服務端通知客戶端處理結果

  • 客戶端依據結果做出反饋 (跳轉到支付成功頁面)

在過去,為了實現這種即時通訊,能讓客戶端正確響應處理結果,最為常用的技術就是輪詢,因為 HTTP 協議的單向性,客戶端只能一遍一遍的主動詢問服務端的處理結果。這種方式有顯見的缺陷,佔用服務端資源不說,還不能實時獲得服務端處理結果。

現在,我們可以使用 WebSocket 協議來處理實時互動,它是一種雙向協議,允許服務端主動推送資訊到客戶端。本篇我們將藉助 Laravel 強大的事件系統來構建實時的互動。你將需要用到以下知識:

  • Laravel Event

  • Redis

  • Socket.io

  • Node.js

Redis

在開始之前,我們需要開啟一個 redis 服務,並在 Laravel 應用中進行配置啟用,因為在整個流程中,我們需要藉助 redis 的訂閱和釋出機制來實現即時通訊。

Redis 是一個開源高效的鍵值對儲存系統。它通常作為一個資料結構伺服器來儲存鍵值對,它可以支援字串,雜湊,列表,集合和有序結合。在 Laravel 中使用 Redis 你需用通過 Composer 來安裝 predis/predis 包檔案。

配置

Redis 在應用中的配置檔案儲存在 config/database.php,在這個檔案中,你可以看到一個包含了 Redis 服務資訊的 redis 陣列:

`redis` => [
  `cluster` => false,

  `default` => [
    `host` => `127.0.0.1`,
    `port` => 6379,
    `database` => 0,
  ],
]

如果你修改了 redis 服務的埠,請保持配置檔案中的埠一致。

Laravel Event

這裡我們需要藉助 Laravel 強大的事件廣播能力:

廣播事件

很多現代化的應用中,會使用 Web Sockets 來實現實時互動的使用者介面。當一些資料在服務端變更時,一條訊息會通過 WebSocket 連線來傳遞到客戶端進行處理。
為了幫助你構建這種型別的應用。Laravel 使通過 WebSocket 連線進行廣播事件變的非常簡單。Laravel 允許你廣播事件來共享事件的名稱到你的服務端和客戶端的 JavaScript 框架。

配置

所有的事件廣播配置選項都被儲存在 config/broadcasting.php 配置檔案中。Laravel 附帶了幾種可用的驅動如 Pusher,Redis,和 Log,我們將使用 Redis 作為廣播驅動,這裡需要依賴 predis/predis 類庫。

由於預設的廣播驅動使用的是 pusher,所以我們需要在 .env 檔案中設定 BROADCAST_DRIVER=redis

我們建立一個 WechatLoginedEvent 事件類用來在使用者掃描微信登入後進行廣播:

<?php

namespace AppEvents;

use AppEventsEvent;
use IlluminateQueueSerializesModels;
use IlluminateContractsBroadcastingShouldBroadcast;

class WechatLoginedEvent extends Event implements ShouldBroadcast
{
    use SerializesModels;

    public $token;
    protected $channel;

    /**
     * Create a new event instance.
     *
     * @param  string $token
     * @param  string $channel
     * @return void
     */
    public function __construct($token, $channel)
    {
        $this->token = $token;
        $this->channel = $channel;
    }

    /**
     * Get the channels the event should be broadcast on.
     *
     * @return array
     */
    public function broadcastOn()
    {
        return [$this->channel];
    }

    /**
     * Get the name the event should be broadcast on.
     *
     * @return string
     */
    public function broadcastAs()
    {
        return `wechat.login`;
    }
}

其中你需要注意 broadcastOn 方法應返回一個陣列,它表示所需廣播的頻道,而 broadcastAs 返回的是一個字串,它表示廣播所觸發的事件,Laravel 預設的是返回事件類的全類名,這裡是 AppEventsWechatLoginedEvent.

最重要的是你需要手動的讓該類實現 ShouldBroadcast 契約。Laravel 在生成事件時,已經自動新增了該名稱空間,該契約只約束 broadcastOn 方法。

事件完成接下來就是觸發事件了,簡單的一行程式碼就可以:

event(new WechatLoginedEvent($token, $channel));

這個操作會自動的觸發事件的執行並將資訊廣播出去。該廣播操作底層藉助了 redis 的訂閱和釋出機制。RedisBroadcaster 會將事件中的允許公開訪問的資料通過給定的頻道釋出出去。如果你想對公開的資料擁有更多的控制,你可以在事件中新增 broadcastWith 方法,它應該返回一個陣列:

/**
 * Get the data to broadcast.
 *
 * @return array
 */
 public function broadcastWith() 
 {
   return [`user` => $this->user->id];
 }

Node.js 和 Socket.io

對於釋出出去的資訊,我們需要一個服務來對接,讓其能對 redis 的釋出能夠進行訂閱,並且能把資訊以 WebSocket 協議轉發出去,這裡我們可以借用 Node.js 和 socket.io 來非常方便的構建這個服務:

// server.js
var app = require(`http`).createServer(handler);
var io = require(`socket.io`)(app);

var Redis = require(`ioredis`);
var redis = new Redis();

app.listen(6001, function () {
  console.log(`Server is running!`) ;
});

function handler(req, res) {
  res.writeHead(200);
  res.end(``);
}

io.on(`connection`, function (socket) {
  socket.on(`message`, function (message) {
    console.log(message)
  })
  socket.on(`disconnect`, function () {
    console.log(`user disconnect`)
  })
});


redis.psubscribe(`*`, function (err, count) {
});

redis.on(`pmessage`, function (subscrbed, channel, message) {
  message = JSON.parse(message);
  io.emit(channel + `:` + message.event, message.data);
});

這裡我們使用 Node.js 引入 socket.io 服務端並監聽 6001 埠,借用 redis 的 psubscribe 指令使用萬用字元來快速的批量訂閱,接著在訊息觸發時將訊息通過 WebSocket 轉發出去。

Socket.io 客戶端

在 web 前端,我們需要引入 Socket.io 客戶端開啟與服務端 6001 埠的通訊,並訂閱頻道事件:

// client.js
let io = require(`socket.io-client`)

var socket = io(`:6001`)
      socket.on($channel + `:wechat.login`, (data) => {
        socket.close()
        // save user token and redirect to dashboard
})

至此整個通訊閉環結束,開發流程看起來就是這樣的:

  • 在 Laravel 中構建一個支援廣播通知的事件

  • 設定需要進行廣播的頻道及事件名稱

  • 將廣播設定為使用 redis 驅動

  • 提供一個持續的服務用於訂閱 redis 的釋出,及將釋出內容通過 WebSocket 協議推送到客戶端

  • 客戶端開啟服務端 WebSocket 隧道,並對事件進行訂閱,根據指定事件的推送進行響應。

PS: 歡迎關注簡書 Laravel 專題,也歡迎 Laravel 相關文章的投稿 :),作者知識技能水平有限,如果你有更好的設計方案歡迎討論交流,如果有錯誤的地方也請批評指正,在此表示感謝 ?

相關文章