使用 Laravel 和 React 构建实时聊天应用

LaravelReact 很棒,感谢 WebSocket 协议 和易于使用的 Laravel Echo 库,我们可以轻松构建实时应用程序。

Laravel

在本文中,我们将使用 Laravel Echo Server 作为具有 Laravel Echo 兼容性的 socket.io 服务器。

Laravel Echo ServerLaravel 8 通信将由 Redis 处理。

我们将启动我们的应用程序,而无需使用 Laravel BreezeInertia.js 构建身份验证系统,我们不需要为 ReactLaravel 通信编写 API。除此之外,我们将使用 TailwindCSS 进行样式设置,这也是 Breeze React stack 的默认设置。

你可以在底部找到完整的演示 repo 和生产演示链接。

Laravel 设置入门#

第 1 步:按照 官方文档 中的指导创建一个新的 Laravel 项目。#

Docker(Laravel Sail) 是开发 Laravel 应用程序的最佳和最快的方法。只需将 “example-app” 更改为你喜欢的任何内容,然后在终端中运行以下命令:

curl -s https://laravel.build/example-app | bash

然后 cd 到文件夹并运行:

./vendor/bin/sail up -d

第一次运行 sail up 命令可能需要一点时间来构建 Docker 镜像。

你可以使用 http://localhost 访问 Laravel 欢迎页面

为 Sail 设置 Shell 别名:

alias sail='[ -f sail ] && bash sail || bash vendor/bin/sail'

通过设置别名,你无需输入 ./vendor/bin

如果你的系统中没有 npmyarnnode ,你可以使用 sail npmsail yarnsail node

第 2 步:安装 Breeze 软件包#

Laravel Breeze 是 Laravel 所有身份验证功能 的最小、简单实现,包括登录、注册、密码重置、电子邮件验证和密码确认。 Laravel Breeze 的默认视图层由简单的 Blade 模板 组成,并带有 Tailwind CSS 样式。

运行:

sail composer require laravel/breeze --dev

使用 React 脚手架提取包:

sail artisan breeze:install react

现在运行以下命令来安装包和迁移:

npm install && npm run devsail artisan migrate

第 3 步:Docker 配置#

打开 docker-compose.yml 并添加以下内容。

        networks:
            - sail
    laravel-echo-server:
        image: 'oanhnn/laravel-echo-server'
        ports:
            - '6001:6001'
        volumes:
            - '${PWD}/laravel-echo-server.json:/app/laravel-echo-server.json'
        networks:
            - sail**networks:
    sail:
        driver: bridge

创建 laravel-echo-server.json 到根文件夹并将其粘贴到开发环境中。你应该稍后进行更改以进行生产。

{
    "authHost": "[http://laravel.test](http://laravel.test/)",
    "authEndpoint": "/broadcasting/auth",
    "clients": [],
    "database": "redis",
    "databaseConfig": {
        "redis": {
            "port": "6379",
            "host": "redis",
   "keyPrefix": "laravel_database_"
        }
    },
    "devMode": true,
    "host": null,
    "port": "6001",
    "protocol": "http",
    "socketio": {},
    "sslCertPath": "",
    "sslKeyPath": "",
    "subscribers": {
        "http": true,
        "redis": true
    },
    "apiOriginAllow": {
        "allowCors": true,
        "allowOrigin": "[http://localhost](http://localhost/)",
        "allowMethods": "GET, POST",
        "allowHeaders": "Origin, Content-Type, X-Auth-Token, X-Requested-With, Accept, Authorization, X-CSRF-TOKEN, X-Socket-Id"
    }
}

删除容器并创建一个新容器:

sail down && sail up -d

第 4 步:Laravel 基本配置#

config/app.php 中取消注释 App\Providers\BroadcastServiceProvider::class

.env 环境变量文件中将 BROADCAST_DRIVER 设置为 redis

第 5 步:安装客户端扩展包#

npm install --save laravel-echo socket.io-client@2.4.0

将此代码添加到 /resources/js/bootstrap.js

import Echo from 'laravel-echo';window.io = require('socket.io-client');window.Echo = new Echo({
    broadcaster: 'socket.io',
    host: window.location.hostname + ':6001',
});

测试 Echo 服务器#

使用 artisan 创建测试事件:

sail artisan make:event TestEvent

我们将创建一个公共频道(无需身份验证)用于手动测试并返回 ['title' => 'Test Title', 'message' => 'Test Message'] 数组。

TestEvent 事件应如下所示:

<?php

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;

class TestEvent implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
     * 创建一个新的事件实例。
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * 获取事件应该广播的频道。
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new Channel('test-channel');
    }

    /**
     * 获取要广播的数据。
     *
     * @return array
     */
    public function broadcastWith()
    {
        return [
            'title' => 'Test Title',
            'message' => 'Test Message',
        ];
    }
}

现在为广播添加一个测试路由:

Route::get('add-notification', function () {
    broadcast(new \App\Events\TestEvent);
    return 'Test';
});

将此添加到 /resources/js/bootstrap.js(仅用于临时,测试后删除)

window.Echo.channel('test-channel').listen('TestEvent', event => {
    console.log(event);
});

编译前端资产(Mix):

npm run dev

访问 http://localhost,在另一个浏览器选项卡上访问 http://localhost/add-notification

你应该会在欢迎页面控制台中看到 broadcastWith() 返回数据。

构建聊天应用#

第 1 步:创建模型和迁移#

使用 artisan 创建一个消息模型

sail artisan make:model Message --migration

转到 database/migrations 文件夹并将 user_idroom_idmessage 列添加到 messages 表 迁移文件中:

$table->integer('user_id')->unsigned();
$table->integer('room_id')->unsigned();
$table->text('message');

使用 artisan 命令创建一个 Room 模型 :

sail artisan make:model Room --migration

在同一文件夹中,将 name 列添加到 rooms 表 迁移文件中:

$table->string('name')->unique();

如果需要,你可以删除时间戳。

执行迁移任务。如果出现错误,你还可以使用 sail artisan cache:clear 清除缓存。

sail artisan migrate

现在我们将通过向模型添加方法来建立关系。

Message 模型中设置 messageroom_id 为可填充,并添加 user()room() 方法。

protected $fillable = ['message', 'room_id'];public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function room()
    {
        return $this->belongsTo(Room::class);
    }

Room 模型中,设置 name 为可填充并添加 messages() 方法。

protected $fillable = ['name'];public function messages()
    {
        return $this->hasMany(Message::class);
    }

将此方法添加到 User 模型:

public function messages()
    {
        return $this->hasMany(Message::class);
    }

第 2 步:MessageSent 事件#

使用 artisan 命令创建一个 MessageSent 事件:

sail artisan make:event MessageSent

现在我们将设置 UserRoomMessage 对象属性,在 broadcastOn() 方法中返回 Presence 通道来满足身份验证要求,而不是传递所有模型对象数据,我们将出于安全原因,将特定的广播数据传递给客户端。

MessageSent 事件应如下所示:

代码已被折叠,点此展开

第 3 步:使用 tinker 生成一些数据库数据#

你可以使用以下命令运行 tinker:

sail artisan tinker

运行以下命令创建五个用户:

\App\Models\User::factory(5)->create();

运行以下命令创建房间:

DB::table('rooms')->insert(array_map(function ($room) {
            return ['name' => $room];
        }, ['general', 'room1', 'room2', 'room3', 'room4']));
}

第 4 步:创建控制器并设置路由配置#

使用 artisan 命令创建一个 ChatsController

sail artisan make:controller ChatsController

在具有 renderChat() 方法的控制器中,我们正在渲染一个包含已保存消息、房间数据的聊天页面; 使用 sendMessage() 方法,我们正在向数据库创建新的消息数据。

已完成的 ChatsController 应如下所示:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\Room;
use App\Events\MessageSent;
use Illuminate\Support\Facades\Response;

class ChatsController extends Controller
{
    /**
     * 使用 Inertia 渲染聊天页面。
     * 
     * @return \Inertia\Response
     */
    public function renderChat(Request $request, $room = null)
    {
        $rooms = Room::all()->pluck('name')->toArray();
        if ($room === 'general') return redirect()->route('chat');
        if (!$room) $room = "general";
        if (!in_array($room, $rooms)) abort(404);
        return Inertia::render('Chat', [
            'chatData' => [
                'messages' => Room::where('name', $room)->first()->messages()->with('user')->get(),
                'rooms' => $rooms,
                'room' => $room
            ]
        ]);
    }

    /**
     * 处理消息发布请求。
     *  
     * @return \Illuminate\Http\JsonResponse
     */
    public function sendMessage(Request $request)
    {
        $request->validate([
            'room' => ['required', 'string', 'max:50'],
            'message' => ['required', 'string', 'max:140'],
        ]);
        $user = $request->user();
        $room = Room::where('name', $request->room)->firstOrFail();
        $message = $user->messages()->create([
            'message' => $request->message,
            'room_id' => $room->id
        ]);
        broadcast(new MessageSent($user, $room, $message))->toOthers();
        return Response::json(['ok' => true]);
    }
}

/broadcasting/auth 是认证请求通道路由。

我们将只允许登录用户(使用在线通道),并且所有登录用户都可以获得身份验证。

routes/channel.php

Broadcast::channel('room.{id}', function ($user) {
    return $user->only('id', 'name');
});

为聊天页面和消息 POST 请求添加路由:

routes/web.php

Route::middleware(['auth', 'verified'])->group(function () {
    Route::get('/chat/{room?}',  [ChatsController::class, 'renderChat'])->name('chat');
    Route::post('/message', [ChatsController::class, 'sendMessage'])->name('message');
});

第 5 步:设置客户端#

我们将使用 Laravel Echo 库的方法来监听事件。

我们将使用 join() 加入出席频道,使用 here() 获取活跃用户,使用 joining() 处理用户加入事件,使用 leaving() 处理用户离开事件,使用 listen() 处理实时消息,使用 listenForWhisper() 处理打字事件

我们需要在 componentDidMount() 生命周期方法中调用这些方法。

基本上,我们将在第一次渲染时获取房间之前的所有消息,消息发送事件将由套接字实时处理,然后我们将其发布到数据库。

转到 resources/js/Components 并创建 Chat.js。聊天组件应如下所示:

代码已被折叠,点此展开

访问 resources/js/Pages/ 并创建 Chat.js:

import Chat from "@/Components/Chat";
import Authenticated from "@/Layouts/Authenticated";
import { Head } from "@inertiajs/inertia-react";
export default function ChatPage(props) {
    return (
        <Authenticated
            auth={props.auth}
            errors={props.errors}
            header={
                <h2 className="font-semibold text-xl text-gray-800 leading-tight">
                    Chat
                </h2>
            }
        >
            <Head title="Chat" />

            <div className="py-2">
                <div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
                    <Chat auth={props.auth} chatData={props.chatData} />
                </div>
            </div>
        </Authenticated>
    );
}

编译前端资源(Mix):

npm run dev

现在你可以通过访问 localhost/chat 访问聊天页面

生产演示:laravel-chat-app.ml/

完成的项目在 GitHub — sinanbekar/laravel-realtime-chat-app:Laravel 实时聊天应用演示。一步一步...

写在最后#

在本文中,我讨论了如何使用 Laravel 和现代技术轻松设置实时聊天应用程序。

当然,这是最简单的实现。你需要为大型生产和安全性进行更好的配置和调整。

如果你需要有关代码或应用程序设置的进一步帮助,欢迎通过文章评论或 GitHub Issue 探讨:)

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://towardsdev.com/build-a-real-time...

译文地址:https://learnku.com/laravel/t/66293

本文为协同翻译文章,如您发现瑕疵请点击「改进」按钮提交优化建议
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。