看完这篇,如果还不懂 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 的好处:
- 不污染业务代码 - 日志、权限、异常处理等逻辑与业务分离
- 复用性强 - 写一次,所有地方都能用
- 动态可插拔 - 想要就加,不想要就删
3. Nest 的 5 种"切面"
Nest 实现了 5 种 AOP 方式,用 Vue 来类比,简直一模一样:
| Nest | Vue 类比 | 作用 |
|---|---|---|
| Middleware | router.beforeEach | 请求进入前、响应后 |
| Guard | 路由守卫 | 权限判断,是否放行 |
| Interceptor | axios 拦截器 | 请求前后统一处理 |
| Pipe | filters / computed | 参数校验、转换 |
| ExceptionFilter | errorHandler | 全局异常处理 |
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 用途 |
|---|---|---|
| Middleware | router.beforeEach | 请求前后打印日志 |
| Guard | 路由守卫 | 登录判断、权限校验 |
| Pipe | filters / computed | 参数校验、类型转换 |
| Interceptor | axios 拦截器 | 统一响应格式、计时 |
| ExceptionFilter | errorHandler | 全局异常处理 |
一句话总结 AOP:
就像 Vue 的路由守卫和 axios 拦截器一样,在不修改业务代码的情况下,偷偷在前后加戏。
Nest 把这套玩得更全面,5 种切面各司其职,让业务代码保持纯粹。