如何在Express与GraphQL/Apollo服务器间共享并修改变量?
解决方案
核心思路:共享单例状态对象
问题根源在于你可能误操作了对象引用,或者没有使用真正的全局单例对象。Node.js模块系统默认是单例的,我们可以把会话跟踪对象抽成独立模块,让Express和GraphQL解析器共享同一个引用,这样两边的修改会即时同步。
步骤1:创建独立的会话跟踪模块
新建session-tracker.js文件,导出全局跟踪对象并添加过期清理逻辑:
// session-tracker.js const userSessionTracker = {}; const TEN_MINUTES = 10 * 60 * 1000; // 定时清理过期会话 setInterval(() => { const now = Date.now(); for (const userId of Object.keys(userSessionTracker)) { if (now - userSessionTracker[userId] > TEN_MINUTES) { // 执行登出逻辑:比如清除Keycloak/Passport会话 delete userSessionTracker[userId]; } } }, 60 * 1000); // 每分钟检查一次 module.exports = userSessionTracker;
步骤2:在Express中使用该模块
在登录路由或认证中间件里更新会话时间,同时添加过期检查:
// Express 初始化文件 const userSessionTracker = require('./session-tracker'); // 登录成功后更新活跃时间 app.post('/login', (req, res) => { const userId = req.user.id; userSessionTracker[userId] = Date.now(); // ...其他登录逻辑 }); // 全局中间件检查会话是否过期 app.use((req, res, next) => { if (req.user) { const userId = req.user.id; const lastActive = userSessionTracker[userId]; if (lastActive && Date.now() - lastActive > TEN_MINUTES) { // 强制登出:清除Passport会话、返回401等 req.logout(); return res.status(401).send('会话已过期'); } } next(); });
步骤3:在GraphQL解析器中使用该模块
不需要通过context传递跟踪对象,直接在解析器里引入模块并更新时间:
// Apollo Server 初始化文件 const userSessionTracker = require('./session-tracker'); const { ApolloServer } = require('apollo-server-express'); const server = new ApolloServer({ typeDefs, resolvers: { Query: { getUserData: (_, __, context) => { const userId = context.user.id; // 更新最后活跃时间 userSessionTracker[userId] = Date.now(); // ...你的查询逻辑 } }, Mutation: { updateUser: (_, args, context) => { const userId = context.user.id; userSessionTracker[userId] = Date.now(); // ...你的变更逻辑 } } }, context: ({ req }) => { // 仅传递用户信息,无需传跟踪对象 return { user: req.user }; } });
为什么之前的方法失效?
- 错误的对象赋值方式:如果你的解析器里是重新赋值
context.userSessionTracker(比如context.userSessionTracker = {...}),这会替换context里的对象引用,而非修改原全局对象的属性,导致Express端看不到更新。正确做法是直接修改对象属性(context.userSessionTracker[userId] = Date.now())。 - Apollo Context的限制误解:文档中“不能破坏性修改contextValue”指的是不要修改context本身的结构(比如添加/删除context属性、重新赋值context),而非禁止修改context中引用对象的属性。修改共享对象的属性不属于破坏性修改。
内容的提问来源于stack exchange,提问作者Quan Nguyen
相关产品推荐
相关产品推荐

