Публикация на тему
В статье рассматривается разработка чата на сокетах с бэкендом на Laravel, фронтендом на Vue. В частности, разбираем следующие темы: разработка бэкенд на Laravel, использование Vue для фронтенда, JWT-аутентификация, Laravel-echo сервер и база redis.
Анотация
Автор
Михалькевич Александр Викторович
Наименование Laravel Vue Socket чат
Автор А.В.Михалькевич
Специальность В статье рассматривается разработка чата на сокетах с бэкендом на Laravel, фронтендом на Vue. В частности, разбираем следующие темы: разработка бэкенд на Laravel, использование Vue для фронтенда, JWT-аутентификация, Laravel-echo сервер и база redis.,
Анотация -
Anotation in English -
Ключевые слова laravel, echo-server, чат, vue, socket
Количество символов 17871
Для понимания кода вам понадобятся следующие навыки:
• Основы PHP и Laravel;
• Умение создавать и работать с компаненами Vue;
• Понимание того, как работает Axios, отправлять и получаеть запросы по HTTP с помощью Axios HTTP client;
• Понимание принципов взаимодействия бэкенда и фронтенда.
• Понимание того, как работает JWT токен.
• Умение запускать node-сервера.
• Умение подключаться к базе redis.
Сперва с помощью команды:
composer create-project laravel/laravel backend
Создадим новый проект. После - создадим базу данных chat и подключимся к ней в файле .env
В этом же файле пропишем необходимые настройки для подключения к базе данных Redis
BROADCAST_DRIVER=redis QUEUE_CONNECTION=redis QUEUE_DRIVER=sync ... REDIS_HOST=127.0.0.1 REDIS_PASSWORD=null REDIS_PORT=6379
Создадим нужные модели и миграции с помощью команд
php artisan make:model Chat -m
и
php artisan make:model Message -m
Далее заполним миграции. Миграция CreateChatsTable:
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateChatsTable extends Migration { public function up(){ Schema::create('chats', function (Blueprint $table){ $table->id(); $table->string('name'); $table->timestamp('created_at')->useCurrent(); $table->timestamp('updated_at')->useCurrent(); }); } public function down(){ Schema::dropIfExists('chats'); } }
И миграция CreateMessagesTable
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateMessagesTable extends Migration { public function up(){ Schema::create('messages', function (Blueprint$table) { $table->id(); $table->unsignedBigInteger('user_id'); $table->unsignedBigInteger('chat_id'); $table->string('message'); $table->timestamp('created_at')->useCurrent(); $table->timestamp('updated_at')->useCurrent(); $table->foreign('user_id')->references('id')->on('users'); $table->foreign('chat_id')->references('id')->on('chats'); }); } public function down(){ Schema::dropIfExists('messages'); } }
Далее запускаем команду
php artisan migrate
Теперь можно приступить к созданию контроллеров приложения.
php artisan make:controller ChatControllerОсновная задача контроллера - взаимодействовать с моделью Chat, и выполнять основные операции по созданию, удалению, обновлению и выводу записей таблицы chats
namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Validator; use App\Models\Chat; class ChatController extends Controller { public function get(Request $request) { return Chat::find($request->id); } public function getAll(Request $request) { return Chat::all(); } public function create(Request $request) { $validator = Validator::make($request->all(), [ 'name' => 'required|string|max:255', ]); if($validator->fails()){ return response()->json($validator->errors()->toJson(), 400); } $chat = Chat::create([ 'name' => $request->get('name'), ]); return response()->json($chat, 201); } public function update(Request $request) { $validator = Validator::make($request->all(), [ 'name' => 'required|string|max:255', ]); if($validator->fails()){ return response()->json($validator->errors()->toJson(), 400); } $chat = Chat::find($request->id); $chat->name = $request->get('name'); $chat->save(); return response()->json($chat); } public function delete(Request $request) { $chat = Chat::find($request->id); $chat->delete(); return response(null, 200); } }
Создадим контроллер MessageController с помощью команды:
php artisan make:controller MessageControllerОсновная задача этого контроллера - создание и вывод сообщенй выбранного чата.
namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Auth; use App\Models\Message; use App\Events\MessageSent; class MessageController extends Controller { public function getAll(Request $request) { return Message::with(['user', 'chat']) ->where('chat_id', $request->chat_id) ->get(); } public function create(Request $request) { $validator = Validator::make($request->all(), [ 'message' => 'required|string|max:2500', ]); if($validator->fails()){ return response()->json($validator->errors()->toJson(), 400); } $message = Message::create([ 'message' => $request->get('message'), 'chat_id' => $request->get('chat_id'), 'user_id' => $request->get('user_id') ]); $user = Auth::user(); event(new MessageSent($user, $message)); return response()->json($message, 201); } }Обратите внимание, что при создании сообщения (экшн create) вызывается событие MessageSent. Класс события необходимо реализовать, сделаем это чуть позже, после того, как создадим все нужные контроллеры и пропишем маршруты.
Сперва необходимо установить модуль JWT-auth
composer require tymon/jwt-auth
Далее опубликовать необходимые файлы:
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
Далее создать секретный ключ для JWT
php artisan jwt:secret
Далее настроить конфигурацию для работы с JWT-токенон. Убедитесь в том, что в файле config/auth.php имеются следующие настройки:
return [ 'defaults' => [ 'guard' => 'api', 'passwords' => 'users', ], 'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'jwt', 'provider' => 'users', ], ], 'providers' => [ 'users' => [ 'driver' => 'eloquent', 'model' => App\Models\User::class, ], ], 'passwords' => [ 'users' => [ 'provider' => 'users', 'table' => 'password_resets', 'expire' => 60, 'throttle' => 60, ], ], 'password_timeout' => 10800, ];
Приложения фронт и бэк будут находиться на разных доменах. Поэтому необходимо подключить модуль кроссдоменных сообщений, иначе браузер будет выдавать ошибку cors.
composer require fruitcake/laravel-corsДалее, внесём неоходимые изменения в файл
/App/Http/Kernel.php
.
В частности, в свойство $middleware
:
protected $middleware = [ // \App\Http\Middleware\TrustHosts::class, \App\Http\Middleware\TrustProxies::class, \Fruitcake\Cors\HandleCors::class, \App\Http\Middleware\PreventRequestsDuringMaintenance::class, \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \App\Http\Middleware\TrimStrings::class, \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, \Fruitcake\Cors\HandleCors::class, ];и в свойство
$routeMiddleware
:
protected $routeMiddleware = [ 'auth' => \App\Http\Middleware\Authenticate::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 'can' => \Illuminate\Auth\Middleware\Authorize::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, 'jwt.verify' => \App\Http\Middleware\JwtMiddleware::class, ];Теперь мы можем использовать
'jwt.verify'
или JwtMiddleware
для защиты маршрутов.
Ещё необходимо установить модуль predis
composer require predis/predisА в файле
config/database.php
должны быть прописаны следующие настройки для redis:
'redis' => [ 'client' => env('REDIS_CLIENT', 'predis'), 'options' => [ 'cluster' => env('REDIS_CLUSTER', 'redis'), 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), ], 'default' => [ 'url' => env('REDIS_URL'), 'host' => env('REDIS_HOST', '127.0.0.1'), 'password' => env('REDIS_PASSWORD', null), 'port' => env('REDIS_PORT', '6379'), 'database' => env('REDIS_DB', '0'), ], 'cache' => [ 'url' => env('REDIS_URL'), 'host' => env('REDIS_HOST', '127.0.0.1'), 'password' => env('REDIS_PASSWORD', null), 'port' => env('REDIS_PORT', '6379'), 'database' => env('REDIS_CACHE_DB', '1'), ], ],
Теперь, когда мы закончили с добавлением маршрутов, аутентификации и конфигурации базы данных и создали необходимые контроллеры, мы можем добавить код, позволяющий общаться между
фронтенд и бэкэнд в режиме реального времени. Во-первых, нам нужно создать класс события, чтобы мы могли вызывать функцию события для трансляции события, которое вызывается в MessageController.
Для этого мы запускаем команду:
php artisan make:event MessageSentДалее находим файл
Events/MessageSent.php
и заменяем его содержимое следующим кодом:
namespace App\Events; use App\Models\User; use App\Models\Message; use Illuminate\Broadcasting\Channel; use Illuminate\Queue\SerializesModels; use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Broadcasting\PresenceChannel; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Contracts\Broadcasting\ShouldBroadcast; class MessageSent implements ShouldBroadcast { use InteractsWithSockets, SerializesModels; /** * User that sent the message * * @var User */ public $user; /** * Message details * * @var Message */ public $message; /** * Create a new event instance. * * @return void */ public function __construct(User $user, Message $message) { $this->user = $user; $this->message = $message; } /** * Get the channels the event should broadcast on. * * @return Channel|array */ public function broadcastOn() { return new Channel('chat'); } public function broadcastAs() { return 'MessageSent'; } }
routes/chanels.php
пропишем следующее:
use Illuminate\Support\Facades\Broadcast; Broadcast::channel('chat', function () { return true; });
Остальные и основные маршруты приложения пропишем в файле routes/api.php
Route::post('register', [UserController::class, 'register']); Route::group([ 'middleware' => 'api', 'prefix' => 'auth' ], function () { Route::post('login', [AuthController::class, 'login']); Route::post('logout', [AuthController::class, 'logout']); Route::post('refresh', [AuthController::class, 'refresh']); Route::post('me', [AuthController::class, 'me']); }); Route::group([ 'middleware' => ['api', 'jwt.verify'], 'prefix' => 'chat' ], function () { Route::get('{id}', [ChatController::class, 'get']); Route::get('', [ChatController::class, 'getAll']); Route::post('create', [ChatController::class, 'create']); Route::put('update/{id}', [ChatController::class, 'update']); Route::delete('delete/{id}', [ChatController::class, 'delete']); }); Route::group([ 'middleware' => ['api', 'jwt.verify'], 'prefix' => 'message' ], function () { Route::get('{chat_id}', [MessageController::class, 'getAll']); Route::post('create', [MessageController::class, 'create']); });
Сперва создадим папку laravel-echo-server на уровне с папкой backend.
В этой папке будет находиться нужный конфиг, и запускать laravel-echo-server будет отсюда.
Теперь нам пригодится последняя актуальная версия базы Redis.
Если у вас уже установлен Redis, то сразу можете переходить к настройке файла laravel-echo-server.json
sudo apt update sudo apt install redis-serverПосле этого можно установить laravel-echo-server. Делать это лучше глобально:
npm i laravel-echo-server
В папке laravel-echo-server необходимо создать файл laravel-echo-server.json со следующим содержимым:
{ "authHost": "http://localhost:8000", "authEndpoint": "/broadcasting/auth", "clients": [ { "appId": "APP_ID", "key": "c84077a4dabd8ab2a60e51b051c9d0ea" } ], "database": "redis", "databaseConfig": { "redis": { "port": "6379", "host": "localhost" }, "sqlite": { "databasePath": "/database/laravel-echo-server.sqlite" }, "publishPresence": true }, "devMode": true, "host": "127.0.0.1", "port": "6001", "protocol": "http", "socketio": {}, "secureOptions": 67108864, "sslCertPath": "", "sslKeyPath": "", "sslCertChainPath": "", "sslPassphrase": "", "subscribers": { "http": true, "redis": true }, "apiOriginAllow": { "allowCors": true, "allowOrigin": "*", "allowMethods": "GET, POST", "allowHeaders": "Origin, Content-Type, X-Auth-Token, X-Requested-With, Accept, Authorization, X-CSRF-TOKEN, X-Socket-Id" } }