从客户端发送一个post请求,路径为:/user/login,请求参数为:{userinfo: ‘xx’,password: ‘xx’},到服务器接收请求内容,触发绑定的函数并且执行相关逻辑完毕,然后返回内容给客户端的整个过程大体上要经过如下几个步骤:
项目需要包支持:
npm install --save rxjs xml2js class-validator class-transformer
rxjs 针对JavaScript的反应式扩展,支持更多的转换运算
xml2js 转换xml内容变成json格式
class-validator、class-transformer 管道验证包和转换器
建立user模块:模块内容结构:
user.controller.ts文件
user.module.ts文件
user.service.ts文件
user.login.dto.ts文件
app.module.ts文件
新建shared文件夹里面分别建立对应的文件夹以及文件:
中间件(middleware) — xml.middleware.ts
守卫(guard) — auth.guard.ts
管道(pipe) — validation.pipe.ts
异常过滤器(filters) — http-exception.filter.ts
拦截器(interceptor) — response.interceptor.ts
文件内容对应如下:
http-exception.filter.ts文件
auth.guard.ts文件
response.interceptor.ts文件
xml.middleware.ts文件
validation.pipe.ts文件
核心文件main.ts内容
所有内容已经准备完毕,现在需要开始进行操作了。
本例中:使用中间件让express支持xml请求并且将xml内容转换为json数组
代码如下:
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response } from 'express';
const xml2js = require('xml2js');
const parser = new xml2js.Parser();
@Injectable()
export class XMLMiddleware implements NestMiddleware {
// 参数是固定的Request/Response/next,
// Request/Response/next对应请求体和响应体和下一步函数
use(req: Request, res: Response, next: Function) {
console.log('进入全局xml中间件...');
// 获取express原生请求对象req,找到其请求头内容,如果包含application/xml,则执行转换
if(req.headers['content-type'] && req.headers['content-type'].includes('application/xml')){
// 监听data方法获取到对应的参数数据(这里的方法是express的原生方法)
req.on('data', mreq => {
// 使用xml2js对xml数据进行转换
parser.parseString(mreq,function(err,result){
// 将转换后的数据放入到请求对象的req中
console.log('parseString转换后的数据',result);
// 这里之后可以根据需要对result做一些补充完善
req['body']= result;
})
})
}
// 调用next方法进入到下一个中间件或者路由
next();
}
}
1、全局注册:
在main.ts中导入需要的中间件模块如:XMLMiddleware
然后使用 app.use(new XMLMiddleware().use)即可
2、模块注册:
在对应的模块中注册如:user.module.ts
注意:
同一路由注册多个中间件的执行顺序为,先是全局中间件执行,然后是模块中间件执行,模块中的中间件顺序按照.apply中注册的顺序执行
守卫控制一些权限内容,如:一些接口需要带上token标记,才能够调用,守卫则是对这个标记进行验证操作的。
本例中代码如下:
import {Injectable,CanActivate,HttpException,HttpStatus,ExecutionContext,} from '@nestjs/common';
@Injectable()
export class AuthGuard implements CanActivate {
// context 请求的(Response/Request)的引用
async canActivate(context: ExecutionContext): Promise {
console.log('进入全局权限守卫...');
// 获取请求对象
const request = context.switchToHttp().getRequest();
// 获取请求头中的token字段
const token = context.switchToRpc().getData().headers.token;
// 如果白名单内的路由就不拦截直接通过
if (this.hasUrl(this.urlList, request.url)) {
return true;
}
// 验证token的合理性以及根据token做出相应的操作
if (token) {
try {
// 这里可以添加验证逻辑
return true;
} catch (e) {
throw new HttpException(
'没有授权访问,请先登录',
HttpStatus.UNAUTHORIZED,
);
}
} else {
throw new HttpException(
'没有授权访问,请先登录',
HttpStatus.UNAUTHORIZED,
);
}
};
// 白名单数组
private urlList: string[] = [
'/user/login'
];
// 验证该次请求是否为白名单内的路由
private hasUrl(urlList: string[], url: string): boolean {
let flag: boolean = false;
if (urlList.indexOf(url) >= 0) {
flag = true;
}
return flag;
}
};
1、全局注册:
在main.ts中导入需要的守卫模块如:AuthGuard
然后使用 app.useGlobalGuards(new AuthGuard()) 即可
2、模块注册:
在需要注册的controller控制器中导入AuthGuard
然后从@nestjs/common中导入UseGuards装饰器
最后直接放置在对应的@Controller()或者@Post/@Get…等装饰器之下即可
注意:
同一路由注册多个守卫的执行顺序为,先是全局守卫执行,然后是模块中守卫执行
我想到自定义返回内容如:
{
"statusCode": 400,
"timestamp": "2020-01-14T08:06:45.265Z",
"path": "/user/login",
"message": "请求失败",
"data": {
"isNotIn": "账号不能为空"
}
}
这个时候就可以使用拦截器来做一下处理了。
拦截器作用:
拦截器的执行顺序分为两个部分:
第一个部分在管道和自定义逻辑(next.handle()方法)之前。
第二个部分在管道和自定义逻辑(next.handle()方法)之后。
如:
中间多了个全局管道以及自定义逻辑,即只有路由绑定的函数有正确的返回值之后才会有next.handle()之后的内容
1、全局注册:
在main.ts中导入需要的模块如:ResponseInterceptor
然后使用 app.useGlobalInterceptors(new ResponseInterceptor()) 即可
2、模块注册:
在需要注册的controller控制器中导入ResponseInterceptor
然后从@nestjs/common中导入UseInterceptors装饰器
最后直接放置在对应的@Controller()或者@Post/@Get…等装饰器之下即可
注意:
同一路由注册多个拦截器时候,优先执行模块中绑定的拦截器,然后其拦截器转换的内容将作为全局拦截器的内容,即包裹两次返回内容如:
{ // 全局拦截器效果
"statusCode": 0,
"timestamp": "2020-01-14T08:20:06.159Z",
"path": "/user/login",
"message": "请求成功",
"data": {
"pagenum": 1, // 模块中拦截器包裹效果
“pageSize": 10
"list": []
}
}
管道是请求过程中的第四个内容,主要用于对请求参数的验证和转换操作。
项目中使用class-validator class-transformer进行配合验证相关的输入操作内容
认识官方的三个内置管道
1.ValidationPipe:基于class-validator和class-transformer这两个npm包编写的一个常规的验证管道,可以从class-validator导入配置规则,然后直接使用验证(当前不需要了解ValidationPipe的原理,只需要知道从class-validator引规则,设定到对应字段,然后使用ValidationPipe即可)
2.ParseIntPipe:转换传入的参数为数字
如:传递过来的是/test?id=‘123’"这里会将字符串‘123’转换成数字123
3.ParseUUIDPipe:验证字符串是否是 UUID(通用唯一识别码,百度了解)
如:传递过来的是/test?id=‘xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx’"这里会验证格式是否正确,不正确则抛出错误,否则调用findOne方法
本例中管道使用如下:
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
@Injectable()
export class ValidationPipe implements PipeTransform{
// value 是当前处理的参数,而 metatype 是属性的元类型
async transform(value: any, { metatype }: ArgumentMetadata) {
console.log('进入全局管道...');
if (!metatype || !this.toValidate(metatype)) {
return value;
}
// plainToClass方法将普通的javascript对象转换为特定类的实例
const object = plainToClass(metatype, value);
// 验证该对象返回出错的数组
const errors = await validate(object);
if (errors.length > 0) {
// 将错误信息数组中的第一个内容返回给异常过滤器
let errormsg = errors.shift().constraints;
throw new BadRequestException(errormsg);
}
return value;
}
// 验证属性值的元类型是否是String, Boolean, Number, Array, Object中的一种
private toValidate(metatype: any): boolean {
const types: Function[] = [String, Boolean, Number, Array, Object];
return !types.includes(metatype);
}
}
1、全局注册:
在main.ts中导入需要的模块如:ValidationPipe
然后使用 app.useGlobalPipes(new ValidationPipe()) 即可
2、模块注册:
在需要注册的controller控制器中导入ValidationPipe
然后从@nestjs/common中导入UsePipes装饰器
最后直接放置在对应的@Controller()或者@Post/@Get…等装饰器之下即可,管道还允许注册在相关的参数上如:@Body/@Query… 等
注意:
同一路由注册多个管道的时候,优先执行全局管道,然后再执行模块管道:
简单来讲就是捕获系统抛出的所有异常,然后自定义修改异常内容,抛出友好的提示。
系统提供了不少内置的系统异常类,需要的时候直接使用throw new XXX(描述,状态)这样的方式即可抛出对应的异常,一旦抛出异常,当前请求将会终止。
注意每个异常抛出的状态码有所不同。如:
BadRequestException — 400
UnauthorizedException — 401
ForbiddenException — 403
NotFoundException — 404
NotAcceptableException — 406
RequestTimeoutException — 408
ConflictException — 409
GoneException — 410
PayloadTooLargeException — 413
UnsupportedMediaTypeException — 415
UnprocessableEntityException — 422
InternalServerErrorException — 500
NotImplementedException — 501
BadGatewayException — 502
ServiceUnavailableException — 503
GatewayTimeoutException — 504
本例中使用的是自定义的异常类,代码如下:
import { ExceptionFilter, Catch, ArgumentsHost, HttpException,Logger,HttpStatus } from '@nestjs/common';
import { Request, Response } from 'express';
@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
// exception 当前正在处理的异常对象
// host 是传递给原始处理程序的参数的一个包装(Response/Request)的引用
catch(exception: HttpException, host: ArgumentsHost) {
console.log('进入全局异常过滤器...');
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
// HttpException 属于基础异常类,可自定义内容
// 如果是自定义的异常类则抛出自定义的status
// 否则就是内置HTTP异常类,然后抛出其对应的内置Status内容
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
// 抛出错误信息
const message =
exception.message ||
exception.message.message ||
exception.message.error ||
null;
let msgLog = {
statusCode: status, // 系统错误状态
timestamp: new Date().toISOString(), // 错误日期
path: request.url, // 错误路由
message: '请求失败',
data: message // 错误消息内容体(争取和拦截器中定义的响应体一样)
}
// 打印错误综合日志
Logger.error(
'错误信息',
JSON.stringify(msgLog),
'HttpExceptionFilter',
);
response
.status(status)
.json(msgLog);
}
}
1、全局注册:
在main.ts中导入需要的模块如:HttpExceptionFilter
然后使用 app.useGlobalFilters(new HttpExceptionFilter()) 即可
2、模块注册:
在需要注册的controller控制器中导入HttpExceptionFilter
然后从@nestjs/common中导入UseFilters装饰器
最后直接放置在对应的@Controller()或者@Post/@Get…等装饰器之下即可
注意:
同一路由注册多个管道的时候,只会执行一个异常过滤器,优先执行模块中绑定的异常过滤器,如果模块中无绑定异常过滤则执行全局异常过滤器