npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@maxtan/nest-core

v1.10.5

Published

A powerful NestJS boot core library with authentication, caching, validation, and logging utilities

Readme

@maxtan/nest-core

NestJS 增强工具包,提供了一系列开箱即用的模块和工具,帮助您快速构建高效、规范的 NestJS 应用。

功能特性

  • 日志系统: 基于 winston 的灵活日志系统,支持控制台、文件、阿里云 SLS 等多种输出方式
  • 操作日志: 基于装饰器的操作日志记录,自动捕获请求信息、执行结果、耗时等
  • 缓存模块: 基于 Redis 的高性能缓存解决方案,支持生命周期管理和批量操作
  • 事务装饰器: 基于装饰器的声明式事务管理,自动处理事务的开启、提交、回滚和资源释放
  • 异常过滤器: 统一的异常处理机制,自动记录异常日志和错误追踪
  • 验证管道: 增强的数据验证管道,支持嵌套验证和配置化
  • 工具函数库: 常用辅助函数集合,使用加密安全的随机数生成
  • 授权模块: 基于 JWT 的认证和授权系统,支持多种 Token 提取策略
  • 雪花算法: 基于 @sapphire/snowflake 的分布式唯一 ID 生成器
  • XML 解析: 高性能 XML 解析装饰器,支持自定义配置和错误处理
  • TypeORM 实体基类: 提供包含通用字段(创建时间、更新时间、创建者、更新者、软删除)的基础实体类
  • 文件上传模块: 基于 Fastify multipart 的文件上传解决方案,支持自动解析、DTO 验证、文件限制和安全类型验证
  • 实体查询构建器: 提供便捷的 TypeORM 查询条件构建工具

安装

npm install @maxtan/nest-core

# 或使用 pnpm
pnpm add @maxtan/nest-core

使用指南

日志模块

基础配置

import { createLogger } from '@maxtan/nest-core'

// 在应用启动时配置日志
createLogger() // 默认配置,日志输出到控制台和文件

使用日志工具

import { logger } from '@maxtan/nest-core'

// 使用默认logger记录不同级别的日志
logger.info('普通信息')
logger.debug('调试信息')
logger.warn('警告信息')
logger.error('错误信息')

// 或使用 log 方法(需要指定级别)
logger.log('info', '普通信息')
logger.log('error', '错误信息', errorObject)

自定义配置选项

import { createLogger } from '@maxtan/nest-core'

// 自定义配置
createLogger({
  useConsole: true, // 是否输出到控制台,默认 true
  maxDays: 30, // 日志文件保留天数,默认 30 天
  maxSize: '20m', // 单个日志文件最大大小,默认 20MB
  sls: {
    // 阿里云 SLS 配置(可选)
    accessKeyId: 'your-ak',
    accessKeySecret: 'your-sk',
    projectName: 'your-project',
    logStoreName: 'your-logstore',
    // 可选配置
    endpoint: 'cn-hangzhou.log.aliyuncs.com',
    topic: 'nest-boot',
    source: 'source',
    env: 'production'
  }
})

日志文件轮转机制

日志系统使用 winston-daily-rotate-file 实现自动轮转:

  1. 按大小轮转:当单个文件达到 maxSize(20MB)时,自动创建新文件

    logs/default-2024-01-01.log       // 当前文件
    logs/default-2024-01-01.1.log.gz  // 第1个文件(已压缩)
    logs/default-2024-01-01.2.log.gz  // 第2个文件(已压缩)
  2. 按日期轮转:每天午夜自动创建新文件

    logs/default-2024-01-01.log.gz
    logs/default-2024-01-02.log.gz
    logs/default-2024-01-03.log      // 当前日期
  3. 自动压缩:旧文件自动压缩为 .gz 格式,节省磁盘空间

  4. 自动清理:超过 maxDays(30天)的文件自动删除

在 NestJS 应用中使用

import { NestFactory } from '@nestjs/core'
import { createLogger, WinstonLoggerService } from '@maxtan/nest-core'
import { AppModule } from './app.module'

async function bootstrap() {
  // 配置日志系统
  createLogger({
    useConsole: true,
    maxDays: 30,
    sls: {
      // SLS 配置...
    }
  })

  const app = await NestFactory.create(AppModule, {
    logger: new WinstonLoggerService() // 使用 Winston 作为 NestJS 日志服务
  })

  await app.listen(3000)
}
bootstrap()

缓存模块

缓存模块提供了基于 Redis 的高性能缓存解决方案,内置生命周期管理,支持批量操作、对象序列化等功能。

import { CacheModule } from '@maxtan/nest-core'

@Module({
  imports: [
    CacheModule.forRoot(
      {
        url: 'redis://localhost:6379'
        // 其他 Redis 客户端选项
      },
      true // 第二个参数表示是否全局注册模块
    )
  ]
})
export class AppModule {}

在服务中使用:

import { Injectable } from '@nestjs/common'
import { CacheService } from '@maxtan/nest-core'

@Injectable()
export class YourService {
  constructor(private readonly cacheService: CacheService) {}

  async getData(key: string) {
    // 从缓存获取数据
    const cached = await this.cacheService.get(key)
    if (cached) return cached

    // 获取数据并缓存
    const data = await this.fetchData()
    await this.cacheService.set(key, JSON.stringify(data), 3600) // 缓存1小时
    return data
  }

  // 使用对象序列化
  async getObjectData(key: string) {
    // 自动 JSON 解析
    const cached = await this.cacheService.getObject<UserData>(key)
    if (cached) return cached

    const data = await this.fetchData()
    await this.cacheService.setObject(key, data, 3600) // 自动 JSON 序列化
    return data
  }

  // 批量操作
  async batchGet(keys: string[]) {
    return await this.cacheService.mget(...keys)
  }

  async batchSet(data: Record<string, any>) {
    await this.cacheService.mset(data)
  }

  // 计数器
  async incrementCounter(key: string) {
    return await this.cacheService.incr(key)
  }

  // 检查连接状态
  async checkHealth() {
    const isReady = await this.cacheService.isReady()
    return { redis: isReady ? 'healthy' : 'unhealthy' }
  }
}

操作日志模块

操作日志模块提供了全局自动记录所有控制器方法执行情况的功能,自动捕获请求信息、执行结果、耗时等。

模块注册

在应用模块中导入 OperationModule

import { Module } from '@nestjs/common'
import { OperationModule } from '@maxtan/nest-core'

@Module({
  imports: [
    // 基础使用 - 默认全局拦截所有控制器方法
    OperationModule.forRoot()
  ]
})
export class AppModule {}

自定义配置

import { Module } from '@nestjs/common'
import { OperationModule } from '@maxtan/nest-core'

@Module({
  imports: [
    OperationModule.forRoot({
      enabled: true, // 是否启用,默认 true
      excludePaths: ['/health', '/metrics'], // 排除不需要记录日志的路径
      global: true, // 是否全局注册,默认 true
      request: false, // 是否默认记录请求参数,默认 false
      response: false, // 是否默认记录响应结果,默认 false
      idField: 'id', // 用户ID字段名,默认 'id'
      nameField: 'name' // 用户名字段名,默认 'name'
    })
  ]
})
export class AppModule {}

使用 @Operation 装饰器

@Operation 装饰器是可选的,即使不使用装饰器,拦截器也会自动记录所有控制器方法的日志。装饰器主要用于:

  1. 添加操作描述
  2. 禁用特定方法的日志记录
  3. 为特定方法配置是否记录请求和响应
import { Controller, Post, Put, Get, Body, Param } from '@nestjs/common'
import { Operation } from '@maxtan/nest-core'

@Controller('users')
export class UserController {
  // 不使用装饰器 - 自动记录日志(根据全局配置)
  @Post()
  async createUser(@Body() dto: CreateUserDto) {
    return this.userService.create(dto)
  }

  // 添加描述并记录请求和响应
  @Put(':id')
  @Operation({
    description: '更新用户信息',
    request: true, // 记录请求参数
    response: true // 记录响应结果
  })
  async updateUser(@Param('id') id: string, @Body() dto: UpdateUserDto) {
    return this.userService.update(id, dto)
  }

  // 仅记录请求,不记录响应
  @Post('login')
  @Operation({
    description: '用户登录',
    request: true
  })
  async login(@Body() dto: LoginDto) {
    return this.authService.login(dto)
  }

  // 禁用日志记录(如健康检查接口)
  @Get('health')
  @Operation({ disabled: true })
  async healthCheck() {
    return { status: 'ok' }
  }
}

配置选项

OperationModuleOptions (模块配置)

| 选项 | 类型 | 默认值 | 说明 | | -------------- | -------- | -------- | --------------------------------------------------------------------------------------------------- | | enabled | boolean | true | 是否启用操作日志 | | excludePaths | string[] | [] | 排除的路径(支持通配符),不记录日志 | | global | boolean | true | 是否全局注册 | | request | boolean | false | 是否默认记录请求参数 | | response | boolean | false | 是否默认记录响应结果 | | idField | string | 'id' | 用户ID字段名。系统会优先使用标准字段 subuserId,如果不存在则使用此字段从 JWT payload 中获取 | | nameField | string | 'name' | 用户名字段名。系统会优先使用 username,如果不存在则使用此字段从 JWT payload 中获取 |

OperationOptions (装饰器配置)

| 选项 | 类型 | 默认值 | 说明 | | ------------- | ------- | ------- | ---------------------------------- | | description | string | - | 操作描述,添加到日志中 | | disabled | boolean | false | 是否禁用此方法的日志记录 | | request | boolean | - | 是否记录请求参数(优先于全局配置) | | response | boolean | - | 是否记录响应结果(优先于全局配置) |

注意:装饰器中的 requestresponse 配置优先级高于模块全局配置。

用户信息提取规则

操作日志会自动从 JWT token 中提取用户信息(如果存在认证)。为了兼容不同的 JWT payload 结构,系统使用以下优先级规则:

用户 ID 提取优先级(从高到低):

  1. payload.sub - JWT 标准字段
  2. payload.userId - 常用的用户ID字段
  3. payload[idField] - 自定义字段(由 idField 配置指定,默认 'id'

用户名提取优先级(从高到低):

  1. payload.username - 常用的用户名字段
  2. payload[nameField] - 自定义字段(由 nameField 配置指定,默认 'name'

这种灵活的配置方式使得操作日志模块可以适配各种 JWT payload 结构,无需修改现有的认证逻辑。

示例

// JWT payload 示例 1:使用标准字段
{
  "sub": "user_12345",
  "username": "john_doe"
}
// 提取结果:userId = "user_12345", username = "john_doe"

// JWT payload 示例 2:使用自定义字段
{
  "id": "12345",
  "name": "John Doe"
}
// 使用默认配置,提取结果:userId = "12345", username = "John Doe"

// JWT payload 示例 3:自定义配置
{
  "uid": "12345",
  "nickname": "JohnD"
}
// 使用配置 { idField: 'uid', nameField: 'nickname' }
// 提取结果:userId = "12345", username = "JohnD"

日志输出示例

基础日志(不记录参数和结果):

{
  "operationId": "1234567890123456789",
  "path": "/users",
  "httpMethod": "POST",
  "userId": "123",
  "username": "admin",
  "status": "success",
  "statusCode": 200,
  "duration": "45ms"
}

带描述和参数的日志:

{
  "operationId": "1234567890123456789",
  "description": "更新用户信息",
  "path": "/users/123",
  "httpMethod": "PUT",
  "userId": "123",
  "username": "admin",
  "params": {
    "username": "newuser",
    "password": "***", // 自动脱敏
    "email": "[email protected]"
  },
  "status": "success",
  "statusCode": 200,
  "duration": "45ms"
}

带结果的日志:

{
  "operationId": "1234567890123456789",
  "description": "更新用户信息",
  "path": "/users/123",
  "httpMethod": "PUT",
  "result": {
    "id": "123",
    "username": "newuser",
    "email": "[email protected]"
  },
  "status": "success",
  "statusCode": 200,
  "duration": "45ms"
}

失败请求:

{
  "operationId": "1234567890123456790",
  "path": "/users/123",
  "httpMethod": "DELETE",
  "status": "failure",
  "statusCode": 500,
  "duration": "12ms",
  "error": {
    "message": "用户不存在",
    "name": "Error"
  }
}

失败请求时,异常过滤器会单独输出堆栈信息(仅 500+ 错误或需要调试的异常):

[Stack Trace] operationId: 1234567890123456790 | errorId: 9876543210

Error: 用户不存在
    at UserService.deleteUser (D:\app\user.service.ts:45:11)
    at UserController.deleteUser (D:\app\user.controller.ts:30:28)
    at @nestjs/core/router/router-execution-context.js:38:29
    ...

字段说明

| 字段 | 说明 | | ------------- | ------------------------------ | | operationId | 操作唯一 ID,用于日志追踪 | | description | 操作描述(可选) | | path | 请求路径(包含路径参数) | | httpMethod | HTTP 方法 | | userId | 用户 ID(从 JWT token 中获取) | | username | 用户名(从 JWT token 中获取) | | params | 请求参数(可选,自动脱敏) | | result | 响应结果(可选) | | status | 操作状态(success/failure) | | statusCode | HTTP 状态码 | | duration | 执行耗时 | | error | 错误信息(失败时,不含 stack) |

错误日志关联

当操作失败时,会输出两条日志:

  1. 操作日志:包含业务上下文和错误信息(不含堆栈)
  2. 堆栈信息:由异常过滤器输出(仅 500+ 错误或需要调试的异常)

两条日志通过 operationId 关联,可以快速定位问题:

# 查看某个操作的所有相关日志
grep "1234567890" app.log

安全特性

  1. 自动脱敏 - 自动隐藏敏感字段:passwordtokensecretaccessTokenrefreshTokenapiKey
  2. 异步记录 - 异步记录日志,不影响请求性能
  3. 唯一追踪 - 每个操作生成唯一 operationId,便于日志追踪和关联
  4. 堆栈分离 - 错误堆栈与操作日志分离,通过 operationId 关联,避免日志冗余
  5. 灵活配置 - 支持全局配置和方法级配置,装饰器配置优先级更高

授权模块

授权模块提供了基于 JWT 的认证和授权功能,可以轻松集成到您的 NestJS 应用中。

基本使用

import { AuthModule } from '@maxtan/nest-core'

@Module({
  imports: [
    AuthModule.register({
      secret: 'your-jwt-secret-key'
    })
  ]
})
export class AppModule {}

自定义配置

您可以自定义 JWT 签名选项:

import { AuthModule } from '@maxtan/nest-core'

@Module({
  imports: [
    AuthModule.register({
      secret: 'your-jwt-secret-key',
      signOptions: {
        expiresIn: '7d', // Token有效期7天
        issuer: 'your-app', // 发行方
        audience: 'your-users' // 目标用户
      }
    })
  ]
})
export class AppModule {}

保护路由

使用 AuthGuard 保护您的路由:

import { Controller, Get, UseGuards } from '@nestjs/common'
import { AuthGuard } from '@maxtan/nest-core'

@Controller('protected')
@UseGuards(AuthGuard)
export class ProtectedController {
  @Get()
  getProtectedData() {
    return { message: '这是受保护的数据' }
  }
}

全局应用 AuthGuard

您可以在应用级别全局应用 AuthGuard,这样所有路由默认都会受到保护:

import { Module } from '@nestjs/common'
import { APP_GUARD } from '@nestjs/core'
import { AuthGuard, AuthModule } from '@maxtan/nest-core'

@Module({
  imports: [
    AuthModule.register({
      secret: 'your-jwt-secret-key'
    })
    // 其他模块...
  ],
  providers: [
    {
      provide: APP_GUARD,
      useClass: AuthGuard
    }
  ]
})
export class AppModule {}

当启用全局 AuthGuard 后,可以使用 @AuthPublic() 装饰器来标记不需要认证的公开路由:

import { Controller, Get } from '@nestjs/common'
import { AuthPublic } from '@maxtan/nest-core'

@Controller('public')
export class PublicController {
  @Get()
  @AuthPublic()
  getPublicData() {
    return { message: '这是公开数据,无需认证' }
  }
}

获取当前用户

从请求中获取当前认证用户:

import { Controller, Get, Request, UseGuards } from '@nestjs/common'
import { AuthGuard } from '@maxtan/nest-core'

@Controller('profile')
@UseGuards(AuthGuard)
export class ProfileController {
  @Get()
  getProfile(@Request() req) {
    // req.user 包含了 JWT payload 中的信息
    return req.user
  }
}

使用 AuthDecorator 获取用户信息

更简洁的方式是使用 AuthDecorator 装饰器来直接获取认证用户信息:

import { Controller, Get, UseGuards } from '@nestjs/common'
import { AuthGuard, AuthDecorator } from '@maxtan/nest-core'

@Controller('profile')
@UseGuards(AuthGuard)
export class ProfileController {
  @Get()
  getProfile(@AuthDecorator() auth) {
    // auth 包含完整的认证用户信息
    return auth
  }

  @Get('username')
  getUsername(@AuthDecorator('username') username: string) {
    // 直接获取指定字段
    return { username }
  }

  @Get('data')
  getData(@AuthDecorator('data') data: any) {
    // 获取自定义数据字段
    return data
  }
}

AuthDecorator 可以接受一个可选的参数,用于指定要获取的 JWT payload 中的字段名。如果不提供参数,将返回整个认证对象。

JWT Token 自定义扩展

@maxtan/nest-core 的认证模块只负责 验证和解析 JWT token,不干涉 token 的生成和加密逻辑。

你可以在外部应用中自由定义 JWT payload 结构和加密方式:

import { JwtService } from '@nestjs/jwt'
import { Injectable } from '@nestjs/common'

@Injectable()
export class AuthService {
  constructor(private jwtService: JwtService) {}

  // 自定义 token 生成逻辑
  async generateToken(user: any) {
    // 自定义 payload 结构
    const payload = {
      sub: user.id, // 标准字段
      username: user.username, // 业务字段
      roles: user.roles, // 自定义字段
      permissions: user.perms, // 自定义字段
      customData: {
        // 任意嵌套结构
        department: user.dept,
        level: user.level
      }
    }

    // 使用自己的加密方式
    return this.jwtService.sign(payload, {
      algorithm: 'RS256', // 支持 RSA 非对称加密
      expiresIn: '7d'
      // 更多自定义配置...
    })
  }
}

支持的自定义选项

  1. Payload 结构:任意自定义字段,AuthPayload 类型支持索引签名
  2. 加密算法:支持 HS256、HS384、HS512、RS256、RS384、RS512 等
  3. 签名密钥:支持对称密钥和 RSA 非对称密钥
  4. 过期策略:自定义 expiresInnotBefore
  5. 多租户支持:通过 issaud 字段实现

AuthGuard 会自动解析你的自定义 payload,并注入到 request.user 中:

@Get('profile')
getProfile(@AuthDecorator() auth) {
  // 可以访问所有自定义字段
  console.log(auth.roles)        // 自定义字段
  console.log(auth.permissions)  // 自定义字段
  console.log(auth.customData)   // 嵌套对象
  return auth
}

事务管理

事务装饰器提供了声明式的数据库事务管理方案,自动处理事务的全部生命周期。

特性

  • 声明式: 使用 @Transactional() 装饰器标记,代码更简洁
  • 自动管理: 自动处理事务开启、提交、回滚和资源释放
  • 异常安全: 异常时自动回滚,确保数据一致性
  • 零侵入: 不干扰业务逻辑,与全局异常过滤器完美配合

基础使用

import { Injectable } from '@nestjs/common'
import { DataSource, QueryRunner } from 'typeorm'
import { Transactional } from '@maxtan/nest-core'

@Injectable()
export class UserService {
  constructor(private dataSource: DataSource) {}

  // 使用 @Transactional 装饰器,事务自动管理
  @Transactional()
  async createUser(userData: CreateUserDto, qr: QueryRunner) {
    // QueryRunner 自动注入到最后一个参数
    const user = await qr.manager.save(User, userData)
    const profile = await qr.manager.save(UserProfile, {
      userId: user.id,
      avatar: userData.avatar
    })
    return { user, profile }
  }

  @Transactional()
  async transferMoney(fromId: string, toId: string, amount: number, qr: QueryRunner) {
    // 复杂事务逻辑,异常时自动回滚
    const fromAccount = await qr.manager.findOne(Account, {
      where: { id: fromId }
    })
    const toAccount = await qr.manager.findOne(Account, {
      where: { id: toId }
    })

    if (fromAccount.balance < amount) {
      throw new Error('余额不足') // 自动回滚
    }

    fromAccount.balance -= amount
    toAccount.balance += amount

    await qr.manager.save(fromAccount)
    await qr.manager.save(toAccount)

    return { success: true }
  }
}

重要说明

  1. 必须注入 DataSource

    @Injectable()
    export class YourService {
      // 必须注入 dataSource
      constructor(private dataSource: DataSource) {}
    }

2. **QueryRunner 参数位置**

   `QueryRunner` 会自动添加到方法的**最后一个参数**:

   ```typescript
   // 原方法签名
   async createUser(userData: CreateUserDto)

   // 使用装饰器后,需要添加 qr 参数
   @Transactional()
   async createUser(userData: CreateUserDto, qr: QueryRunner)
  1. 异常处理

    装饰器会自动回滚事务并透传异常,由全局异常过滤器统一处理:

    @Transactional()
    async someMethod(data: any, qr: QueryRunner) {
      if (!data.isValid) {
        // 抛出异常,事务自动回滚
        throw new BadRequestException('数据无效')
      }
      // 业务逻辑...
    }

工作原理

@Transactional 装饰器的执行流程:

// 1. 自动开启事务
const qr = dataSource.createQueryRunner()
await qr.connect()
await qr.startTransaction()

try {
  // 2. 执行业务方法,将 qr 作为最后一个参数传入
  const result = await originalMethod(...args, qr)

  // 3. 自动提交事务
  await qr.commitTransaction()
  return result
} catch (error) {
  // 4. 异常时自动回滚
  if (qr.isTransactionActive) await qr.rollbackTransaction()
  throw error // 透传异常
} finally {
  // 5. 自动释放连接
  if (!qr.isReleased) await qr.release()
}

与 CommonService 对比

传统方式(使用 CommonService)

async createUser(userData: CreateUserDto) {
  return await this.commonService.runInTransaction(
    this.dataSource,
    async (qr) => {
      const user = await qr.manager.save(User, userData)
      const profile = await qr.manager.save(UserProfile, { userId: user.id })
      return { user, profile }
    }
  )
}

新方式(使用装饰器)

@Transactional()
async createUser(userData: CreateUserDto, qr: QueryRunner) {
  const user = await qr.manager.save(User, userData)
  const profile = await qr.manager.save(UserProfile, { userId: user.id })
  return { user, profile }
}

优势:

  • ✅ 代码更平坦,无嵌套回调
  • ✅ 可读性更好,一眼就知道是事务方法
  • ✅ 符合 NestJS 装饰器设计哲学
  • ✅ 更易于维护和测试

注意事项

  1. 不要在控制器中使用

    事务装饰器应该使用在 Service 层,不要在 Controller 中使用:

    // ❌ 错误:不要在 Controller 中使用
    @Controller('users')
    export class UserController {
      @Post()
      @Transactional()
      async create(@Body() dto: CreateUserDto, qr: QueryRunner) {
        // ...
      }
    }
    
    // ✅ 正确:在 Service 中使用
    @Injectable()
    export class UserService {
      @Transactional()
      async create(dto: CreateUserDto, qr: QueryRunner) {
        // ...
      }
    }
  2. 调用方法时不需要传入 QueryRunner

    // 在 Controller 或其他 Service 中调用
    await this.userService.createUser(userData) // 不需要传 qr
  3. 嵌套事务

    如果需要在一个事务方法中调用另一个事务方法,需要传递 QueryRunner:

    @Transactional()
    async createUserWithOrder(data: any, qr: QueryRunner) {
      const user = await qr.manager.save(User, data.user)
      // 复用同一个 QueryRunner,不会开启新事务
      const order = await this.createOrder(data.order, qr)
      return { user, order }
    }
    
    // 此方法可以独立使用,也可以被包含在其他事务中
    @Transactional()
    async createOrder(orderData: any, qr: QueryRunner) {
      return await qr.manager.save(Order, orderData)
    }

XML 解析装饰器

XML 解析装饰器提供了简单易用的 XML 请求体解析功能,支持自定义配置和错误处理。

基本使用

import { Controller, Post } from '@nestjs/common'
import { XmlDecorator } from '@maxtan/nest-core'

@Controller('api')
export class ApiController {
  @Post('xml')
  async handleXml(@XmlDecorator() xmlData: any) {
    console.log('解析的 XML 数据:', xmlData)
    return { success: true, data: xmlData }
  }
}

自定义配置

import { Controller, Post } from '@nestjs/common'
import { XmlDecorator } from '@maxtan/nest-core'

@Controller('api')
export class ApiController {
  @Post('xml-custom')
  async handleXmlCustom(
    @XmlDecorator({
      parserOptions: {
        ignoreAttributes: false,
        attributeNamePrefix: '@_',
        textNodeName: '#text'
      },
      unwrap: true // 自动解包根节点
    })
    xmlData: any
  ) {
    console.log('解析的 XML 数据:', xmlData)
    return { success: true, data: xmlData }
  }
}

配置选项

| 选项 | 类型 | 默认值 | 说明 | | --------------- | ---------- | ------ | ------------------ | | parserOptions | X2jOptions | - | 自定义解析器选项 | | unwrap | boolean | false | 是否自动解包根节点 |

TypeORM 实体基类

BaseEntity 是一个基础实体类,提供了常用的审计字段和自动时间戳管理功能。

此外,还提供了实用的查询构建工具函数:

buildWhere 查询构建器

buildWhere 函数是一个智能的 TypeORM 查询条件构建工具,能够自动清理无效值、过滤非实体字段并处理软删除。

import { buildWhere } from '@maxtan/nest-core'
import { Like, MoreThan } from 'typeorm'

// 单条件查询(AND)- 自动清理无效值
const where1 = buildWhere({ name: 'John', age: 25, status: null }, UserEntity)
// 生成: { name: 'John', age: 25, deletedAt: IsNull() }

// 多条件查询(OR)
const where2 = buildWhere([{ name: 'John' }, { age: 25 }], UserEntity)
// 生成: [{ name: 'John', deletedAt: IsNull() }, { age: 25, deletedAt: IsNull() }]

// 自动过滤前端传来的无效值
const where3 = buildWhere(
  { name: 'John', age: null, email: '', type: 'undefined' },
  UserEntity
)
// 生成: { name: 'John', deletedAt: IsNull() }
// 自动过滤: null、空字符串、'undefined' 字符串

// 支持 TypeORM 操作符
const where4 = buildWhere(
  { name: Like('%John%'), age: MoreThan(18) },
  UserEntity
)
// 生成: { name: Like('%John%'), age: MoreThan(18), deletedAt: IsNull() }

特点

  • 智能清理:自动过滤 nullundefined、空字符串、'null' 字符串、'undefined' 字符串
  • 字段验证:自动排除非实体字段(支持继承链)
  • 软删除:自动附加 deletedAt: IsNull() 条件(用户手动指定时不覆盖)
  • 操作符支持:完全支持 TypeORM 操作符(Like、Not、MoreThan、In 等)
  • 查询模式:支持单对象(AND)和数组(OR)两种模式
  • 性能优化:实体字段缓存,避免重复遍历继承链

applyLike 智能模糊查询

applyLike 函数用于智能应用 Like 操糊查询,自动清理无效值并支持多种匹配模式。

import { applyLike, buildWhere } from '@maxtan/nest-core'

// 方式 1: 使用默认包含匹配
const dto1 = applyLike({ name: 'John', age: 25 }, ['name'])
// 生成: { name: Like('%John%'), age: 25 }

// 方式 2: 使用前缀匹配(推荐,可用索引,性能好)
const dto2 = applyLike({ name: 'John', code: 'A001' }, ['name', 'code'], 'start')
// 生成: { name: Like('John%'), code: Like('A001%') }

// 方式 3: 字段级精确控制
const dto3 = applyLike(
  { name: 'John', email: 'test', description: 'info' },
  {
    name: 'start',        // 前缀匹配:Like('John%')
    email: 'end',         // 后缀匹配:Like('%test')
    description: 'both'   // 包含匹配:Like('%info%')
  }
)

// 配合 buildWhere 使用
find(query: any, options: FindManyOptions<User> = {}) {
  // 步骤 1: 应用 Like 模糊查询(自动清理无效值)
  const dto = applyLike(query, ['name', 'username'], 'start')
  
  // 步骤 2: 构建查询条件(自动处理软删除)
  options.where = buildWhere(dto, User)
  
  return this.repository.find(options)
}

匹配模式对比

| 模式 | 语法 | 能否用索引 | 性能 | 使用场景 | |------|------|-----------|------|----------| | 'start' | Like('abc%') | ✅ 可以 | 快 | 推荐:搜索框自动补全、用户名/编码前缀查询 | | 'end' | Like('%abc') | ❌ 不可以 | 慢 | 文件扩展名、邮箱后缀查询 | | 'both' | Like('%abc%') | ❌ 不可以 | 最慢 | 全文搜索(数据量小时可用) |

特点

  • 自动清理:内部调用 cleanQueryParams,过滤所有无效值
  • 灵活配置:支持全局模式和字段级模式
  • 性能优化:推荐使用 'start' 模式,可利用数据库索引
  • 类型安全:只对字符串值应用 Like,其他类型保持不变

cleanObject 对象清理

cleanObject 函数用于清理对象中的无效值,支持灵活的配置选项。

默认行为(推荐用于 ValidationPipe):

import { cleanObject } from '@maxtan/nest-core'

// 默认只过滤 null 和 undefined,保留空字符串
const data = {
  name: 'John',
  age: null,           // 过滤
  status: undefined,   // 过滤
  email: ''            // 保留 ✅
}

const cleanData = cleanObject(data)
// 结果: { name: 'John', email: '' }

完整过滤(推荐用于 TypeORM 查询):

// 过滤所有无效值(包括空字符串、'null'/'undefined' 字符串)
const query = {
  name: 'John',
  age: null,           // 过滤
  status: undefined,   // 过滤
  type: '',            // 过滤
  email: 'null',       // 过滤(字符串'null')
  phone: 'undefined'   // 过滤(字符串'undefined')
}

const cleanQuery = cleanObject(query, {
  filterEmptyString: true,
  filterNullString: true,
  filterUndefinedString: true
})
// 结果: { name: 'John' }

配置选项

export interface CleanObjectOptions {
  filterNull?: boolean              // 过滤 null 值,默认 true
  filterUndefined?: boolean          // 过滤 undefined 值,默认 true
  filterEmptyString?: boolean        // 过滤空字符串,默认 false
  filterNullString?: boolean         // 过滤 'null' 字符串,默认 false
  filterUndefinedString?: boolean    // 过滤 'undefined' 字符串,默认 false
}

使用场景

  1. ValidationPipe 场景(默认配置):

    // 只过滤 null 和 undefined,保留有效的空字符串
    // 空字符串需配合 @EmptyStringToNull 装饰器使用
    return cleanObject(object)
  2. TypeORM 查询场景(完整过滤):

    // buildWhere 和 applyLike 内部已使用完整过滤
    const where = buildWhere(query, User)
    const likeQuery = applyLike(query, ['name'], 'start')
  3. 自定义场景

    // 只需指定需要改变的选项
    cleanObject(data, { filterEmptyString: true })

嵌套对象支持

// 递归处理嵌套对象(TypeORM 关联查询)
const nested = {
  name: 'John',
  user: {
    id: 1,
    type: null,
    role: ''
  }
}

const clean = cleanObject(nested, { filterEmptyString: true })
// 结果: { name: 'John', user: { id: 1 } }

完整使用示例

import { Injectable } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository, FindManyOptions } from 'typeorm'
import { applyLike, buildWhere } from '@maxtan/nest-core'
import { User } from './user.entity'

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User)
    private repository: Repository<User>
  ) {}

  // 示例 1: 简单查询
  async find(query: any, options: FindManyOptions<User> = {}) {
    // 一行代码完成:清理无效值 + Like 查询 + 软删除过滤
    options.where = buildWhere(
      applyLike(query, ['name', 'username'], 'start'),
      User
    )
    return this.repository.find(options)
  }

  // 示例 2: 复杂查询(字段级控制)
  async search(dto: SearchDto, options: FindManyOptions<User> = {}) {
    const where = applyLike(dto, {
      name: 'start',        // 姓名前缀搜索(快)
      username: 'start',    // 用户名前缀搜索(快)
      email: 'both',        // 邮箱全文搜索
      phone: 'end'          // 手机号后缀搜索
    })
    
    options.where = buildWhere(where, User)
    return this.repository.find(options)
  }

  // 示例 3: 手动控制软删除
  async findWithDeleted(query: any, options: FindManyOptions<User> = {}) {
    const dto = applyLike(query, ['name'], 'start')
    
    // 手动指定 deletedAt,buildWhere 不会覆盖
    dto.deletedAt = Not(IsNull()) // 只查询已删除的
    
    options.where = buildWhere(dto, User)
    options.withDeleted = true
    return this.repository.find(options)
  }
}

基础使用

import { Entity, PrimaryColumn } from 'typeorm'
import { BaseEntity } from '@maxtan/nest-core'

@Entity('users')
export class User extends BaseEntity {
  @PrimaryColumn({ type: 'bigint' })
  id: string

  @Column()
  username: string

  @Column()
  email: string
}

继承 BaseEntity 后,实体会自动拥有以下字段:

| 字段名 | 数据库列名 | 数据库类型 | 应用层类型 | 说明 | | ----------- | ------------ | ---------- | ---------- | ------------------------------------------------ | | createdAt | created_at | bigint | number | 创建时间(毫秒时间戳),插入时自动设置 | | createdBy | created_by | varchar | string | 创建者(可为空),需手动设置 | | updatedAt | updated_at | bigint | number | 更新时间(毫秒时间戳),插入或更新时自动设置 | | updatedBy | updated_by | varchar | string | 更新者(可为空),需手动设置 | | deletedAt | deleted_at | bigint | number | 删除时间(软删除标记),默认不返回(需显式查询) |

自动时间戳

BaseEntity 使用 TypeORM 的生命周期钩子自动管理时间戳:

// 插入时
const user = new User()
user.username = 'john'
// createdAt 和 updatedAt 会自动设置为当前时间
await repository.save(user)

// 更新时
user.email = '[email protected]'
// updatedAt 会自动更新为当前时间,createdAt 保持不变
await repository.save(user)

软删除支持

deletedAt 字段用于软删除功能,默认设置了 select: false 和索引,不会在查询时自动返回:

import { IsNull, Not } from 'typeorm'

// 查询未删除的记录(推荐)
// 由于 deletedAt 默认 select: false,不需要额外过滤
const activeUsers = await repository.find()

// 如需显式过滤未删除记录
const activeUsers = await repository.find({
  where: { deletedAt: IsNull() }
})

// 查询已删除的记录(需显式选择 deletedAt)
const deletedUsers = await repository.find({
  where: { deletedAt: Not(IsNull()) },
  select: ['id', 'username', 'deletedAt'], // 必须显式指定
  withDeleted: true // TypeORM 选项
})

// 软删除(手动设置 deletedAt)
user.deletedAt = Date.now() // 注意:deletedAt 是 number 类型
await repository.save(user)

// 恢复软删除的记录
user.deletedAt = null
await repository.save(user)

注意事项

  • deletedAt 字段类型为 number(不是 string),设置时需转换
  • 默认 select: false,查询已删除记录时必须显式指定
  • 该字段已建立索引,查询性能良好

时间戳字段

时间戳字段在数据库中存储为 bigint 类型,TypeORM 会自动转换为 number 类型:

// bigint 自动转换为 number
const user = await repository.findOne({ where: { id: '123' } })
console.log(user.createdAt) // 1700000000000 (number)
console.log(typeof user.createdAt) // "number"

// 可以直接用于 JSON 响应和日期转换
return user // 自动序列化为数字
const date = new Date(user.createdAt) // 直接转换,无需 Number()

Datetime 字段处理

对于 datetime/datetime2/date/timestamp 等类型的时间字段,推荐直接使用 Date 类型,由响应拦截器自动转换为时间戳:

import { Column } from 'typeorm'
import { Between, MoreThan } from 'typeorm'

// 实体定义
@Entity()
export class Book {
  @Column({ 
    type: 'datetime', 
    nullable: true
  })
  readTime: Date  // 直接使用 Date 类型
}

// 读取数据:数据库 datetime → Date 对象
const book = await repository.findOne({ where: { id: 1 } })
console.log(book.readTime) // Date 对象

// 写入数据:直接赋值 Date 对象
book.readTime = new Date()
await repository.save(book)

// 查询条件:直接使用 Date 对象,类型完全匹配
const startDate = new Date('2025-01-01')
const endDate = new Date('2025-12-31')

// 范围查询
const books = await repository.find({
  where: {
    readTime: Between(startDate, endDate)  // ✓ 类型安全
  }
})

// 大于查询
const recentBooks = await repository.find({
  where: {
    readTime: MoreThan(new Date(Date.now() - 7 * 24 * 60 * 60 * 1000))
  }
})

重要提示

  • 实体字段使用 Date 类型,查询时类型完全匹配,无需额外转换
  • 响应拦截器会自动将 Date 转换为时间戳返回给前端
  • 前端收到的是时间戳(毫秒),例如:1734785802000
  • 支持 MySQL datetime、SQL Server datetime2、PostgreSQL timestamp 等多种数据库类型
  • 自动处理空值(null/undefined

自定义创建者和更新者

你可以在业务逻辑中手动设置创建者和更新者:

// 创建时设置创建者
const user = new User()
user.username = 'john'
user.createdBy = currentUserId // 手动设置
await repository.save(user)

// 更新时设置更新者
user.email = '[email protected]'
user.updatedBy = currentUserId // 手动设置
await repository.save(user)

或者结合拦截器自动设置:

import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'
import { Observable } from 'rxjs'
import { tap } from 'rxjs/operators'

@Injectable()
export class AuditInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest()
    const userId = request.user?.id // 从认证信息中获取用户ID

    return next.handle().pipe(
      tap((data) => {
        // 在保存实体前设置审计字段
        if (data && userId) {
          if (!data.createdBy) data.createdBy = userId
          data.updatedBy = userId
        }
      })
    )
  }
}

雪花算法

雪花算法用于生成全局唯一的分布式 ID,基于 @sapphire/snowflake 实现:

基础使用

import { createSnowflake } from '@maxtan/nest-core'

// 创建默认雪花算法实例
const snowflake = createSnowflake()

// 生成唯一ID
const id = snowflake.generateSnowflakeId()
console.log(id) // 输出类似: "1234567890123456789"

自定义配置

import { createSnowflake } from '@maxtan/nest-core'

// 自定义工作节点ID和起始时间
const snowflake = createSnowflake({
  workerId: 1, // 工作节点ID
  epoch: new Date('2024-01-01T00:00:00.000Z') // 自定义起始时间
})

const id = snowflake.generateSnowflakeId()

支持的配置选项

interface SnowflakeOptions {
  workerId?: number // 工作节点ID,默认为1
  epoch?: number | string | Date // 起始时间,默认为2025-01-01 00:00:00 UTC
}

实例缓存

相同配置参数的实例会被自动缓存,避免重复创建:

// 这两个调用会返回同一个实例
const snowflake1 = createSnowflake({ workerId: 1 })
const snowflake2 = createSnowflake({ workerId: 1 })

console.log(snowflake1 === snowflake2) // true

文件上传模块

基于 NestJS + Fastify 的文件上传解决方案,支持自动解析、DTO 验证和文件大小限制。

特性

  • 自动解析:自动处理 multipart/form-data 请求
  • DTO 验证:使用 class-validator 验证表单字段
  • 文件限制:支持文件大小和数量限制
  • 类型安全:完整的 TypeScript 类型定义
  • 全局拦截:使用装饰器即可启用
  • Buffer 模式:文件自动转换为 Buffer

安装配置

1. 注册 Fastify Multipart 插件

main.ts 中注册 @fastify/multipart 插件:

import fastifyMultipart from '@fastify/multipart'

async function bootstrap() {
  const app = await NestFactory.create<NestFastifyApplication>(AppModule, new FastifyAdapter())

  // 注册 multipart 插件
  await app.register(fastifyMultipart)

  await app.listen(3000)
}

2. 导入 MultipartModule

app.module.ts 中导入模块:

import { MultipartModule } from '@maxtan/nest-core'

@Module({
  imports: [
    // 使用默认配置
    MultipartModule.forRoot(),

    // 或自定义配置
    MultipartModule.forRoot({
      maxFileSize: 50 * 1024 * 1024, // 50MB
      maxFiles: 10 // 最多 10 个文件
    })
  ]
})
export class AppModule {}

基础用法

import { Controller, Post } from '@nestjs/common'
import { Multipart, MultipartData } from '@maxtan/nest-core'

@Controller('upload')
export class UploadController {
  @Post('file')
  @Multipart() // 标记为 multipart 路由
  async uploadFile(@MultipartData() data) {
    const { fields, files } = data

    console.log('表单字段:', fields)
    console.log('上传文件:', files)

    // 文件信息
    const file = files[0]
    console.log('文件名:', file.filename)
    console.log('大小:', file.size)
    console.log('MIME:', file.mimetype)
    console.log('Buffer:', file.buffer)

    return { success: true }
  }
}

文件类型验证与安全配置

为了提高安全性,模块支持 MIME 类型白名单和文件扩展名黑名单验证:

import { UPLOAD_CONFIG } from '@maxtan/nest-core'

// 1. 只允许图片上传
@Post('avatar')
@Multipart({ 
  allowedMimeTypes: UPLOAD_CONFIG.ALLOWED_IMAGE_TYPES,
  maxFileSize: 5 * 1024 * 1024 // 5MB
})
async uploadAvatar(@MultipartData() data: ParsedMultipart) {
  // 只接受 jpeg, png, gif, webp 等图片类型
}

// 2. 只允许文档上传
@Post('document')
@Multipart({ 
  allowedMimeTypes: UPLOAD_CONFIG.ALLOWED_DOCUMENT_TYPES,
  maxFiles: 3
})
async uploadDoc(@MultipartData() data: ParsedMultipart) {
  // 只接受 PDF、Word、Excel 等文档
}

// 3. 自定义 MIME 类型白名单
@Post('custom')
@Multipart({ 
  allowedMimeTypes: ['image/jpeg', 'application/pdf'],
  forbiddenExtensions: ['.exe', '.bat'] // 额外禁止
})
async uploadCustom(@MultipartData() data: ParsedMultipart) {
  // 自定义类型限制
}

内置常量:

import { UPLOAD_CONFIG } from '@maxtan/nest-core'

// 允许的图片类型
UPLOAD_CONFIG.ALLOWED_IMAGE_TYPES
// ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml', 'image/bmp']

// 允许的文档类型
UPLOAD_CONFIG.ALLOWED_DOCUMENT_TYPES
// ['application/pdf', 'application/msword', ...]

// 允许的压缩文件类型
UPLOAD_CONFIG.ALLOWED_ARCHIVE_TYPES
// ['application/zip', 'application/x-zip-compressed', ...]

// 禁止的文件扩展名(默认)
UPLOAD_CONFIG.FORBIDDEN_EXTENSIONS
// ['.exe', '.bat', '.cmd', '.sh', '.ps1', '.vbs', '.scr', '.dll', '.so', '.dylib', '.app']

验证机制:

  1. MIME 类型验证:检查文件的 mimetype 是否在白名单中
  2. 扩展名验证:检查文件扩展名是否在黑名单中(默认禁止可执行文件)
  3. 自动拦截:不符合规则的文件会立即抛出 BadRequestException

使用 DTO 验证

定义 DTO 类:

import { IsString, IsOptional } from 'class-validator'

export class UploadDto {
  @IsString()
  title: string

  @IsString()
  @IsOptional()
  description?: string
}

在控制器中使用:

@Post('avatar')
@Multipart(UploadDto) // 传入 DTO 类
async uploadAvatar(@MultipartData() data) {
const { fields, files } = data

// fields 已自动验证和转换为 DTO 实例
console.log('标题:', fields.title)
console.log('描述:', fields.description)

const avatar = files[0]
// 处理头像上传...

return { success: true }
}

只获取文件或字段


// 只获取文件
@Post('files-only')
@Multipart()
async uploadFiles(@MultipartData('files') files: UploadedFile[]) {
console.log('上传了', files.length, '个文件')
return files.map(f => ({ name: f.filename, size: f.size }))
}

// 只获取字段
@Post('fields-only')
@Multipart(CreateDto)
async createWithFields(@MultipartData('fields') fields: CreateDto) {
console.log('表单数据:', fields)
return fields
}

类型定义

UploadedFile

interface UploadedFile {
  /** 原始文件名 */
  filename: string
  /** 文件大小(字节) */
  size: number
  /** MIME 类型 */
  mimetype: string
  /** 文件内容缓冲区 */
  buffer: Buffer
  /** 表单字段名 */
  fieldname: string
  /** 编码类型 */
  encoding: string
}

MultipartModuleOptions

interface MultipartModuleOptions {
  /** 是否启用,默认 true */
  enabled?: boolean
  /** 最大文件大小(字节),默认 20MB */
  maxFileSize?: number
  /** 最大文件数量,默认 100 */
  maxFiles?: number
  /** 允许的 MIME 类型列表 */
  allowedMimeTypes?: string[]
  /** 禁止的文件扩展名列表 */
  forbiddenExtensions?: string[]
  /** class-validator 验证选项 */
  validatorOptions?: ValidatorOptions
  /** class-transformer 转换选项 */
  transformOptions?: ClassTransformOptions
}

错误处理

模块会自动抛出以下异常:

  • 文件大小超限BadRequestException: 文件 example.jpg 超过大小限制(最大 20.00MB)
  • 文件数量超限BadRequestException: 最多只能上传 100 个文件
  • 文件类型不允许BadRequestException: 不支持的文件类型: application/x-msdownload,只允许: image/jpeg, image/png
  • 禁止的扩展名BadRequestException: 禁止上传的文件类型: .exe
  • DTO 验证失败BadRequestException: title should not be empty

注意事项

  1. 必须注册 Fastify 插件:在 main.ts 中必须先注册 @fastify/multipart 插件
  2. Buffer 模式:所有文件自动转换为 Buffer,适合小文件上传
  3. 内存限制:大文件上传可能占用较多内存,建议设置合理的 maxFileSize
  4. 全局拦截器:模块使用全局拦截器,只处理带 @Multipart() 装饰器的路由

验证管道与 DTO 转换装饰器

ValidationPipe 验证管道

增强的数据验证管道,支持嵌套对象验证和配置化。

import { ValidationPipe } from '@maxtan/nest-core'
import { APP_PIPE } from '@nestjs/core'

@Module({
  providers: [
    {
      provide: APP_PIPE,
      useClass: ValidationPipe
    }
  ]
})
export class AppModule {}

在控制器中使用:

import { Controller, Post, Body, Query } from '@nestjs/common'
import { ValidationPipe } from '@maxtan/nest-core'

@Controller('users')
export class UserController {
  // 方式1:使用全局配置
  @Post()
  async create(@Body() dto: CreateUserDto) {
    return dto
  }

  // 方式2:单独配置
  @Get()
  async list(
    @Query(new ValidationPipe({ showAllErrors: true })) 
    query: ListDto
  ) {
    return query
  }
}

DTO 字符串转换装饰器

提供了便捷的装饰器用于处理字符串字段的转换,基于 class-transformer@Transform 实现。

EmptyStringToNull

将空字符串转换为 null自动集成 Trim 功能(先去除首尾空格,再判断是否为空):

import { EmptyStringToNull } from '@maxtan/nest-core'
import { IsString, IsOptional } from 'class-validator'

export class UpdateUserDto {
  @EmptyStringToNull()
  @IsOptional()
  description?: string | null
}

// 处理流程:
// '  '         → null
// '  hello  '  → 'hello'
// 'hello'      → 'hello'

Trim

自动去除字符串首尾空格:

import { Trim } from '@maxtan/nest-core'
import { IsString } from 'class-validator'

export class CreateUserDto {
  @Trim()
  @IsString()
  name: string  // '  hello  ' 会被转为 'hello'
}

完整示例:

import { EmptyStringToNull, Trim, PageDto } from '@maxtan/nest-core'
import { IsString, IsOptional, IsInt } from 'class-validator'
import { Type, Expose } from 'class-transformer'

export class UserListDto extends PageDto {
  // 可选字段:空字符串转 null(自动 Trim)
  @Expose()
  @EmptyStringToNull()
  @IsOptional()
  keyword?: string

  // 可选字段:空字符串转 null(自动 Trim)
  @Expose()
  @EmptyStringToNull()
  @IsOptional()
  description?: string | null

  // 必填字段:只需 Trim
  @Expose()
  @Trim()
  @IsString()
  username: string

  // 数字类型:不需要处理空字符串
  @Expose()
  @Type(() => Number)
  @IsInt()
  @IsOptional()
  status?: number
}

使用场景对比:

| 装饰器 | 转换规则 | 适用场景 | |--------|---------|----------| | @EmptyStringToNull() | Trim + 空字符串 → null | 所有可选字符串字段(推荐) | | @Trim() | 去除首尾空格 | 必填字符串字段 |

异常过滤器

异常过滤器会捕获应用中的所有异常,并以统一的格式返回响应,同时记录详细的错误日志:

import { APP_FILTER } from '@nestjs/core'
import { AllExceptionsFilter } from '@maxtan/nest-core'

@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: AllExceptionsFilter
    }
  ]
})
export class AppModule {}

配置参考

AllLoggerOptions

| 参数 | 类型 | 描述 | | ------------ | ------------------ | -------------------------------- | | useConsole | boolean | 是否输出到控制台,默认 true | | maxDays | number | 日志文件保留天数,默认 30 天 | | maxSize | string | 单个日志文件最大大小,默认 '20m' | | sls | SlsAppenderOptions | 阿里云 SLS 配置(可选) |

SlsAppenderOptions

| 参数 | 类型 | 描述 | | ----------------- | ------ | ----------------------------------------------- | | accessKeyId | string | 阿里云访问密钥ID | | accessKeySecret | string | 阿里云访问密钥密码 | | endpoint | string | SLS服务端点,默认'cn-hangzhou.log.aliyuncs.com' | | projectName | string | SLS项目名称 | | logStoreName | string | SLS日志存储名称 | | topic | string | 日志主题,默认'nest-boot' | | source | string | 日志来源,默认'source' | | env | string | 环境标识,默认'production' |

AuthModuleOptions

| 参数 | 类型 | 描述 | | ------------- | -------------- | ---------------------------------------- | | secret | string | 用于签名 JWT 的密钥 | | signOptions | JwtSignOptions | JWT 签名选项,包含过期时间、发行方等配置 |

SnowflakeOptions

| 参数 | 类型 | 描述 | | ---------- | ------------------------ | ---------------------------------------- | | workerId | number | 工作节点ID,用于分布式环境中区分不同节点 | | epoch | number | string | Date | 起始时间,默认为2025-01-01 00:00:00 UTC |

最佳实践

  1. 在启动时配置全局日志:使用 createLogger 函数在应用初始化时配置日志系统,确保所有日志都能被正确捕获。

  2. 使用不同的日志级别:根据信息的重要程度选择合适的日志级别,如调试信息使用 debug,错误信息使用 error 等。

  3. 区分环境配置

    • 开发环境:启用控制台日志,使用较低的日志级别(如 debug)
    • 生产环境:可关闭控制台日志,使用较高的日志级别(如 info 或 warn),启用 SLS 日志
  4. 合理设置日志保留期:根据存储空间和审计要求设置合适的 maxDays 值,默认 30 天,并启用日志压缩。

  5. 错误处理结合异常过滤器:使用 AllExceptionsFilter 统一处理异常,确保所有异常都被正确记录,并带有错误追踪 ID。

  6. 利用阿里云 SLS 进行日志分析:在生产环境中使用阿里云 SLS 进行集中式日志管理和分析,方便问题排查。

  7. 合理使用缓存

    • 对频繁访问但不常变化的数据使用缓存
    • 设置合理的过期时间
    • 使用 setObject/getObject 自动处理 JSON 序列化
    • 利用批量操作(mget/mset)提升性能
  8. 操作日志记录最佳实践

    • 通过 OperationModule.forRoot() 全局启用自动日志记录
    • 使用 excludePaths 排除健康检查、监控等不需要记录的路径
    • 全局不启用 requestresponse,避免日志过大
    • 对重要操作使用 @Operation 装饰器按需启用请求和响应记录
    • 利用自动脱敏保护敏感信息
    • 利用 operationId 关联业务日志和错误堆栈
  9. 授权与认证安全

    • 使用足够长且复杂的 JWT 密钥
    • 设置合理的 Token 过期时间
    • 考虑在生产环境中使用非对称加密算法(RS256)
    • 实现 Token 刷新机制以提高安全性
  10. DTO 字符串处理

  • 可选字符串字段统一使用 @EmptyStringToNull()(自动 Trim)
  • 必填字符串字段使用 @Trim()
  • 配合 @Expose()excludeExtraneousValues: true 保证入参可控
  1. 雪花算法使用建议

    • 在分布式环境中为不同节点设置不同的 workerId
    • 确保各节点的系统时间同步,避免时钟回拨问题
    • 根据业务需求选择合适的起始时间(epoch)
    • 利用实例缓存特性,避免重复创建相同配置的实例
    • 使用加密安全的随机数生成器
  2. 事务管理

    • 使用 @Transactional() 装饰器声明式管理事务
    • 装饰器自动处理事务的开启、提交、回滚和资源释放
    • 在 Service 层使用,不要在 Controller 中使用
    • QueryRunner 自动注入到方法的最后一个参数
    • 异常自动回滚并透传,由全局异常过滤器统一处理
  3. TypeORM 实体设计

    • 继承 BaseEntity 获得标准审计字段(创建时间、更新时间、创建者、更新者、软删除标记)
    • 利用 @BeforeInsert@BeforeUpdate 钩子自动管理时间戳,无需手动设置
    • 时间戳字段(bigint)TypeORM 会自动转换为 number 类型
    • 日期时间字段(datetime/datetime2/timestamp)直接使用 Date 类型,查询时类型安全
    • 响应拦截器会自动将 Date 转换为时间戳返回给前端
    • 使用 deletedAt 实现软删除,保留历史数据(注意:deletedAtnumber 类型)
    • deletedAt 默认不返回(select: false),查询已删除记录时需显式指定
    • 结合拦截器或装饰器自动设置 createByupdateBy 字段
    • 所有审计字段均已建立索引,支持高效查询
  4. 系统配置常量

    • 使用 UPLOAD_CONFIG 统一管理文件上传相关配置
    • 利用内置的 ALLOWED_IMAGE_TYPESALLOWED_DOCUMENT_TYPES 常量
    • 默认启用 FORBIDDEN_EXTENSIONS 黑名单,禁止上传可执行文件
    • 所有默认值统一从常量模块导入,避免硬编码
    • 根据业务需求灵活配置 MIME 类型白名单和扩展名黑名单

许可证

ISC