Nest AOP 面向切面编程 - 像 Vue 一样简单

56 阅读4分钟

看完这篇,如果还不懂 Nest 的 AOP,欢迎来评论区锤我 😄


1. 先说 Vue 里的"切面"

你有没有觉得 Vue 和 Nest 很多地方挺像的?

今天就着用 Vue 的方式,聊聊 Nest 的 AOP(面向切面编程)

先回忆一下 Vue 里的这几个常见场景:

场景一:路由守卫

// Vue Router 路由守卫
router.beforeEach((to, from, next) => {
  console.log('开始跳转');
  next();
});

每次页面跳转,都会先经过这个"拦截器",可以在这里做登录判断、权限校验。

场景二:axios 拦截器

// 请求拦截器
axios.interceptors.request.use(config => {
  config.headers.Authorization = 'Bearer token';
  return config;
});

// 响应拦截器
axios.interceptors.response.use(response => {
  return response.data;
});

发起请求前统一加 token,响应后统一处理数据格式。

场景三:全局错误处理

// Vue 全局错误捕获
Vue.config.errorHandler = (err, vm, info) => {
  console.error('全局错误:', err);
};

无论哪个组件报错,都能统一捕获处理。


看完这些例子,你会发现:这些场景都有一个共同特点 ——

不修改业务代码,却在业务代码的"前后"偷偷干了点别的事。

这就是 AOP(面向切面编程) 的核心思想。


2. 什么是 AOP?

用一个图来表示:

请求进来 → [切面1][切面2] → 业务逻辑 → [切面3] → 响应回去

就像切豆腐一样,在业务逻辑的横切面上添加通用逻辑。

AOP 的好处

  1. 不污染业务代码 - 日志、权限、异常处理等逻辑与业务分离
  2. 复用性强 - 写一次,所有地方都能用
  3. 动态可插拔 - 想要就加,不想要就删

3. Nest 的 5 种"切面"

Nest 实现了 5 种 AOP 方式,用 Vue 来类比,简直一模一样:

NestVue 类比作用
Middlewarerouter.beforeEach请求进入前、响应后
Guard路由守卫权限判断,是否放行
Interceptoraxios 拦截器请求前后统一处理
Pipefilters / computed参数校验、转换
ExceptionFiltererrorHandler全局异常处理

4. 逐个讲,保证懂

① Middleware - 就像 Vue Router 的 beforeEach

作用:在请求进入 Controller 之前、响应返回之后执行逻辑

// Nest Middleware
@Injectable()
export class LogMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: () => void) {
    console.log('请求前:', req.url);
    next();  // 放行
    console.log('响应后');
  }
}

Vue 对比

// Vue Router
router.beforeEach((to, from, next) => {
  console.log('跳转前:', to.path);
  next();
});

使用方式

// 在 Module 中配置
configure(consumer: MiddlewareConsumer) {
  consumer.apply(LogMiddleware).forRoutes('*');  // 所有路由
}

② Guard - 就像 Vue 的登录判断

作用:判断是否有权限访问某个路由,返回 true 放行,false 拒绝

// Nest Guard
@Injectable()
export class LoginGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    return request.session?.user != null;  // 有登录态就放行
  }
}

Vue 对比

// Vue Router 导航守卫
router.beforeEach((to, from, next) => {
  if (to.meta.requiresAuth && !isLoggedIn) {
    next('/login');  // 没登录,跳转登录页
  } else {
    next();
  }
});

使用方式

// 用在某个 Controller 上
@Controller('user')
@UseGuards(LoginGuard)
export class UserController {}

③ Interceptor - 就像 axios 的拦截器

作用:在 Controller 方法执行前后统一处理,比如计时、统一返回格式

// Nest Interceptor
@Injectable()
export class TimeInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const start = Date.now();

    return next.handle().pipe(
      tap(() => console.log(`耗时: ${Date.now() - start}ms`))
    );
  }
}

Vue 对比

// axios 请求/响应拦截器
axios.interceptors.request.use(config => {
  const start = Date.now();
  config.metadata = { start };
  return config;
});

axios.interceptors.response.use(response => {
  const duration = Date.now() - response.config.metadata.start;
  console.log(`请求耗时: ${duration}ms`);
  return response;
});

使用方式

@Controller('user')
@UseInterceptors(TimeInterceptor)
export class UserController {
  @Get()
  getUser() {}
}

④ Pipe - 就像 Vue 的 computed / filters

作用:对请求参数进行校验转换

// Nest Pipe
@Injectable()
export class ValidatePipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    if (isNaN(value)) {
      throw new BadRequestException('参数必须是数字');
    }
    return Number(value);  // 转换为数字
  }
}

Vue 对比

// Vue 2 filters
filters: {
  parseNumber(value) {
    return Number(value);
  }
}

// Vue 3 computed
const num = computed(() => Number(props.value));

使用方式

@Get(':id')
getUser(@Param('id', ParseIntPipe) id: number) {
  return id + 1;  // 自动转成数字
}

Nest 还内置了很多 Pipe:

  • ParseIntPipe - 转数字
  • ParseBoolPipe - 转布尔值
  • ValidationPipe - 校验对象

⑤ ExceptionFilter - 就像 Vue 的 errorHandler

作用:统一捕获和处理异常

// Nest ExceptionFilter
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const response = host.switchToHttp().getResponse();
    response.status(exception.getStatus()).json({
      message: exception.getResponse()
    });
  }
}

Vue 对比

// Vue 全局错误处理
Vue.config.errorHandler = (err, vm, info) => {
  console.error('捕获到错误:', err);
  // 上报错误到服务器
  reportError(err);
};

使用方式

@Controller()
@UseFilters(HttpExceptionFilter)
export class AppController {}

5. 一张图总结

请求进来
   │
   ▼
┌──────────────────────────────────────┐
│  Middleware(请求前后)               │  ← 像 Vue router.beforeEach
└──────────────────────────────────────┘
   │
   ▼
┌──────────────────────────────────────┐
│  Guard(权限判断)                   │  ← 像 Vue 路由守卫
└──────────────────────────────────────┘
   │
   ▼
┌──────────────────────────────────────┐
│  Pipe(参数校验/转换)               │  ← 像 Vue filters
└──────────────────────────────────────┘
   │
   ▼
┌──────────────────────────────────────┐
│  Controller(业务逻辑)               │
└──────────────────────────────────────┘
   │
   ▼
┌──────────────────────────────────────┐
│  Interceptor(统一处理响应)          │  ← 像 axios 拦截器
└──────────────────────────────────────┘
   │
   ▼
┌──────────────────────────────────────┐
│  ExceptionFilter(异常处理)         │  ← 像 Vue errorHandler
└──────────────────────────────────────┘
   │
   ▼
  响应

6. 为什么要用 AOP?

想象一下如果没有 AOP:

@Controller('user')
export class UserController {
  @Get()
  getUser() {
    // 1. 写日志
    console.log('开始请求');

    // 2. 检查权限
    if (!isLogin) throw new UnauthorizedException();

    // 3. 校验参数
    if (!id) throw new BadRequestException();

    // 4. 业务逻辑
    const user = this.userService.findOne();

    // 5. 统一返回格式
    return { code: 0, data: user };

    // 6. 捕获异常
    try {
      // ...
    } catch (e) {
      return { code: -1, message: e.message };
    }
  }
}

乱不乱?累不累?

用了 AOP 之后:

@Controller('user')
@UseGuards(LoginGuard)           // 权限判断
@UseInterceptors(ResponseInterceptor)  // 统一响应格式
@UseFilters(HttpExceptionFilter)  // 异常处理
export class UserController {
  @Get()
  getUser(@Query('id', ParseIntPipe) id: number) {  // 参数校验
    return this.userService.findOne(id);  // 只需要写业务逻辑!
  }
}

爽不爽?


7. 总结

概念Vue 类比Nest 用途
Middlewarerouter.beforeEach请求前后打印日志
Guard路由守卫登录判断、权限校验
Pipefilters / computed参数校验、类型转换
Interceptoraxios 拦截器统一响应格式、计时
ExceptionFiltererrorHandler全局异常处理

一句话总结 AOP

就像 Vue 的路由守卫和 axios 拦截器一样,在不修改业务代码的情况下,偷偷在前后加戏

Nest 把这套玩得更全面,5 种切面各司其职,让业务代码保持纯粹。