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

Node.js中SQL查询报错时如何通过Socket向客户端发送消息

实现方案

你现有代码存在两个基础问题,需要先修正再完成消息推送:

  • 把Express的POST路由直接写在Socket.IO的connect回调内是错误写法,HTTP请求和Socket连接是独立链路,这种写法无法直接获取发起请求对应的Socket实例
  • 提前在SQL执行前调用res.send(),后续SQL执行完成后再次调用res.send()会触发「响应已发送」的报错,所有响应需要等SQL执行完成后统一返回

方案1:保留现有HTTP接口,通过SocketID匹配对应连接

该方案不需要改动你现有接口的整体逻辑,只需要补全SocketID的传递和匹配逻辑即可。

  1. 客户端改造:发起/addDiel请求时,把当前连接的socket.id作为参数一起传给服务端,示例(以axios为例):

    // 你原来的接口请求参数里新增socketId字段
    axios.post('/addDiel', {
      MenoDielu: xxx,
      DruhDielu: xxx,
      ProjektName: xxx,
      socketId: socket.id // 传入当前Socket连接的唯一标识
    })
    

    你现有客户端监听receiveDUPmessage的代码不需要改动,保持原样即可。

  2. 服务端改造:把POST路由挪到Express路由的正确位置(不要写在io.on('connect')回调里),在ER_DUP_ENTRY错误分支通过传入的socketId找到对应连接推送消息,修正后的代码示例:

    // Socket连接逻辑单独维护
    io.on('connect', (socket) => {
      console.log("User connected: " + socket.id);
      // 注意:原代码disconnet拼写错误,修正为disconnect
      socket.on('disconnect', () => {
        console.log("disconnected: " + socket.id);
      })
    })
    
    // POST路由放到Express路由配置段
    app.post('/addDiel', (req, res) => {
      pool.getConnection((err, connection) => {
        if(err) {
          console.error(err);
          // 不要直接throw err打崩进程,返回错误响应
          return res.status(500).send({code: 500, msg: '数据库连接失败'});
        }
        console.log(`Pripojene ako ID ${connection.threadId}`)
        const params = req.body;
        const ProjektNameDB = params.ProjektName.split('.').join("_");
    
        connection.query('INSERT INTO `Skener_db`.`?` (`MenoDielu`, `DruhDielu`, `DatumCas`) VALUES (?, ?, NOW())', [ProjektNameDB, params.MenoDielu, params.DruhDielu],(err, rows)=> {
          connection.release()
          if(!err) {
            console.log(`Hodnota ${params.MenoDielu} bola pridana.`);
            // SQL执行成功再返回响应,删除你之前提前写的res.send
            return res.send({
              code: 200,
              MenoDielures: params.MenoDielu, 
              DruhDielures: params.DruhDielu,
              ProjektNameres: params.ProjektName,
              msg: '添加成功'
            })
          } else {
            console.log(err);
            if (err.code === 'ER_DUP_ENTRY') {
              // 通过传入的socketId获取对应连接实例
              const targetSocket = io.sockets.sockets.get(params.socketId);
              if (targetSocket) {
                // 触发客户端监听的事件,推送错误消息
                targetSocket.emit('receiveDUPmessage', 'Diel už bol oskenovaný. Oskenuj ďaľší.');
              }
              return res.send({
                code: 400,
                alertMessage: 'Diel už bol oskenovaný. Oskenuj ďaľší.'
              })
            }
            return res.status(500).send({code:500, msg: '数据库执行错误'})
          }
        })
      })     
    })
    

方案2:改用Socket自定义事件实现添加逻辑(更简单)

如果不需要严格保留HTTP接口,可以直接把添加数据的逻辑改成Socket自定义事件,事件回调里可以直接拿到当前连接的socket实例,不需要额外传递、匹配socketId,代码更简洁:

  1. 服务端逻辑:
    io.on('connect', (socket) => {
      console.log("User connected: " + socket.id);
      socket.on('disconnect', () => {
        console.log("disconnected: " + socket.id);
      })
      // 直接监听客户端发起的addDiel事件
      socket.on('addDiel', (params) => {
        pool.getConnection((err, connection) => {
          if(err) {
            console.error(err);
            return socket.emit('addDielRes', {code:500, msg:'数据库连接失败'});
          }
          console.log(`Pripojene ako ID ${connection.threadId}`)
          const ProjektNameDB = params.ProjektName.split('.').join("_");
          connection.query('INSERT INTO `Skener_db`.`?` (`MenoDielu`, `DruhDielu`, `DatumCas`) VALUES (?, ?, NOW())', [ProjektNameDB, params.MenoDielu, params.DruhDielu],(err, rows)=> {
            connection.release();
            if(!err) {
              console.log(`Hodnota ${params.MenoDielu} bola pridana.`);
              socket.emit('addDielRes', {code:200, msg:'添加成功', data: params});
            } else {
              console.log(err);
              if(err.code === 'ER_DUP_ENTRY') {
                // 直接用当前socket实例推送消息,不需要额外查找
                socket.emit('receiveDUPmessage', 'Diel už bol oskenovaný. Oskenuj ďaľší.');
              }
              socket.emit('addDielRes', {code:500, msg:'数据库执行错误'});
            }
          })
        })
      })
    })
    
  2. 客户端改造:把原来的POST请求改成Socket事件发送即可:
    // 替换原来的axios.post请求
    socket.emit('addDiel', {
      MenoDielu: xxx,
      DruhDielu: xxx,
      ProjektName: xxx
    })
    // 如果需要接收添加结果,可以监听addDielRes事件
    socket.on('addDielRes', (res) => {
      console.log('添加结果', res);
    })
    
    原有的receiveDUPmessage监听逻辑不需要改动。

注意事项

  • 用户刷新页面后socket.id会重新生成,每次发请求/事件时取最新的socket.id即可,不需要手动清理失效连接,Socket.IO会自动回收断开的实例
  • 数据库报错时不要直接throw err,会直接终止Node进程,必须捕获后返回错误响应/事件
  • 方案1如果是多实例部署(多个Node进程负载均衡),需要配置Socket.IO的redis适配器,否则会找不到对应socket实例;单实例部署无此问题

内容的提问来源于stack exchange,提问作者Kristian Masar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 03:21:45