npm package: @lark-apaas/nestjs-common
Description: Common NestJS utilities
Latest version: 0.1.8

# @lark-apaas/nestjs-common

提供可复用的 NestJS 通用能力与类型定义，当前内置请求级上下文服务 `RequestContextService`，用于在一次 HTTP 请求的生命周期内安全地读写上下文数据（如请求 ID、路径、方法、用户/租户/应用信息、IP 等）。

## 特性

- 请求级上下文（AsyncLocalStorage）
  - 在同一请求链路中安全传递上下文数据
  - 支持在中间件、拦截器、服务、控制器中读写
- 纯粹的 NestJS 通用包
  - 不依赖具体业务模块（例如日志、可观测），便于复用

## 安装

```bash
yarn add @lark-apaas/nestjs-common
```

或使用 npm：

```bash
npm install @lark-apaas/nestjs-common
```

## 环境要求

- Node.js >= 18.0.0
- NestJS >= 10.4.20

## 快速开始

### 1. 在顶层模块中注册为单例 Provider（推荐）

```ts
// app.module.ts
import { Module } from '@nestjs/common';
import { RequestContextService } from '@lark-apaas/nestjs-common';

@Module({
  providers: [RequestContextService],
  exports: [RequestContextService],
})
export class AppModule {}
```

> 建议在应用的根模块或全局模块中注册为单例，以确保整个应用共享同一个 `AsyncLocalStorage` 实例。

### 2. 在中间件入口写入请求上下文

```ts
// observable-context.middleware.ts（示例）
import { Injectable, NestMiddleware } from '@nestjs/common';
import type { Request, Response, NextFunction } from 'express';
import { randomUUID } from 'crypto';
import { RequestContextService } from '@lark-apaas/nestjs-common';

@Injectable()
export class ObservableContextMiddleware implements NestMiddleware {
  constructor(private readonly reqCtx: RequestContextService) {}

  use(req: Request & { userContext?: any }, res: Response, next: NextFunction) {
    const requestId = (req.headers['x-tt-log-id'] as string) || (req.headers['x-request-id'] as string) || randomUUID();
    const path = req.originalUrl ?? req.url;
    const uc = req.userContext ?? {};

    res.setHeader('x-log-trace-id', requestId);

    this.reqCtx.run(
      {
        requestId,
        path,
        method: req.method,
        userId: uc.userId,
        tenantId: uc.tenantId,
        appId: uc.appId,
        ip: (req.ip as string) ?? undefined,
      },
      () => next(),
    );
  }
}
```

### 3. 在任意位置读取上下文

```ts
import { Injectable } from '@nestjs/common';
import { RequestContextService } from '@lark-apaas/nestjs-common';

@Injectable()
export class DemoService {
  constructor(private readonly reqCtx: RequestContextService) {}

  doSomething() {
    const ctx = this.reqCtx.getContext();
    // 或者精确获取某个键
    const requestId = this.reqCtx.get('requestId');
    // ... 使用上下文执行你的逻辑
    return { requestId, path: ctx?.path, method: ctx?.method };
  }
}
```

## API 概览

`RequestContextService` 源码位置：packages/server/nestjs-common/src/request-context.service.ts

- `run(context, callback)`：在 `AsyncLocalStorage` 中启动一次上下文存储并执行回调。
  - 参考：packages/server/nestjs-common/src/request-context.service.ts:15-20
- `setContext(partial)`：在当前上下文中合并写入部分字段。
  - 参考：packages/server/nestjs-common/src/request-context.service.ts:22-30
- `getContext()`：读取当前完整上下文。
  - 参考：packages/server/nestjs-common/src/request-context.service.ts:32-34
- `get(key)`：读取当前上下文中的特定键。
  - 参考：packages/server/nestjs-common/src/request-context.service.ts:36-38

`RequestContextState` 默认字段：

```ts
interface RequestContextState {
  requestId: string;
  path?: string;
  method?: string;
  userId?: string;
  appId?: string;
  tenantId?: string;
  ip?: string;
  /** x-tt-env header，用于环境标识透传（如灰度/测试环境） */
  ttEnv?: string;
  /** 是否为系统账号 */
  isSystemAccount?: boolean;
  [key: string]: unknown;
}
```

## 与其他模块的协作建议

- 与日志/可观测模块协作
  - 在 HTTP 入口（中间件）调用 `run(...)` 写入上下文；
  - 在日志或可观测的记录点读取上下文并合并到结构化日志或埋点数据中。
- 仅白名单传递敏感信息
  - 避免直接把所有请求头写入上下文，建议只传递必要字段（如 trace-id、user-id、tenant-id、app-id）。
- 与 HTTP 客户端协作（x-tt-env 透传）
  - `ttEnv` 字段用于存储 `x-tt-env` header，支持灰度/测试环境标识透传；
  - `@lark-apaas/nestjs-core` 的 `PlatformHttpClientService` 会自动读取此字段并透传给下游服务。

## TypeScript 支持

本包使用 TypeScript 编写并导出完整类型：

```ts
export { RequestContextService, type RequestContextState } from '@lark-apaas/nestjs-common';
```

如需在 `Express.Request` 上扩展 `userContext` 等业务字段，请在你自己的项目中通过 `declare global` 进行类型声明扩展。

## 许可证

MIT

## 关键字

- nestjs
- common
- context
- als
- server
- typescript