Nest-node版的Spring——(第一部分官方精简)

  # 官方说明,学习

一点一点的学习,我们这里有三套的教程 ,建议顺序

框架的哲学

近几年由于 Node.js 的出现,JavaScript 成为了前端和后端的「lingua franca5」,前端方面出现了 Angular, React, Vue 等众多的 UI 框架,后端方面也有像 Express, Koa 这样优秀的框架出现,但这些框架都没有高效地解决一个核心问题 — 架构,而我nest就是这样的一个框架

开始

新建一个项目

请确定,你在本地安装了 nest的cli npm i -g @nestjs/cli就好了

npm i -g @nestjs/cli
nest new project-name  // 创建一个新的项目

这个时候我们进入,3000端口就能看到东西了
Hello word

重点说明 main.js入口

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
// 2.这样就指定了编译平台
import { NestExpressApplication } from '@nestjs/platform-express'
// import { NestFastifyApplication } from '@nestjs/platform-fastify'

async function bootstrap() {
  // 1.指定设使用说明品台,二选一,Express+Fastify 
  // 3.总结:一般情况下,我们是不写的
    const app = await NestFactory.create<NestExpressApplication>(AppModule);
  await app.listen(3000);
}

bootstrap();

MVC中的C,控制器

初始化

我们来看看mvc中的控制器,由控制器来负责,处理客户端请求并发送响应内容,就是负责处理指定请求与应用程序的对应关系,路由则决定具体处理哪个请求。

使用@装饰器,来进行限定路由的功能
装饰器,有给类指定的,也有给方法指定的

我们需要使用ng cli的指令来生成一个控制器,它配套会生成一些我们需要的东西,

# 假设我们生成一个ctas 猫咪控制器
nest g controller cats

# 产生的代码
CREATE src/cats/cats.controller.spec.ts (479 bytes) 
# 文件是用于写测试用例
CREATE src/cats/cats.controller.ts (97 bytes)  
# 生成了一个控制器
UPDATE src/app.module.ts (322 bytes)
# 更新了app.module.ts 模块
  • 看一下我们生成的项目解构
    -|src
    -|------| cats
    -|------------|cats.controller.spec.ts
    -|------------|cats.controller.ts // 主要的业务逻辑

-我们往里面丢一些代码逻辑
/cats.controller.ts

import { Controller, Get } from '@nestjs/common';

@Controller('cats')  //类装饰器 
export class CatsController {
  @Get() //方法装饰器 表示 HTTP 请求装饰器
    // 当你需要自定义状态码的时候,默认是200
  @HttpCode(201)
  findAll(): string {
    // 如果返回一个 JavaScript 对象或者数据,将自动序列化成 JSON。

    return 'This action returns all cats';
    // 当我们访问3000/cats的时候就会走到这里来,这里就是一个路由
  }
}

处理请求参数

处理请求参数是非常正常的操作,有是在Node中至关重要的一件事情

  1. 我们使用装饰器来处理这些不同的东西
    @Request() req
    @Response() res
    @Next() next
    @Session() req.session
    @Param(key?: string) req.params / req.params[key]
    @Body(key?: string) req.body / req.body[key]
    @Query(key?: string) req.query / req.query[key]
    @Headers(name?: string) req.headers / req.headers[name]

  2. 如何获取到其它的方法呢?很简单,他们都有自己各自的装饰器
    @Put(), @Delete(), @Patch(), @Options(), @Head(), and @All(),

接下俩我们定义一个Delete还有另一个带地址的@get 还有@Post()
/cats.contarll.ts

  // 测试一下如何搞匹配地址
  @Get('hello')
  Hellow(){
    return '哈哈哈哈'
  }

  // 测试获取post参数
  @Post()
  getPost( @Body() s:Object ){
    return s
  }

与之相互对应的http测试

### 测试带路劲的
GET  http://127.0.0.1:3000/cats/hello

### 测试post

POST  http://127.0.0.1:3000/cats
Content-Type: application/json

{
  "name":"zhanhsang",
  "age":"18"
}
  1. 我们来看一到底如何获取到这些参数,特别是post 还有get 还req.params req.query req.params

首先是@Query(key?: string)
/cats.contarll.ts

  @Get()
  @HttpCode(201)
// 注意这个findAll不是http的方法,是Nest中获取所有请求的方法
findAll( @Query() q:string ): string {
    console.log(q);  
    // 1. 获取req.qury中的参数,类似于req.query,   
    // ====> http://127.0.0.1:3000/cats?test=hahahah,这个后面可以跟很多的参数
    // { teste: 'hahah', hahah: '99999' }
    return 'This action returns all cats';
  }

补充说明params和query

req.params 
   http://localhost:3000/10

   app.get("/:id",function (req,res) {
      res.send(req.params["id"]);
      // res.send(req.params.id)
    });

req.query
  http://localhost:3000/?id=10

  app.get("/",function (req,res) {
      res.send(req.query["id"]);
  });

建议安装@types/express 包来获取 Request 的相关类型提示

  1. 如何处理动态的路由呢?我是说get/1 和 get/3 get/454656都是一个控制器

好办:id就好了,这个时候就需要@Param了
/cats.contarll.ts

 @Get(':id')
  testDynamic( @Param() params:Object ):Object  {
    return params
  }
  

  
  // 注意啊,由于你与爱心你定义了一个:id 那么后续的:hhaha将不会呗接受
  @Get(':id')
  testDynamic( @Param() params ):Object  {
    console.log(params.id);
    return params
  }

  // 错误的写法
  @Get(':hahaha')
  testDynamic1( @Param('hahaha') id ):Object  {
    console.log(id);
    return id
  }

 #### 第二轮测试
GET http://127.0.0.1:3000/cats/testDynamic



#### 测试
GET http://127.0.0.1:3000/cats/hahaha


结果

{
  "id": "testDynamic"
}


{
  "id": "hahah"
}

Provideru依赖注入的设计理念 Providers = 提供者

Providers 主要的设计理念来自于控制反转(Inversion of Control,简称 IOC )模式中的依赖注入(Dependency Injection)特性。
装饰器方法会优先于类被解析执行
Nest 实现注入是基于类的构造函数的
在我们这一章中,我们将会学习如下的知识点

  • Services
  • 依赖注入
  • 注入作用域
  • 自定义的 Providers
  • 可选的 Providers
  • 基于属性的注入
  • 注册 Provider

我们在学习的之前需要了解nest的一个架构思维,spring

Services

实际上,这一章节讲得最多得还是这个内容就是把,服务Services写出来,而且
Services中的知识点都是围绕着,Provider 走的

  • 先来看看我们的几个核心为Serive服务的文件
  1. 类型文件,这里全是与cast模块相关的接口类型
    /cat.inteface.ts
// 在这个文件夹下,我们来实现与cats模块相关的各个接口
export interface Cat {
  name: string;
  age: number;
  breed: string;
}
  1. 服务本体
    /cats.serveoc.ts
import { Injectable } from '@nestjs/common';
import { Cat } from './interface/cat.interface'

@Injectable()  // Injectable,可以注射的。一开始就声明了 你是可到处注射的,服务Service
export class CatsService {
  private readonly cats: Cat[] = [];  //定义一个只读属性,它实现了Cat接口

  // 定义两个方法,创建还有获取
  create(cat: Cat) {
    this.cats.push(cat);
    console.log( this.cats,'添加成功' );   
  }

  findAll(): Cat[] {
    return this.cats;
  }
}

建议使用cil生成,serveice.ts

nest g service cats
  1. 去控制器中注入就好了
import { Controller, Get, HttpCode, Query, Post, Body, Param } from '@nestjs/common';

// 依赖注入我的 业务逻辑处理Service
import { CatsService } from './cats.service';
import { Cat } from './interface/cat.interface';

@Controller('cats')  //类实例化之前就告诉了这个类,你是一个控制器
export class CatsController {
  // 控制器汇总引用我们的seveice
  private readonly catsService: CatsService
  constructor(catsService: CatsService) {
    this.catsService = catsService
  }
  
  @Post('create')
  async create(@Body() createCatDto:any) {
    this.catsService.create(createCatDto);
  }

  @Get('getAll')
  async All(): Promise<Cat[]> {
    return this.catsService.findAll();
  }
+++++++
  1. 测试效果

POST http://127.0.0.1:3000/cats/create
Content-Type: application/json

{
  "name":"loawng",
  "age":18,
  "breed":"2020-1-1"
}

### 测试
GET  http://127.0.0.1:3000/cats/getAll

[ { name: 'laoli', age: 18, breed: '2020-1-1' } ] 添加成功

[
  {"name":"laoli","age":18,"breed":"2020-1-1"},{"name":"loawng","age":18,"breed":"2020-1-1"}
  ]

依赖注入

所谓的依赖注入,就是找了一个IOC Container中间商

注入作用域

Providers是有生命周期的,当app启动的时候,就会不初始化,我们这里先不详细的介绍

自定义的 Providers

这里的表达的是你可以自定义的整一个IOC Container中间商,用来管理各个业务模块之间的依赖,这里先了解

可选的 Providers

当你需要一个依赖的时候,这个依赖并不一定呗nest内置的ioc解析,我们可以用@Optional()来装束一个非必须的参数

@Injectable()
export class HttpService<T> {
  constructor(
    @Optional() 
    @Inject('HTTP_OPTIONS') 
    private readonly httpClient: T
  ) {}
}

基于属性的注入

这里想表述的是这个意思,当多个Providers 相互依赖的时候,
通过在子类的构造函数中调用 super() 方法并不是很优雅,为了避免这种情况我们可以在属性上使用 @Inject() 装饰器。

@Injectable()
export class HttpService<T> {
  @Inject('HTTP_OPTIONS') // 注入你父类上的某个方法
  private readonly httpClient: T;
}

注册 Provider

我们的控制器是Serivce的消费者,
我们需要将这些 Service 注册到 Nest 上,注意啊这里指的是注册到nest上,让它全局可用

/app/modelus.ts


// 引入装饰器
import { Module } from '@nestjs/common';

// 基础的一个控制器还有与之相关的注入的依赖
import { AppController } from './app.controller';
import { AppService } from './app.service';

// 第二个 控制器还有与之相关的注入的依赖
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service';

@Module({
  imports: [],
  // 通过装饰器,全部完成注册
  controllers: [AppController, CatsController],
  providers: [AppService, CatsService],
})
export class AppModule {}

M模块

模块用来统筹所有的contarll还有serveice,有单独的服务于一个一个cotrlarr的模块,还有一个根应用的模块,这些模块是一个一个具体的业务功能所在
controller负责把模块一个一个组装起来

一个使用了 @Module() 装饰的类。@Module()

初始化中的app.modulde模块

基础的构建模块

我们先来初始化中的模块app.moudel长啥样,然后我们再来看如何把cats模块给抽离出来
/app.modelu.ts


// 引入装饰器
import { Module } from '@nestjs/common';

// 基础的一个控制器还有与之相关的注入的依赖
import { AppController } from './app.controller';
import { AppService } from './app.service';

// 第二个 控制器还有与之相关的注入的依赖
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service';

@Module({
  imports: []
  controllers: [AppController, CatsController], // 控制器集合
  providers: [AppService, CatsService],  // 可以被 Nest 的注入器初始化的 providers,至少会在此模块中共享,说白了就是被注入的要在模块中共享的
})
export class AppModule {}

如何封装自己的cats模块呢?实际上也非常的简单
使用cli快速构建,当然你也可以自己一个一个的敲,但是不建议

nest g module cats
# 这样就生成了一个初始状态的cats.modelu.ts 它和我们的cats是配套的

刚开始的时候,啥都没有



import { Module } from '@nestjs/common';
@Module()
export class CatsModule {}


我们把相关依赖的的丢进去


import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Module({
  controllers:[CatsController],
  providers:[CatsService]
})
export class CatsModule {}


在我们执行cli的时候有一个细节,就是我们的app.moludel被更新了
更新了吧模块注入的代码


// 引入装饰器
import { Module } from '@nestjs/common';

// 基础的一个控制器还有与之相关的注入的依赖
import { AppController } from './app.controller';
import { AppService } from './app.service';

// 第二个 控制器还有与之相关的注入的依赖
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service';

// 被注入了进来,cats相关的模块被注入了进来     <====================
import { CatsModule } from './cats/cats.module';   

@Module({
  imports: [CatsModule],  // 引用的其它模块的集合,   <====================
  // 通过装饰器,全部完成注册
  controllers: [AppController, CatsController], // 控制器集合
  providers: [AppService, CatsService],  // 可以被 Nest 的注入器初始化的 providers,至少会在此模块中共享,说白了就是被注入的要在模块中共享的
})
export class AppModule {}


如果我有个模块需要共享给其它模块使用呢?

实际上也非常的简单,我们只需要在需要共享的模块中加一个属性,如何在接受的模块也导入一下就好了

import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Module({
  imports:[],  // 我没有什么是需要依赖其它的模块的,所以这里为空
  controllers:[CatsController],
  providers:[CatsService],
  exports:[CatsService]
})
export class CatsModule {}

问题来了哈,假设我这儿有这样的事情哈我希望 a --> b ---> c 我的意思是 a被我使用了,如何我使用的时候对a做了一定的数据附加,然后我又想丢给c用这个已经改造过的a 那么怎么操作呢?
重复倒出就好了


imports: [CommonModule],
  exports: [CommonModule],

模块的依赖注入

假设 ,现在啊,有这样的事情,需要你在a模块中注入一些属性,配置一些参数,以便于a模块对不同的场景做出不同的反应

// 非常的简单,我们说过了,万物皆对象,在ts中万物皆类,计入是@Moulde修饰的,那么它本身也是一个类,是类就有对应的构造器,于是乎

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {
  constructor(private readonly catsService: CatsService) {}  // 这样就好了
}

全局的模块,还有动态的模块

  1. 全局的模块

全局的模块,我们使用@Global装饰一下就好了。这样全局就都可以用了,非常的简单

@Global()
@Module({
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService],
})
export class CatsModule {}
  1. 动态的模块

如何让模块适应不同的业务场景呢?依赖注入是一种方式,这个也是一种更加优雅的方式

import { Module, DynamicModule } from '@nestjs/common';
import { createDatabaseProviders } from './database.providers';  // 自定义的注射器💉
import { Connection } from './connection.provider';  // 自定义的注射器💉

@Module({
  providers: [Connection],  //模块被注射了
})
export class DatabaseModule {
  static forRoot(entities = [], options?): DynamicModule {
    const providers = createDatabaseProviders(options, entities);
    return {
      module: DatabaseModule,
      providers: providers,
      exports: providers,
    };
  }
}

理解就好了,我们后续来详细的说明如何使用
说明 模块的静态方法 forRoot 返回一个动态模块,可以是同步或者异步模块。

中间件

和express中的中间件是一毛一样的

这里我们就来造一个logger日志记录中间件

首先我们用类来实现一个中间件

/ logger.middleware.ts

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response } from 'express';

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

   // 我们给 请求的数据带上一点点标记  注意不能 req.XX = XXX 去加,是加不上去的
    let Name_Scope = { bmlaoli:{age:18} }
    Object.assign(req,Name_Scope ) 

    next();
  }
}

同样的道理,这里也是支持依赖注入了,这里不详细的说明了

如何使用中间件?

我们可以在具体的路由中,中使用,也可以使用通配符统配调一些路由,也可以全局的使用

  • 中间件有带你特殊,我们不能直接在@model中列出来,我们只能 用模块类的 configure() 方法来设置它们

/app.module.ts

export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('cats');
      // .forRoutes({ path: 'cats', method: RequestMethod.GET })  再具体匹配一些路由和方法,我们还可以使用通配符,这不详细的说明
  }
}

中间件消费者

MiddlewareConsumer 是一个帮助类。它提供了几种内置方法来管理中间件。他们都可以被简单地链接起来。forRoutes() 可接受一个字符串、多个字符串、对象、一个控制器类甚至多个控制器类。

/app.module.ts


export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)  // 用来搬定多个中间件
      .forRoutes('cats');
      // .forRoutes({ path: 'cats', method: RequestMethod.GET })  再具体匹配一些路由和方法,我们还可以使用通配符,这不详细的说明
  }
}

函数式中间件,这里不讲了

全局挂载中间件,全局挂载中间件还是比较有意思的

直接往app上丢就好了
maint.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
// 2.这样就指定了编译平台
import { NestExpressApplication } from '@nestjs/platform-express'
// import { NestFastifyApplication } from '@nestjs/platform-fastify'
import { LoggerMiddleware } from './cats/middleware/logger.middleware'
async function bootstrap() {
  // 1.指定设使用说明品台,二选一,Express+Fastify 
  // 3.总结:一般情况下,我们是不写的
    const app = await NestFactory.create<NestExpressApplication>(AppModule);
      const logger =  new LoggerMiddleware() // 类 实例化 ===> 对象 ===> 对象中有方法
    app.use( logger.use  )
  await app.listen(3000);
}

bootstrap();
posted @ 2020-07-13 16:09  BM老李  阅读(613)  评论(0编辑  收藏  举报