最近在項目中一直在開發即時消息的應用場景,本次將經過Laravel來構建一個即時的匹配系統,可適用於基本的即時對戰小遊戲,須要用到的知識點以下:php
下面開始實戰操做:css
composer install predis //安裝redis擴展包 npm install laravel-echo //安裝laravel-echo客戶端 npm install -g laravel-echo-server //安裝laravel-echo-server 服務端
具體部署教程可參照以前寫的博客:
打造你的Laravel即時應用前端
在咱們的這個基礎系統中,須要用戶(user)和比賽(game),比賽主要是用於將匹配到的用戶放到一塊兒,下面將經過3張表來完成這個系統的的初始設計。ios
1.比賽表設計laravel
$table->unsignedInteger('user_id')->index(); $table->unsignedInteger('rival_id')->index()->comment('對手ID'); $table->unsignedInteger('winner_id')->default(0)->comment('勝利者ID'); $table->unsignedInteger('reward')->default(0)->comment('獎勵'); $table->json('data')->nullable()->comment('比賽數據'); $table->tinyInteger('status')->default(0)->comment('比賽狀態'); $table->timestamps(); $table->timestamp('end_at')->nullable()->comment('結束時間');
2.用戶匹配表設計redis
$table->increments('id'); $table->unsignedInteger('user_id')->unique(); $table->tinyInteger('online_status')->default(0); $table->timestamp('online_at')->nullable()->index(); $table->unsignedInteger('in_game_id')->default(0)->comment('在房間ID'); $table->timestamps();
咱們經過Vue來實現一個簡單的視圖,運用到了props、axios等基礎組件來實現.npm
<template> <div class="game-status"> <div class="user-info"> <div class="text-left">您的遊戲信息以下:</div> <div class="clearfix"> <div class="float-left" v-if="me"">您的暱稱:{{ me.name }}</div> <div class="float-right" v-if="rival">對手暱稱:{{ rival.name }}</div> </div> </div> <div class="alert alert-success" role="alert">{{ gameStatus }}</div> <button type="button" class="btn btn-success">開始匹配</button> </div> </template> <script> import Echo from "laravel-echo"; export default { props: ["user"], data() { return { gameStatus: "等待匹配中", isMatching: true, meId: null, me: null, rival: null }; }, mounted() { this.me = JSON.parse(this.user); console.log(this.me); this.meId = this.me.id; }, }; </script>
若是你使用的css框架是bootstrap,構建出來的應該會是這樣。json
提升一個matchUser的接口給前端來進行調用,首先會去查詢user_games的記錄,不存在則建立一條,稍後再查詢當前是否有正在進行中的比賽,存在的話,則拋出異常提醒用戶,不然進入匹配隊列。bootstrap
//查找或建立用戶 $userGame = UserGame::firstOrCreate(['user_id' => $user->id]); //是否有遊戲中,防止重複請求 $game = $userGame->hasPlaying(); throw_if(!is_null($game), GameException::class, '您已在PK中,請進入遊戲!'); //用戶上線 $userGame->online(); //分發到匹配隊列中 dispatch(new MatchGame($this))->onQueue('game-match');
當用戶成功進入匹配隊列後,咱們會將全部匹配中的用戶進行對手分配,每次獲取兩個用戶爲一局比賽,邏輯以下:axios
UserGame::with('user')->onlined()->orderBy('online_at')->chunk(100, function ($collect) { foreach ($collect->chunk(2) as $userGames) { if (count($userGames) < 2) { continue; } $user1 = $userGames->first()->user; $user2 = $userGames->last()->user; $game = $this->gameService->createGame($user1, $user2); //發送socket通知雙方用戶開始 $this->gameService->sendSocket($game, 'NewGame'); } });
當匹配接口和匹配隊列完成後,就開始對視圖進行綁定及掛載Echo socket通訊。
<template> <div class="game-status"> <div class="user-info"> <div class="text-left">您的遊戲信息以下:</div> <div class="clearfix"> <div class="float-left" v-if="me"">您的暱稱:{{ me.name }}</div> <div class="float-right" v-if="rival">對手暱稱:{{ rival.name }}</div> </div> </div> <div class="alert alert-success" role="alert">{{ gameStatus }}</div> <button type="button" class="btn btn-success" @click="matchGame">開始匹配</button> </div> </template> <script> import Echo from "laravel-echo"; export default { props: ["user"], data() { return { gameStatus: "等待匹配中", isMatching: true, meId: null, me: null, rival: null }; }, mounted() { this.me = JSON.parse(this.user); console.log(this.me); this.meId = this.me.id; }, methods: { matchGame: function() { let _this = this; this.isMatching = false; //1.發送匹配其餘用戶請求 this.$axios.get("/match?user_id=" + this.meId).then(res => { _this.gameStatus = "正在匹配中"; }); this.mountedUserEventListen(); }, mountedUserEventListen: function() { //監聽當前用戶Channel && NewGame event let echo = this.initEcho(); let _this = this; echo.private("App.User." + this.meId).listen("NewGame", function(e) { let game = e.game; _this.gameStatus = "用戶匹配成功"; _this.rival = _this.meId == game.user_id ? game.rival : game.user; }); }, initEcho: function() { if (window.Echo == null) { window.io = require("socket.io-client"); window.Echo = new Echo({ broadcaster: "socket.io", host: window.location.hostname + ":6001", auth: { headers: { Authorization: "Bearer " + this.me.api_token } } }); return window.Echo; } } } }; </script>
運行指令:
laravel-echo-server start //開啓socket服務 php artisan queue:work //開啓隊列消費 php artisan queue:work --queue=game-match //開啓匹配隊列消費
本篇主要經過後端角度出發,來使用隊列技術,實現了用戶的即時匹配系統,核心知識點主要在laravel隊列和laravel-echo的使用,經過隊列將匹配中的用戶進行組合,建立一局新比賽,經過服務端廣播NewGame,客戶端監聽NewGame事件來達到即時匹配的效果。