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

React游戏购卡包功能:Express请求3-4次后停止响应

问题:React游戏购买卡包后Express服务器多次操作无响应

项目背景

  • 开发React游戏,玩家使用essences货币购买三种卡包:mini(500)、normal(1500)、mega(3000)
  • 每个卡包按钮绑定BuyPack函数,传入对应价格参数

前端BuyPack函数代码

const BuyPack = (arg) => {

    let nb = (document.getElementById('essences').innerText) // that obviously sucks lol
    let nbInt = parseInt(nb) // converts innerText to int
    const newSolde = (nbInt - arg) // The calculated amount after the purchase

    // Update visually the amount of essences
    localStorage.setItem('essences', newSolde) 
    document.getElementById('essences').innerHTML = newSolde; // Let's set the new amount

    let datas = {id: localStorage.getItem('id'), typeOfPack: arg}; // id = the user id / arg = 500, 1500 or 3000, it's basically the pack price.

    console.log('datas', datas) // Output : datas {id: '196', typeOfPack: 500}
    Axios.post('http://localhost:3001/buyPack', {data: datas}) // The axios call that doesn't work as expected

}

后端Express接口代码

app.post('/buyPack', function (req, res) {
  const userId = (req.body.data.id)
  const typeOfPack = (req.body.data.typeOfPack)
  console.log(typeOfPack)

  // I make a first request to get the "pre-buy" amount of money
  query("select essences FROM users WHERE id = ?", [userId], (err, rows) => {
    const actualEssences = (rows[0].essences);  // The amount we just got
    // Second query where we update and decrease the amount of money for the user
    query("update users set essences = " + (actualEssences - typeOfPack) + " where id = " + userId)
  })
})

问题现象

  • 执行3-4次购买操作后,Express服务器停止响应
  • 客户端视觉上货币数值减少正常,点击日志能打印,但后端无后续请求日志输出

问题分析与修复方案

1. 后端未处理请求响应与数据库错误

原后端接口没有向客户端返回任何响应,也未处理数据库查询的异常,导致客户端请求长时间挂起,服务器连接池被耗尽,最终无法接收新请求。

修改后端代码:

app.post('/buyPack', function (req, res) {
  const userId = req.body.data.id;
  const typeOfPack = req.body.data.typeOfPack;
  console.log(typeOfPack);

  query("select essences FROM users WHERE id = ?", [userId], (err, rows) => {
    // 处理查询错误
    if (err) {
      console.error('查询用户余额失败:', err);
      return res.status(500).json({success: false, message: '查询失败'});
    }
    // 处理用户不存在的情况
    if (!rows || rows.length === 0) {
      return res.status(404).json({success: false, message: '用户不存在'});
    }

    const actualEssences = rows[0].essences;
    // 使用参数化查询避免SQL注入,同时处理更新错误
    query("update users set essences = ? where id = ?", [actualEssences - typeOfPack, userId], (updateErr) => {
      if (updateErr) {
        console.error('更新用户余额失败:', updateErr);
        return res.status(500).json({success: false, message: '购买失败'});
      }
      // 返回成功响应给客户端
      res.status(200).json({success: true, newEssences: actualEssences - typeOfPack});
    });
  });
});

2. 前端先本地计算余额存在数据不一致风险

原前端先通过DOM获取余额并计算,再请求后端,容易出现本地数据与后端数据不一致的情况,且DOM解析可能出错。应改为依赖后端返回的正确余额更新本地状态。

修改前端代码:

const BuyPack = async (arg) => {
    const userId = localStorage.getItem('id');
    if (!userId) {
        console.error('用户未登录');
        return;
    }

    try {
        const response = await Axios.post('http://localhost:3001/buyPack', {data: {id: userId, typeOfPack: arg}});
        if (response.data.success) {
            const newSolde = response.data.newEssences;
            localStorage.setItem('essences', newSolde);
            document.getElementById('essences').innerHTML = newSolde;
        } else {
            // 购买失败时恢复本地显示的余额
            const currentEssences = localStorage.getItem('essences');
            document.getElementById('essences').innerHTML = currentEssences;
            console.error('购买失败:', response.data.message);
        }
    } catch (err) {
        console.error('请求失败:', err);
        // 请求异常时恢复本地余额
        const currentEssences = localStorage.getItem('essences');
        document.getElementById('essences').innerHTML = currentEssences;
    }
}

3. 解决竞态条件与SQL注入风险

原后端先查询再更新的逻辑存在竞态条件(多次快速请求时,查询的余额可能已经过期),且字符串拼接SQL语句存在注入风险。改为原子更新语句,确保操作的安全性与一致性:

app.post('/buyPack', function (req, res) {
  const userId = req.body.data.id;
  const typeOfPack = req.body.data.typeOfPack;
  console.log(typeOfPack);

  // 原子更新:同时检查余额是否足够,避免竞态条件
  query("UPDATE users SET essences = essences - ? WHERE id = ? AND essences >= ?", 
        [typeOfPack, userId, typeOfPack], 
        (err, result) => {
            if (err) {
                console.error('更新失败:', err);
                return res.status(500).json({success: false, message: '购买失败'});
            }
            // 没有更新行说明余额不足或用户不存在
            if (result.affectedRows === 0) {
                return res.status(400).json({success: false, message: '余额不足或用户不存在'});
            }
            // 返回更新后的余额
            query("SELECT essences FROM users WHERE id = ?", [userId], (selectErr, rows) => {
                if (selectErr) {
                    console.error('查询新余额失败:', selectErr);
                    return res.status(200).json({success: true});
                }
                res.status(200).json({success: true, newEssences: rows[0].essences});
            });
        });
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 20:25:27