even

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

nest.js的服务端的仓库地址: https://github.com/yufengctbu/nest-service.git  (目前还在整理中)

1、前期准备工作

环境配置参考node里的nodemon或者webpack

在学习nest.js前需要了解它的反映机制 Reflect.defineMetadata, 学习这个需要安装和引入库‘reflect-metadata’这个库

npm i reflect-metadata --save
import 'reflect-metadata'

console.log(Reflect.defineMetadata); //可以打印出对应的方法说明安装成功

Reflect-Metadata的方法介绍

import 'reflect-metadata'

let Info = {
  name: 'aaa',
  getName() {
    return this.name
  },
}

//定义在对象上的元数据
Reflect.defineMetadata('info', '这是个对象元数据信息', Info)
let msg = Reflect.getMetadata('info', Info)
console.log(msg)

//定义在对象属性上的元数据
Reflect.defineMetadata('paramInfo', '这是个对象属性元数据', Info, 'name')
let paramMsg = Reflect.getMetadata('paramInfo', Info, 'name')
console.log(paramMsg)

//判断元数据上是否有指定的数据
console.log(Reflect.hasMetadata('info', Info)) // true
console.log(Reflect.hasMetadata('paramInfo', Info, 'getName')) // false

//如果子类继承了父类原型链上的方法,那么同时也会继承原型链上的metadata,这个时候就可以使用hasOwnMetadata来判断是否是自有的属性
console.log(Reflect.hasOwnMetadata('info', Info))

注意:使用defineMetadata不会影响原数据本身,只会增加元数据

Reflect中类中的使用

import 'reflect-metadata'

@Reflect.metadata('name', 'metaData_className') //这个是直接定义,相当于defineMetadata
class Test {
  @Reflect.metadata('name', 'metaData_className_state')
  public state?: string

  @Reflect.metadata('name', 'metaData_className_method')
  public check(param: string): void {}
}

console.log(Reflect.getMetadata('name', Test))
console.log(Reflect.getMetadata('name', Test.prototype, 'state'))
console.log(Reflect.getMetadata('name', Test.prototype, 'check'))

//获取所有的原型上的key值
console.log(Reflect.getMetadataKeys(Test.prototype))
//获取原型上指定属性的key值
console.log(Reflect.getMetadataKeys(Test.prototype, 'state'))

// 注意: 特殊类型
// design:type  表示属性的声明的类型
// design:paramtypes  表示函数参数的声明类型,返回的是一个数组
// design:returntype 表示函数的返回类型

注意:Reflect.metadata如何修复的是属性或者类里的方法,那么相当对应的属性值是挂载在prototype原型上

可以通过自己写装饰器,将这个东西封装入自己封装的装饰器中

import 'reflect-metadata'

const classMetadata = <T>(name: string, value: string|number|boolean) => {
    return (classT: T) => {
        Reflect.defineMetadata(name, value, classT)
    }
}

const methodMetadata = <T>(name: string, value: string|number|boolean) => {
    return (classT: T, methodT: string|symbol) => {
        Reflect.defineMetadata(name, value, classT, methodT)
    }
}

@classMetadata('name', 'ven')
class Test {

    @methodMetadata('name', 'state')
    public state: string

    @methodMetadata('name', 'method')
    public check(param: string): void {
    }
}

console.log(Reflect.getMetadata('name', Test))
console.log(Reflect.getMetadata('name', Test.prototype, 'state'))
console.log(Reflect.getMetadata('name', Test.prototype, 'check'))

依赖注入小试

import 'reflect-metadata'

class Logger {
  public constructor() {
    console.log('this is logger')
  }
  public getLog() {}
}

// 使用依赖注入,实例化声明属性的类
function Inject(injectId: string): PropertyDecorator {
  return function (targetClassPrototype, propName) {
    let PropClass = Reflect.getMetadata('design:type', targetClassPrototype, propName)
    let PropClassObj = new PropClass()
    console.log(PropClassObj)
  }
}

class Active {
  @Inject('Logger')
  public log?: Logger

  public getPeo() {
    return this.log
  }
}

//使用依赖注册实例化构造器参数中声明的类
function InjectContructor(injectId?: string): ParameterDecorator {
  return function (targetClassPrototype, propertyKey, parameterIndex) {
    let propClass = Reflect.getMetadata('design:paramtypes', targetClassPrototype)
    let loggerClass = new propClass[parameterIndex]()
  }
}

class Person {
  public constructor(@InjectContructor('Logger') private logger: Logger, private count: number) {}
}

注意:以上方法的使用需要建立在tsconfig.json中target:es5的基础上,否则获取不到相关的值

2、nest框架的安装

$ npm i -g @nestjs/cli
$ nest new project-name

注意:安装完@nestjs/cli后,创建的模板在一定时间内nest的版本是固定的,如果需要升级nest核心库的版本,就需要升级一下模板的版本使用命令

yarn global add @nestjs/schematics

3、nest结构介绍

 

在nest.js中路径是指向根目录下,如 src/controllers表示的是根目录下的controllers文件夹

4、controller的使用

 controller的创建,可以使用命令:

nest g controller 模块名称

就会独立生成指定文件的模块,里面有 模块名称.controller.ts   模块名称.controller.spec.ts

controller的入门案例

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller() //前缀是/
export class AppController {
  // 只需要在构造函数里声明依赖,IOC窗容器会自动帮你注入实例,你直接调用就可以了
  constructor(private readonly appService: AppService) {}

  @Get() //表示的是/的响应, 控制器里面一般只用于接收参数,返回响应,并不会真正处理业务
  getHello(): string {
    return this.appService.getHello();
  }

  @Get('api')  // 访问的/api的路径进行响应的
  getApi(): Array<string> {
    return ['aa', 'bbb', 'cc']
  }
}

注意:在使用controller的时候 @controller('prefix')是表示一个控制器的修饰符,prefix表示访问路径的前缀,如@controller('user'), 那么@get('/api')时则表示访问/user/api路径时的响应, 这里的@get表示发送请求的类型是get

 controller方法的响应方式

通常来讲,controller里的响应会自动返回200的状态码,通常来讲,有两种响应方式

 注意:通常来讲安装完整项目,内部已经安装了@types/express这个模块了

import { Controller, Get, Req, Res, Query, Ip, HttpStatus, Param, Header, Post, Body, UploadedFile } from '@nestjs/common';
import { AppService } from './app.service';
import { Request, Response } from 'express'

@Controller('user') //前缀是/user
export class AppController {
  // 只需要在构造函数里声明依赖,IOC窗容器会自动帮你注入实例,你直接调用就可以了
  constructor(private readonly appService: AppService) {}

  @Get() //表示的是/user的响应, 控制器里面一般只用于接收参数,返回响应,并不会真正处理业务
  getHello(): string {
    return this.appService.getHello();
  }

  @Get('api')  // 访问的/user/api的路径进行响应的
  getApi(@Req() request: Request, @Res() response: Response, @Query('age') query: string): any {
    response.status(HttpStatus.OK).json({
      ...request.query,
      query
    })
  }

  @Get('ip')  // 获取ip
  getIp(@Req() request: Request, @Ip() ip ) {
    return [request.ip, ip]
  }

  @Get('header') // 获取host相关的信息
  @Header('token', '123')
  getHeader(@Req() request: Request) {
    return [request.headers, request.header('token')] // 这个是一个对象,如果是request,header则是一个方法
  }

  @Get('param/:id') // 或者使用:id
  getParams(@Param('id') id: string) {
    return id;
  }

  @Post('param')
  getPost(@Req() request: Request, @Body() body, @UploadedFile() files) {
    return [request.method, request.body, body, files]
  }
}

注意:如果引入了@Res() 那么return 将不起作用,需要用response.json或response.send进行返回,但是对于模板的渲染还是按原有方法操作

注意:路由跳转在response里面,  response.redirect('/test')表示跳转到路由下 

5、配置静态资源

文档地址: https://docs.nestjs.com/techniques/mvc

import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { AppModule } from './app.module';
import {join} from 'path'

async function bootstrap() {

  const app = await NestFactory.create<NestExpressApplication>(AppModule);

  //写法一
  // app.useStaticAssets('public')  //没有配置虚拟路径 访问目录 http://localhost:3000/资源名称
  //写法二
  app.useStaticAssets('public', { //设置虚拟路径   那么访问的时候就需要用  http://localhost:3000/static/资源名称
    prefix: '/static/'
  })
  //写法三
  // app.useStaticAssets(join(__dirname, '..', 'public'),{  // 注意该写法与上面写法是一样的效果
  //   prefix: '/static/', 
  // });

  await app.listen(3000);
}
bootstrap();

注意:以上是main.ts入口文件,注意__dirname是指在项目下的dist文件,所以在指定路径的时候,需要注意指向

 6、配置模板引擎

在nest.js中使用的是hbs模板引擎,但是这边使用的是ejs那么需要配置这个模板引擎

npm i ejs --save

把模板引擎配置到入口文件中

app.setBaseViewsDir(join(__dirname, '..', 'views')) // 放视图的文件
app.setViewEngine('ejs'); // 指定模板引擎
//注意: 配置模板的时候, 需要声明create的类型,否则会提示不存在该方法
  const app = await NestFactory.create<NestExpressApplication>(AppModule);

注意:上述代码指定了视图的根目录为views文件夹下,引擎是ejs, 这就需要在根目录下创建views,渲染的时候会自动读取views文件夹下的文件

import { Controller, Get, Render } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  
  ...

  @Get('test')
  @Render('test/index') //指定的是views下test文件下的index.ejs文件
  public testInfo(): {[key: string]: string|number} {
    return {name: 'ven', age: 20, img: '/static/6.jpg'} //返回需要渲染的数据
  }
  ...
}

具体的ejs的使用可以参看ejs的官方文档

7、nest.js中的服务

Nestjs 中的服务可以是 service 也可以是 provider。他们都可以通过 constructor 注 入依赖关系。服务本质上就是通过@Injectable() 装饰器注解的类。在 Nestjs 中服务相 当于 MVC 的 Model。 

服务的创建

nest g service 服务名  (推荐)
nest g provider 服务名

帮助信息
nest g --help 
import { Injectable } from '@nestjs/common';

@Injectable()
export class YfService {
    getAll() {
        return ['aa', 'bb', 'cc']
    }
}

注意:系统自动生成服务后,会在app.module.ts中的providers中自动引入 ,如果没有自动引用,则需要手动引入

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { YfController } from './yf/yf.controller';
import { AppService } from './app.service';
import { YfService } from './yf/yf.service';


@Module({
  imports: [],
  controllers: [AppController, YfController],
  providers: [AppService, YfService],
})
export class AppModule {}

那么如果在需要在controller中使用,也需要进行声明

import { Controller, Get } from '@nestjs/common';
import { YfService } from './yf.service';

@Controller('yf')
export class YfController {
    public constructor(private yfservice: YfService) {}

    @Get()
    public index() {
        return this.yfservice.getAll()
    }
}

nestjs中其他三种的服务注入

import { UseFactoryService } from './../../providers/common.service';
import {
  UseClassService,
  UseValueService,
} from '@app/providers/common.service';
import { Module } from '@nestjs/common';
import { CourseController } from './course.controller';
import { CourseService } from './course.service';

@Module({
  controllers: [CourseController],
  providers: [
    CourseService,
    // 方式一, provide对应controller里inject()括号的值,表示的是类的标识,也可以取其他名称, 使用useClass那么就直接传入类名,系统内部进行实例化
    { provide: UseClassService, useClass: UseClassService },
    // 方式二 使用useValue, 那么就需要自己进行实例化
    { provide: UseValueService, useValue: new UseValueService() },
    // 方式三 使用useFactory,那么传入的是一个回调函数,返回该实例的对象,这时这个回调里的值则是inject里的provider
    {
      provide: UseFactoryService,
      useFactory: (useClassService: UseClassService) => {
        useClassService.check();
        return new UseFactoryService();
      },
      inject: [UseClassService],
    },
  ],
})
export class CourseModule {}

对应的服务类

export class UseClassService {
  public check() {
    console.log('this is use class');
  }
}

export class UseValueService {
  public check() {
    console.log('this is use value');
  }
}

export class UseFactoryService {
  public check() {
    console.log('this is use factory');
  }
}

controller的调用

import { UseFactoryService } from './../../providers/common.service';
import {
  UseClassService,
  UseValueService,
} from '@app/providers/common.service';
import { Controller, Get, Inject } from '@nestjs/common';

@Controller('course')
export class CourseController {
  public constructor(
    // 注意这里括号时面的标识对应的是provider里的标识,也可以取其他名字,如果名字与类名一样,那么也可以进行省略
    @Inject(UseClassService) private commonService: UseClassService,
    @Inject(UseValueService) private useValueService: UseValueService,
    @Inject(UseFactoryService) private useFactoryService: UseFactoryService,
  ) {}
  @Get()
  public index() {
    this.commonService.check();
    this.useValueService.check();
    this.useFactoryService.check();
    return 'this is index';
  }
}

8、nest中cookie的使用

NestJs 中使用 Cookie 的话我们可以用 cookie-parser来实现 文档位置: https://docs.nestjs.com/techniques/cookies

安装

npm i cookie-parser
npm i -D @types/cookie-parser

引入到main.ts中

import * as cookieParser from 'cookie-parser';
app.use(cookieParser('加密字符串')); //如果需要加密,则需要配置加密字符串,建议配置,配置后可使用加密状态也可以不使用加密

controller中的使用

import { Controller, Get, Req, Res } from '@nestjs/common';
import { YfService } from './yf.service';
import { Response, Request } from 'express';

@Controller('yf')
export class YfController {
    public constructor(private yfservice: YfService) {}

    @Get()
    public index(@Res() res: Response) {
        // res.cookie('name', 'this is test name', {maxAge: 90000, httpOnly: true})   =》不加密
        //这里设置了过期时间,以及是否只有http访问
        res.cookie('name', 'this is test name', {maxAge: 90000, httpOnly: true, signed: true})  =》 加密
        res.send('this is test')
    }

    @Get('cookie')
    public getCookie(@Req() req: Request): string {
        // return req.cookies.name   =》 不加密访问
        // 加密后的cookie访问
        return req.signedCookies.name  =》 加密访问
    }
}

cookie-parser的参数说明

 

domain: 域名

expires : 过 期 时 间 ( 秒 ) , 在 设 置 的 某 个 时 间 点 后 该 Cookie 就 会 失 效 , 如 expires=Wednesday, 09-Nov-99 23:12:40 GMT maxAge: 最大失效时间(毫秒),设置在多少后失效

secure: 当 secure 值为 true 时,cookie 在 HTTP 中是无效,在 HTTPS 中才有效

path: 表示 cookie 影响到的路,如 path=/。如果路径不能匹配时,浏览器则不发送这 个 Cookie

httpOnly:是微软对 COOKIE 做的扩展。如果在 COOKIE 中设置了“httpOnly”属性,则通 过程序(JS 脚本、applet 等)将无法读取到 COOKIE 信息,防止 XSS 攻击产生

signed : 表 示 是 否 签 名 cookie, 设 为 true 会 对 这 个 cookie 签 名 , 这 样 就 需 要 用 res.signedCookies 而不是 res.cookies 访问它。被篡改的签名 cookie 会被服务器拒绝,并且 cookie 值会重置为它的原始值

设置cookie

res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true })
res.cookie('name', 'tobi', { domain: '.example.com', path: '/admin', secure: true });
res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly:true })

获取cookie

req.cookies.name

删除cookie

res.cookie('rememberme', '', { expires: new Date(0)});
res.cookie('username','zhangsan',{domain:'.ccc.com',maxAge:0,httpOnly:true});

9、nest.js debugger调试

https://github.com/nestjs/docs.nestjs.com/issues/217

打开界面

在.vscode下的launch.json里配置如下:

{
  // Use IntelliSense to learn about possible attributes.
  // Hover to view descriptions of existing attributes.
  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "attach",
      "name": "Attach NestJS WS",
      "port": 9229,
      "restart": true,
      "stopOnEntry": false,
      "protocol": "inspector"
    }
  ]
}

在项目路径下支行npm run start:debug

在vscode中启动调试界面

 

 这个时候调用chrome里的inspect监听9229端口

 

 当代码运行那指定的debugger的地点时就会启用断点,打印指定的变量了

同时也可以点击下面的界面进行调试

使用vscode进行debug

 

 生成新的launch.json的配置文件

{
    // 使用 IntelliSense 了解相关属性。
    // 悬停以查看现有属性的描述。
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "node debug",  // 可自定义debugger的名字
            "skipFiles": ["<node_internals>/**"],
            "program": "${workspaceFolder}\\dist\\main.js"  // 这个需要替换成自己目录下的项目入口文件
        }
    ]
}

 

 通过以上配置就可以进行愉快的debugger开发了

10、自定义装饰器

以设置cookie为例封装一个获取cookie的装饰器

import { createParamDecorator, ExecutionContext } from "@nestjs/common";
import { Request } from "express";

export const Cookies = createParamDecorator((data: string, ctx: ExecutionContext) => { // ctx可以获取到上下文的request和response
    const request: Request = ctx.switchToHttp().getRequest();
    return data? request.signedCookies?.[data]: request.signedCookies;
})

这样就可以在controller中进行使用了

import { Controller, Get, Res  } from '@nestjs/common';
import { Response } from 'express';
import { Cookies } from 'src/utils/decorators';
import { YfService } from './yf.service';

@Controller('yf')
export class YfController {
    public constructor(private yfservice: YfService) {}

    @Get()
    public index(@Res() res: Response) {
        res.cookie('name', 'this is test name', {maxAge: 90000, httpOnly: true, signed: true})
        res.send('this is test')
    }

    @Get('cookie')
    public getCookie(@Cookies('name') data): string {
        return data;
    }
}

 装饰器嵌套

import { applyDecorators } from '@nestjs/common';

export function Auth(...roles: Role[]) {
  return applyDecorators(
    SetMetadata('roles', roles),
    UseGuards(AuthGuard, RolesGuard),
    ApiBearerAuth(),
    ApiUnauthorizedResponse({ description: 'Unauthorized' }),
  );
}

 使用

@Get('users')
@Auth('admin')
findAllUsers() {}

 11、typescript的路径配置

 项目的路径配置,可以参看typescript知识点拾遗

posted on 2021-07-03 13:04  even_blogs  阅读(1271)  评论(1编辑  收藏  举报