laravel广播使用笔记,基于laravel-websocket扩展包

阅读文档

Laravel 广播系统文档

laravel-websockets文档

安装需要的用的依赖

//websocket服务端
composer require beyondcode/laravel-websockets

//发布数据迁移文件
php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="migrations"

//发布配置文件
php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="config"

//安装队列管理面板 或者 使用php artisan queue:work也可以
composer require laravel/horizon
php artisan horizon:install

//执行迁移
php artisan migrate

//前端依赖
npm install laravel-echo pusher-js

修改配置

.env 配置

//广播驱动设置为pusher
BROADCAST_DRIVER=pusher

//队列驱动改为redis
QUEUE_CONNECTION=redis

//使用laravel-websockets做后端,pusher配置随便填写
PUSHER_APP_ID=yangliuan
PUSHER_APP_KEY=yangliuan
PUSHER_APP_SECRET=yangliuan
//注意一定要注释这行,否则laravel-websockets不生效
#PUSHER_APP_CLUSTER=mt1

//websocket端口号
LARAVEL_WEBSOCKETS_PORT=6001

config/app.php 配置

取消如下Provider的注释

/*
 * Application Service Providers...
 */
...
// App\Providers\BroadcastServiceProvider::class,

config/broadcasting.php 配置

按如下修改

'connections' => [

        'pusher' => [
            'driver' => 'pusher',
            'key' => env('PUSHER_APP_KEY'),
            'secret' => env('PUSHER_APP_SECRET'),
            'app_id' => env('PUSHER_APP_ID'),
            'options' => [
                'cluster' => env('PUSHER_APP_CLUSTER'),
                 //本地开发关闭安全连接
                'useTLS' => false,
                //本地host配置
                'host' => '127.0.0.1',
                //端口
                'port' => env('LARAVEL_WEBSOCKETS_PORT', 6001),
                //协议
                'scheme' => 'http',
            ],
        ],

config/websockets.php

'apps' => [
        [
            'id' => env('PUSHER_APP_ID'),
            'name' => env('APP_NAME'),
            'key' => env('PUSHER_APP_KEY'),
            'secret' => env('PUSHER_APP_SECRET'),
            'path' => env('PUSHER_APP_PATH'),
            'capacity' => null,
            //是否开启客户端发送消息
            'enable_client_messages' => false,
            //是否开启统计
            'enable_statistics' => true,
        ],
    ],

测试案例场景

使用laravel excel 队列导出文件后,自动提示并下载,使用公共频道 demo地址

后端代码

注册频道

route/channels.php 文件,可以自定义频道名称

Broadcast::channel('excel', function () {
    return true;
});

创建ExcelExportCompletedEvent事件

php artisan make:event ExcelExportCompletedEvent

<?php
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;

//注意事件一定要实现ShouldBroadcast接口
class ExcelExportCompletedEvent implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    //public属性会自动转换为广播数据
    //自定义广播数据  文档 https://learnku.com/docs/laravel/9.x/broadcasting/12223#b2f5d1
   
    public $excel_path;

    public $disk;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct(string $excel_path, string $disk)
    {    
        //文件路径
        $this->excel_path = $excel_path;
        //磁盘
        $this->disk = $disk;
    }

    /**
     * 监听频道
    *  监听自己的定义频道名称
     * 
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new Channel('excel');
    }
}

创建导出文件 ExcelDemoPictureQueryExport 细节略过详情看 laravel excel文档

创建通知队列,用于触发时间

php artisan make:job ExcelNotifyJob

class ExcelNotifyJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $attach;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct(array $attach)
    {
        $this->attach = $attach;
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        //发送广播通知
        ExcelExportCompletedEvent::dispatch($this->attach['file_name'], $this->attach['disk']);
    }
}

控制器代码

/**
 * 字段导出图片 使用队列 并接受广播通知
 *
 * @param Request $request
 * @return void
 */
public function queueImages(Request $request)
{
  $file_name = 'excel-demo-'.date('YmdHis').\mt_rand(100000, 999999).'.xlsx';
  $disk = 'public';
  Excel::queue(new ExcelDemoPictureQueryExport(), $file_name, $disk)
  //导出成功后,使用任务链调用excel通知job
  //DOC:https://learnku.com/docs/laravel/8.5/queues/10395#dispatching-jobs
  //DOC:https://docs.laravel-excel.com/3.1/exports/queued.html#appending-jobs
  ->chain([
      new ExcelNotifyJob(compact('file_name', 'disk'))
  ]);

  return response()->json();
}

//下载文件方法
public function store(Request $request)
{
    $request->validate([
       'storage_path' => 'bail|required|string',
       'disk' => 'bail|nullable|string',
    ]);
    $realPath = Storage::disk($request->input('disk') ?? 'public')
       ->path($request->input('storage_path'));

    return response()->download($realPath)->deleteFileAfterSend();
}

前端代码

使用 Laravel Jetstream Inertia.js  构建

封装laravel-echo.js

import Echo from 'laravel-echo';

window.pusher = require('pusher-js');

const echo = new Echo({
    broadcaster: 'pusher',
    key: 'yangliuan',
    wsHost: window.location.hostname,
    wsPort: 6001,
    forceTLS: false,
    enabledTransports: ['ws', 'wss'],
})

export { echo }

Excel.vue

<template>
 <app-layout title="Dashboard">
  ...
  <a href="#" @click="queueImagesClick">
  <div class="mt-3 flex items-center text-sm font-semibold text-indigo-700">
  <div>队列导出图片并用广播接受通知</div>
  <div class="ml-1 text-indigo-500">
  <svg viewBox="0 0 20 20" fill="currentColor" class="w-4 h-4"><path fill-rule="evenodd" d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg>
 </div>
 </div>
 </a>
 </app-layout>
</template>

<script>
 ...
 //导入laravel echo 
 import { echo } from '@/laravel-echo'

 export default defineComponent({
    components: {
       AppLayout,
       JetApplicationLogo,
       Link
    },
    created() {
      //监听公共频道excel,响应下载excel文件
       echo.channel('excel')
           .listen('ExcelExportCompletedEvent', (e) => {
               alert('ExcelExportCompletedEvent')
               this.downloadExcel(e.excel_path,e.disk)
               console.log(e);
           })
    },
    methods: {
       //下载方法
       downloadExcel(excel_path,disk) {
          const download_url =  '/api/files/download?storage_path=' + excel_path + '&disk=' + disk
          window.open(download_url)
       },
      //点击时间调用队列导航图片接口
       queueImagesClick() {
          axios.post('/api/excel/export/queue-images').then( response => {
             console.log(response)
          })
       }
  }
})
</script>
//开启websocket服务
php artisan websockets:serve

//开启horizon队列
php artisan horizon

演示

授权频道案例

nginx代理websocket

laravel websocket文档

websocket单独使用一个域名不和http接口共享域名,配置如下

server {
    listen 80;
    server_name websocket.exhibition.demo;

    location / {
        proxy_pass             http://127.0.0.1:6001;
        proxy_read_timeout     60;
        proxy_connect_timeout  60;
        proxy_redirect         off;

        # Allow the use of websockets
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

第二种方式websocket和http接口公用一个域名

map $http_upgrade $type {
  default "";
  websocket "ws";
}

server {
    listen 80;
    server_name api2.exhibition.demo;
    root /home/yangliuan/Code/Php/business-logic/laravel-exhibition/public;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";

    index index.php;
    charset utf-8;

    location = /favicon.ico {
        access_log off; log_not_found off;
    }

    location = /robots.txt {
        access_log off; log_not_found off;
    }
   
   //http
    location /  {
        try_files $uri $uri/ /index.php?$query_string;
    }
 
   //websocket
    location @ws  {
        proxy_pass             http://127.0.0.1:6001;
        proxy_set_header Host  $host;
        proxy_read_timeout     60;
        proxy_connect_timeout  60;
        proxy_redirect         off;

        # Allow the use of websockets
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }

    location ~ [^/]\.php(/|$) {
        fastcgi_pass unix:/dev/shm/php-cgi.sock;
        fastcgi_index index.php;
        include fastcgi.conf;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

参考

websocket实现手机扫码登陆