• Nest.js开发


    Nest.js开发博文

    文档链接奉上:Nest.js官方文档Nest.js官方仓库

    一篇记录自己学习Nest.js心路历程的文章,以便日后回顾复习的博文。

    Nest.js安装

    这里使用Nest.js脚手架快速生成项目结构。首先,安装Next.js/cli.

    $ npm i -g @nestjs/cli
    
    • 1

    接下来使用:

    $ nest new project-name
    
    • 1

    创建一个Nest.js项目。

    若遇到下载包缓慢的问题,可以试着切换当前npm的镜像源

    • 查看当前源
    npm config get registry
    
    • 1

    http://nexus.liuheco.com/repository/npm-all/

    • 切换淘宝源
    npm config set registry https://registry.npm.taobao.org
    
    • 1
    • 切换成原始源
    npm config set registry https://registry.npmjs.org
    
    • 1

    Snipaste_2022-11-14_22-40-09

    现在我们可以看一下它的目录结构。

    src
     ├── app.controller.spec.ts
     ├── app.controller.ts
     ├── app.module.ts
     ├── app.service.ts
     └── main.ts
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    文件名说明
    app.controller.apes.ts对于基本控制器的单元测试样例
    app.controller.ts带有单个路由的基本控制器示例
    app.modules.ts应用程序的根模块
    app.service.ts带有单个方法的基本服务
    main.ts应用程序入口文件。它使用NestFactory用来创建Nest应用实例

    Nest.js帮助菜单

    可以使用

    nest 命令 --选项
    
    • 1
    //语法 
    nest g [文件类型] [文件名] [文件目录]
    
    • 1
    • 2
    • 创建模块

    nest g posts创建一个posts模块,文件目录不写,默认创建和文件名一样的post目录,在post目录下新建一个posts.module.ts

    • 创建控制器
    nest g co posts
    
    • 1
    • 创建服务类
    nest g service posts
    
    • 1

    注意创建顺序:先创建Module,再创建Controller和Service,这样创建出来的文件在Module中自动注册,反之,后创建Module,Controller和Service会被注册到外层的app.module.ts

    快速搭建项目。其他命令可以使用指令nest -h查看。

    上面的步骤可能比较繁琐,若需要生成CRUD,我们也可以使用下面的代码一键生成。

    nset g resource [名称]
    
    • 1

    Nest.js使用swagger

    nest非常友好的为开发者内置了swagger,这样你写好的接口不仅有文档,而且易于管理。

    官方文档链接:nest_OpenAI

    1. 安装
    $ npm install --save @nestjs/swagger
    
    • 1
    1. 引入
    main.ts
     
      import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
      
      const config = new DocumentBuilder()
        .setTitle('Cats example')
        .setDescription('The cats API description')
        .setVersion('1.0')
        .addTag('cats')
        .build();
      const document = SwaggerModule.createDocument(app, config);
      SwaggerModule.setup('api', app, document);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在main.ts中引入swagger,并可以对启动地址进行修改,上面的例子是在项目根路径/api中启动,你也可以根据自己的喜好修改请求地址。

    配置好后,访问http://localhost:5000/api#/

    Snipaste_2022-11-20_10-07-54

    可以看到我之前写的接口已经全部展示出来了。

    在控制器里,我们可以使用装饰器@ApiTags('默认')来对写好的接口文档进行修改。可以看到我在app.config.ts中对名称进行了配置,将其修改为默认。相应的,接口文档中的内容也发生了变化。

    import { Controller, Get } from '@nestjs/common';
    import { ApiTags } from '@nestjs/swagger';
    import { AppService } from './app.service';
    //标注这是一个控制器
    @Controller()
    @ApiTags('默认')
    export class AppController {
      //构造函数,依赖注入
      constructor(private readonly appService: AppService) { }
      //装饰器
      @Get()
      getHello(): string {
        return this.appService.getHello();
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    使用@ApiOperation({ summary: '内容' })可以修改接口的描述。

    目前我们swagger接口参数都还没有,所以我们可以给代码添加相应的装饰器来让我们的接口文档更加清晰。

    我们也可以使用@ApiProperty({ description: '帖子标题' })对后端需要的参数进行标注.

    Snipaste_2022-11-20_22-17-00

    Nest.js编写接口

    得益于Nest.js功能强大的装饰器,使得我们不必像express.js那样使用req,res去接收参数了。取而代之的是一个个封装好功能的装饰器@Body(),@Param('id')等,极大的提高了开发者的工作效率。

    import { Body, Controller, Delete, Get, Param, Post, Put, Query } from '@nestjs/common';
    import { ApiOperation, ApiProperty, ApiTags } from '@nestjs/swagger';
    import { query } from 'express';
    
    /**
     * 数据传输对象
     */
    class createPostDto {
      @ApiProperty({ description: '帖子标题' })
      title: string
      @ApiProperty({ description: '帖子内容' })
      content: string
    }
    
    /**
     * 更新帖子对象
     */
    
    class updatePostDte {
      @ApiProperty({ description: '帖子标题' })
      title: string
      @ApiProperty({ description: '帖子内容' })
      content: string
    }
    
    
    @Controller('posts')
    @ApiTags('帖子')
    export class PostsController {
      @Get()
      @ApiOperation({ summary: '获取帖子' })
      index() {
        return [
    
        ]
      }
      @Post()
      @ApiOperation({ summary: '创建帖子' })
      creat(@Body() body: createPostDto) {
        body
        return {
          success: true
        }
      }
      @Get(':id')
      @ApiOperation({ summary: '博客详情' })
      detail(@Param('id') id: string) {
        return {
          id,
          success: true
        }
    
      }
    
      @Put(':id')
      @ApiOperation({ summary: '编辑博客' })
      update(@Param('id') id: string, @Body() body: updatePostDte) {
        return {
          body,
          id,
          success: true
        }
      }
    
      @Delete(':id')
      @ApiOperation({ summary: '删除博客' })
      remove(@Param('id') id: string) {
        return {
          success: true
        }
      }
    
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

    Nest.js连接数据库

    连接mongodb数据库

    使用命令安装mongoose:

    npm install --save @nestjs/mongoose mongoose
    
    • 1

    mongodb数据库对node.js要友好一些,是因为mongodb对数据库的字段有类型以及属性的约束,借助mongoose模块也可以快速完成代码的增删改查(CRUD)操作.

    假若我们需要对ly-blogs中的user表进行操作,我们可以创建user相关的模块,服务,控制模块

    • 创建userModule
    nest g module user server
    
    • 1
    // user.module.ts
    import { Module } from '@nestjs/common';
    
    @Module({})
    export class UserModule {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 创建Controller
    nest g controller user server
    
    • 1
    // user.controller.ts
    import { Controller, Get } from '@nestjs/common';
    
    @Controller('user')
    export class UserController {
      @Get('users')
      findAll(): string {
        return "All User's Info"; // [All User's Info] 暂时代替所有用户的信息
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    findAll方法写成异步方法

    // user.controller.ts
    import { Controller, Get } from '@nestjs/common';
    
    @Controller('user')
    export class UserController {
      @Get('users')
      async findAll(): Promise<any> {
        return await this.xxx.xxx(); // 一些异步操作
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 创建provider
    nest g service user server
    
    • 1
    // user.service.ts
    import { Injectable } from '@nestjs/common';
    
    @Injectable()
    export class UserService {}
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在app.module.ts中引入mongoose的连接模块

    // app.module.ts
    import { Module } from '@nestjs/common';
    import { MongooseModule } from '@nestjs/mongoose';
    import { AppController } from './app.controller';
    import { AppService } from './app.service';
    import { UserModule } from './server/user/user.module';
    
    @Module({
      imports: [MongooseModule.forRoot('mongodb://localhost/xxx'), UserModule],
      controllers: [AppController],
      providers: [AppService]
    })
    export class AppModule {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    代码中的mongodb://localhost:/xxx就是本地数据库的地址。

    之后我们引入Mongoose分模块
    这里我们先要创建一个数据表的格式,在 src/server/user 文件夹下创建一个 user.schema.ts 文件,定义一个数据表的格式:

    // user.schema.ts
    import { Schema } from 'mongoose';
    
    export const userSchema = new Schema({
      _id: { type: String, required: true }, // 覆盖 Mongoose 生成的默认 _id
      user_name: { type: String, required: true },
      password: { type: String, required: true }
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    然后将我们的 user.module.ts 文件修改成这样:

    // user.module.ts
    import { Module } from '@nestjs/common';
    import { MongooseModule } from '@nestjs/mongoose';
    import { UserController } from './user.controller';
    import { userSchema } from './user.schema';
    import { UserService } from './user.service';
    
    @Module({
      imports: [MongooseModule.forFeature([{ name: 'Users', schema: userSchema }])],
      controllers: [UserController],
      providers: [UserService]
    })
    export class UserModule {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    连接mysql数据库

    使用命令安装typeorm:

    npm i mysql2 @nestjs/typeorm -S
    
    • 1

    为什么要用Typeorm呢?
    我们如果直接使用Node.js操作mysql提供的接口,那么编写的代码就很底层了,因为这些查询语句都需要我们自己去写。
    下面是一个插入数据的例子。

    • 例子:插入数据代码
    connect.query(`INSERT INTO posts (title, content) VALUES ('${title}', '${content}')`,
      (err,data) => {
        if (err){
          console.error(err);
        }else{
          console.log(data);
        }
      }
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    所以我们可以对每一条数据进行一个格式的约定,相当于把关系数据库的变结构映射到对象身上,这便是orm技术。

    使用后,我们的代码就会是这样:

    await connect.getRepository(Posts).save({
      title:'Nest.js入门',
      content:'文章内容描述'
    })
    
    • 1
    • 2
    • 3
    • 4

    连接方法一

    将配置文件分成开发和上线
    在项目根目录下创建两个文件.env.env_prod,分别存的是开发和生产环境下用到的配置。

    //数据库地址
    DB_HOST = localhost
    //数据库端口
    DB_PORT=3306
    //数据库登录名
    DB_USER=root
    //数据库登录密码
    DB_PASSWD=root
    //数据库名称
    DB_DATABASE=ly-blogs
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    .env.prod中的是上线要用的数据库信息,如果你的项目要上传到线上管理,为了安全性考虑,建议这个文件添加到.gitignore中。

    接着创建config文件夹(和src同级),然后再创建一个env.ts用于根据不同环境读取相应的配置文件。

    import * as fs from 'fs';
    import * as path from 'path';
    const isProd = process.env.NODE_ENV === 'production';
    
    function parseEnv() {
      const localEnv = path.resolve('./env');
      const prodEnv = path.resolve('./env.prod');
    
      if (!fs.existsSync(localEnv) && !fs.existsSync(prodEnv)) {
        throw new Error('缺少环境配置文件');
      }
    
      const filePath = isProd && fs.existsSync(prodEnv) ? prodEnv : localEnv;
      return { path:filePath };
    }
    export default parseEnv();
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    app.module.ts中连接数据库

    import { TypeOrmModule } from '@nestjs/typeorm';
    import { ConfigService, ConfigModule } from '@nestjs/config';
    import envConfig from '../config/env';
    
    @Module({
      imports: [
        ConfigModule.forRoot({ 
        isGlobal: true,  // 设置为全局
        envFilePath: [envConfig.path] 
       }),
        TypeOrmModule.forRootAsync({
          imports: [ConfigModule],
          inject: [ConfigService],
          useFactory: async (configService: ConfigService) => ({
            type: 'mysql', // 数据库类型
            entities: [],  // 数据表实体
            host: configService.get('DB_HOST', 'localhost'), // 主机,默认为localhost
            port: configService.get<number>('DB_PORT', 3306), // 端口号
            username: configService.get('DB_USER', 'root'),   // 用户名
            password: configService.get('DB_PASSWORD', 'root'), // 密码
            database: configService.get('DB_DATABASE', 'blog'), //数据库名
            timezone: '+08:00', //服务器上配置的时区
            synchronize: true, //根据实体自动创建数据库表, 生产环境建议关闭
          }),
        }),
        PostsModule,
      ],
     ...
    })
    export class AppModule {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    配置环境变量,推荐官方提供的@nestjs/config
    Nest.js_9X_配置

    连接方法二

    在根目录下创建ormconfit.json文件(与src同级),而不是将配置对象传递给forRoot()的方式

    { 
        "type": "mysql",
        "host": "localhost", 
        "port": 3306, 
        "username": "root", 
        "password": "root", 
        "database": "blog", 
        "entities": ["dist/**/*.entity{.ts,.js}"], 
        "synchronize": true  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    注意:设置 synchronize: true 不能被用于生产环境,否则您可能会丢失生产环境数据

    之后再app.module.ts中不带任何选项调用forRoot()

    import { Module } from '@nestjs/common';
    import { TypeOrmModule } from '@nestjs/typeorm';
    
    @Module({ 
        imports: [TypeOrmModule.forRoot()],
    })
    export class AppModule {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    Nest.js中的一些架构设计

    模块(Module)

    示例:app.module.ts

    每个应用程序至少有一个模块,一个根模块。根模块是Nest开始安排应用程序树的地方。如果程序不那么复杂的话,根模块可能是应用程序中唯一的模块。

    控制器(Controller)

    示例:app.controller.ts

    负责处理传入的 请求 和客户端返回的 响应

    控制器

    类比与Java的controller层,控制器的目的是接收应用的特定请求。路由机制控制哪个控制器接收哪些请求。通常,每个控制器有多个路由,不同的路由可以执行不同的操作。控制器处理前台的请求后,返回相应的处理结果返回给客户端。

    服务层(Service)

    示例:app.service.ts

    负责具体处理前端发过来的请求,处理业务逻辑,进行数据库操作,通过构造函数注入给控制器使用。

    接口统一

    设置拦截器,对返回数据进行统一格式的包装:

    1. 设置一个过滤器:
    nest g filter core/filter/http-exception
    
    • 1
    1. 过滤器代码实现:
    import {ArgumentsHost,Catch, ExceptionFilter, HttpException} from '@nestjs/common';
    
    @Catch(HttpException)
    export class HttpExceptionFilter implements ExceptionFilter {
      catch(exception: HttpException, host: ArgumentsHost) {
        const ctx = host.switchToHttp(); // 获取请求上下文
        const response = ctx.getResponse(); // 获取请求上下文中的 response对象
        const status = exception.getStatus(); // 获取异常状态码
    
        // 设置错误信息
        const message = exception.message
          ? exception.message
          : `${status >= 500 ? 'Service Error' : 'Client Error'}`;
        const errorResponse = {
          data: {},
          message: message,
          code: -1,
        };
    
        // 设置返回的状态码, 请求头,发送错误信息
        response.status(status);
        response.header('Content-Type', 'application/json; charset=utf-8');
        response.send(errorResponse);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    然后需要在main.ts中全局注册

    import { TransformInterceptor } from './core/interceptor/transform.interceptor';
    
    async function bootstrap() {
      const app = await NestFactory.create<NestExpressApplication>(AppModule);
      ...
       // 注册全局错误的过滤器
      app.useGlobalInterceptors(new TransformInterceptor());
      await app.listen(9080);
    }
    bootstrap();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    接下来对请求错误进行统一的返回,返回请求错误只需要抛出异常即可:

    import { HttpException } from "@nestjs/common";
    
    throw new HttpException('用户已经存在',401);
    
    • 1
    • 2
    • 3

    再对请求成功返回的格式进行统一的处理,可以使用nest.js拦截器来进行实现。

    接口风格

    推荐使用restful接口,即使用一个接口,完成增删改查。你可能回问后端怎么知道你时增,还是删呢?可别忘了那几种请求方式。GET,POST,PUT,DELETE。通过使用对应的装饰器(@GET,@PUT,@DELETE)来实现对不同接口的区分。

    restful版本

    restful一共有三个版本,我们默认使用第一种URL Versioning

    类型含义/解释
    Header Versioning自定义请求标头将指定版本
    Media Type Versioning请求的Accept标头将指定版本
    URL Versioning版本将在请求的url中传递

    我们可以在对应模块的controller中,通过:

    @Controller({
        path:"xxx",
        version:'1'
    })
    
    • 1
    • 2
    • 3
    • 4

    这样,我们就设置了restful版本是1

    我们也可以使用@Version来单独对某个接口进行版本控制

    +import { Controller, Get, Post, Body, Patch, Param, Delete, Version } from '@nestjs/common';
    import { TeacherService } from './teacher.service';
    import { CreateTeacherDto } from './dto/create-teacher.dto';
    import { UpdateTeacherDto } from './dto/update-teacher.dto';
    
    @Controller('teacher')
    
    export class TeacherController {
      constructor(private readonly teacherService: TeacherService) { }
    
      @Post()
      create(@Body() createTeacherDto: CreateTeacherDto) {
        return this.teacherService.create(createTeacherDto);
      }
    
    
      @Get()
    +  @Version('1')
      findAll() {
        return this.teacherService.findAll();
      }
    
      @Get(':id')
      findOne(@Param('id') id: string) {
        return this.teacherService.findOne(+id);
      }
    
      @Patch(':id')
      update(@Param('id') id: string, @Body() updateTeacherDto: UpdateTeacherDto) {
        return this.teacherService.update(+id, updateTeacherDto);
      }
    
      @Delete(':id')
      remove(@Param('id') id: string) {
        return this.teacherService.remove(+id);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    这里再给大家拓展一点状态码的知识:

    状态码具体含义常见状态码
    1xx提示信息,表示目前是协议处理的中间状态,还需要后续的操作;
    2xx成功,报文已经收到并被正确处理200,204,206
    3xx重定向,资源位置发生变动,需要客户端重新发送请求301,302,304
    4xx客户端错误,请求报文有问题,服务器无法处理400,403,404
    5xx服务器错误,服务器在处理请求时内部发生错误500,501,502,503

    例如访问路径为:http://localhost:5100/v1/teacher/123

    中间有v1证明这个接口是v1版本的

    如果需要实现这种效果,我们就需要开启这个功能。

    实现

    main.ts

    import { NestFactory } from '@nestjs/core';
    +import { VersioningType } from '@nestjs/common'
    import { AppModule } from './app.module';
    
    async function bootstrap() {
    const app = await NestFactory.create(AppModule);
    +  app.enableVersioning({
    +    type: VersioningType.URI
    +  })
      await app.listen(5100);
    }
    bootstrap();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    VersioningType一共有四种方式:

    /**
     * @publicApi
     */
    export declare enum VersioningType {
        URI = 0,
        HEADER = 1,
        MEDIA_TYPE = 2,
        CUSTOM = 3
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    枚举意义
    URL = 0在url中携带版本信息
    HEADER = 1在请求头中携带版本信息
    MEDIA_TYPE = 2请求的Accept标头将指定版本
    CUSTOM = 3自定义(客制化)

    控制器

    Request 对象代表 HTTP 请求,并具有查询字符串,请求参数参数,HTTP 标头(HTTP header) 和 正文(HTTP body)的属性(在这里阅读更多)。在多数情况下,不必手动获取它们。 我们可以使用专用的装饰器,比如开箱即用的 @Body()@Query() 。 下面是 Nest 提供的装饰器及其代表的底层平台特定对象的对照列表。

    使用方法参数装饰器,可以帮助我们快速获取参数:

    装饰器方法获取的目标参数
    @Response()res
    @Next()next
    @Session()req.session
    @Param(key?:string)req.param/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]
    @Request(),@Req()req
    @Ip()req.ip

    一,Nestjs 中使用模板引擎和配置静态资源

    1.NestJS 中配置静态资源

    官方文档:https://docs.nestjs.com/techniques/mvc

    app.useStaticAssets('public');
    
    • 1

    完整代码:

    import { NestFactory } from '@nestjs/core';
    import { AppModule } from './app.module';
    import { NestExpressApplication } from '@nestjs/platform-express';
    async function bootstrap() {
    	const app = await NestFactory.create<NestExpressApplication>(AppModule);
    	app.useStaticAssets('public');
    	await app.listen(3000);
    }
    bootstrap();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在根目录新建 public 目录,然后在目录里面保存一个图片比如 1.jpg,这样就可以通过 http://localhost:3000/1.jpg 来访问图片

    我们也可以配置虚拟目录,比如我们想通过 http://localhost:3000/static/1.jpg 来访问 public 目录里面的文件,这时候代码如下:

    app.useStaticAssets(join(__dirname, '..', 'public'),{
    	prefix: '/static/', //设置虚拟路径
    });
    
    • 1
    • 2
    • 3
    import { NestFactory } from '@nestjs/core';
    import { AppModule } from './app.module';
    import { NestExpressApplication } from '@nestjs/platform-express';
    import {join} from 'path';
    async function bootstrap() {
    	const app = await NestFactory.create<NestExpressApplication>(AppModule);
    	// app.useStaticAssets('public');
    	app.useStaticAssets(join(__dirname, '..', 'public'),{
    		prefix: '/static/', //设置虚拟路径
    	});
    	await app.listen(3000);
    }
    bootstrap();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    2.NestJs中配置模板引擎

    官方文档:https://docs.nestjs.com/techniques/mvc

    1. 安装对应的模板引擎 比如ejs

      cnpm i ejs --save
      
      • 1
    2. 配置模板引擎

      app.setBaseViewsDir(join(__dirname, '..', 'views')) // 放视图的文件
      app.setViewEngine('ejs');
      
      • 1
      • 2

      完整代码

      import { NestFactory } from '@nestjs/core';
      import { AppModule } from './app.module';
      import { NestExpressApplication } from '@nestjs/plat
      import {join} from 'path';
      async function bootstrap() {
      	const app = await NestFactory.create<NestExpressApplication>(AppModule);
      	// app.useStaticAssets('public');
      	app.useStaticAssets(join(__dirname, '..', 'public'),{
      		prefix: '/static/', //设置虚拟路径
      	});
      	app.setBaseViewsDir(join(__dirname, '..', 'views')) // 放视图的文件
      	app.setViewEngine('ejs');
      	await app.listen(3000);
      }
      bootstrap();
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
    3. 渲染页面

      import { Get, Controller, Render } from '@nestjs/common';
      @Controller()
      export class AppController {
          @Get()
          @Render('index')
          root() {
          	return { message: 'Hello world!' };
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    4. 项目根目录新建views目录,然后新建index.ejs

      
      
      
          
          
          
          Document
      
      
          这是 ejs 演示代码
      
      <%=message%>
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15

    3.NestJs中模板引擎结合Post演示,以及路由跳转

    import { Controller, Get, Post, Body,Response, Render} from '@nestjs/common';
    @Controller('user')
    export class UserController {
        @Get()
        @Render('default/user')
        index(){
        	return {"name":"张三"};
        }
        @Post('doAdd')
        doAdd(@Body() body,@Response() res){
        	console.log(body);
        	res.redirect('/user'); //路由跳转
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    模板

    DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Documenttitle>
    <link rel="stylesheet" href="/static/base.css">
    head>
    <body>
        <img src="/static/1.png" alt="" />
        <br>
        <form action="/user/doAdd" method="post">
        <input type="text" name="username" placeholder="请输入用户名" />
        <br>
        <br>
        <input type="text" name="age" placeholder="年龄" />
        <br>
        <br>
        <input type="submit" value="提交">
    form>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    二,NestJs中的服务

    1.关于NestJs中的服务

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

    2.NestJS 中创建和使用

    1. 创建服务

      nest g servic
      
      • 1
    2. 使用服务

      • 需要在根模块引入并配置
      • 在用到的地方引入并配置

    三,Nestjs 中的 Cookie

    一,Cookie简介

    • HTTP 是无状态协议。简单地说,当你浏览了一个页面,然后转到同一个网站的另一个页 面,服务器无法认识到这是同一个浏览器在访问同一个网站。每一次的访问,都是没有任何 关系的。如果我们要实现多个页面之间共享数据的话我们就可以使用 Cookie 或者 Session 实 现

    • cookie 是存储于访问者的计算机中的变量。可以让我们用同一个浏览器访问同一个域 名的时候共享数据。

    二,cookie特点

    ● cookie 保存在浏览器本地

    ● 正常设置的 cookie 是不加密的,用户可以自由看到;

    ● 用户可以删除 cookie,或者禁用它

    ● cookie 可以被篡改

    ● cookie 可以用于攻击

    ● cookie 存储量很小。未来实际上要被 localStorage 替代,但是后者

    三,NestJs中使用Cookie

    NestJs 中使用 Cookie 的话我们可以用 cookie-parser 来实现

    https://docs.nestjs.com/techniques/cookies#cookies

    1. 安装

      cnpm i cookie-parser --save
      npm i -D @types/cookie-parser
      
      • 1
      • 2
    2. 在main.ts中引入’cookie-parser’

      import * as cookieParser from 'cookie-parser'
      
      • 1
    3. 在main.ts中配置中间件

      app.use(cookieParser());
      
      • 1
    4. 设置cookie

      res.cookie("name",'zhangsan',{maxAge: 900000, httpOnly: true});
      //HttpOnly 默认 false 不允许 客户端脚本访
      
      • 1
      • 2
    5. 获取Cookies

      @Get('getCookies')
      getCookies(@Request() req){
      	return req.cookies.name;
      }
      
      • 1
      • 2
      • 3
      • 4

    四,NestJs中Cookie中的一些参数

    属性类型描述
    domainString域名
    expiresDate过 期 时 间 ( 秒 ) , 在 设 置 的 某 个 时 间 点 后 该 Cookie 就 会 失 效 , 如 expires=Wednesday, 09-Nov-99 23
    maxAgeString最大失效时间(毫秒),设置在多少后失效
    secureBoolean当 secure 值为 true 时,cookie 在 HTTP 中是无
    pathString表示 cookie 影响到的路,如 path=/。如果路径不能匹配时,浏览器则不发送这 个 Cookie
    httpOnlyBoolean是微软对 COOKIE 做的扩展。如果在 COOKIE 中设置了“httpOnly”属性,则通 过程序(JS 脚本、applet 等)将无法读取到 COOKIE 信息,防止 XS
    signedBoolean表 示 是 否 签 名 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 }
    
    • 1
    • 2
    • 3
    • 4

    获取cookie

    req.cookies.name
    
    • 1

    删除cookie

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

    五,NestJs中的cookie加密

    1. 配置中间件的时候需要传参

      app.use(cookieParser('123456'));
      
      • 1
    2. 设置cookie的时候配置sigined属性

      res.cookie('userinfo','hahaha',{domain:'.ccc.com',maxAge:900000,httpOnly:true,signed:true});
      
      • 1
    3. siginedCOokies调用设置的cookie

      console.log(req.signedCookies);
      
      • 1

    四,Nestjs 中的模块

    一,关于NestJs中的模块

    模块是具有 @Module() 装饰器的类。 @Module() 装饰器提供了元数据,Nest 用它来组织应用 程序结构。

    每个 Nest 应用程序至少有一个模块,即根模块。根模块是 Nest 开始安排应用程序树的地 方。事实上,根模块可能是应用程序中唯一的模块,特别是当应用程序很小时,但是对于大型程序来说这是没有意义的。在大多数情况下,您将拥有多个模块,每个模块都有一组紧密 相关的功能。

    @module() 装饰器接受一个描述模块属性的对象:

    属性description
    providers由 Nest 注入器实例化的提供者,并且可以 至少在整个模块中共享
    controller必须创建的一组控制器
    imports导入模块的列表,这些模块导出了此模块中所需的提供者
    exports由本模块提供并应用在其他模块中可用的提供者子集

    二,NestJs中创建模块

    nest g module [模块名]
    
    • 1

    Snipaste_2023-02-10_09-13-11

    三,NestJs中的共享模块

    实际上,每个模块都是一个共享模块。一旦创建就能被任意模块重复使用。假设我们将在几个模块之间共享 CatsService 实例。 我们需要把 CatsService 放到 exports 数组中,如下所示:

    import { Module } from '@nestjs/common';
    import { CatsController } from './cats.controller';
    import { CatsService } from './cats.s
    @Module({
    controllers: [CatsController],
    providers: [CatsService],
    exports: [CatsService]
    })
    export class CatsModule {
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    要想让其他模块使用本模块中的方法,必须先进行暴露,只有暴露了,使用该模块的其他模块才可以使用该模块里面的一些方法。

    五,NestJs中的Session

    一,Session简介

    session 是另一种记录客户状态的机制,不同的是 Cookie 保存在客户端浏览器中,而 session 保存在服务器上。

    二,Session的工作流程

    当浏览器访问服务器并发送第一次请求时,服务器端会创建一个 session 对象,生成一个类似于 key,value 的键值对,然后将 key(cookie)返回到浏览器(客户)端,浏览器下次再访问时,携带 key(cookie), 找到对应的 session(value)。 客户的信息都保存在 session 中

    三,NestJs中express-session的使用

    1. 安装express-session

      npm i express-sess
      npm i -D @types/express-session --save
      
      • 1
      • 2
    2. 引入express-session

      import * as session from 'express-session';
      
      • 1
    3. 设置官方文档提供的中间件

      app.use(session({ secret: 'keyboard cat', cookie: { maxAge: 60000 	}}))
      
      • 1
    4. 使用

      设置值 req.session.username = "张三";
      获取值 req.session.usernam
      
      • 1
      • 2

    四,express-session的常用参数:

    app.use(session({
        secret: '12345',
        name: 'name',
        cookie: {maxAge: 60000},
        resave: false,
        saveUninitialized: true
    }));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    参数作用
    secret一个 String 类型的字符串,作为服务器端生成 session 的签名。
    name返回客户端的 key 的名称,默认为 connect.sid,也可以自己设置
    resave强制保存 session 即使它并没有变化,。默认为 true。建议设置成 false。 don’t save session if unmodifie
    saveUninitialized强制将未初始化的 session 存储。当新建了一个 session 且未设定属性或值时,它就处于 未初始化状态。在设定一个 cookie 前,这对于登陆验证,减轻服务端存储压力,权限控制是有帮助的。(默 认:true)。建议手动添加。
    cookie设置返回到前端 key 的属性,默认值为{ path: ‘/’, httpOnly: true, secure: false, maxAge: null }。
    rolling在每次请求时强行设置 cookie,这将重置 cookie 过期时间(默认:false)

    五,express-session的常用方法:

    req.session.destroy(function(err) { /*销毁 session*/
    })
    req.session.username='张三'; //设置 session
    req.session.username //获取 session
    req.session.cookie.maxAge=0; //重新设置 cookie 的过期时
    
    • 1
    • 2
    • 3
    • 4
    • 5
  • 相关阅读:
    基于openwrt创建应用程序教程
    AWS SAP-C02 考试指南
    第1章 Linux基础知识 -- 了解Linux历史和linux目录结构
    【数据结构】插入排序:直接插入排序、折半插入排序、希尔排序的学习知识总结
    Python每日一练(牛客新题库)——第21天:内置函数
    Pandas-pd.to_numeric函数知识点总结
    stm32的ADC采样率如何通过Time定时器进行控制
    docker离线安装和使用
    JsonUtility和LitJson的特点与区别
    Mysql学习之——增删改查语句
  • 原文地址:https://blog.csdn.net/liyuchenii/article/details/127974709