npm.io
1.1.54 • Published 3d ago

@lark-apaas/fullstack-nestjs-core

Licence
MIT
Version
1.1.54
Deps
15
Size
3.0 MB
Vulns
0
Weekly
1.6K

@lark-apaas/fullstack-nestjs-core

FullStack NestJS Core 是一个为 NestJS 全栈应用提供核心功能的工具包,包括平台集成、CSRF 保护、用户上下文管理和开发工具等。

特性

  • 平台模块: 一站式集成 Config、Logger、Database、Auth 等核心功能
  • HTTP 客户端: 自动集成 @nestjs/axios,提供 HTTP 请求能力,自动打印请求日志
  • CSRF 保护: 提供完整的 CSRF Token 生成和验证机制
  • 用户上下文: 自动从请求头中提取并注入用户上下文信息
  • 视图上下文: 将用户信息和 CSRF Token 注入到模板渲染上下文
  • 旧路径兼容: 灰度迁移期间自动将旧路径 302 重定向到新路径,实现平滑过渡
  • 开发工具: 自动生成 Swagger 文档、OpenAPI JSON 和 TypeScript 客户端 SDK
  • 应用配置: 一键配置 Logger、Cookie Parser、全局前缀等

安装

npm install @lark-apaas/fullstack-nestjs-core

或使用 yarn:

yarn add @lark-apaas/fullstack-nestjs-core

环境要求

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

快速开始

方案 1: 使用 PlatformModule(推荐)

最简单的方式是使用 PlatformModule,它会自动集成所有核心功能:

import { Module } from '@nestjs/common';
import { PlatformModule } from '@lark-apaas/fullstack-nestjs-core';

@Module({
  imports: [
    PlatformModule.forRoot({
      enableCsrf: true,           // 是否启用 CSRF 保护,默认 true
      csrfRoutes: '/api/*',       // CSRF 保护的路由,默认 '/api/*'
      alwaysNeedLogin: true,      // 是否所有路由都需要登录,默认 true
    }),
  ],
})
export class AppModule {}

PlatformModule 自动集成:

  • ConfigModule (环境变量配置)
  • LoggerModule (日志系统)
  • HttpModule (HTTP 客户端,自动打印请求日志)
  • DataPaasModule (数据库连接)
  • AuthNPaasModule (认证系统)
  • UserContextMiddleware (用户上下文)
  • LegacyPathRedirectMiddleware (旧路径兼容重定向)
  • CsrfTokenMiddleware + CsrfMiddleware (CSRF 保护)
  • ViewContextMiddleware (视图上下文)
  • ValidationPipe (全局验证管道)
方案 2: 使用 configureApp 辅助函数

main.ts 中使用 configureApp 快速配置应用:

import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { configureApp } from '@lark-apaas/fullstack-nestjs-core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create<NestExpressApplication>(AppModule);

  // 一键配置:Logger、CookieParser、GlobalPrefix、DevTools
  await configureApp(app);

  await app.listen(3000);
}
bootstrap();

configureApp 会自动:

  • 注册 AppLogger
  • 注册 cookie-parser 中间件
  • 根据 CLIENT_BASE_PATH 环境变量设置全局前缀
  • 在非生产环境自动挂载 DevToolsV2Module
方案 3: 手动配置各个模块

如果需要更细粒度的控制,可以单独使用各个模块:

import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import {
  CsrfTokenMiddleware,
  CsrfMiddleware,
  UserContextMiddleware,
  ViewContextMiddleware,
} from '@lark-apaas/fullstack-nestjs-core';

@Module({})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    // 用户上下文中间件
    consumer
      .apply(UserContextMiddleware)
      .forRoutes('*');

    // CSRF Token 生成(用于视图渲染)
    consumer
      .apply(CsrfTokenMiddleware)
      .exclude('/api/(.*)')
      .forRoutes('*');

    // 视图上下文注入
    consumer
      .apply(ViewContextMiddleware)
      .exclude('/api/(.*)')
      .forRoutes('*');

    // CSRF 验证(用于 API 保护)
    consumer
      .apply(CsrfMiddleware)
      .forRoutes('/api/*');
  }
}

核心模块

PlatformModule

全局平台模块,集成了所有核心功能。

配置选项
选项 类型 默认值 描述
enableCsrf boolean true 是否启用 CSRF 保护
csrfRoutes string | string[] '/api/*' CSRF 保护应用的路由
alwaysNeedLogin boolean true 是否所有路由都需要登录
使用示例
@Module({
  imports: [
    PlatformModule.forRoot({
      enableCsrf: true,
      csrfRoutes: ['/api/*', '/admin/*'],
      alwaysNeedLogin: false,
    }),
  ],
})
export class AppModule {}
DevToolsV2Module(推荐)

使用 @hey-api/openapi-ts 生成高质量的 TypeScript 客户端 SDK。

新特性:

  • 使用最新的 @hey-api/openapi-ts 生成器
  • 自动注入 baseURL 配置到生成的客户端
  • 自动给所有生成的文件添加 // @ts-nocheck 注释
配置选项
选项 类型 默认值 描述
basePath string '/' API 基础路径
docsPath string '/api/docs' Swagger UI 的挂载路径
openapiOut string './client/src/api/gen/openapi.json' OpenAPI JSON 输出路径
needSetupServer boolean false 是否挂载 Swagger UI 服务器
needGenerateClientSdk boolean true 是否生成客户端 SDK
clientSdkOut string './client/src/api/gen' 客户端 SDK 输出目录
swaggerOptions object - Swagger 文档配置
使用示例
import { NestFactory } from '@nestjs/core';
import { DevToolsV2Module } from '@lark-apaas/fullstack-nestjs-core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // 在开发环境挂载开发工具
  if (process.env.NODE_ENV !== 'production') {
    await DevToolsV2Module.mount(app, {
      basePath: '/app',
      docsPath: '/api_docs',
      needSetupServer: true,
      needGenerateClientSdk: true,
      swaggerOptions: {
        title: 'My API',
        version: '1.0.0',
      },
    });
  }

  await app.listen(3000);
}
bootstrap();
生成的文件
client/src/api/gen/
├── openapi.json              # OpenAPI 规范文件
├── client.config.ts          # 客户端配置(自动生成 baseURL)
├── types.gen.ts              # TypeScript 类型定义
├── sdk.gen.ts                # SDK 函数
└── client/
    └── client.gen.ts         # Axios 客户端

所有生成的 .ts 文件都会自动添加 // @ts-nocheck 注释,避免类型检查错误。

DevToolsModule(旧版)

使用 openapi-typescript-codegen 生成客户端 SDK(不推荐,建议迁移到 DevToolsV2Module)。

import { DevToolsModule } from '@lark-apaas/fullstack-nestjs-core';

await DevToolsModule.mount(app, {
  docsPath: 'api-docs',
  openapiOut: './openapi.json',
  needSetupServer: true,
  needGenerateClientSdk: true,
  clientSdkOut: './src/sdk',
});

HTTP 客户端

PlatformModule 自动集成了 @nestjs/axios,提供开箱即用的 HTTP 请求能力。

特性
  • 自动集成: 导入 PlatformModule 后即可直接使用,无需额外配置
  • 自动日志: 所有 HTTP 请求和响应自动打印到日志系统
  • 标准 API: 完全遵循 @nestjs/axios 的标准用法
  • 默认配置: 自动设置 5 秒超时和最多 5 次重定向
基础使用
import { Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { firstValueFrom } from 'rxjs';

interface User {
  id: string;
  name: string;
  email: string;
}

@Injectable()
export class UserService {
  constructor(private readonly httpService: HttpService) {}

  async getUserById(id: string): Promise<User> {
    const { data } = await firstValueFrom(
      this.httpService.get<User>(`https://api.example.com/users/${id}`)
    );
    return data;
  }

  async createUser(userData: Partial<User>): Promise<User> {
    const { data } = await firstValueFrom(
      this.httpService.post<User>('https://api.example.com/users', userData)
    );
    return data;
  }

  async updateUser(id: string, userData: Partial<User>): Promise<User> {
    const { data } = await firstValueFrom(
      this.httpService.put<User>(`https://api.example.com/users/${id}`, userData)
    );
    return data;
  }

  async deleteUser(id: string): Promise<void> {
    await firstValueFrom(
      this.httpService.delete(`https://api.example.com/users/${id}`)
    );
  }
}
带配置的请求
async getUserWithHeaders(id: string) {
  const { data } = await firstValueFrom(
    this.httpService.get(`https://api.example.com/users/${id}`, {
      headers: {
        'Authorization': 'Bearer token',
        'X-Custom-Header': 'value',
      },
      params: {
        include: 'profile',
      },
      timeout: 10000, // 10秒超时
    })
  );
  return data;
}
自动日志输出

所有 HTTP 请求会自动打印到日志系统:

[HttpService] HTTP Request {
  method: 'GET',
  url: 'https://api.example.com/users/123',
  headers: { ... },
  params: { include: 'profile' },
  data: undefined
}

[HttpService] HTTP Response {
  method: 'GET',
  url: 'https://api.example.com/users/123',
  status: 200,
  statusText: 'OK',
  data: { id: '123', name: 'John', email: 'john@example.com' }
}

错误请求也会自动记录:

[HttpService] HTTP Response Error {
  method: 'GET',
  url: 'https://api.example.com/users/999',
  status: 404,
  statusText: 'Not Found',
  data: { message: 'User not found' },
  message: 'Request failed with status code 404'
}
高级用法
直接访问 Axios 实例
constructor(private readonly httpService: HttpService) {
  // 访问底层的 axios 实例
  const axiosInstance = this.httpService.axiosRef;

  // 添加自定义拦截器
  axiosInstance.interceptors.request.use((config) => {
    config.headers['X-Custom'] = 'my-value';
    return config;
  });
}
使用 RxJS 操作符
import { map, catchError } from 'rxjs/operators';
import { of } from 'rxjs';

async getUsers() {
  return this.httpService.get<User[]>('https://api.example.com/users').pipe(
    map(response => response.data),
    catchError(error => {
      console.error('Error fetching users:', error);
      return of([]);
    })
  );
}
默认配置

PlatformModule 注册 HttpModule 时使用以下默认配置:

HttpModule.register({
  timeout: 5000,      // 5 秒超时
  maxRedirects: 5,    // 最多 5 次重定向
})
注意事项
  1. 记得使用 firstValueFrom: @nestjs/axios 返回的是 Observable,需要转换为 Promise
  2. 记得解构 data: 响应数据在 response.data
  3. 类型安全: 使用泛型指定响应数据类型 httpService.get<User>(...)

中间件

LegacyPathRedirectMiddleware

CLIENT_BASE_PATH 灰度切换到新路径格式(/app/:appId)后,将旧路径的页面请求 302 重定向到新路径,实现平滑过渡。已内置于 PlatformModule,无需手动注册。

重定向规则
环境 (req.userContext.env) 旧路径前缀 重定向目标
preview /af/p/:appId/... CLIENT_BASE_PATH/...
runtime /spark/faas/:appId/... CLIENT_BASE_PATH/...
触发条件
  • CLIENT_BASE_PATH/app/:appId 格式开头(新路径格式)
  • 请求路径匹配对应环境的旧前缀
  • 请求路径不以 /api 开头(仅拦截页面请求)
示例
CLIENT_BASE_PATH = /app/cli_abc123

# 预览态旧链接访问
GET /af/p/cli_abc123/pages/home?tab=1
→ 302 /app/cli_abc123/pages/home?tab=1

# 运行态旧链接访问
GET /spark/faas/cli_abc123/pages/home
→ 302 /app/cli_abc123/pages/home
注意事项
  • 中间件依赖 UserContextMiddleware 已填充 req.userContext,注册顺序不可颠倒
  • 旧路径格式(CLIENT_BASE_PATH 不是 /app/:appId)下该中间件是空操作,没有额外开销
  • 路径后缀和 query string 完整保留
CsrfTokenMiddleware

生成 CSRF Token 并设置到 Cookie 中,用于视图渲染场景。

配置选项
选项 类型 默认值 描述
cookieKey string 'suda-csrf-token' Cookie 中存储 Token 的键名
cookieMaxAge number 2592000000 (30天) Cookie 过期时间(毫秒)
cookiePath string '/' Cookie 路径
使用示例
CsrfTokenMiddleware.configure({
  cookieKey: 'my-csrf-token',
  cookieMaxAge: 86400000, // 1
});

consumer
  .apply(CsrfTokenMiddleware)
  .exclude('/api/(.*)')
  .forRoutes('*');
CsrfMiddleware

验证请求中的 CSRF Token,用于保护 API 接口。

配置选项
选项 类型 默认值 描述
headerKey string 'x-suda-csrf-token' 请求头中 Token 的键名
cookieKey string 'suda-csrf-token' Cookie 中 Token 的键名
使用示例
CsrfMiddleware.configure({
  headerKey: 'x-my-csrf-token',
  cookieKey: 'my-csrf-token',
});

consumer
  .apply(CsrfMiddleware)
  .forRoutes('/api/*');

注意: 确保 CsrfTokenMiddlewareCsrfMiddlewarecookieKey 配置一致。

UserContextMiddleware

从请求头中提取用户上下文信息并注入到 req.userContext

注入的上下文
req.userContext = {
  userId: string | undefined;    // 用户 ID(来自 x-user-id 请求头)
  tenantId: string | undefined;  // 租户 ID(来自 x-tenant-id 请求头)
  appId: string;                 // 应用 ID(来自 x-app-id 请求头)
}
使用示例
@Controller()
export class AppController {
  @Get('profile')
  getProfile(@Req() req: Request) {
    const { userId, tenantId, appId } = req.userContext;
    return { userId, tenantId, appId };
  }
}
ViewContextMiddleware

将用户上下文和 CSRF Token 注入到 res.locals,用于模板渲染。

注入的变量
res.locals = {
  csrfToken: string;   // CSRF Token
  userId: string;      // 用户 ID
  tenantId: string;    // 租户 ID
  appId: string;       // 应用 ID
}
使用示例
// 在模板引擎中可以直接使用这些变量
// EJS 示例:
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
<div>User ID: <%= userId %></div>

TypeScript 支持

本包完全使用 TypeScript 编写,并提供完整的类型定义。

扩展 Express Request 类型
declare global {
  namespace Express {
    interface Request {
      csrfToken?: string;
      userContext: {
        userId?: string;
        tenantId?: string;
        appId: string;
      };
    }
  }
}

环境变量

变量名 描述 默认值
CLIENT_BASE_PATH 应用的全局路径前缀 -
NODE_ENV 运行环境 -

许可证

MIT

关键字

  • plugin
  • nestjs
  • server
  • typescript
  • fullstack

Keywords