Hapi框架下请求作用域内当前用户追踪实现方案问询
基于Hapi框架实现请求作用域的全局用户访问器
核心实现思路
利用Node.js内置的AsyncLocalStorage(Node.js 14.17.0+/12.20.0+)来追踪异步请求上下文,实现请求作用域内的用户数据隔离,无需手动传递request对象到服务/工具层。
1. 实现globalRequestAccessor工具模块
创建一个封装异步上下文的工具类,统一处理用户数据的读写和生命周期管理:
// ./utils/globalRequestAccessor.js const { AsyncLocalStorage } = require('async_hooks'); const asyncLocalStorage = new AsyncLocalStorage(); const globalRequestAccessor = { // 初始化请求上下文:在请求开始时注入用户数据 startContext(user) { const store = { currentUser: user }; return asyncLocalStorage.run(store, () => {}); }, // 更新当前用户(可选,用于中途修改用户数据场景) setCurrentUser(user) { const store = asyncLocalStorage.getStore(); if (store) store.currentUser = user; }, // 获取当前请求的用户数据 getCurrentUser() { const store = asyncLocalStorage.getStore(); return store?.currentUser; }, // 手动销毁上下文(可选,AsyncLocalStorage会自动清理) endContext() { asyncLocalStorage.exit(); } }; module.exports = globalRequestAccessor;
2. 集成Hapi请求生命周期钩子
通过Hapi的server.ext钩子,在请求开始前自动初始化上下文,请求结束后清理:
// ./server.js const Hapi = require('@hapi/hapi'); const globalRequestAccessor = require('./utils/globalRequestAccessor'); const PlanController = require('./Controller/PlanController'); const init = async () => { const server = Hapi.server({ port: 3000, host: 'localhost' }); // 请求前置钩子:初始化用户上下文 server.ext('onPreHandler', (request, h) => { globalRequestAccessor.startContext(request.session?.user); return h.continue; }); // 请求后置钩子:确保上下文清理 server.ext('onPostResponse', (request, h) => { globalRequestAccessor.endContext(); return h.continue; }); // 注册业务路由 server.route({ method: 'POST', path: '/plans', handler: PlanController.createPlan }); await server.start(); console.log('Server running on %s', server.info.uri); }; process.on('unhandledRejection', (err) => { console.error(err); process.exit(1); }); init();
3. 业务代码中使用
控制器和服务层无需传递request对象,直接调用工具类获取用户:
// ./Controller/PlanController.js const PlanService = require('../Service/PlanService'); module.exports.createPlan = async (request, h) => { // 业务逻辑:无需手动设置用户,上下文已自动初始化 const result = await PlanService.create({ name: '新计划' }); return h.response(result); };
// ./Service/PlanService.js const globalRequestAccessor = require('../utils/globalRequestAccessor'); const create = async (planData) => { const currentUser = globalRequestAccessor.getCurrentUser(); // 业务逻辑:使用当前用户数据关联创建计划 return { ...planData, creatorId: currentUser?.id, creatorName: currentUser?.name }; }; module.exports = { create };
兼容旧Node.js版本方案(Node <14.17)
如果项目使用低版本Node,可替换为cls-hooked库(需先执行npm install cls-hooked),用法几乎一致:
// ./utils/globalRequestAccessor.js(cls-hooked版本) const cls = require('cls-hooked'); const namespace = cls.createNamespace('request-context'); const globalRequestAccessor = { startContext(user) { return namespace.run(() => { namespace.set('currentUser', user); }); }, setCurrentUser(user) { if (namespace.active) namespace.set('currentUser', user); }, getCurrentUser() { return namespace.get('currentUser'); }, endContext() { namespace.exit(); } }; module.exports = globalRequestAccessor;
关键注意事项
- 异步上下文自动追踪:所有请求生命周期内的异步操作(
await、Promise、setTimeout等)都会自动继承当前上下文,无需额外处理。 - 并发安全:每个请求的上下文完全隔离,不会出现多请求用户数据混淆的问题。
- 请求外调用限制:在请求处理流程外调用
getCurrentUser会返回undefined,符合设计预期。
内容的提问来源于stack exchange,提问作者Arjunasarathy M
相关产品推荐
相关产品推荐

