NestJs 快速入门
npm i -g @nestjs/cli,nest new car-report 快速创建car-report 项目。src目录下面有main,controller,service,module文件。main.ts是项目的入口,它引入AppModule,创建服务,监听3000端口。AppModule是一个注解@Module()的类,也称为app模块。由于项目启动时引入AppModule,它也称为根模块。模块有什么作用,体现在@Module的参数上,import引入其它模块,controllers提供控制器,处理请求和响应。根模块引入其它模块,其它模块也提供了controller, 其它模块再引入其它模块,它们也提供了controller,通过import 构建起了整个应用,对应的controller分别处理各个模块的请求,职责清晰,因此模块是构建NestJs应用的基石。
控制器是注解了@Controller的类,类的每一个方法再注解@Get,@Post等,该方法就处理get和post请求。项目启动,从AppModule开始,找到所有import的module,根据每一个module提供的controllers,NestJs会构建一个路由映射表,请求和Controller的方法一一对应。当客户端发来请求时,NestJs就能知道调用哪个方法。AppController
@Controller() export class AppController { @Get() getHello(): string { return this.appService.getHello(); } }
@Controller()没有参数,@Get也没有参数,就相当于根路径。路由映射就是 / --> getHelllo()。当请求 / 时会调用getHello方法,方法也称为路由处理器。npm run start:dev 启动服务,postman get请求localhost:3000,返回hello world。controller调用了appService,这是一种设计模式,controller负责接受客户端请求,service负责处理业务逻辑,repository负责和数据库打交道,controller调用service,service调用repository。创建一个messages模块,新建messages目录,目录里面messages.controller.ts,messages.module.ts, messages.service.ts, messages.repository.ts。messages.repository.ts 假设从数据库中查数据,
export class MessageRepo { findOne() { return { message: 'first NestJs demo' } } }
servcie调用repository,但repository是个类,所以在service 中要先创建repository的一个实例,messages.service.ts
import { MessageRepo } from './messages.repository'; export class MessagesService { private messageRepo: MessageRepo; constructor() { this.messageRepo = new MessageRepo(); } getMessages() { return this.messageRepo.findOne(); } }
controller调用service,messages.controller.ts类的构造函数中,创建一个service的实例。
import { Controller, Get } from '@nestjs/common'; import { MessagesService } from './messages.service'; @Controller('messages') export class MessageController { messagesService: MessagesService constructor() { this.messagesService = new MessagesService() } @Get() getMessages() { return this.messagesService.getMessages() } }
@Controlloer有一个参数messages,表示它只处理以messages开头的请求, 由于@Get没有参数 ,/messages就会调用getMessages方法。messages.module.ts,注册controller,
import { Module } from '@nestjs/common'; import { MessageController } from './messages.controller'; @Module({ controllers: [MessageController] }) export class MessagesModule {}
AppModule 中,import MessagesModule,
import { Module } from '@nestjs/common'; import { MessagesModule } from './messages/messages.module'; @Module({ imports: [MessagesModule], }) export class AppModule { }
npm run start:dev启动服务,AppModule import了MessagesModule,找到了MessagesModule注册的controller,创建了一个controller实例,同时建立了/messages到getMessages的映射,postman请求http://localhost:3000/messages, 返回了{ "message": "first NestJs demo" }。当controller的方法返回基本数据类型时,直接返回。当返回对象或数组时,会序列化为json。NestJs有一个依赖注入的概念,不需要在构造函数中手动创建实例,而是使用参数声明需要什么实例(依赖),程序运行时,让NestJs自动注入这个实例。controller依赖service, 所以MessagesController 的构造函数修改如下
constructor(messageService: MessagesService) {// 让NestJs注入MessagesService的一个实例 this.messagesService = messageService; }
service依赖repository,MessagesService 的构造函数修改如下
constructor(messageRepo: MessageRepo) { // 让NestJs注入MessageRepo的一个实例 this.messageRepo = messageRepo; }
参数声明了依赖,怎么让NestJs提供依赖?NestJs怎么知道提供哪个依赖?NestJs的依赖注入,需要你手动告诉它,提供哪些依赖,这就是@Module的provider,provider是对NestJs说的,NestJs是一个provider。
import { Module } from '@nestjs/common'; import { MessageController } from './messages.controller'; import { MessagesService } from './messages.service'; import { MessageRepo } from './messages.repository'; @Module({ controllers: [MessageController], providers: [MessagesService, MessageRepo] }) export class MessagesModule {}
providers: [MessagesService, MessageRepo]表示,当NestJs碰到MessagesService,就注入一个MessagesService的实例,当碰到MessageRepo时,就注入MessageRepo的一个实例。当创建Contoller的实例时,就会碰到MessageService,就会注入一个MessagesService的实例。NestJs需要创建MessagesService和MessageRepo的实例,所以这两个类要用@Injectable()修饰,表示是可以被注入的。
import { Injectable } from '@nestjs/common'; @Injectable() export class MessagesService {} @Injectable() export class MessageRepo {}
NestJs 启动后,创建一个依赖注入的容器或注入器(Nest DI Container/Injector),它是一个对象,包含class列表和他们的依赖。它会查找项目中的provider,然后把它们注册到容器中。然后容器开始分析类的依赖(看构造函数的参数),然后创建一些内部记录来表示依赖,比如,service类依赖repositroy, repositroy类它没有依赖。然后在某些时候, 告诉DI容器创建controller的实例,DI就会分析contoller的依赖,它依赖Service,service再依赖resposotry。DI先创建reposiory的实例,然后再创建service的实例, 最后创建controller的实例,并返回。DI第一步是分析类的依赖,第二步是创建contoller实例并返回。controller实例是自动创建的
AppModule怎么使用MessagesModule中的service 呢?service属于自己的module,当一个service 要被其他module 使用时,它要被export 出去。MessagesModule export 出去MessagesService
@Module({ controllers: [MessageController], providers: [MessagesService, MessageRepo], exports: [MessagesService] }) export class MessagesModule {}
一个模块要使用另一个模块的service,就要把另一个模块引入,AppModule要import MessagesModule, 然后在使用service的地方依赖注入到构造函数中,AppModule已经import 过了MessagesModule,假设AppService使用MessagesService,AppService修改如下
import { Injectable } from '@nestjs/common'; import { MessagesService } from './messages/messages.service'; @Injectable() export class AppService { constructor(private messageService: MessagesService) {} getHello(): string { return 'Hello World!' + this.messageService.getMessages().message; } }
写一个真实user的登录注册。首先要有数据库,用TypeORM操作MySQL,npm install --save @nestjs/typeorm typeorm mysql2 安装依赖。TypeORM有实体(Entity)的概念,对应MySql的一张表,它是一个类,代表表名,属性代表列。创建users目录,user目录下创建users.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; @Entity() export class User { @PrimaryGeneratedColumn() id: number; @Column() admin: boolean; @Column() email: string; @Column({default: true}) password: string; }
连接数据库,在AppModule
import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './users/users.entity'; @Module({ imports: [TypeOrmModule.forRoot({ type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: '123', database: 'test', entities: [User], synchronize: true, })] }) export class AppModule { }
TypeORM模块的import方式不太一样,它是调用forRoot方法返回一个模块,这种模块称为动态模块,相对应的,MessagesModule 称为静态模块。静态模块功能是固定的,由@Module定义,import的时候,直接import 模块名,不用调用方法。不管是静态模块还是动态模块,模块一旦创建,它是单例的,存在某个地方,但又不全局可用(every module has its own set of imported modules in its scope)。Typeorm模块在AppMoule 中创建,它就已经存在,连接到数据库了,users模块中只使用user功能,所以在users中,Typeorm.forfeature('user'),让user功能在users模块中使用。当使用一个动态module 的时候,要么配置module的行为,要么引入某个feature。users目录下,创建users.module.ts
import { Module } from '@nestjs/common'; import { UsersController } from './users.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './users.entity'; import { UsersService } from './users.service'; @Module({ imports: [TypeOrmModule.forFeature([User])], controllers: [UsersController], providers: [UsersService] }) export class UsersModule { }
创建users.controller.ts
import { Controller, Get, Post } from '@nestjs/common'; import { UsersService } from './users.service'; @Controller('users') export class UsersController { constructor(private usersService: UsersService) { } @Post('/signup') async createUser() {} @Post('/signin') async singin() {} @Get('/:id') async getUser() {} }
创建users.service.ts,本来是要创建users.repository.ts的,但有了实体,TypeORM会自动创建对应的repostiory对象来操作表,不用手动创建Repository类了,只需要在service中注入创建好的repostiory对象,users.service.ts 如下
import { Injectable } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; import { User } from "./users.entity"; @Injectable() export class UsersService { constructor( @InjectRepository(User) private usersRepository: Repository<User>, ) {} }
Repositoy类型使用泛型Repository<User>。创建user就要接收客户端传递过来的参数,NestJs有@Req装饰器,只要函数参数被@Req修饰,参数就是request请求对象,函数中可以获取到request.body, request.params, request.query等对象,由于这些对象非常常见,NestJs为每一个对象都创建装饰器,@Body,@Params, @Query。@Body修饰的参数直接就是客户端传递过来的数据。那参数是什么类型?客户端传递过来emai和password, 那就创建一个类,有email 和password属性,参数就是这个类类型。创建create-user.ts
export class CreateUser { email:string; password: string }
users controller中的createUser 函数
@Post('/signup') async createUser(@Body() body: CreateUser) {}
CreateUser类也称为DTO(Data Transfer object)类,用于接受客户端传递过来的数据。客户端传递过来email和password,它是以字符串(JSON.stringfy())的形式传递过来的,服务端需要JSON.parse进行解析,然后创建CreateUser一个实例对象,把解析出来的数据赋值给对象,然后再把对象赋值给body,body是CreateUser的一个实例对象。能接受数据了,还要对数据进行验证。NestJs有一个pipe 的概念,数据到达路由处理函数之前要经过一系列过程,pipe就是其中之一,可用于验证。NestJ提供ValidationPipe和两个npm包。Class-transformer包把plain object转化成一个类的实例。Class-validation 包使用注解验证,然后把验证结果给到validation pipe。npm install class-transformer class-validator, createUser 函数@Body(ValidationPipe),CreateUser 类添加验证
import { IsString, IsEmail } from 'class-validator'; export class CreateUser { @IsEmail() email:string; @IsString() password: string }
validation 还有一个group的概念,按照条件进行验证,给ValidationPipe进行传参group: ['create],然后在CreateUser中添加验证条件的时候,也添加group: ['create]。如果对每个客户端数据都进行验证,在每一个路由处理函数都添加ValidationPipe,比较麻烦,可以开启全局验证。useGlobalPipes(new ValidationPipe()), useGlobalPipes对所有的请求都应用它包含的pipe。ValidationPipe 验证每一个请求,如果DTO类没有添加验证规则,也不会对请求进行验证。main.ts
import { ValidationPipe } from "@nestjs/common"; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe({ whitelist: true })) await app.listen(3000); } bootstrap();
验证的过程如下
有个问题,validation pipe是怎么知道使用哪个验证的规则的来验证哪一个路由的?尤其是typescript 编译成js,类型擦除后?ts 配置emitDecoratorMetadata, 把类型信息添加到js中, 编译后在js代码,dist目录,users下面的users.controller.js
exports.UsersController = UsersController; __decorate([ (0, common_1.Post)('/signup'), __param(0, (0, common_1.Body)(common_1.ValidationPipe)), __metadata("design:type", Function), __metadata("design:paramtypes", [create_user_1.CreateUser]), __metadata("design:returntype", Promise) ], UsersController.prototype, "createUser", null);
现在把接受到的参数存储到数据库,调用userService中的create 方法,
async createUser(@Body(ValidationPipe) body: CreateUser) { await this.usersService.create(body.email, body.password) return {message: 'create successfully'} }
在UsersService类下面添加create方法,Typeorm生成的repository对象有create,save,insert,update,delete,find等方法。向数据库中插入数据,有两种实现方式,create之后调用save,
async create(email: string, password: string) { const user = this.usersRepository.create({ email, password, admin: true }); await this.usersRepository.save(user); }
和直接调用insert。
async create(email: string, password: string) {await this.usersRepository.insert({ email, password, admin: false })
}
在AppModule中import UserModule, npm run start:dev启动服务,
同样的,update也有两种方式,先findOne,再调用save方式,或直接调用update 方法。delete也有两种方式,先findOne,再调用remove方法,或直接调用delete方法。为什么会有save和remove 方法呢?因为在Entity 中可以定义一些hooks,@AfterInsert, @AfterUpdate, 只有调用这两个方法的时候,它们才会执行,直接调用insert,update,delete不会执行,但save 方法,性能可能不高,因为,当实体不存在时,它执行insert操作,当实体存在时,它执行update操作,每天都要执行两次query查询,先find,再insert或update。
登录singin,都会返回token,以后每一个请求都带有token,就知道谁在请求。npm install --save @nestjs/jwt。在UserModule中,
import { JwtModule } from '@nestjs/jwt'; export const secret = 'addfsdsfdf' imports: [TypeOrmModule.forFeature([User]), JwtModule.register({ global: true, secret: secret, signOptions: { expiresIn: '1h' }, })]
再UserService中注入JwtService,实现signIn方法
import { JwtService } from '@nestjs/jwt'; import { Injectable, UnauthorizedException } from "@nestjs/common"; constructor( @InjectRepository(User) private usersRepository: Repository<User>, private jwtService: JwtService ) { } async signIn( email: string, password: string) { const user = await this.usersRepository.findOne({where: { email }}); if (user?.password !== password) { throw new UnauthorizedException(); } const payload = { sub: user.id }; return await this.jwtService.signAsync(payload) }
在UserController 中
@Post('/signin') async singin(@Body(ValidationPipe) body: CreateUser) { const token = await this.usersService.signIn(body.email, body.password) return { access_token: token }; }
getUser方法,只有用户登录,才能调用访问,有些路由是要保护起来的,如果没有登录,就不能访问,这要用到guard,有一个canActivate(), 返回true or false,true表示允许访问,false表示不允许访问。用户登录就是验证token,创建AuthGuard.ts
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { secret } from './users.module'; import { Request } from 'express'; @Injectable() export class AuthGuard implements CanActivate { constructor(private jwtService: JwtService) { } async canActivate(context: ExecutionContext): Promise<boolean> { const request = context.switchToHttp().getRequest(); const token = this.extractTokenFromHeader(request); if (!token) { throw new UnauthorizedException(); } try { await this.jwtService.verifyAsync(token,{secret}); } catch { throw new UnauthorizedException(); } return true; } private extractTokenFromHeader(request: Request): string | undefined { const [type, token] = request.headers.authorization?.split(' ') ?? []; return type === 'Bearer' ? token : undefined; } }
getUser方法,那就使用UseGuard进行保护
@Get('/:id') @UseGuards(AuthGuard) async getUser() { }
实现getUser,获取id参数,用@Param装饰器,
async getUser(@Param('id') id: string) { return await this.usersService.findOne(id); }
userService实现findOne,
findOne(id: number) { if (!id) return null; return this.usersRepository.findOneBy({ id }); }
但这时有一个问题,controller中调用findOne的id是string类型,但service中,id接受的是number类型,这是可以用pipe,@param('id', ParseIntPipe) 把id转换成int 类型。
async getUser(@Param('id', ParseIntPipe) id: number) { return await this.usersService.findOne(id); }
pipe通常做两件事情,一个是类型转换,一个是验证用户的输入。在以上的方法中,抛出了异常,比如throw new UnauthorizedException(),NestJs有一层Exception filter,当应用程序中抛出了异常,而没有被捕获时,它会把异常转换成合适response,比如 throw NotFoundExeption 时, nextJs会返回404,not found。对exception 进行过滤,返回合适的响应。
但返回值中有password,应该要去掉才对,这样用到拦截器。拦截器实现一个NestInterface,intercept 里面正常写,拦截请求,return next.handle() 对拦截响应,对路由处理器的返回值进行处理。它返回的是rxjs的observer,有map等操作,map中的data 就是路由处理器返回的data。 返回值去掉password,创建serialInteceptor.ts
import { CallHandler, ExecutionContext, NestInterceptor, UseInterceptors } from "@nestjs/common"; import { Observable, map } from "rxjs"; export class SerializeIntercepter implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler<any>): Observable<any> | Promise<Observable<any>> { return next.handle().pipe( map(data => { Reflect.deleteProperty(data, 'password'); // true return data; }) ) } }
getUser用userInterceptor.
@Get('/:id') @UseGuards(AuthGuard) @UseInterceptors(SerializeIntercepter) async getUser(@Param('id', ParseIntPipe) id: number) { return await this.usersService.findOne(id); }
可以把拦截器包起来,形成一个装饰器,serialInteceptor.ts
export function Serialize() { return UseInterceptors(SerializeIntercepter); }
getUser 去掉@UseInterceptors(SerializeIntercepter), 直接使用@Serialize()。再创建一个report 模块,一辆汽车的报告,用户创建它,admin 用户批准它。nest cli 提供了一些命令来创建module,controller和service, nest g module reports,nest g controller reports, nest g service reports,手动在reports目录建reports.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm'; @Entity() export class Report { @PrimaryGeneratedColumn() id: number; @Column({default: false}) approved: boolean; @Column() price: number; @Column() year: number @Column() mileage: number }
然后在AppModule 中,Typeorm的配置项entities中,添加Report
entities: [User, Report],
用户创建report, createReport 中要知道用户的信息,admin批准report,那还要判断登录的用户是不是admin,如要不是,批准的api就不能被调用,需要创建AdminGuard。从客户端请求中,只能得到userId,所以其它信息还要从数据库里面取。这里要用到中间件,这是由中间件,guard,拦截器的执行顺序决定的。
在中间件中,调用userService,获取到用户信息,然后把信息添加到request对象上,后面执行的guard,拦截器,路由处理器都能获取到request对象上在user信息。在src目录下,创建current-user.middlewire.ts
import { Injectable, NestMiddleware } from '@nestjs/common'; import { Request, Response, NextFunction } from 'express'; import { UsersService } from './users/users.service'; import { JwtService } from '@nestjs/jwt'; import { secret } from './users/users.module'; @Injectable() export class CurrentUserMiddleware implements NestMiddleware { constructor(private user: UsersService, private jwtService: JwtService) { } async use(req: Request, res: Response, next: NextFunction) { const [, token] = req.headers.authorization?.split(' ') ?? []; if (token) { try { const result = await this.jwtService.verify(token, { secret }); const user = await this.user.findOne(result.sub); // @ts-ignore req.currentUser = user; } catch (error) { console.log(error) } } next(); } }
中间件的使用比较特别,使用中间件的module要实现NestModule, 在configure中配置,比如在AppModule中配置中间件
import { CurrentUserMiddleware } from './current-user.middlewire'; import { MiddlewareConsumer, NestModule } from '@nestjs/common'; export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(CurrentUserMiddleware) .forRoutes('*'); // 或for('/reports') } }
由于中间件在AppModule中引入的,使用了UserService,UserModule还要exports 出去UserService。
@Module({ // .... providers: [UsersService], exports: [UsersService] }) export class UsersModule { }
现在createReport可以获取到user信息了,但怎么在report中保存user信息呢?这涉及到了关系,report和user有1对多的关系,
在user实体中, 添加属性
@Entity() export class User { // ... @OneToMany(() => Report, (report) => report.user ) reports: Report[] // 数组表示多个report }
在report 实体添加属性
@Entity() export class Report { // ... @ManyToOne(() => User, (user) => user.reports) user: User }
oneToMany或ManyToOne为什么第一个参数是函数。这是因为,User Entity中使用Report Entity, Report Entity 中又使用User Entity,循环依赖了,不能直接使用,所以要用函数包起来,以后执行,而不是加载文件的时候执行。第二个函数参数的意思是关联的实体,返回值是定义的实体, 通过关联的实体report怎么找回到定义report的实体(User),report entity 有一个user字段,就是定义reports的实体(User实体中有reports属性)。Report实体有一个user字段,存储report时,给report的user属性赋值一个user实体,当真正存储到数据库时,会从user实体中取出id,存储到数据库。ReporstController
import { Body, Controller, Post, Req, UseGuards } from '@nestjs/common'; import { AuthGuard } from '../users/AuthGuard'; import { ReportsService } from './reports.service'; @Controller('reports') export class ReportsController { constructor(private readonly reportsService: ReportsService) { } @Post() @UseGuards(AuthGuard) async createReport(@Body() body: any, @Req() req: any) { //body 的类型本来是一个DTO类型,简单起见,写了any const userReturn = await this.reportsService.create(body, req.currentUser) return userReturn } }
ReportsService
import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { User } from 'src/users/users.entity'; import { Repository } from 'typeorm'; import { Report } from './reports.entity' @Injectable() export class ReportsService { constructor( @InjectRepository(Report) private reportsRepository: Repository<Report>, ) { } create(reportDto: any, user: User) { const report = this.reportsRepository.create(reportDto); // @ts-ignore report.user = user; return this.reportsRepository.save(report) } }
由于在Service中注入了Report,所以在ReportsModule中 imports: [TypeOrmModule.forFeature([Report])],
repository的save方法把整个关联的user 实体都返回了。还有就是controller 接收了@req参数,能不能也像@Body一样,直接获取user?这要自定义一个参数装饰器createParaDecorator. 在src目录下,currentUser.ts
import {createParamDecorator, ExecutionContext} from '@nestjs/common' export const CurrentUser = createParamDecorator( (data: never, context: ExecutionContext) => { const request = context.switchToHttp().getRequest(); return request.currentUser } )
controller
import { CurrentUser } from '../currentUser'; import { User } from '../users/users.entity'; @Post() @UseGuards(AuthGuard) async createReport(@Body() body: any, @CurrentUser() user: User) { const userReturn = await this.reportsService.create(body, user) // @ts-ignore const newUser = { ...userReturn, userId: userReturn.user.id }; // @ts-ignore delete newUser.user; return newUser }
现在写一个approve, 就是把report的approve属性,改成true. 它需要admin权限,写一个AdminGuard。在report目录下,admin.guard.ts
import { CanActivate, ExecutionContext} from '@nestjs/common' export class AdminGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { const request = context.switchToHttp().getRequest(); if(!request.currentUser) { return false } if(request.currentUser.admin) { return true } else { return false } } }
ReportsController 添加一个patch 路由
@Patch('/:id') @UseGuards(AdminGuard) async approveReport(@Param('id') id: number, @Body() body: { approved: boolean }) { return await this.reportsService.changeApproval(id, body.approved); }
ReportsService 添加 changeApproval 方法
async changeApproval(id: number, approved: boolean) { const report = await this.reportsRepository.findOne({ where: { id } }) if (!report) { throw new NotFoundException('not found') } report.approved = approved; return this.reportsRepository.save(report) }
当查询条件比较复杂的时候,就不能简单地用findOne和find了,就要使用createQueryBuilder,比如查询price是5000, mileage 也是5000等。在Controller 中,
@Get() async getOneReport() { return this.reportsService.getReport(); }
在Service 中
async getReport() { return await this.reportsRepository.createQueryBuilder('report') .where('report.price= :price', {price: 5000}) .andWhere("report.mileage = :mileage", { mileage: 5000 }) .getOne() }
当fetch reprot时,不会自动fetch user。同样的,当fetch user的时候,也不会自动fetch report。
config 配置环境,npm i @nestjs/config, @nestjs/config内部使用dotenv。Dotenv的目的是, 把不同的环境变量(命令行定义的环境变量, .env 文件定义的环境变量)收集起来, 形成一个对象(process.env),返回给你。 如果各个方法定义的环境变量有冲突,命令行中定义的优先级高。
@nestjs/config 提供了依赖注入的功能。 每一个环境不同的.env 文件,然后,configroot.forRoot() 加载不同的配置文件(命令行配置env环境变量),
provider 定制化:当provider提供一个类名时,是标准引入,提供一个对象时,是定制引入。对象的一个key是provide,表示提供什么,真实的作用就是一个token标示符。另外一个key是useValue或useClass或useFactory, 当NestJs遇到provider指定的token后,就会用useValue指定的value,或useClass指定的类的实例对象,或调用useFactory,来注到token中。标准引入其实就是provide和useClass都是类名。providers: [{provide: ReportsService, useClass:ReportsService}]