接着上一篇记录一下学习笔记
管道
管道是具有 @Injectable() 装饰器的类。管道应实现 PipeTransform 接口。
- 转换:管道将输入数据转换为所需的数据输出
- 验证:对输入数据进行验证,如果验证成功继续传递; 验证失败则抛出异常;
管道转换
NestJS 有一些内置管道可用
ValidationPipeParseIntPipeParseBoolPipeParseArrayPipeParseUUIDPipeDefaultValuePipeParseEnumPipeParseFloatPipe
用法:
@Get()
create(
@Query('id', ParseIntPipe) id: number, // string 转 int
) {
}
自定义转换
user-by-id.pipe.ts
@Injectable()
export class UserByIdPipe implements PipeTransform<string, number> {
transform(value: string, metadata: ArgumentMetadata): UserEntity {
const id = parseInt(value);
// 转换需要的数据
const userEntity: UserEntity = await this.userRepo.find(id)
return userEntity;
}
}
控制器
@Get(':id') findOne(@Param('id', UserByIdPipe) userEntity: UserEntity) {
return userEntity;
}
管道验证 DTO
安装
npm i --save class-validator class-transformer
写 DTO 文件
add-project.dto.ts
import { IsArray, IsNotEmpty, IsNumber, IsString } from "class-validator"
export class AddProjectDto {
// 客户表的 id
@IsNotEmpty()
@IsNumber() // 注意不要写小写 isNumber()
customerId: number
// 应用表的 id
@IsNotEmpty()
@IsNumber()
applicationId: number
// 专案名称,同一个客户下名字不能重复
@IsNotEmpty()
@IsString()
projectName: string
// 项目描述
@IsNotEmpty()
@IsString()
desc: string
// 元件,数组
@IsArray()
compnentIds: number[]
}
控制器
// 新增专案
@Post()
async addProject(@Body() addProjectDto: AddProjectDto) {
return this.trackingService.addProject(addProjectDto)
}
基本上这样就可以了, 如果有错, 会抛出错误信息. class-validator 写法, 可以看官网
全局验证
main.js
// 全局注册通用验证管道 ValidationPipe
app.useGlobalPipes(new ValidationPipe());
validation.pipe.ts
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
@Injectable()
export class ValidationPipe implements PipeTransform<any>{
// value 是当前处理的参数, metatype 是属性的元类型, data 是当前处理的参数名, type 是 'body' | 'query' | 'param' | 'custom'
async transform(value: any, { metatype, data, type }: ArgumentMetadata) {
// 全局验证进去这里, 可以对数据进行验证.
// 这里验证 tab_status, 是不是数组 ['Alive', 'PilotRun', 'MP', 'P/L', 'Drafts'] 之中之一, 如果不是, 抛出异常.
if (data === 'tab_status') {
const arr = ['Alive', 'PilotRun', 'MP', 'P/L', 'Drafts']
const result = arr.indexOf(value)
if (result < -1) {
throw new HttpException(data + " 必须包含 'Alive', 'PilotRun', 'MP', 'P/L', 'Drafts' 之一", HttpStatus.BAD_REQUEST)
}
}
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);
}
}
当然, 上面的全局验证数据, 也可以做局部验证.
tab_status.validation.ts
import { ArgumentMetadata, HttpException, HttpStatus, Logger, PipeTransform } from "@nestjs/common";
export class TabStatusValidation implements PipeTransform {
// value 是当前处理的参数, metatype 是属性的元类型, data 是当前处理的参数名, type 是 'body' | 'query' | 'param' | 'custom'
transform(value: any, { metatype, data, type }: ArgumentMetadata) {
if (data === 'tab_status') {
const arr = ['Alive', 'PilotRun', 'MP', 'P/L', 'Drafts']
const result = arr.indexOf(value)
if (result < -1) {
throw new HttpException(data + " 必须包含 'Alive', 'PilotRun', 'MP', 'P/L', 'Drafts' 之一", HttpStatus.BAD_REQUEST)
}
}
return value
}
}
控制器
// TabStatusValidation 局部验证 tab_status
@Get('list')
getList(@Query('tab_status', TabStatusValidation) tabStatus: string, @Query('sort') sort: string) {
return this.trackingService.getList({ tabStatus, sort });
}
守卫
守卫有一个单独的责任。它们根据运行时出现的某些条件(例如权限,角色,访问控制列表等)来确定给定的请求是否由路由处理程序处理。 这通常称为授权。在传统的 Express 应用程序中,通常由中间件处理授权。中间件是身份验证的良好选择。到目前为止,访问限制逻辑大多在中间件内。这样很好,因为诸如 token 验证或将 request 对象附加属性与特定路由没有强关联。
中间件不知道调用 next() 函数后会执行哪个处理程序。另一方面,守卫可以访问 ExecutionContext 实例,因此确切地知道接下来要执行什么。它们的设计与异常过滤器、管道和拦截器非常相似,目的是让您在请求/响应周期的正确位置插入处理逻辑,并以声明的方式进行插入。这有助于保持代码的简洁和声明性。
守卫在每个中间件之后执行,但在任何拦截器或管道之前执行。
token
auth.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
const request: Request = context.switchToHttp().getRequest();
// 白名单验证
if (this.hasUrl(this.urlList, request.url)) {
return true;
}
// 取得 token
const token = context.switchToRpc().getData().headers.authorization;
if (token) {
const isTure = token // token 验证, 代码实际处理, token 可以用 jwtService
// ...
if (isTure) {
return true
} else {
throw new HttpException("token 已经失效", HttpStatus.UNAUTHORIZED)
}
} else {
throw new HttpException("请请先登录", HttpStatus.UNAUTHORIZED)
}
}
// 白名单
private urlList: string[] = [
'/api/v1/user/login',
'/api/v1/user/pwd',
'/api/v1/user/forget',
'/api/v1/user/verifycode',
];
// 验证请求是否为白名单的路由
private hasUrl(urlList: string[], url: string): boolean {
let flag: boolean = false;
if (urlList.indexOf(url.split('?')[0]) >= 0) {
flag = true;
}
return flag;
}
}
全局注册
main.ts
// 全局注册权限验证守卫
app.useGlobalGuards(new AuthGuard());
路由角色认证
利用装修器验证 admin 路由等..
roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
控制器
cats.controller.ts
@Post()
@Roles('admin') // 验证有没有 admin 权限, 这个权限可以放在 token 里加密在解密, 也可以后面自己查找验证
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
路由守卫
roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) { }
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get<string[]>('roles', context.getHandler());
if (!roles) {
return true;
}
// 取得 token
const token = context.switchToRpc().getData().headers.authorization;
// 这里可以把用户权限放 token 里面. 在这里可以解密取得 user 用户信息
先对 token 验证
const user = request.user;
return matchRoles(roles, user.roles);
}
}
更进一步写法, 以下是都是伪代码, 意思传达就好.
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) { }
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get<string[]>('roles', context.getHandler());
// 取得 token
const token = context.switchToRpc().getData().headers.authorization;
// 这里可以把用户权限放 token 里面. 在这里可以解密取得 user 用户信息
先对 token 验证
if(token) {
// 验证通过
return true;
} else {
// 抛异常
}
if (!roles) {
return true;
}
// 这里是 token 解密后取得 user
const user = token.user;
// 对比是不是有权限
return matchRoles(roles, user.roles);
}
}
拦截器
拦截器具有一系列有用的功能,这些功能受面向切面编程(AOP)技术的启发。它们可以:
- 在函数执行之前/之后绑定额外的逻辑
- 转换从函数返回的结果
- 转换从函数抛出的异常
- 扩展基本函数行为
- 根据所选条件完全重写函数 (例如, 缓存目的)
Response 拦截后, 统一返回数据格式
{
statusCode: 0,
message: '成功',
data: data
}
response.interceptor.ts
import { Injectable, NestInterceptor, CallHandler } from '@nestjs/common'
import { map } from 'rxjs/operators'
import {Observable} from 'rxjs'
interface data<T>{
data:T
}
@Injectable()
export class Response<T = any> implements NestInterceptor {
intercept(context, next: CallHandler):Observable<data<T>> {
return next.handle().pipe(map(data => {
return {
statusCode: 0,
message: '成功',
data: data
}
}))
}
}
全局注册
main.ts
// 全局注册拦截器
app.useGlobalInterceptors(new ResponseInterceptor())
当然, 也可以局部注册
cats.controller.ts
@UseInterceptors(ResponseInterceptor)
export class CatsController {}
以上就是简单的记录一下, 详情可以看官方文档