文章问题导向
如何使用管道?
如何使用守卫?
如何使用拦截器?
如何使用过滤器?
如何使用中间件?
如果你都有了答案,可以忽略本文章,或去nest学习导图寻找更多答案
分别作用
管道:数据处理与转换,数据验证
守卫:验证用户登陆,保护路由
拦截器:对请求响应进行拦截,统一响应内容
过滤器:异常捕获
中间件:日志打印
装饰器:获取数据,实现守卫
更多使用方式,请了解执行时机再选用适用的方式
执行顺序(时机)
使用(注册)方式
两种使用方式:局部,全局
两种使用类型:内置,自定义
全局使用: 管道 - 守卫 - 拦截器 - 过滤器 - 中间件
统一在main.ts文件中使用,全局生效
import { NestFactory } from '@nestjs/core';
import { ParseIntPipe } from '@nestjs/common';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './common/filters/http-exception.filter';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { AuthGuard } from './common/guard/auth.guard';
import { AuthInterceptor } from './common/interceptors/auth.interceptor';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
//全局使用管道:这里使用的是内置,也可以使用自定义管道,在下文
app.useGlobalPipes(new ParseIntPipe());
//全局使用中间件
app.use(LoggerMiddleware)
//全局使用过滤器
//这里使用的是自定义过滤器,先别管,先学会怎么在全局使用
app.useGlobalFilters(new HttpExceptionFilter());
//全局使用守卫
app.useGlobalGuards(new AuthGuard());
//全局使用拦截器
app.useGlobalInterceptors(new AuthInterceptor());
await app.listen(3000);
}
bootstrap();
管道
内置管道
常用内置管道,从@nestjs/common导出
ParseIntPipe:将字符串数字转数字
ValidationPipe:验证管道
更多待更新
局部使用管道
局部有三种使用方式
- 匹配整个路径,使用UseFilters
- 只匹配某个接口,使用UseFilters
- 在获取参数时匹配,一般使用内置管道
例子
import {
Controller,
Get,
Put,
Body,
Param,
UsePipes,
ParseIntPipe
} from '@nestjs/common';
import { myPipe } from '../../common/pipes/user.pipe';
@Controller('user')
@UsePipes(new myPipe()) //局部方式1:匹配整个/user, get请求和put请求都会命中
export class UserController {
@Get(':id')
getUserById(@Param('id', new ParseIntPipe()) id) { //局部方式3:只匹配/user的get请求,使用的是内置管道
console.log('user', typeof id);
return id;
}
@Put(':id')
@UsePipes(new myPipe()) //局部方式2:只匹配/user的put请求
updateUser(@Body() user, @Param('id') id) {
return {
user,
id,
};
}
}
自定义管道
使用快捷命令生成:nest g pi myPipe common/pipes
import {
ArgumentMetadata,
Injectable,
PipeTransform,
BadRequestException,
} from '@nestjs/common';
//自定义管道必须实现自PipeTransform,固定写法,该接口有一个transform方法
//transform参数:
//value:使用myPipe时所传递的值,可以是param传递的的查询路径参数,可以是body的请求体
//metadata:元数据,可以用它判断是来自param或body或query
@Injectable()
export class myPipe implements PipeTransform<string> {
transform(value: string, metadata: ArgumentMetadata) {
if (metadata.type === 'body') {
console.log('来自请求体', value);
}
if (metadata.type === 'param') {
console.log('来自查询路径', value);
const val = parseInt(value, 10);
//如果不是传递一个数字,抛出错误
if (isNaN(val)) {
throw new BadRequestException('Validation failed');
}
return val;
}
return value;
}
}
中间件
局部使用中间件
注意: 需要在使用模块的module中注册
import { Module, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middlerware';
import { UserModule } from './modules/user/user.module';
@Module({
imports:[ UserModule ]
})
export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware) //应用中间件
.exclude({ path: 'user', method: RequestMethod.POST }) //排除user的post方法
.forRoutes('user'); //监听路径 参数:路径名或*,*是匹配所以的路由
// .forRoutes({ path: 'user', method: RequestMethod.POST }, { path: 'album', method: RequestMethod.ALL }); //多个
// .apply(UserMiddleware) //支持多个中间件
// .forRoutes('user')
}
}
自定义中间件
使用快捷命令生成:nest g mi logger common/middleware
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response } from 'express';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
//req:请求参数
//res:响应参数
//next:执行下一个中件间
use(req: Request, res: Response, next: () => void) {
const { method, path } = req;
console.log(`${method} ${path}`);
next();
}
}
过滤器
局部使用过滤器
import {
Controller,
Get,
UseFilters,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { HttpExceptionFilter } from '../../common/filters/http-exception.filter';
//局部使用过滤器
@UseFilters(new HttpExceptionFilter())
@Controller('/user')
export class ExceptionController {
@Get()
getUserById(@Query() { id }): string {
if (!id) {
throw new HttpException(
{
status: HttpStatus.BAD_REQUEST,
message: '请求参数id 必传',
error: 'id is required',
},
HttpStatus.BAD_REQUEST,
);
}
return 'hello error';
}
}
自定义过滤器
使用快捷命令生成:nest g f myFilter common/filters
import {
ArgumentsHost,
Catch,
ExceptionFilter,
HttpException,
} from '@nestjs/common';
//必须实现至ExceptionFilter,固定写法,该接口只有一个catch方法
//catch方法参数:
//exception:当前正在处理的异常对象
//host:传递给原始处理程序的参数的一个包装(Response/Request)的引用
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter<HttpException> {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const status = exception.getStatus(); //获取状态码
const exceptionRes: any = exception.getResponse(); //获取响应对象
const { error, message } = exceptionRes;
//自定义的异常响应内容
const msgLog = {
status,
timestamp: new Date().toISOString(),
path: request.url,
error,
message,
};
response.status(status).json(msgLog);
}
}
守卫
自定义守卫
使用快捷命令生成:nest g gu myGuard common/guards
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core'; //反射器,作用与自定义装饰器桥接,获取数据
//自定义守卫必须CanActivate,固定写法,该接口只有一个canActivate方法
//canActivate参数:
//context:请求的(Response/Request)的引用
//通过守卫返回true,否则返回false,返回403状态码
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private readonly reflector: Reflector) { }
// 白名单数组
private whiteUrlList: string[] = ['/user'];
// 验证该次请求是否为白名单内的路由
private isWhiteUrl(urlList: string[], url: string): boolean {
if (urlList.includes(url)) {
return true;
}
return false;
}
canActivate(context: ExecutionContext): boolean {
// 获取请求对象
const request = context.switchToHttp().getRequest();
//console.log('request', request.headers);
//console.log('request', request.params);
//console.log('request', request.query);
//console.log('request', request.url);
// 用法一:验证是否是白名单内的路由
if (this.isWhiteUrl(this.whiteUrlList, request.url)) {
return true;
} else {
return false;
}
// 用法二:使用反射器,配合装饰器使用,获取装饰器传递过来的数据
const roles = this.reflector.get<string[]>('roles', context.getHandler());
//console.log(roles); // [ 'admin' ]
//http://localhost:3000/user/9?user=admin,如果与装饰器传递过来的值匹配则通过,否则不通过
//真实开发中可能从cookie或token中获取值
const { user } = request.query;
if (roles.includes(user)) {
return true;
} else {
return false;
}
// 其他用法
// 获取请求头中的token字段
const token = context.switchToRpc().getData().headers.token;
// console.log('token', token);
// 获取session
const userinfo = context.switchToHttp().getRequest().session;
// console.log('session', userinfo);
return true;
}
}
自定义装饰器
自定义守卫中使用到了自定义装饰器
nest g d role common/decorator
//这是快捷生成的代码
import { SetMetadata } from '@nestjs/common';
//SetMetadata作用:将获取到的值,设置到元数据中,然后守卫通过反射器才能获取到值
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
局部使用守卫
import {
Controller,
Get,
Delete,
Param,
UsePipes,
UseGuards,
ParseIntPipe,
} from '@nestjs/common';
import { AuthGuard } from '../../common/guard/auth.guard';
import { Role } from '../../common/decorator/role.decorator'; //自定义装饰器
@UseGuards(AuthGuard) //局部使用守卫,守卫整个user路径
@Controller('user')
export class UserController {
@Get(':id')
getUserById(@Param('id', new ParseIntPipe()) id) {
console.log('user', typeof id);
return id;
}
@Delete(':id')
@Role('admin') //使用自定义装饰器,传入角色,必须是admin才能删除
removeUser(@Param('id') id) {
return id;
}
}
拦截器
自定义拦截器
使用快捷命令生成:nest g in auth common/intercepters
在全局使用
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';
//自定义拦截器必须实现自NestInterceptor,固定写法,该接口只有一个intercept方法
//intercept参数:
//context:请求上下文,可以拿到的Response和Request
@Injectable()
export class AuthInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
console.log('拦截器', request.url);
return next.handle().pipe(
map((data) => {
console.log('全局响应拦截器方法返回内容后...');
return {
status: 200,
timestamp: new Date().toISOString(),
path: request.url,
message: '请求成功',
data: data,
};
}),
);
}
}
学习更多