更新提醒(2026-10):本文寫於 2022 年,使用 beyondcode/laravel-websockets、Jetstream Livewire 與 Laravel Mix 的設定。這些套件的版本與前端建置方式可能已與新版 Laravel 不相容(新版專案多半改用 Vite,官方也提供 Reverb),請自行確認各套件目前的維護狀態。較新的作法可參考 【RealTime】Laravel Reverb - 官方 WebSocket Server 完整實作。
本篇介紹如何在 Laravel 框架中,利用 WebSocket 製作一個聊天室。
先想一下整體流程:
登入→列表→加入聊天室→聊天室→傳送、接收訊息(socket)
接著規劃大致需要的資料表(Tables):
users(會員)
rooms(聊天室)
user_room(會員與聊天室的關聯)
messages(聊天訊息)
這個範例是用自己的主機當作 socket 伺服器
不想自己設定主機參數的話,可以改用 Pusher
# 新增Project
laravel new {project}
修改 .env
# .env
BROADCAST_DRIVER=pusher
PUSHER_APP_ID=chat_room
PUSHER_APP_KEY=chatchatchat
PUSHER_APP_SECRET=chatchatchatchatchatchat
修改 bootstrap.js
# /resources/js/bootstarp.js
import Echo from 'laravel-echo'
window.Pusher = require('pusher-js');
window.Echo = new Echo({
broadcaster: 'pusher',
encrypted: false,
key: process.env.MIX_PUSHER_APP_KEY,
cluster: process.env.MIX_PUSHER_APP_CLUSTER,
wsHost: window.location.hostname,
wsPort: 6001,
forceTLS: false,
disableStats: true
});
安裝套件
# 會員套件&啟用
npm install
composer require laravel/jetstream
php artisan jetstream:install livewire
npm run dev
# socket套件
composer install
composer require pusher/pusher-php-server ^4.1
npm install --save laravel-echo pusher-js
composer require beyondcode/laravel-websockets
php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="migrations"
php artisan migrate
php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="config"
資料庫規劃
users

rooms

user_room

messages

接著規劃 broadcast 的 channel:
至少在聊天室裡要能即時推播訊息(依 room_id 區分)。
(以下為部分程式碼)
app\Http\Controllers\MessageController
namespace App\Http\Controllers;
use App\Models\Room;
use App\Models\RoomJoin;
use App\Models\Message;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Events\RoomMessageChannelEvent;
use Carbon\Carbon;
class MessageController extends Controller
{
/**
* @var Room
*/
public $model;
/**
* @var RoomJoin
*/
public $join;
/**
* @var Message
*/
public $message;
/**
* RoomController constructor.
*
* @param Room $room
* @param RoomJoin $join
* @param Message $message
*/
public function __construct(Room $room, RoomJoin $join, Message $message)
{
$this->model = $room;
$this->join = $join;
$this->message = $message;
}
/**
* @param \Illuminate\Http\Request $request
* @param $id
*
* @Author: Roy
* @DateTime: 2021/10/23 下午 12:15
*/
public function store(Request $request, $id)
{
if ($this->checkRoomAndUser($id) === false) {
return response()->json([
'status' => false,
'message' => '房間狀態有誤',
'redirect_uri' => route('room.chat', ['id' => $id]),
]);
}
$this->message->create(
[
'user_id' => Auth::user()->id,
'room_id' => $id,
'date' => Carbon::now()->toDateString(),
'content' => $request->get('content'),
]
);
broadcast((new RoomMessageChannelEvent(['user' => Auth::user(), 'content' => $request->get('content')], $id)));
return response()->json([
'status' => true,
'message' => null,
]);
}
}
app\event\RoomMessageChannelEvent
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Arr;
use Carbon\Carbon;
/**
* Class RoomMessageChannelEvent
*
* @package App\Events\Rooms
* @Author: Roy
* @DateTime: 2021/10/21 下午 02:34
*/
class RoomMessageChannelEvent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $message;
public $room_id;
/**
* RoomMessage constructor.
*
* @param $message
* @param $room_id
*
* @Author: Roy
* @DateTime: 2021/10/21 上午 11:54
*/
public function __construct($message, $room_id)
{
$this->room_id = $room_id;
$this->message = (object) [
'user' => (object) [
'id' => Arr::get($message, 'user.id'),
'name' => Arr::get($message, 'user.name'),
'image' => sprintf("https://ui-avatars.com/api/?name=%s&color=7F9CF5&background=EBF4FF",
Arr::get($message, 'user.name')),
],
'content' => Arr::get($message, 'content'),
'time' => Carbon::now()->format('H:i'),
];
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PresenceChannel(sprintf("chat.%s", $this->room_id));
}
}
前端的 JavaScript 使用 laravel-echo、jQuery 與 Mustache。
啟動 WebSocket(預設連接埠:6001)
# 預設port:6001
php artisan websockets:serve
//php artisan websockets:serve --port=6001
範例畫面
GitHub 範本
https://github.com/cc711612/chat_room
範本
參考文獻
https://beyondco.de/docs/laravel-websockets/getting-started/introduction
https://learnku.com/docs/laravel/8.x/broadcasting/9388#presence-channels
延伸閱讀
- 【RealTime】Laravel Reverb - 官方 WebSocket Server 完整實作:同樣是即時聊天的需求,可以改用官方 Reverb 實作,兩者做法可對照
- 【RealTime】Websocket VS SSE (Server-Sent Events):聊天室之外的單向推播場景,也許 SSE 更簡單

No comments yet.