Laravel Reverb 广播使用

安装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,
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,658评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,482评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,213评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,395评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,487评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,523评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,525评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,300评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,753评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,048评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,223评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,905评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,541评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,168评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,417评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,094评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,088评论 2 352

推荐阅读更多精彩内容