diff --git a/src/shared/lib/request-context.test.ts b/src/shared/lib/request-context.test.ts new file mode 100644 index 0000000..f0bd509 --- /dev/null +++ b/src/shared/lib/request-context.test.ts @@ -0,0 +1,45 @@ +import { describe, it, expect } from "vitest" +import { + requestContextStorage, + getRequestContext, +} from "./request-context" + +describe("request-context", () => { + describe("getRequestContext (无上下文)", () => { + it("返回空对象", () => { + expect(getRequestContext()).toEqual({}) + }) + }) + + describe("requestContextStorage.run (有上下文)", () => { + it("在 run 回调内 getRequestContext 返回注入的上下文", () => { + const ctx = { requestId: "req-123", userId: "user-456" } + requestContextStorage.run(ctx, () => { + expect(getRequestContext()).toEqual(ctx) + }) + }) + + it("run 回调返回值正常透传", () => { + const result = requestContextStorage.run( + { requestId: "req-789" }, + () => "return-value" + ) + expect(result).toBe("return-value") + }) + + it("嵌套 run 内层覆盖外层", () => { + requestContextStorage.run({ requestId: "outer" }, () => { + expect(getRequestContext().requestId).toBe("outer") + requestContextStorage.run({ requestId: "inner" }, () => { + expect(getRequestContext().requestId).toBe("inner") + }) + expect(getRequestContext().requestId).toBe("outer") + }) + }) + + it("run 回调退出后 getRequestContext 恢复为空", () => { + requestContextStorage.run({ requestId: "temp" }, () => {}) + expect(getRequestContext()).toEqual({}) + }) + }) +}) diff --git a/src/shared/lib/request-context.ts b/src/shared/lib/request-context.ts new file mode 100644 index 0000000..103490e --- /dev/null +++ b/src/shared/lib/request-context.ts @@ -0,0 +1,27 @@ +import { AsyncLocalStorage } from "node:async_hooks" + +/** + * 请求上下文,贯穿整个请求生命周期。 + * + * 仅在 Node.js Runtime 中可用(proxy.ts 是 Edge Runtime,不导入此模块)。 + * 通过 withRequestContext 高阶函数注入。 + */ +export interface RequestContext { + requestId: string + userId?: string + module?: string +} + +export const requestContextStorage = new AsyncLocalStorage() + +/** + * 获取当前请求上下文(若存在)。 + * + * - 在 withRequestContext 包装的调用栈内:返回完整上下文 + * - 在调用栈外(如顶层模块初始化、定时任务):返回空对象 + * + * pino logger 的 mixin 配置会自动调用此函数混入 requestId。 + */ +export function getRequestContext(): Partial { + return requestContextStorage.getStore() ?? {} +}