You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel Echo结合Soketi无法广播事件:是否为配置问题?

问题需求

我希望在用户的文章动态更新完成后,广播FeedGenerated事件,让前端捕获该事件,进而允许用户手动刷新动态。目前FeedGenerated事件已被UpdateFeedArticles监听器监听,且监听器的日志记录功能正常,但Soketi未捕获到任何事件内容(无Docker日志输出)。


事件广播方式

FeedGenerated::broadcast($user);

FeedGenerated事件代码

<?php

namespace App\Providers;

use App\Models\User;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithBroadcasting;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class FeedGenerated implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels, InteractsWithBroadcasting;

    /**
     * 广播事件使用的队列连接名称
     *
     * @var string
     */
    public string $connection = 'redis';

    /**
     * 广播任务所在的队列名称
     *
     * @var string
     */
    public string $queue = 'default';

    /**
     * 创建事件实例
     */
    public function __construct(public User $user)
    {
        //
    }

    /**
     * 获取事件广播的频道
     *
     * @return array<int, \Illuminate\Broadcasting\Channel>
     */
    public function broadcastOn(): array
    {
        return [
            new Channel("user"),
        ];
    }
}

UpdateFeedArticles监听器代码

<?php

namespace App\Providers;

use App\Providers\FeedGenerated;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;

class UpdateFeedArticles
{
    /**
     * 创建监听器实例
     */
    public function __construct()
    {
        //
    }

    /**
     * 处理事件
     */
    public function handle(FeedGenerated $event): void
    {
        \Log::debug('Event triggered', ['event' => $event, 'listener' => $this]);
    }
}

Laravel Echo初始化代码

import Echo from 'laravel-echo';

import Pusher from 'pusher-js';

window.Pusher = Pusher;

window.Websocket = new Echo({
    broadcaster: 'pusher',
    key: import.meta.env.VITE_PUSHER_APP_KEY,
    cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
    wsHost: import.meta.env.VITE_PUSHER_HOST,
    wsPort: import.meta.env.VITE_PUSHER_PORT,
    wssPort: import.meta.env.VITE_PUSHER_PORT,
    forceTLS: false,
    encrypted: false,
    disableStats: true,
    enabledTransports: ['ws', 'wss'],
});

Websocket.connector.pusher.connection.bind('connected', () => {
    console.log('✅ Soketi Connected!');
});

Websocket.connector.pusher.connection.bind('disconnected', () => {
    console.log('👋 Soketi Disconnected!');
});

Websocket.connector.pusher.connection.bind('failed', () => {
    console.log('❌ Soketi Connection Failed!');
});

docker-compose.yml配置

version: '3'

networks:
  network_shapes:
    ipam:
      driver: default
    driver: bridge

services:
  phpfpm:
    container_name: '${APP_NAME}'
    build:
      args:
        user: '${WWWUSER}'
        uid: '${WWWGROUP}'
      context: ./docker
      dockerfile: Dockerfile
    working_dir: /var/www/html
    volumes:
      - ./:/var/www/html
      - 'tmpfiles:/tmp'
    networks:
      - network_shapes
    depends_on:
      - mariadb
    restart: unless-stopped

  soketi:
    image: 'quay.io/soketi/soketi:latest-16-alpine'
    environment:
      SOKETI_DEBUG: '${SOKETI_DEBUG:-1}'
      SOKETI_METRICS_SERVER_PORT: '${SOKETI_METRICS_SERVER_PORT}'
    ports:
      - '${SOKETI_PORT:-6001}:6001'
      - '${SOKETI_METRICS_SERVER_PORT:-9601}:9601'
    networks:
      - network_shapes
    depends_on:
      - redis

  nginx:
    image: ${NGINX_IMAGE}
    networks:
      - network_shapes
    ports:
      - '127.0.0.1:${APP_PORT}:80'
    volumes:
      - ./docker/nginx/tkt.conf:/etc/nginx/conf.d/default.conf
      - ./:/var/www/html
      - 'tmpfiles:/tmp'
    links:
      - phpfpm
    depends_on:
      - mariadb
    restart: unless-stopped

  mongo:
    ...

  redis:
    image: 'redis:latest'
    ports:
      - '127.0.0.1:${REDIS_PORT:-6379}:6379'
    volumes:
      - 'redis_volume:/data'
    networks:
      - network_shapes
    healthcheck:
      test: [ "CMD", "redis-cli", "ping" ]
      retries: 3
      timeout: 5s
    restart: unless-stopped

  mariadb:
    ...

  mailhog:
    ...

volumes:
  tmpfiles:
    driver: local
  mariadb_volume:
    driver: local
  redis_volume:
    driver: local

Nginx tkt.conf配置

server {
    listen 80;

    server_name localhost www.localhost .localhost *.localhost;

    index index.php index.html;

    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;

    root /var/www/html/public;

    client_max_body_size 100M;

    location /mailhog/ {
        proxy_pass      http://mailhog:8025/;
        proxy_set_header    Host             $host;
        proxy_set_header    X-Real-IP        $remote_addr;
        proxy_set_header    X-Forwarded-For  $proxy_add_x_forwarded_for;
        proxy_set_header    X-Client-Verify  SUCCESS;
        proxy_read_timeout 1800;
        proxy_connect_timeout 1800;
        chunked_transfer_encoding on;
        proxy_set_header X-NginX-Proxy true;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_http_version 1.1;
        proxy_redirect off;
        proxy_buffering off;
    }

    location / {
        try_files $uri $uri/ /index.php?$query_string;
        gzip_static on;
    }

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass phpfpm:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }

    location /websocket/ {
         proxy_pass http://127.0.0.1:6001;
         proxy_http_version 1.1;
         proxy_set_header Upgrade $http_upgrade;
         proxy_set_header Connection "Upgrade";
         proxy_set_header Host $host;
         proxy_read_timeout 86400; # 根据需要调整
    }
}

当前运行情况

执行事件广播命令

docker exec -t shapppes php artisan feed:generate 01h739jr4vk8pq3yefsp22c964
Scoring articles for Sonny Stroman...

Feed created.

Horizon日志

docker exec -t shapppes php artisan horizon
Horizon started successfully.
  2023-08-07 13:04:56 App\Actions\Article\Feed\GenerateFeed .......... RUNNING
  2023-08-07 13:04:57 App\Actions\Article\Feed\GenerateFeed .... 899.66ms DONE
  2023-08-07 13:04:57 App\Providers\FeedGenerated .................... RUNNING
  2023-08-07 13:04:57 App\Providers\FeedGenerated ............... 61.82ms DONE

Redis查询结果(Laravel Telescope可见)

eval -- Push the job onto the queue...
redis.call('rpush', KEYS[1], ARGV[1])
-- Push a notification onto the "notify" queue...
redis.call('rpush', KEYS[2], 1) 2 queues:default queues:default:notify {"uuid":"bf2e429e-7702-4758-bedd-d072914a3215","displayName":"App\\Providers\\FeedGenerated","job":"Illuminate\\Queue\\CallQueuedHandler@call","maxTries":null,"maxExceptions":null,"failOnTimeout":false,"backoff":null,"timeout":null,"retryUntil":null,"data":{"commandName":"Illuminate\\Broadcasting\\BroadcastEvent","command":"O:38:\"Illuminate\\Broadcasting\\BroadcastEvent\":14:{s:5:\"event\";O:27:\"App\\Providers\\FeedGenerated\":1:{s:4:\"user\";O:45:\"Illuminate\\Contracts\\Database\\ModelIdentifier\":5:{s:5:\"class\";s:15:\"App\\Models\\User\";s:2:\"id\";s:26:\"01h739jr4vk8pq3yefsp22c964\";s:9:\"relations\";a:4:{i:0;s:4:\"page\";i:1;s:14:\"page.bookmarks\";i:2;s:10:\"page.image\";i:3;s:4:\"tags\";}s:10:\"connection\";s:5:\"mysql\";s:15:\"collectionClass\";N;}}s:5:\"tries\";N;s:7:\"timeout\";N;s:7:\"backoff\";N;s:13:\"maxExceptions\";N;s:10:\"connection\";N;s:5:\"queue\";N;s:15:\"chainConnection\";N;s:10:\"chainQueue\";N;s:19:\"chainCatchCallbacks\";N;s:5:\"delay\";N;s:11:\"afterCommit\";N;s:10:\"middleware\";a:0:{}s:7:\"chained\";a:0:{}}"},"telescope_uuid":"99d54a11-c8fc-489f-8676-a575ab0466bf","id":"bf2e429e-7702-4758-bedd-d072914a3215","attempts":0,"type":"broadcast","tags":["App\\Models\\User:01h739jr4vk8pq3yefsp22c964"],"silenced":false,"pushedAt":"1691413497.6378"}

异常情况

Soketi容器无任何日志输出,未捕获到广播事件。


排查与解决建议

  1. 检查Soketi环境变量配置

    • 确保.env中BROADCAST_DRIVER=pusher,且PUSHER_APP_KEY、PUSHER_APP_ID、PUSHER_APP_SECRET与Soketi配置一致
    • Soketi容器需设置SOKETI_AUTH_HOST为Laravel服务地址(如http://phpfpm,因为同属Docker网络)
    • 开启Soketi调试模式:SOKETI_DEBUG=1,查看详细日志
  2. 修正事件广播频道配置

    • 当前broadcastOn返回的是公共频道Channel("user"),若需针对单个用户,应使用PrivateChannel("user.{$this->user->id}"),并确保前端订阅对应频道
    • 前端需添加频道订阅代码:
      Websocket.channel('user') // 若改为私有频道则用.private('user.xxx')
        .listen('FeedGenerated', (e) => {
          console.log('Feed更新完成', e);
          // 触发手动刷新逻辑
        });
      
  3. 修正Nginx代理配置

    • 当前Nginx的/websocket/代理指向127.0.0.1:6001,但在Docker网络中应指向soketi:6001
    • 调整代理配置:
      location /websocket/ {
           proxy_pass http://soketi:6001;
           proxy_http_version 1.1;
           proxy_set_header Upgrade $http_upgrade;
           proxy_set_header Connection "Upgrade";
           proxy_set_header Host $host;
           proxy_set_header X-Real-IP $remote_addr;
           proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
           proxy_read_timeout 86400;
      }
      
  4. 检查Laravel广播配置

    • 确保config/broadcasting.php中pusher配置正确:
      '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'),
              'host' => env('PUSHER_HOST', 'soketi'),
              'port' => env('PUSHER_PORT', 6001),
              'scheme' => 'http',
              'encrypted' => false,
              'useTLS' => false,
          ],
      ],
      
  5. 验证队列与广播任务

    • 确保Horizon或队列 worker 正在运行,且处理default队列的任务
    • 检查FeedGenerated事件的broadcastUsing或队列配置是否正确指向Redis

内容的提问来源于stack exchange,提问作者Gaël Duval

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 08:36:16