前言
Laravel 實時通訊的三板斧分別是 Websocket 元件 Laravel-websocket(pusher)
, Laravel-echo
和 Broadcast
廣播功能來實現。
之前寫過一篇 部落格:一起來實現單使用者登入 —— 完成監聽 ,本文算是一個進階版,文章篇幅有點長,如果你能耐心看完呢,我想你會有收穫的。
原始碼地址: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\BroadcastServiceProvider::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
表,建立幾個話題,直接寫庫即可。
再開啟 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 協議》,轉載必須註明作者和本文連結