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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 19:57:19