@maxtan/nest-core
v1.10.5
Published
A powerful NestJS boot core library with authentication, caching, validation, and logging utilities
Maintainers
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 实现自动轮转:
按大小轮转:当单个文件达到
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个文件(已压缩)按日期轮转:每天午夜自动创建新文件
logs/default-2024-01-01.log.gz logs/default-2024-01-02.log.gz logs/default-2024-01-03.log // 当前日期自动压缩:旧文件自动压缩为
.gz格式,节省磁盘空间自动清理:超过
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 装饰器是可选的,即使不使用装饰器,拦截器也会自动记录所有控制器方法的日志。装饰器主要用于:
- 添加操作描述
- 禁用特定方法的日志记录
- 为特定方法配置是否记录请求和响应
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字段名。系统会优先使用标准字段 sub 和 userId,如果不存在则使用此字段从 JWT payload 中获取 |
| nameField | string | 'name' | 用户名字段名。系统会优先使用 username,如果不存在则使用此字段从 JWT payload 中获取 |
OperationOptions (装饰器配置)
| 选项 | 类型 | 默认值 | 说明 |
| ------------- | ------- | ------- | ---------------------------------- |
| description | string | - | 操作描述,添加到日志中 |
| disabled | boolean | false | 是否禁用此方法的日志记录 |
| request | boolean | - | 是否记录请求参数(优先于全局配置) |
| response | boolean | - | 是否记录响应结果(优先于全局配置) |
注意:装饰器中的
request和response配置优先级高于模块全局配置。
用户信息提取规则
操作日志会自动从 JWT token 中提取用户信息(如果存在认证)。为了兼容不同的 JWT payload 结构,系统使用以下优先级规则:
用户 ID 提取优先级(从高到低):
payload.sub- JWT 标准字段payload.userId- 常用的用户ID字段payload[idField]- 自定义字段(由idField配置指定,默认'id')
用户名提取优先级(从高到低):
payload.username- 常用的用户名字段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) |
错误日志关联
当操作失败时,会输出两条日志:
- 操作日志:包含业务上下文和错误信息(不含堆栈)
- 堆栈信息:由异常过滤器输出(仅 500+ 错误或需要调试的异常)
两条日志通过 operationId 关联,可以快速定位问题:
# 查看某个操作的所有相关日志
grep "1234567890" app.log安全特性
- 自动脱敏 - 自动隐藏敏感字段:
password、token、secret、accessToken、refreshToken、apiKey - 异步记录 - 异步记录日志,不影响请求性能
- 唯一追踪 - 每个操作生成唯一
operationId,便于日志追踪和关联 - 堆栈分离 - 错误堆栈与操作日志分离,通过
operationId关联,避免日志冗余 - 灵活配置 - 支持全局配置和方法级配置,装饰器配置优先级更高
授权模块
授权模块提供了基于 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'
// 更多自定义配置...
})
}
}支持的自定义选项:
- Payload 结构:任意自定义字段,
AuthPayload类型支持索引签名 - 加密算法:支持 HS256、HS384、HS512、RS256、RS384、RS512 等
- 签名密钥:支持对称密钥和 RSA 非对称密钥
- 过期策略:自定义
expiresIn、notBefore等 - 多租户支持:通过
iss、aud字段实现
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 }
}
}重要说明
必须注入 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)异常处理
装饰器会自动回滚事务并透传异常,由全局异常过滤器统一处理:
@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 装饰器设计哲学
- ✅ 更易于维护和测试
注意事项
不要在控制器中使用
事务装饰器应该使用在 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) { // ... } }调用方法时不需要传入 QueryRunner
// 在 Controller 或其他 Service 中调用 await this.userService.createUser(userData) // 不需要传 qr嵌套事务
如果需要在一个事务方法中调用另一个事务方法,需要传递 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() }特点:
- ✅ 智能清理:自动过滤
null、undefined、空字符串、'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
}使用场景:
ValidationPipe 场景(默认配置):
// 只过滤 null 和 undefined,保留有效的空字符串 // 空字符串需配合 @EmptyStringToNull 装饰器使用 return cleanObject(object)TypeORM 查询场景(完整过滤):
// buildWhere 和 applyLike 内部已使用完整过滤 const where = buildWhere(query, User) const likeQuery = applyLike(query, ['name'], 'start')自定义场景:
// 只需指定需要改变的选项 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 Serverdatetime2、PostgreSQLtimestamp等多种数据库类型 - 自动处理空值(
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']验证机制:
- MIME 类型验证:检查文件的
mimetype是否在白名单中 - 扩展名验证:检查文件扩展名是否在黑名单中(默认禁止可执行文件)
- 自动拦截:不符合规则的文件会立即抛出
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
注意事项
- 必须注册 Fastify 插件:在
main.ts中必须先注册@fastify/multipart插件 - Buffer 模式:所有文件自动转换为 Buffer,适合小文件上传
- 内存限制:大文件上传可能占用较多内存,建议设置合理的
maxFileSize - 全局拦截器:模块使用全局拦截器,只处理带
@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 |
最佳实践
在启动时配置全局日志:使用
createLogger函数在应用初始化时配置日志系统,确保所有日志都能被正确捕获。使用不同的日志级别:根据信息的重要程度选择合适的日志级别,如调试信息使用 debug,错误信息使用 error 等。
区分环境配置:
- 开发环境:启用控制台日志,使用较低的日志级别(如 debug)
- 生产环境:可关闭控制台日志,使用较高的日志级别(如 info 或 warn),启用 SLS 日志
合理设置日志保留期:根据存储空间和审计要求设置合适的
maxDays值,默认 30 天,并启用日志压缩。错误处理结合异常过滤器:使用 AllExceptionsFilter 统一处理异常,确保所有异常都被正确记录,并带有错误追踪 ID。
利用阿里云 SLS 进行日志分析:在生产环境中使用阿里云 SLS 进行集中式日志管理和分析,方便问题排查。
合理使用缓存:
- 对频繁访问但不常变化的数据使用缓存
- 设置合理的过期时间
- 使用
setObject/getObject自动处理 JSON 序列化 - 利用批量操作(
mget/mset)提升性能
操作日志记录最佳实践:
- 通过
OperationModule.forRoot()全局启用自动日志记录 - 使用
excludePaths排除健康检查、监控等不需要记录的路径 - 全局不启用
request和response,避免日志过大 - 对重要操作使用
@Operation装饰器按需启用请求和响应记录 - 利用自动脱敏保护敏感信息
- 利用
operationId关联业务日志和错误堆栈
- 通过
授权与认证安全:
- 使用足够长且复杂的 JWT 密钥
- 设置合理的 Token 过期时间
- 考虑在生产环境中使用非对称加密算法(RS256)
- 实现 Token 刷新机制以提高安全性
DTO 字符串处理:
- 可选字符串字段统一使用
@EmptyStringToNull()(自动 Trim) - 必填字符串字段使用
@Trim() - 配合
@Expose()和excludeExtraneousValues: true保证入参可控
雪花算法使用建议:
- 在分布式环境中为不同节点设置不同的 workerId
- 确保各节点的系统时间同步,避免时钟回拨问题
- 根据业务需求选择合适的起始时间(epoch)
- 利用实例缓存特性,避免重复创建相同配置的实例
- 使用加密安全的随机数生成器
事务管理:
- 使用
@Transactional()装饰器声明式管理事务 - 装饰器自动处理事务的开启、提交、回滚和资源释放
- 在 Service 层使用,不要在 Controller 中使用
- QueryRunner 自动注入到方法的最后一个参数
- 异常自动回滚并透传,由全局异常过滤器统一处理
- 使用
TypeORM 实体设计:
- 继承
BaseEntity获得标准审计字段(创建时间、更新时间、创建者、更新者、软删除标记) - 利用
@BeforeInsert和@BeforeUpdate钩子自动管理时间戳,无需手动设置 - 时间戳字段(
bigint)TypeORM 会自动转换为number类型 - 日期时间字段(
datetime/datetime2/timestamp)直接使用Date类型,查询时类型安全 - 响应拦截器会自动将
Date转换为时间戳返回给前端 - 使用
deletedAt实现软删除,保留历史数据(注意:deletedAt为number类型) deletedAt默认不返回(select: false),查询已删除记录时需显式指定- 结合拦截器或装饰器自动设置
createBy和updateBy字段 - 所有审计字段均已建立索引,支持高效查询
- 继承
系统配置常量:
- 使用
UPLOAD_CONFIG统一管理文件上传相关配置 - 利用内置的
ALLOWED_IMAGE_TYPES、ALLOWED_DOCUMENT_TYPES常量 - 默认启用
FORBIDDEN_EXTENSIONS黑名单,禁止上传可执行文件 - 所有默认值统一从常量模块导入,避免硬编码
- 根据业务需求灵活配置 MIME 类型白名单和扩展名黑名单
- 使用
许可证
ISC
