Express.js跨路由共享变量方案及全局变量并发风险咨询
Express跨路由共享请求变量的最佳实践
我希望在一个Express.js路由中获取变量,并在另一个路由中使用。请问最佳方案是否为使用中间件?若是,该如何实现?另外,若在服务端初始位置声明全局变量,在同时处理多用户/请求时(对应React Native应用,非浏览器)是否会引发异常行为?
用户提供的原始代码:
const { Station } = require("../models/station"); const express = require("express"); const router = express.Router(); var deviceId; var directionNumber; var metroNumberChoice; router.post("/postUserDirectionAndLineNumberAndDeviceId", async (req, res) => { try { directionNumber = req.body.direction; deviceId = req.body.deviceId; metroNumberChoice = req.body.metroNumberChoice; res.status(200).json({ message: "User direction received successfully" }); } catch (error) { console.error(error); res.status(500).json({ message: "Internal Server Error" }); } }); router.post("/getlocation", (req, res) => { try { console.log("deviceId", deviceId); const findMetro = async () => { console.log( "directionNumber:", directionNumber, "metroNumberChoice:", metroNumberChoice, "deviceId:", deviceId ); } catch (error) { console.log(error); } });
一、全局变量的问题:绝对会引发异常行为
直接在路由文件顶部声明全局变量(如deviceId、directionNumber)是严重错误的做法:
- Express基于单线程事件循环模型,所有请求共享同一份内存空间。当多个用户/请求并发处理时,后一个请求会直接覆盖全局变量的值,导致前一个请求拿到完全错误的数据。
- 举个例子:用户A提交
deviceId=123后,用户B紧接着提交deviceId=456,当用户A触发/getlocation请求时,拿到的会是用户B的456,完全打乱业务逻辑。
二、最佳方案:请求对象(req)+ 中间件
中间件是Express处理请求生命周期的核心机制,用来在路由间共享请求级别的变量非常合适——每个请求都有独立的req对象,不会互相干扰。
实现方式分两种场景:
场景1:两个路由属于同一次请求链
如果/postUserDirectionAndLineNumberAndDeviceId和/getlocation是同一次请求流程中的前后步骤,可直接用中间件把变量挂载到req上:
const { Station } = require("../models/station"); const express = require("express"); const router = express.Router(); // 中间件:解析并存储用户地铁信息到req对象 const attachMetroInfo = (req, res, next) => { try { req.metroInfo = { directionNumber: req.body.direction, deviceId: req.body.deviceId, metroNumberChoice: req.body.metroNumberChoice }; next(); // 传递到下一个路由/中间件 } catch (error) { console.error(error); res.status(500).json({ message: "Internal Server Error" }); } }; // 先执行中间件,再处理getlocation路由 router.post("/postUserDirectionAndLineNumberAndDeviceId", attachMetroInfo, (req, res) => { res.status(200).json({ message: "User direction received successfully" }); }); router.post("/getlocation", (req, res) => { try { const { directionNumber, deviceId, metroNumberChoice } = req.metroInfo; console.log("deviceId", deviceId); console.log( "directionNumber:", directionNumber, "metroNumberChoice:", metroNumberChoice, "deviceId:", deviceId ); // 执行你的findMetro逻辑 // const metro = await Station.find(...) res.status(200).json({ /* 返回数据 */ }); } catch (error) { console.log(error); res.status(500).json({ message: "Internal Server Error" }); } });
场景2:两个路由是独立请求
如果两个路由是用户发起的两次独立请求(比如先提交信息,之后再调用获取位置),中间件无法直接共享变量,此时需要用会话存储或缓存/数据库:
- 会话存储:用
express-session将用户信息存在服务端会话中,后续请求通过会话ID(可放在请求头或Cookie中)获取数据 - 缓存/数据库:以
deviceId为键,将用户地铁信息存在Redis或数据库中,后续请求携带deviceId查询数据
示例(Redis缓存方案):
const redis = require("redis"); const client = redis.createClient(); // 提交信息路由:存储到Redis router.post("/postUserDirectionAndLineNumberAndDeviceId", async (req, res) => { try { const { direction, deviceId, metroNumberChoice } = req.body; await client.setEx(`metro:${deviceId}`, 3600, JSON.stringify({ directionNumber: direction, metroNumberChoice })); res.status(200).json({ message: "User direction received successfully" }); } catch (error) { console.error(error); res.status(500).json({ message: "Internal Server Error" }); } }); // 获取位置路由:从Redis读取 router.post("/getlocation", async (req, res) => { try { const { deviceId } = req.body; // 客户端需要携带deviceId const metroInfoStr = await client.get(`metro:${deviceId}`); if (!metroInfoStr) { return res.status(400).json({ message: "No metro info found for this device" }); } const { directionNumber, metroNumberChoice } = JSON.parse(metroInfoStr); console.log("deviceId", deviceId); console.log( "directionNumber:", directionNumber, "metroNumberChoice:", metroNumberChoice, "deviceId:", deviceId ); // 执行你的业务逻辑 res.status(200).json({ /* 返回数据 */ }); } catch (error) { console.log(error); res.status(500).json({ message: "Internal Server Error" }); } });
三、总结
- 绝对不要用全局变量存储请求级别的用户数据,会引发并发冲突
- 同一次请求链内共享变量:用中间件挂载到
req对象 - 跨请求共享用户数据:优先用会话存储或缓存/数据库
内容的提问来源于stack exchange,提问作者Bilel Rahmouni
相关产品推荐
相关产品推荐

