安装laravel
composer create-project --prefer-dist laravel/laravel laravel-reverb-chat
cd laravel-reverb-chat
配置.env
cp .env.example .env
vim .env
php artisan install:broadcasting
npm install --save laravel-echo pusher-js
查看配置文件已生成
# .env
REVERB_APP_ID=836301
REVERB_APP_KEY=o2kwvwhasspm4ek1u
REVERB_APP_SECRET=krwrs2wk4oyxpsh
REVERB_HOST="localhost"
REVERB_PORT=8080
REVERB_SCHEME=http
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"
房间表生成迁移
php artisan make:model Room --migration
编辑 database/migrations
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('rooms', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('rooms');
}
};
执行迁移
php artisan migrate
生成数据
php artisan make:seeder RoomsTableSeeder
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class RoomsTableSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
DB::table('rooms')->insert([
['name' => 'Room 1'],
['name' => 'Room 2'],
['name' => 'Room 3'],
['name' => 'Room 4'],
['name' => 'Room 5'],
]);
}
}
执行
php artisan db:seed
创建 app/Events/MessageSent.php
<?php
namespace App\Events;
use App\Models\ChatMessage;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\Channel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class MessageSent implements ShouldBroadcastNow
{
use Dispatchable;
use InteractsWithSockets;
use SerializesModels;
public $userName;
public $roomId;
public $message;
public function __construct($userName, $roomId, $message)
{
$this->userName = $userName;
$this->roomId = $roomId;
$this->message = $message;
}
public function broadcastOn() : Channel
{
return new Channel('chat.' . $this->roomId);
}
public function broadcastWith()
{
return [
'userName' => $this->userName,
'message' => $this->message,
];
}
}
创建 resources/views/rooms/index.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Chat Rooms</title>
</head>
<body>
<div id="app">
<h1>Chat Rooms</h1>
<ul>
@foreach($rooms as $room)
<li>
<a href="{{ route('rooms.show', $room->id) }}">Join {{ $room->name }}</a>
</li>
@endforeach
</ul>
</div>
</body>
</html>
创建 resources/views/rooms/chat.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chat Room: {{ $room->name }}</title>
@vite(['resources/css/app.css'])
@vite(['resources/js/app.js'])
</head>
<body>
<div id="app">
<h2>Chat Room: {{ $room->name }}</h2>
<div id="messages"
style="border: 1px solid #ccc; margin-bottom: 10px; padding: 10px; height: 300px; overflow-y: scroll;">
<!-- Messages will be displayed here -->
</div>
<input type="text" id="messageInput" placeholder="Type your message here..." autofocus>
<button onclick="sendMessage()">Send</button>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
const roomId = "{{ $room->id }}";
Echo.channel(`chat.${roomId}`)
.listen('MessageSent', (e) => {
const messages = document.getElementById('messages');
const messageElement = document.createElement('div');
messageElement.innerHTML = `<strong>${e.userName}:</strong> ${e.message}`;
messages.appendChild(messageElement);
messages.scrollTop = messages.scrollHeight; // Scroll to the bottom
});
})
function sendMessage() {
const messageInput = document.getElementById('messageInput');
const message = messageInput.value;
messageInput.value = ''; // Clear input
const roomId = "{{$room->id}}"
fetch(`/rooms/${roomId}/message`, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}',
'Content-Type': 'application/json'
},
body: JSON.stringify({message: message})
}).catch(error => console.error('Error:', error));
}
</script>
</body>
</html>
修改 resources/js/app.js
import './bootstrap';
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;
window.Echo = new Echo({
broadcaster: 'reverb',
key: import.meta.env.VITE_REVERB_APP_KEY,
wsHost: import.meta.env.VITE_REVERB_HOST,
wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
enabledTransports: ['ws', 'wss'],
});
生成控制器 php artisan make:controller RoomsController
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Room;
class RoomsController extends Controller
{
public function index()
{
$rooms = Room::all();
return view('rooms.index',[
'rooms' => $rooms
]);
}
public function show(Room $room)
{
return view('rooms.chat', [
'roomId' => $room->id,
'room' => $room,
'messages' => []
]);
}
}
生成控制器 php artisan make:controller ChatController
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use App\Events\MessageSent;
class ChatController extends Controller
{
public function postMessage(Request $request, $roomId)
{
$userName = 'User_' . Str::random(4);
$messageContent = $request->input('message');
MessageSent::dispatch($userName, $roomId, $messageContent);
return response()->json(['status' => 'Message sent successfully.']);
}
}
routes/web.php 文件
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\RoomsController;
use App\Http\Controllers\ChatController;
Route::get('/', function () {
return view('welcome');
});
Route::get('/rooms', [RoomsController::class, 'index'])->name('rooms.index');
Route::get('/rooms/{room}', [RoomsController::class, 'show'])->name('rooms.show');
Route::post('/rooms/{roomId}/message', [ChatController::class, 'postMessage'])->name('api.rooms.message.post');
运行项目
php artisan serve
npm run dev
php artisan queue:listen
php artisan reverb:start
# 可以设置特定的主机或端口,默认8080
php artisan reverb:start --host=127.0.0.1 --port=9000
访问 http://127.0.0.1:8000/rooms
在https域名下配置, 本地生成证书可以使用 https://github.com/FiloSottile/mkcert
# nginx 代理
location /app { # WebSocket 路径
proxy_pass http://127.0.0.1:8080; # Laravel Reverb 运行的地址
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# .env
REVERB_HOST="xxxxx.com"
REVERB_SCHEME=https
REVERB_PORT=443
本地开发如果报错cURL error 60: SSL certificate problem: 找到修改为false
/www/wwwroot/demo/chat/laravel-reverb-chat/vendor/guzzlehttp/guzzle/src/Client.php
'verify' => false,
网友评论