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

Laravel+Node.js双后端下如何实现WebSocket服务端消息推送

问题场景
  • 服务架构:Laravel 后端、Node.js 后端、前端应用
  • 业务链路:前端发起请求到 Laravel → Laravel 转发请求到 Node.js → Node.js 通过 WebSocket 向指定客户端推送实时消息
  • 现存问题:尝试在 Node.js 业务控制器中调用 WebSocket 实例推送消息时,导入的实例值为 undefined,推送逻辑无法执行。

现有代码

Index.js

const app = require('express')();
const http = require('http');
const WebSocket = require('ws')

//initialize a simple http server
const server = http.createServer(app);
app.get('/', function(req, res) {
  res.sendFile(__dirname + '/index.html');
});
let sendNotification;
//initialize the WebSocket server instance
const wss = new WebSocket.Server({ server });
let socketapi = require('./socketapi')
socketapi.start(wss)
//start our server
server.listen(process.env.PORT || 5555, () => {
    console.log(`Server started on port ${server.address().port} :)`);
});

socketapi.js

module.exports ={
    start: (wss) => {
        wss.on('connection', (ws) => {
            console.log('connected!!!');
            console.log(socketIds)
            //connection is up, let's add a simple simple event
            // triggerMessage('data');
            ws.id=uuidv4()
            ws.on('message', (message) => {
                console.log('received: %s', message);
                // ws.send(`Hello, you sent -> ${message}`);
            });
        });
    }
 }
问题原因

实例导入为undefined的核心原因是现有代码仅在socketapi.js中暴露了WebSocket初始化方法,没有保存初始化后的服务实例、客户端连接池,也没有对外暴露可直接调用的推送方法,其他业务文件导入时自然无法获取可用的WebSocket操作对象。此外原有代码还存在未导入uuidv4依赖、未定义socketIds变量、未做连接断开清理、未建立用户身份和连接的映射关系等问题,无法实现精准推送。

实现步骤

1. 改造socketapi.js,统一封装WebSocket能力

将连接管理、推送逻辑全部收敛到该文件,初始化后对外暴露可直接调用的推送方法,无需在业务文件中直接操作wss实例。

先安装缺失的uuid依赖:
npm i uuid

替换socketapi.js代码为以下内容:

const { v4: uuidv4 } = require('uuid');
// 连接池:统一存储所有在线连接,支持按连接ID、用户ID索引
const clientMap = new Map();
let wssInstance = null;

module.exports = {
    // 初始化WebSocket服务
    start: (wss) => {
        wssInstance = wss;
        wss.on('connection', (ws) => {
            // 为每个连接生成临时唯一ID
            const connectId = uuidv4();
            ws.id = connectId;
            clientMap.set(connectId, ws);
            console.log(`新客户端连接,ID:${connectId},当前在线数:${wssInstance.clients.size}`);

            // 处理客户端上行消息
            ws.on('message', (rawMsg) => {
                try {
                    const msg = JSON.parse(rawMsg.toString());
                    // 约定:前端连接建立后主动上报绑定的用户ID,用于精准推送
                    if (msg.type === 'bind' && msg.userId) {
                        ws.userId = msg.userId;
                        const userKey = `user_${msg.userId}`;
                        if (!clientMap.has(userKey)) clientMap.set(userKey, []);
                        clientMap.get(userKey).push(ws);
                        console.log(`用户${msg.userId}绑定连接成功`);
                    }
                } catch (err) {
                    console.log(`收到非JSON格式消息:${rawMsg.toString()}`);
                }
            });

            // 连接关闭时清理连接池
            ws.on('close', () => {
                clientMap.delete(ws.id);
                if (ws.userId) {
                    const userKey = `user_${ws.userId}`;
                    const userConnections = clientMap.get(userKey) || [];
                    const filtered = userConnections.filter(conn => conn.id !== ws.id);
                    filtered.length ? clientMap.set(userKey, filtered) : clientMap.delete(userKey);
                }
                console.log(`客户端断开,ID:${connectId},当前在线数:${wssInstance.clients.size}`);
            });

            // 连接异常处理
            ws.on('error', (err) => {
                console.error(`连接${connectId}异常:`, err);
                ws.terminate();
            });
        });
    },
    // 给指定连接ID的客户端推送
    sendToConnect: (connectId, message) => {
        const ws = clientMap.get(connectId);
        if (ws && ws.readyState === ws.OPEN) {
            const sendMsg = typeof message === 'string' ? message : JSON.stringify(message);
            ws.send(sendMsg);
            return true;
        }
        return false;
    },
    // 给指定用户ID的所有在线端推送
    sendToUser: (userId, message) => {
        const userKey = `user_${userId}`;
        const connections = clientMap.get(userKey) || [];
        if (!connections.length) return false;
        const sendMsg = typeof message === 'string' ? message : JSON.stringify(message);
        connections.forEach(ws => {
            if (ws.readyState === ws.OPEN) ws.send(sendMsg);
        });
        return true;
    },
    // 全量广播消息
    broadcast: (message) => {
        if (!wssInstance) return false;
        const sendMsg = typeof message === 'string' ? message : JSON.stringify(message);
        wssInstance.clients.forEach(ws => {
            if (ws.readyState === ws.OPEN) ws.send(sendMsg);
        });
        return true;
    }
}

2. 前端增加连接绑定逻辑

前端建立WebSocket连接后,主动上报当前登录用户的唯一ID(和Laravel侧用户ID保持一致),建立用户和连接的映射关系:

// 前端连接代码
const ws = new WebSocket(`ws://你的Node服务地址:5555`);
ws.onopen = () => {
    // 从本地登录态中获取当前用户ID
    const currentUserId = localStorage.getItem('userId');
    ws.send(JSON.stringify({
        type: 'bind',
        userId: currentUserId
    }));
}
// 监听推送消息
ws.onmessage = (event) => {
    const msg = JSON.parse(event.data);
    if (msg.type === 'notification') {
        // 处理推送通知逻辑,比如弹窗提醒
        console.log('收到推送:', msg.content);
    }
}

3. Node.js业务层调用推送方法

在处理Laravel请求的路由/控制器文件中,直接导入封装好的socketapi模块即可调用推送能力,不需要单独导入wss实例:

const express = require('express');
const router = express.Router();
// 导入封装好的WebSocket模块
const socketapi = require('./socketapi');

// 接收Laravel请求的接口
router.post('/push-notification', (req, res) => {
    const { targetUserId, notifyContent } = req.body;
    // 执行业务逻辑
    // 给目标用户推送消息
    const pushResult = socketapi.sendToUser(targetUserId, {
        type: 'notification',
        content: notifyContent,
        timestamp: Date.now()
    });
    res.json({
        code: 0,
        msg: '请求处理完成',
        pushStatus: pushResult ? '推送成功' : '目标用户不在线'
    });
});

module.exports = router;

记得在Index.js中挂载该路由:

// 放在server.listen之前
app.use(express.json()); // 解析post请求的json参数
app.use('/api', require('./你的路由文件名'));

4. Laravel侧触发推送

Laravel处理完自身业务逻辑后,调用Node.js暴露的推送接口即可触发消息推送:

<?php
// Laravel控制器中的业务逻辑
use Illuminate\Support\Facades\Http;

// 业务处理完成后,调用Node.js接口
$response = Http::post('http://Node服务内网地址:5555/api/push-notification', [
    'targetUserId' => $需要接收推送的用户ID,
    'notifyContent' => '你有一条新的业务通知'
]);

生产环境建议给Laravel到Node.js的接口加签名校验,避免接口被恶意调用。

内容的提问来源于stack exchange,提问作者Bhargav Kanodiya

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 04:18:20