從 0 開始打造聊天室,搞定 Laravel 實時通訊 —— 準備工作

MArtian發表於2022-02-01

從 0 開始打造聊天室,搞定 Laravel 實時通訊 —— 準備工作

前言

Laravel 實時通訊的三板斧分別是 Websocket 元件 Laravel-websocket(pusher), Laravel-echoBroadcast 廣播功能來實現。

之前寫過一篇 部落格:一起來實現單使用者登入 —— 完成監聽 ,本文算是一個進階版,文章篇幅有點長,如果你能耐心看完呢,我想你會有收穫的。

從 0 開始打造聊天室,搞定 Laravel 實時通訊 —— 準備工作

原始碼地址:github.com/hiccup711/chatting


準備工作

新建一個 Laravel 專案,使用 Installer 或者 Composer 隨意。

因為實時通訊需要使用 Laravel 框架的廣播功能,相關配置可以在 app/config/broadcasting.php 檔案中看到,Laravel 框架預設是沒有載入廣播服務的,首先我們需要先載入它。

開啟 app/config/app.php 找到下方的 providers

    /*
    * Application Service Providers...
    */
    App\Providers\AppServiceProvider::class,
    App\Providers\AuthServiceProvider::class,
    App\Providers\BroadcastServiceProvider::class, // 開啟廣播服務功能
    App\Providers\EventServiceProvider::class,
    App\Providers\RouteServiceProvider::class,

簡單講一下 App\Providers\EventServiceProvider::class,開啟這個檔案,看到裡面註冊了路由 Broadcast::routes(); 並載入了 routes/channels.php 檔案,這是對使用者訪問頻道的身份驗證

Broadcast::channel('App.Models.User.{id}', function ($user, $id) {
    return (int) $user->id === (int) $id;
});

看著是不是有點眼熟?這個鑑權形式和 Policy 有些類似,可以理解為請求的使用者 ID 和當前授權的使用者 ID 是否相等,如果相等,就可以訪問這個頻道,這個我們後面使用私有頻道時會再次講到。

建立事件

php artisan make:event ChatEvent

如果需要傳送廣播,需要繼承 ShouldBroadcast 介面。
開啟 app/events/ChatEvent.php

<?php
...
class ChatEvent implements ShouldBroadcast // 實現介面

測試廣播

我們已經定義好了 event 事件,下面來測試一下,開啟 routes/web.php

Route::get('/', function () {
    event(new \App\Events\ChatEvent()); // 觸發一下即可
    return view('welcome');
});

開啟你的專案地址首頁,重新整理一下,然後我們去 storage/logs 目錄,會看到剛剛的一條廣播日誌

[2022-01-22 15:55:45] local.INFO: Broadcasting [App\Events\ChatEvent] on channels [chatting] with payload:
{
    "socket": null
}

功能正常,因為我們沒有寫任何內容,訊息是 null

想要自定義訊息也很簡單,回到 ChatEvent.php

class ChatEvent implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;
    public $message;
    /**
    * Create a new event instance. * * @return void
    */  
    public function __construct($message)
    {
        $this->message = $message;
    }

    public function broadcastOn()
    {  
        return new Channel('chatting');
    }
}

再回到 web.php

Route::get('/', function () {
 event(new \App\Events\ChatEvent('Say Hello'));
 return view('welcome');
});

再去看一眼 storage/logs

[2022-01-22 16:05:13] local.INFO: Broadcasting [App\Events\ChatEvent] on channels [chatting] with payload:
{
    "message": "Say Hello",
    "socket": null
}

至於為什麼會寫到日誌裡面,那是因為 Laravel 預設在 .env 環境檔案中把 BROADCAST_DRIVER 設定為了 log。

一切都準備好了,接下來我們開始實現聊天室功能。


建立資料表

php artisan make:model Topic -a // 話題表

遷移檔案

public function up()
{
 Schema::create('topics', function (Blueprint $table) {
  $table->id();
  $table->string('name');
  $table->timestamps();
  });
}

一個話題 Topic 有多個討論 Discussions
一個使用者 User 可以參加多個 Topic

php artisan make:model Discussion -a // 討論表

遷移檔案

public function up()
    {
        Schema::create('discussions', function (Blueprint $table) {
            $table->id();
            $table->integer('user_id')->index();
            $table->integer('topic_id')->index();
            $table->string('body');
            $table->timestamps();

            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
            $table->foreign('topic_id')->references('id')->on('topics')->onDelete('cascade');
        });
    }

一個 Topic 也可以有多個使用者 User

php artisan make:migration create_topic_user_table --create=topic_user // 使用者和話題的中間表,多對多關係

遷移檔案

public function up()
    {
        Schema::create('topic_user', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('user_id')->index(); // 注意欄位型別必須和關聯表的欄位型別一致,Laravel 的 ID 欄位型別是 unsignedBigInteger
            $table->unsignedBigInteger('topic_id')->index();
            $table->timestamps();
        });
    }

執行遷移

php artisan migrate

來定義一下模型關係

Models/User.php

// 一個使用者擁有多條發言
public function discussions()
{
    return $this->hasMany(Discussion::class);
}
// 一個使用者可以加入多個討論話題,一個話題擁有多個使用者
public function topics()
{
    return $this->belongsToMany(Topic::class);
}

Models/Disscussion

protected $guarded = [];

public function user()
{
    return $this->belongsTo(User::class);
}

public function topic()
{
    return $this->belongsTo(Topic::class);
}

Model/Topic

protected $guarded = [];

public function users()
{
    return $this->belongsToMany(User::class);
}

public function discussions()
{
    return $this->hasMany(Discussion::class)->with('user); // 返回使用者的討論資料時,一併返回 user 資訊,後面會用到
}

前端腳手架

新增前端模板,我們僅在開發環境中使用,因為正式的生產環境都是打包好的前端檔案,不需要載入這個包,所以我們新增 --dev 引數。

composer require laravel/ui --dev

引入 Vue 和 Bootstrap

php artisan ui bootstrap
php artisan ui vue
php artisan ui:auth // 建立使用者腳手架

安裝前端依賴並編譯

npm install && npm run dev

如果你遇到了以下報錯:

Error: Cannot find module 'webpack/lib/rules/DescriptionDataMatcherRulePlugin'

嘗試升級 vue-loader

npm update vue-loader

現在前端檔案應該編譯成功了,因為我們要編寫一些前端程式碼,所以還需要配置一下 laravel mix,讓開發流程更順暢。

開啟專案根目錄的 webpack.mix.js 檔案:

mix.js('resources/js/app.js', 'public/js')
    .vue().version()  // 新增 .version() 避免快取
    .sass('resources/sass/app.scss', 'public/css');

mix.browserSync({
    proxy: 'chat.test' // 你的本地專案地址
});

我們使用了 broswerSync 這樣就不需要每次更改前端程式碼後都去 F5 重新整理頁面。

再次執行

npm run watch

這時,你的瀏覽器應該會自動開啟專案地址,並且每次修改前端程式碼都會自動重新整理。

再來新增前端的 websocket 監聽元件

npm install laravel-echo pusher-js --save

接下來我們來新增一些測試資料

首先開啟專案地址 /register,註冊兩個賬號,然後開啟 topics 表,建立幾個話題,直接寫庫即可。

從 0 開始打造聊天室,搞定 Laravel 實時通訊 —— 前端部分

再開啟 topic_user,關聯使用者與話題的關係,如果你懶得手動寫的話,直接複製執行以下 SQL 程式碼。

INSERT INTO `chat`.`topic_user`(`id`, `user_id`, `topic_id`, `created_at`, `updated_at`) VALUES (1, 1, 1, '2022-01-31 13:45:50', '2022-01-31 13:45:50');
INSERT INTO `chat`.`topic_user`(`id`, `user_id`, `topic_id`, `created_at`, `updated_at`) VALUES (2, 1, 2, '2022-01-31 13:46:00', '2022-01-31 13:46:04');
INSERT INTO `chat`.`topic_user`(`id`, `user_id`, `topic_id`, `created_at`, `updated_at`) VALUES (3, 2, 1, '2022-01-31 13:46:11', '2022-01-31 13:46:13');

一切準備就緒,下面我們只需要專注開發部分就可以了。

本作品採用《CC 協議》,轉載必須註明作者和本文連結
我從未見過一個早起、勤奮、謹慎,誠實的人抱怨命運。

相關文章