2025/03/01

NestJS AOP

深入理解 NestJS AOP 面向切面编程的核心概念与实战应用

MVC 架构流程

在 MVC 架构下,请求都会发送给 Controller,由它来调度 Model 层的 service,完成对应的业务逻辑:

Controller → Model [ Service、Repository ] → View

AOP(面向切面编程)

NestJS 提供了 AOP(Aspect Oriented Programming,面向切面编程) 的能力。

什么是 AOP?

请求发过来,会经过 Controller(控制器)、Service(服务)、Repository(数据库)访问的逻辑。如果想在这个逻辑中加入一些通用型的代码,比如在 Controller 调用之前和之后加入一个执行通用逻辑的阶段,就需要使用 AOP。


一、Middleware(中间件)

1.1 全局中间件

// main.ts
import {NestFactory} from '@nestjs/core'
import {NextFunction} from 'express'
import {AppModule} from './app.module'

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

    app.use((req: Request, res: Response, next: NextFunction) => {
        console.log('before')
        next()
        console.log('after')
    })

    await app.listen(process.env.PORT ?? 3000)
}

bootstrap()

访问示例输出:

// before
// after

Handler 代码:

import {Controller, Get} from '@nestjs/common'
import {AppService} from './app.service'

@Controller()
export class AppController {
    constructor(private readonly appService: AppService) {
    }

    @Get()
    getHello(): string {
        console.log('handler...')
        return this.appService.getHello()
    }

    @Get('aaa')
    aaa() {
        console.log('aaa')
        return 'aaa'
    }

    @Get('bbb')
    bbb() {
        console.log('bbb')
        return 'bbb'
    }
}

输出结果:

// before /
// handler...
// after
// before /aaa
// aaa
// after
// before /bbb
// bbb
// after

1.2 路由中间件

除了全局中间件,NestJS 还支持路由中间件。

生成中间件:

nest g middleware log --no-spec --flat
参数说明
--no-spec不生成单元测试文件
--flat不生成单独的目录

配置路由中间件:

// app.module.ts
import {MiddlewareConsumer, Module, NestModule} from '@nestjs/common'
import {AppController} from './app.controller'
import {AppService} from './app.service'
import {LogMiddleware} from './log.middleware'

@Module({
    imports: [],
    controllers: [AppController],
    providers: [AppService],
})
export class AppModule implements NestModule {
    configure(consumer: MiddlewareConsumer) {
        consumer.apply(LogMiddleware).forRoutes('aaa*')
    }
}

访问结果:

// before /bbb
// bbb
// after
// before /aaa
// before middleware
// aaa
// after middleware
// after

只有在访问 aaa 开头的路由时,中间件才会生效。


二、Guard(守卫)

Guard 是路由守卫的意思,可以在调用 Controller 之前判断权限,返回 truefalse 来决定是否放行。

生成 Guard:

nest g guard login --no-spec --flat

使用示例:

import {Controller, Get, UseGuards} from '@nestjs/common'

@Controller()
export class AppController {
    @Get('aaa')
    @UseGuards(LoginGuard) // 使用权限判断
    aaa() {
        console.log('aaa')
        return 'aaa'
    }
}

2.1 全局 Guard

像中间件一样,Guard 也可以全局使用:

// main.ts
import {LoginGuard} from './login.guard'

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

    app.use((req: Request, res: Response, next: NextFunction) => {
        console.log('before', req.url)
        next()
        console.log('after')
    })

    app.useGlobalGuards(new LoginGuard())

    await app.listen(process.env.PORT ?? 3000)
}

三、Interceptor(拦截器)

拦截器可以在目标 Controller 方法后加入一些逻辑。

生成 Interceptor:

nest g interceptor time --no-spec --flat

使用方式和 Guard 差不多。

3.1 Interceptor 与 Guard 的区别

特性GuardInterceptorMiddleware
Metadata 支持✅ 支持✅ 支持❌ 不支持
作用范围路由/全局路由/全局路由/全局
放行时机Controller 之前Controller 之后/响应前请求最外层

注意:

  • 可以在 Controller 和 Handler 上添加 Metadata,这种数据只能在 Interceptor 或 Guard 里取出,Middleware 不行
  • 支持在每个路由单独启用,只作用于某一个 Handler

四、Pipe(管道)

Pipe 主要用于参数验证和转换。

生成 Pipe:

nest g pipe validate --no-spec --flat

4.1 自定义参数验证 Pipe

// validate.pipe.ts
import {
    ArgumentMetadata,
    BadRequestException,
    Injectable,
    PipeTransform,
} from '@nestjs/common'

@Injectable()
export class ValidatePipe implements PipeTransform {
    transform(value: any, metadata: ArgumentMetadata) {
        // 这里的 value 就是传入的参数
        // 如果不能转成数字,就返回参数错误,否则乘 10 再传入 handler
        if (Number.isNaN(Number.parseInt(value))) {
            throw new BadRequestException(`参数 ${metadata.data} 错误`)
        }
        return typeof value === 'number' ? value * 10 : Number.parseInt(value) * 10
    }
}

使用示例:

import {Controller, Get, Query} from '@nestjs/common'
import {ValidatePipe} from './validate.pipe'

@Controller()
export class AppController {
    @Get('ccc')
    ccc(@Query('num', ValidatePipe) num: number) {
        return num + 1
    }
}

效果: 当参数错误时,会返回 400 的错误响应。

4.2 内置 Pipe

NestJS 提供了多种内置 Pipe:

Pipe功能
ValidationPipe数据验证
ParseIntPipe转换为整数
ParseFloatPipe转换为浮点数
ParseBoolPipe转换为布尔值
ParseArrayPipe转换为数组
ParseUUIDPipe验证 UUID
ParseEnumPipe验证枚举值

Pipe 同样可以对某个参数生效,也能对全局生效。


五、ExceptionFilter(异常过滤器)

NestJS 提供了 ExceptionFilter 来支持对抛出的异常做统一处理。

例如 throw new BadRequestException() 就是使用了异常处理。

生成 ExceptionFilter:

nest g filter test --no-spec --flat

使用示例:

// test.filter.ts
import {
    ArgumentsHost,
    BadRequestException,
    Catch,
    ExceptionFilter,
} from '@nestjs/common'
import {Response} from 'express'

@Catch(BadRequestException)
export class TestFilter implements ExceptionFilter {
    catch(exception: BadRequestException, host: ArgumentsHost) {
        const response: Response = host.switchToHttp().getResponse()
        response.status(400).json({
            statusCode: 400,
            message: `400${exception.message}`,
        })
    }
}

效果: 这样处理后,异常项返回的响应就统一被格式化了。


AOP 执行顺序总结

Request
Middleware (中间件)
Guard (守卫) → 返回 false 则阻断
Interceptor (拦截器) - Before
Pipe (管道) - 参数验证/转换
Controller Handler
Interceptor (拦截器) - After
ExceptionFilter (异常过滤器) - 如有异常
Response

组件对比表

组件主要作用执行时机
Middleware请求预处理、日志记录等请求最外层
Guard权限验证Controller 之前
Interceptor响应转换、日志记录等Controller 前后
Pipe参数验证、转换Handler 调用前
ExceptionFilter统一异常处理发生异常时