Skip to content

nest.js

NestJS 是什么

  • NestJS 是一个用于构建高效、可扩展的服务器端应用程序的框架,它基于 TypeScript,并且受到 Angular 框架的启发。NestJS 提供了一种模块化的结构,允许开发人员使用常见的设计模式,如依赖注入、控制器和模块,来构建可维护且可扩展的应用程序。它内置了许多功能,包括路由管理、中间件支持、异常处理、数据验证等,使开发人员可以专注于业务逻辑的实现而不必担心底层的基础设施。NestJS 支持多种常见的服务器端技术,如 Express、Fastify 等,使开发人员可以选择最适合其需求的技术栈。

IOC DI

  • IoC(控制反转)是一种软件设计原则,它将控制权从程序代码中移动到了外部容器或框架中。传统的程序设计中,程序代码通常直接控制其他类和对象的创建和调用。而在 IoC 中,控制权被反转,即不再由程序代码直接控制,而是由外部容器或框架来管理对象的创建和调用。这种做法有助于降低组件之间的耦合度,提高代码的灵活性可维护性

  • DI(依赖注入)是 IoC 的一种实现方式,它是一种将对象之间的依赖关系从代码中移动到外部容器或框架配置中的技术。在 DI 中,一个类不再负责创建它所依赖的对象,而是由外部容器或框架来负责将依赖的对象注入到该类中。这样做的好处是,类之间的依赖关系变得更加灵活和可配置,提高了代码的可测试性和可维护性。

  • 综合来说,IoC 和 DI 都是为了降低软件组件之间的耦合度,提高代码的灵活性、可测试性和可维护性而设计的。通过将控制权和依赖关系交给外部容器或框架来管理,使得代码更加模块化、可扩展和易于理解。

生命周期

生命周期

模块用途

模块用途

装饰器

  • 装饰器是一种元编程技术,允许在方法属性上附加额外的行为。

类装饰器

  • 装饰器装饰在类上,target 指向的是类本身,而不是类的原型对象。
  • 可以扩展类的行为,例如添加新的方法、属性、拦截方法调用等。
ts
function myDecorator(target: any) {
  target.code = "10000";
  target.prototype.newMethod = function () {
    console.log("This is a new method added by the decorator");
  };
  target.prototype.name = "hl";
  return target; // 必须返回类本身,或者不返回参数
}

@myDecorator
class MyClass {}

let myClass = new MyClass();
console.log(MyClass.code); // 10000
myClass.newMethod(); // This is a new method added by the decorator
console.log(myClass.name); // hl
ts
function myDecorator(target) {
  return target;
}
// es5 写法
var myClass = myDecorator(function MyClass() {});

类属性装饰器

  • 第一个参数 target 是指被装饰的目标对象的原型(prototype)。在这个示例中,target 是 MyClass 类的原型对象。
  • 第二个参数 propertyKey 是指被装饰的属性的名称,在这个示例中就是 'name'。
ts
function myDecorator(target, propertyKey) {
  target.fn = function () {
    console.log(this.name);
  };
  console.log(propertyKey); // name
}

class MyClass {
  @myDecorator
  public name: string = "hl";
}

let myClass = new MyClass();

myClass.fn();

类函数装饰器(AOP)

  • 第一个参数 target 是指被装饰的目标对象的原型(prototype)。在这个示例中,target 是 MyClass 类的原型对象。
  • 第二个参数 propertyKey 是指被装饰的函数的名称,在这个示例中就是 'name'。
  • 第三个参数 descriptor 这是一个对象,描述了被装饰的方法或属性的特性。对于方法,descriptor 对象通常包含 value(方法实现)和 writable、enumerable、configurable 等属性(这些属性定义了方法或属性的行为)。
ts
function myDecorator(target, propertyKey, descriptor) {
  console.log(target);
  console.log(propertyKey); // setName
  let oldFn = descriptor.value;
  descriptor.value = function (...args) {
    console.log("pre_log");
    oldFn.apply(this, args);
    console.log("post_log");
  };
}

class MyClass {
  @myDecorator
  setName() {
    console.log("log");
  }
}

let myClass = new MyClass();
myClass.setName();

函数参数装饰器

  • 第一个参数 target 是指被装饰的目标对象的原型(prototype)。在这个示例中,target 是 MyClass 类的原型对象。
  • 第二个参数 propertyKey 是指被装饰的函数的名称,在这个示例中就是 'name'。
  • 第三个参数 descriptor 是索引值
ts
function myDecorator(target, propertyKey, descriptor) {
  console.log(target);
  console.log(propertyKey); // setName
  console.log(descriptor); // 0
}

class MyClass {
  setName(@myDecorator name: string) {}
}

let myClass = new MyClass();

类装饰器模拟@GET

ts
function GET(path: string) {
  return (target, propertyKey, descriptor) => {
    let oldFn = descriptor.value;
    let user = {
      id: "1000000",
      name: "hl",
    };
    oldFn(user);
  };
}

class UserController {
  @GET("/user")
  findUser(user) {
    console.log("find user", user);
  }
}

new UserController().findUser;

RESTful 接口

  • RESTful 是一种资源模型,它定义了一个资源模型,描述了资源资源的``操作资源之间的关系。RESTful 接口是一种基于 HTTP 协议的接口,它使用 HTTP 方法来操作资源,并使用 URI 来标识资源
ts
@Controller("user")
class UserController {
  // 1、查询GET
  @GET(":id")
  findOne() {}

  // 2、提交POST
  @POST
  create() {}

  // 3、更新 PUT PATCH
  @PUT(":id")
  update() {}

  // 4、删除 DELETE
  @DELETE(":id")
  delete() {}
}

阿里巴巴开发手册---DO、VO、BO、DTO、POJO的区别

  • DO Entity:Domain Object 即数据库表字段 一一对应关系(有人称它实体类)

  • BO:Business Object 即业务对象,Service层向上传传输的对象。是多个DO的组合形式

  • VO:view oject 展示层对象,通过接口向前端输出展示的对象

  • DTO:Date Transfer Object 数据传输对象,controller --> Service 或 Service --> dao 传递参数都可以使用,一般用于接收入参

  • POJO:(简称PO) Plain Ordinary Object 普通的对象

  • Req:Request

  • Resp:Response

  • Query:数据查询对象,各层接收上层的查询请求。注意超过2个参数的查询封装,禁止使用Map类来传输。

各层命名规约:

A)Service/DAo层方法命名规约

  • 1、获取单个对象的方法用 get 做前缀。
  • 2、获取多个对象的方法用list做前缀。
  • 3、获取统计值的方法用count做前缀。
  • 4、插入的方法用save/insert做前缀。
  • 5、删除的方法用remove/delete做前缀。
  • 6、修改的方法用 update 做前缀。

B)领域模型命名规约

  • 1、数据对象:xxxDO,xxx即为数据表名。
  • 2、数据传输对象:xXxDTO,xXx为业务领域相关的名称。
  • 3、展示对象:xxxV0,xxx一般为网页名称。
  • 4、POJO是DO/DT0/BO/VO的统称,禁止命名成xXxPOJ0。

Providers

  • 1、@Provides 用于提供依赖,@Inject 用于注入依赖。

语法糖写法

ts
@Module({
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}

@Controller('v1/user')
export class UserController {
  constructor(private readonly userService: UserService) {}
}

自定义service名称

ts
@Module({
  controllers: [UserController],
  providers: [
    {
      provide: 'UserService',
      useClass: UserService,
    },
  ],
})
export class UserModule {}
export class UserModule {}

@Controller('v1/user')
export class UserController {
  constructor(@Inject('UserService') private readonly userService: UserService) {}
}

注入值

ts
@Module({
  controllers: [UserController],
  providers: [
    {
      provide: 'jobs',
      useValue: ['java', 'fs', 'ios'],
    },
  ],
})
export class UserModule {}
export class UserModule {}

@Controller('v1/user')
export class UserController {
  constructor(@Inject('jobs') private readonly jobs: Array<string>) {}
}

模块Module

  • @Module 用于定义模块,@Controller 用于定义控制器,@Injectable 用于定义服务。

共享模块

  • exports 用于导出Service,imports 用于导入模块。
ts
// User
@Module({
  controllers: [UserController],
  providers: [
    UserService,
  ],
  exports: [UserService], // 需要导出的模块
})
export class UserModule {}

// Pay
@Module({
	imports: [UserModule], // 需要导入模块
  controllers: [PayController],
  providers: [PayService],
})
export class PayModule {}

@Controller('v1/pay')
export class PayController {
  constructor(
    private readonly payService: PayService,
    private readonly userService: UserService,
  ) {}
}

全局模块

  • @Global 用于定义全局模块, 其他模块可以直接使用,不需要导入。
ts
// User
@Global()
@Module({
  controllers: [UserController],
  providers: [
    UserService,
  ],
  exports: [UserService], // 需要导出的模块
})

中间件Middleware

  • 中间件是在路由处理程序之前调用的函数。中间件函数可以访问 request 和 response 对象,以及应用请求-响应周期中的 next() 中间件函数
  • 如果中间件中的next不进行调用,请求会被挂起。
  • 执行的顺序和Koa执行的中间件一样
ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: () => void) {
		console.log('Request...');
    next();
		console.log('Response...');
  }
}


export class UserModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggerMiddleware).forRoutes(UserController);
  }
}

全局中间件

ts
export default function globalMiddleware(req, res, next) {
	console.log('globalMiddleware')
  next();
	console.log('globalMiddleware')
}

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.setGlobalPrefix('api');
  app.use(globalMiddleware);
  await app.listen(3000);
}

拦截器

  • 拦截器是一种强大的机制,用于在执行请求处理器之前之后,对请求进行一些预处理或者后处理操作。拦截器可以用于日志记录、异常处理、权限验证、数据转换等方面,从而实现了一种高度可重用性和模块化的解决方案。
ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    console.log('Before...');
    const now = Date.now();
    return next.handle().pipe(
      map(data => {
        console.log(`After... ${Date.now() - now}ms`);
        return data;
      }),
    );
  }
}

// 局部拦截器
@UseInterceptors(LoggingInterceptor)
@Controller('example')
export class ExampleController {
  @Get()
  getData() {
    return { message: 'Data returned.' };
  }
}

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.setGlobalPrefix('api');
  app.use(globalMiddleware);
  // 全局拦截器
  app.useGlobalInterceptors(new LoggingInterceptor());
  await app.listen(3000);
}

异常过滤器

  • 过滤器是一种用于在请求处理过程中捕获异常并做出适当响应的中间件。过滤器允许您在应用程序中全局或局部地处理异常,并返回自定义的错误响应。
ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    const status = exception.getStatus();

    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
    });
  }
}


import { Controller, Get, UseFilters } from '@nestjs/common';
import { HttpExceptionFilter } from './http-exception.filter';

@UseFilters(HttpExceptionFilter)
@Controller('example')
export class ExampleController {
  @Get()
  getData() {
    throw new HttpException('Not Found', 404);
  }
}

{
  "statusCode": 404,
  "timestamp": "2022-01-01T00:00:00.000Z",
  "path": "/example"
}

管道

  • (Pipe)是一种用于处理输入数据的抽象层,它可以转换输入数据、验证数据的合法性,并且可以进行一系列的转换或者过滤操作,以确保数据符合应用程序的要求。
ts
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';

@Injectable()
export class ValidationPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    if (!value) {
      throw new BadRequestException('Validation failed: No data provided');
    }
    return value;
  }
}


import { Controller, Post, Body, UsePipes } from '@nestjs/common';
import { ValidationPipe } from './validation.pipe';

@UsePipes(ValidationPipe)
@Controller('example')
export class ExampleController {
  @Post()
  create(@Body() data: any) {
    console.log(data);
    return 'Data received';
  }
}

守卫

  • 守卫有单一的责任。它们根据运行时存在的某些条件(如权限、角色、ACL 等)确定给定请求是否将由路由处理程序处理。这通常称为授权。授权(及其通常与之合作的身份验证)通常由传统 Express 应用中的 中间件 处理。