请求上下文

在上一章节中,我们学会了把 jwt 解析出的 user 挂在到 ctx。
但是我们在非中间件中使用的时候,是不太方便获取的。

像 Java 用 ThreadLocal,在 Node 中可以用 AsyncLocalStorage 来做“每个请求线程的私有上下文”。

创建上下文实例

// utils/request-context.ts
import { AsyncLocalStorage } from 'async_hooks';

const asyncLocalStorage = new AsyncLocalStorage<Map<string, any>>();
export default {
  run: (callback: (...args: any[]) => void) => {
    asyncLocalStorage.run(new Map(), callback);
  },
  set: (key: string, value: any) => {
    const store = asyncLocalStorage.getStore();
    if (store) store.set(key, value);
  },
  get: (key: string) => {
    const store = asyncLocalStorage.getStore();
    return store ? store.get(key) : undefined;
  }
};

封装一个中间件

再封装一个中间件,并在项目中使用。

// middleware/request-context.ts
import context from '../utils/reques-context.ts';

export default function requestContextMiddleware() {
    return async (ctx, next) => {
        await new Promise((resolve, reject) => {
            context.run(() => {
                next().then(resolve).catch(reject);
            });
        });
    };
}
// app.ts
app.use(koaJwt); // 1. 使用 koa-jwt 中间件(验证 JWT)
app.use(contextMiddleware()); // 2. 自定义中间件:创建上下文容器(多了这行:重点这里)
app.use(userMount); // 3. 自定义中间件:解析 token 并挂载 payload 到 ctx 和 context上
app.use(router()); // 4. 路由

最后我们将上一章节userMount 中间件也做一点改动

// middleware/user-mount.ts
import jwt from "jsonwebtoken";
import context from '../utils/reques-context';

const userMount = async (ctx, next) => {
    if (ctx.header?.authorization) {
        const token = ctx.header.authorization.replace('Bearer ', '');
        try {
            const user = jwt.verify(token, process.env.JWT_SECRET);
            ctx.state.user = user;
            context.set('user', ctx.state.user); // 多了这行
        } catch (err) {
            console.error('Token 解析失败:', err);
        }
    }
    await next();
}
export default userMount;

使用

比如我们在 service 层使用

// service/user.ts
export const queryOne = async (params) => {
  const user = context.get('user');
  console.log('当前登录用户:', user);
  // ...
};

这样咱们就实现的是一种“登录后挂载用户信息到全局上下文”,这样在 非请求链(比如 utils、service、job、定时任务等) 中也能拿到当前登录用户 —— 类似 Java 中的 ApplicationContext + ThreadLocal 的组合。