OrzLee

这个世界上只有一个问题
那就是时间问题

Laravel 5.7 Broadcast + laravel echo 实现 WebSocket C/S 实时通信

laravel_broadcast.jpg

前言

laravel Broadcast 平时项目中接触得比较少,最近公司年会活动需要做一个抽奖项目。要求用一太手机控制其他在抽奖页面的用户同时开始抽奖以及显示抽奖结果。提出项目需求时第一个想到的就是WebSocket,相对于使用AJAX轮询请求来说,WebSocket的实时性要高出不少,而且AJAX轮询请求服务器有太多不必要的请求,对于搞开发的来说这种不必要的请求就应该避免,不做多余操作。以前在项目中使用的WebSocket,也是抽奖项目,但是不是基于laravel框架,这次项目确实动力十足。

实现流程

实现步骤

配置

  1. app.php中启用广播系统服务提供者

         /*
          * Application Service Providers...
          */
         ...
         App\Providers\BroadcastServiceProvider::class,
         ...
  2. 修改.envBROADCAST_DRIVER配置位置redis

    ...
    BROADCAST_DRIVER=redis
    ...
  3. laravel redis客户端扩展: 查看database.phpredis['client'] 如果使用predis请安装composer包

     composer require predis/predis

    如果使用phpredis就需要自己编译PHP的reids扩展。 phpredis效率要高于predis,按自己项目需求取舍

  4. 自定义配置文件config/lottery.php: 在该项目中使用的是扩展包开发,配置文件中有些命名空间应该修改为自己对应的,可以先定义,看完文章后再回过头来就明白了。

    return [
     /*
      * 频道名称
      */
     'channel_name' => env('LOTTERY_CHANNEL_NAME','sweepstakes'),
    
     /*
      * 路由路径
      */
     'route' => 'lottery',
    
     /*
      * 控制器
      */
     'controller' => \Lijianmin\Lottery\Contollers\LotteryController::class,
    
     /*
      * 频道验证
      */
     'broadcast' => \Lijianmin\Lottery\Broadcasting\LotteryChannel::class,
    
     /*
      * 缓存
      */
     'cache' => [
         'prefix' => 'lottery-cache',
         'expiration_time' => 5,   //默认过期时间,小时
         'redis_cache_db' => env('REDIS_CACHE_DB',1),
     ],
    ];

注册广播事件

项目中事件包括 活动开始、入场、抽奖开始、开奖。 由于合作开发,我将它写出扩展包,定制接口方便对接。

  1. 首先创建出所需事件,如果不需要扩展包开发可以:

     php artisan make:event event-name

    或者 在app/Providers/EventServiceProvider.php成员变量$listen内追加:

         /**
          * The event listener mappings for the application.
          *
          * @var array
          */
         protected $listen = [
             'App\Events\EventName' => [
                 'App\Listeners\ListenserName',
             ],
             ...
         ];
    
     ...

    然后执行生成事件命令

     php artisan event:generate
  2. 事件注册完毕后需要实现Illuminate\Contracts\Broadcasting\ShouldBroadcast接口,需要实现broadcastOn方法,该方法返回一个频道或者一个频道数组。broadcastAs方法自定义广播名称,我希望还是自己定义,不然后面laravel-echo会很尴尬:

     use Illuminate\Broadcasting\InteractsWithSockets;
     use Illuminate\Broadcasting\PresenceChannel;
     use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
     use Illuminate\Contracts\Queue\ShouldQueue;
     use Illuminate\Foundation\Events\Dispatchable;
     use Illuminate\Queue\SerializesModels;
    
     class ActivityStart implements ShouldBroadcast
     {
         use Dispatchable, InteractsWithSockets, SerializesModels;
    
         public $option;
    
         public function __construct($option = []) {
             $this->option = $option;
         }
    
         public function broadcastOn() {
    
             return new PresenceChannel(config('lottery.channel_name'));
         }
    
         public function broadcastAs(){
    
             return 'activityStart';
         }
     }

    config('lottery.channel_name')应该修改为自己定义的频道名称,或者也可以像orzlee一样使用配置文件定义,方便修改。 每个事件都需要创建对应的文件,如果只是为了通知前端可以不需要事件处理器。

    对于频道分为三种:

    • Channel-普通频道。
    • PrivateChannel-私有频道,需要自定义授权认证。
    • PresenceChannel-类似于私有频道,但是比私有频道更加高级。

频道授权

  1. 首先自己创建一个频道:

     php artisan make:channel LotteryChannel
  2. routes/channels.php中添加频道(主要是频道授权验证):

     Broadcast::channel(config('lottery.channel_name'),config('lottery.broadcast'));
  3. 实现授权验证方法: PrivateChannel私有频道只需要返回true或者false,PresenceChannel频道在验证成功就需要返回一个用户信息数组,验证失败返回false或者null

     class LotteryChannel
     {
         ...
    
         /**
          * Authenticate the user's access to the channel.
          *
          * @param  \App\User  $user
          * @return array|bool
          */
         public function join()
         {
             if(\Auth::check()){
                 $user = \Auth::user();
                 return [
                     'user' => [
                         'id' => $user->id,
                         'name' => $user->names,
                     ],
                     'isLotteryDraw' => $this->lotteryService->isLotteryDraw()
                 ];
             }
    
             return false;
         }
     }

搭建laravel-echo-server服务:

  1. 安装最新版NodeJS和npm(如果已经安装可以跳过):

     curl -sL https://deb.nodesource.com/setup_9.x | sudo -E bash -
     sudo apt-get install -y nodejs
    
     ##如果需要指定版本修改链接中的数字即可
     curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
  2. 进入项目目录执行: 先把需要安装的一次性装完,orzlee已经写成command,部署服务器一行命令就全部安装好了

     npm install -g laravel-echo-server
     npm i --save socket.io-client
     npm i --save laravel-echo
     npm install
  3. 初始化laravel-echo-server按实际情况填写,选错了也不要紧,到时候在修改

     laravel-echo-server init

    项目下会生成laravel-echo-server.json配置文件: 注意authHost,devMode,port,protocol几个重要参数就好了。

     {
     "authHost": "http://127.0.0.1:8000",
     "authEndpoint": "/broadcasting/auth",
     "clients": [],
     "database": "redis",
     "databaseConfig": {
         "redis" : {},
         "sqlite": {
             "databasePath": "/database/laravel-echo-server.sqlite"
         }
     },
     "devMode": true,
     "host": null,
     "port": "6001",
     "protocol": "http",
     "socketio": {},
     "sslCertPath": "",
     "sslKeyPath": "",
     "sslCertChainPath": "",
     "sslPassphrase": "",
     "subscribers": {
         "http": true,
         "redis": true
     },
     "apiOriginAllow": {
         "allowCors": false,
         "allowOrigin": "",
         "allowMethods": "",
         "allowHeaders": ""
     }
    }
  4. 启动laravel-echo-server

     laravel-echo-server start

    laravel-echo-server.jpg

前端laravel-echo对接

  1. 修改resources/js/bootstrap.js(5.6文件在public目录下): 默认内容是注释了的,而且使用的是pusher,说实话pusher是付费服务,而且服务器都在国外,对国内实在是不友好。有想法的可以去了解下,每天有免费请求限制,主要是用于开发调试。

     /**
      * Echo exposes an expressive API for subscribing to channels and listening
      * for events that are broadcast by Laravel. Echo and event broadcasting
      * allows your team to easily build robust real-time web applications.
      */
    
     import Echo from 'laravel-echo'
    
     window.io = require('socket.io-client');
     // window.Pusher = require('pusher-js');
    
     window.Echo = new Echo({
         broadcaster: 'socket.io',
         host: window.location.hostname + ':6001'
     });

    其实做完这一步就可以打包编译NodeJS了: prod代表生产环境,如果还需要调试可以使用dev或者watch,watch模式文件改动会自动编译。

     npm run prod
  2. 前端页面配置事件监听: 注意监听事件名称,如果在上文中使用了broadcastAs命名事件名称,那么只需要在名称前加入.就好了,不然你需要把命名空间以.分割,例如:App.Lottery.ActivityStart,实在是太罗嗦了。

     <!-- Scripts -->
     <script src="//{{ Request::getHost() }}:6001/socket.io/socket.io.js"></script>
     <script src="{{ asset('js/app.js') }}"></script>
     <script>
         let lottery_server = Echo.join('{{ config('lottery.channel_name') }}');
         lottery_server.here(function (users) {
         //本来使用私有频道,但是找了半天也没有找到连接成功的事件才改用`PresenceChannel`频道,
         //在`here`事件会在连接成功返回所有当前频道的用户信息
             console.log(e)
         });
    
         lottery_server.joining(function (user) {
         //新用户加入频道返回用户信息
             console.log(e)
         });
    
         lottery_server.listen('.activityStart', (e) => {
         //活动开始
             console.log('activityStart',e);
         });
    
         lottery_server.listen('.admission', (e) => {
         //入场,其实这个事件在`PresenceChannel`频道下已经多此一举了
                 console.log('admission',e);
             });
    
         lottery_server.listen('.newcomerAdmission', (e) => {
         //新用户加入,这个也是多余的
             console.log('newcomerAdmission',e);
         });
    
         lottery_server.listen('.lottery', (e) => {
         //摇奖事件
             console.log('lottery',e);
         });
    
         lottery_server.listen('.lotteryResult', (e) => {
         //摇奖结果事件
             console.log('lotteryResult',e);
         });
    </script>

    到此整个前对接已经完成。

后端事件触发

其实最简单的就是后端。例如用户入场,用户登陆后触发AdmissionEvent事件,在定义事件时构造函数中设定前端所需的参数,数组也好对象也好,都会传给前端。 发送入场事件:

event(new AdmissionEvent());

lottery_event_test.jpg

每个事件我都会在构造函数中添加option数组,除了必须传递的参数以外方便附加其他参数。

结语

像这种类似于C/S模式开发要考虑的情况非常多,用户已经入场,关闭浏览器再次入场情况,首先名单不能重复添加,已中奖用户在入场时必须实时传递,在开奖状态中用户离开页面,然后在进入页面也应该保持前端正在开奖的效果,所以开奖状态要实时传递。还有很多很多需要考虑的问题,然后经过不停的测试。在年会上需要开31个奖,真是心惊胆战的,测试是还是有些问题的,还好在年会上没有出大漏子(真心不是后端问题,前端开奖结果效果问题),包括后端都去调试前端开奖动画效果,真心累。自从写好laravel-echo部署上线,真的没有出现过问题,还是很稳定的。测试时,最多30多台手机,活动开始之前有个图片遮罩,活动开始事件触发全部向上拉开遮罩,真的很壮观。多台手机之间延迟非常小,体验很不错,年会现场效果也是非常棒。

本原创文章未经允许不得转载 | 当前页面:OrzLee » Laravel 5.7 Broadcast + laravel echo 实现 WebSocket C/S 实时通信

评论