NestJS笔记

概述:本篇文章是NestJS笔记,包括了Nest的基本使用、连接数据库、日志操作。

一、NestJS 官方 cli

1. 快速上手

1.1 全局安装 cli

npm i -g @nestjs/cli

1.2 创建项目

nest new [项目名]

1.3 查看项目命令

  1. 查看 nest 命令
Usage: nest <command> [options]

Options:
  -v, --version                                   Output the current version. # 输出当前版本。
  -h, --help                                      Output usage information. # 输出使用情况信息。

Commands:
  new|n [options] [name]                          Generate Nest application. # 生成Nest应用程序。
  build [options] [app]                           Build Nest application. # 构建Nest应用程序。
  start [options] [app]                           Run Nest application. # 运行Nest应用程序。
  info|i                                          Display Nest project details. # 显示Nest项目详细信息。
  add [options] <library>                         Adds support for an external library to your project. # 将对外部库的支持添加到项目中。
  generate|g [options] <schematic> [name] [path]  Generate a Nest element. # 生成Nest元素。
    Schematics available on @nestjs/schematics collection: # 可以在@nestjs/示意图集合中找到示意图:
      ┌───────────────┬─────────────┬──────────────────────────────────────────────┐
      │ name          │ alias       │ description                                  │
      │ application   │ application │ Generate a new application workspace         │
      │ class         │ cl          │ Generate a new class                         │
      │ configuration │ config      │ Generate a CLI configuration file            │
      │ controller    │ co          │ Generate a controller declaration            │
      │ decorator     │ d           │ Generate a custom decorator                  │
      │ filter        │ f           │ Generate a filter declaration                │
      │ gateway       │ ga          │ Generate a gateway declaration               │
      │ guard         │ gu          │ Generate a guard declaration                 │
      │ interceptor   │ itc         │ Generate an interceptor declaration          │
      │ interface     │ itf         │ Generate an interface                        │
      │ library       │ lib         │ Generate a new library within a monorepo     │
      │ middleware    │ mi          │ Generate a middleware declaration            │
      │ module        │ mo          │ Generate a module declaration                │
      │ pipe          │ pi          │ Generate a pipe declaration                  │
      │ provider      │ pr          │ Generate a provider declaration              │
      │ resolver      │ r           │ Generate a GraphQL resolver declaration      │
      │ resource      │ res         │ Generate a new CRUD resource                 │
      │ service       │ s           │ Generate a service declaration               │
      │ sub-app       │ app         │ Generate a new application within a monorepo │
      └───────────────┴─────────────┴──────────────────────────────────────────────┘
  1. 查看 nest g 的命令
nest g --help
Usage: nest generate|g [options] <schematic> [name] [path]

Generate a Nest element.
  Schematics available on @nestjs/schematics collection:
    ┌───────────────┬─────────────┬──────────────────────────────────────────────┐
    │ name          │ alias       │ description                                  │
    │ application   │ application │ Generate a new application workspace         │
    │ class         │ cl          │ Generate a new class                         │
    │ configuration │ config      │ Generate a CLI configuration file            │
    │ controller    │ co          │ Generate a controller declaration            │
    │ decorator     │ d           │ Generate a custom decorator                  │
    │ filter        │ f           │ Generate a filter declaration                │
    │ gateway       │ ga          │ Generate a gateway declaration               │
    │ guard         │ gu          │ Generate a guard declaration                 │
    │ interceptor   │ itc         │ Generate an interceptor declaration          │
    │ interface     │ itf         │ Generate an interface                        │
    │ library       │ lib         │ Generate a new library within a monorepo     │
    │ middleware    │ mi          │ Generate a middleware declaration            │
    │ module        │ mo          │ Generate a module declaration                │
    │ pipe          │ pi          │ Generate a pipe declaration                  │
    │ provider      │ pr          │ Generate a provider declaration              │
    │ resolver      │ r           │ Generate a GraphQL resolver declaration      │
    │ resource      │ res         │ Generate a new CRUD resource                 │
    │ service       │ s           │ Generate a service declaration               │
    │ sub-app       │ app         │ Generate a new application within a monorepo │
    └───────────────┴─────────────┴──────────────────────────────────────────────┘

Options:
  -d, --dry-run                      Report actions that would be taken without writing out results. # 测试文件
  -p, --project [project]            Project in which to generate files.
  --flat                             Enforce flat structure of generated element.
  --no-flat                          Enforce that directories are generated.
  --spec                             Enforce spec files generation. (default: true)
  --spec-file-suffix [suffix]        Use a custom suffix for spec files.
  --skip-import                      Skip importing (default: false)
  --no-spec                          Disable spec files generation. # -g --no-spec 不创建测试文件
  -c, --collection [collectionName]  Schematics collection to use.
  -h, --help                         Output usage information.

例如:

  1. 生成一个user模块
nest g module user
  1. 生成一个user模块的controller
nest g controller user

2.创建第一个 NestJS 应用

项目目录结构

src
 |—— app.controller.spec.ts # 测试文件
 |—— app.controller.ts # 控制器:书写路由
 |—— app.module.ts # 根模块
 |—— app.service.ts # 书写逻辑部分
 |—— main.ts # 入口文件

3. 编写接口

  1. app.controller.ts文件中:
import { Controller, Get } from "@nestjs/common";
import { AppService } from "./app.service";

// 该路由模块的基础路径
@Controller("api")
export class AppController {
  constructor(private readonly appService: AppService) {}

  // get 请求路径:/api/app
  @Get("app")
  getApp(): string {
    return "hello nestjs";
  }
}
  1. main.ts文件中,添加全局统一请求路径

使用app.setGlobalPrefix(xx),例如:app.setGlobalPrefix('/api/v1'),请求的路径就是:/api/v1/xxx

import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.setGlobalPrefix("/api/v1");
  await app.listen(3000);
}
bootstrap();
  1. 将逻辑拆分到xx.service.ts

user.service.ts为例:

  • user.service.ts中:
import { Injectable } from "@nestjs/common";

@Injectable()
export class UserService {
  getUser() {
    return {
      code: 200,
      msg: "success",
      data: [],
    };
  }
}
  • user.controller.ts中:

将 userService 注入到容器中

constructor(private userService: UserService) {}

import { Controller, Get } from "@nestjs/common";
import { UserService } from "./user.service";

@Controller("user")
export class UserController {
  // 将userService注入到容器中(依赖注入)
  // 写法1:语法糖
  constructor(private userService: UserService) {}
  // 写法2:等价写法1
  userService: UserService;
  constructor() {
    this.userService = new UserService();
  }

  @Get("user")
  getUser(): Object {
    return this.userService.getUser();
  }
}

二、请求参数

使用注解的方式获取请求的参数

1. params参数

请求路径:http://127.0.0.1:3000/api/v1/user/1

  • 写法1:@Param()
@Get('/:id')
findParmas(@Param() params: any) {
    console.log(params);
    return 'success'
}

打印结果为:

{ id: '1' }
  • 写法2:@Param('id')
@Get('/:id')
findParmas(@Param('id') id: any) {
    console.log(id);
    return 'success'
}

打印结果为:

1

2. query参数

请求路径:http://127.0.0.1:3000/api/v1/user?username=wifi&password=123

@Get()
findUser(@Query() query: any) {
    console.log(query);       
    return 'success'
}

打印结果为:

{ username: 'wifi', password: '123' }

3. body参数

请求路径:

POST /api/v1/user/body HTTP/1.1
Host: 127.0.0.1:3000
User-Agent: Apifox/1.0.0 (https://apifox.com)
Accept: */*
Host: 127.0.0.1:3000
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded

username=wifi&password=123
@Post('/body')
getBody(@Body() body: any) {
    console.log(body);
    return 'success'
}

打印结果为:

{ username: 'wifi', password: '123' }

4. headers参数

请求路径:

添加了一个Authorization

GET /api/v1/user HTTP/1.1
Host: 127.0.0.1:3000
Authorization: ea341c68-0939-493f-824c-53bd76792a61
Host: 127.0.0.1:3000
@Get()
getHeaders(@Headers() headers: any) {
    console.log(headers);       
    return 'success'
}

打印结果为:

{
  authorization: '6e8b0c8e-b9f4-4a22-a289-c15f43f0a705',
  host: '127.0.0.1:3000'
}

5. req参数(所有参数)

@Get()
getReq(@Request() req: any) {
    console.log(req);       
    return 'success'
}

会打印请求对象所有信息

三、多环境配置两种方案

1. dotenv

支持.env文件

1.1 安装dotenv

npm i dotenv

1.2 使用

  • 新建.env文件
TOKEN=1234567890
USERNAME=wifi
PASSWORD=123456
  • index.js使用
require("dotenv").config();
console.log(process.env);

打印的结果为:

{
	...
  npm_config_prefix: '/usr/local',
  npm_node_execpath: '/usr/local/bin/node',
  TOKEN: '1234567890',
  USERNAME: 'wifi',
  PASSWORD: '123456'
}

2. config

支持yamlymljson文件

2.1 安装config

npm i config

2.2 json 文件格式(基本使用)

  • 新建config目录,在config目录下新建default.json
// config/default.json
{
  "token": "my-token",
  "db": {
    "host": "localhost",
    "port": 27017,
    "username": "root",
    "password": "123456"
  }
}
  • index.js使用
const config = require("config");
// 该db需要跟default.json的db对应
const dbConfig = config.get("db");
console.log(dbConfig);

输出结果为:

{
  host: 'localhost',
  port: 27017,
  username: 'root',
  password: '123456'
}

2.3 json 文件格式(进阶使用)

  • config目录下新建production.json文件(文件名不可自定义)
{
  "db": {
    "host": "www.wifi.com",
    "port": 8080
  }
}
  • 在终端中切换 node 运行环境,将环境换为production

winset NODE_ENV=production

macexport NODE_ENV=production

这时候他会合并default.jsonproduction.json的字段,以production.json为准,打印结果为:

{
  host: 'www.wifi.com',
  port: 8080,
  username: 'root',
  password: '123456'
}

2.4 yaml 文件格式

需要安装js-yaml,也会自动合并字段

  • default.yamlproduction.yaml
# default.yaml
token: my-token
db:
  host: localhost
  port: 27017
  username: root
  password: 123456
# production.yaml
db:
  host: www.wifi.com
  port: 8080
  • 运行index.js

结果为:

{
  host: 'www.wifi.com',
  port: 8080,
  username: 'root',
  password: 123456
}

2.5 corss-env 使用

可以在package.json中配置脚本环境

  • package.json
"scripts": {
  "pro": "cross-env NODE_ENV=production nodemon index.js", // production环境
  "dev": "cross-env NODE_ENV=development nodemon index.js" // development环境
}
  • config目录下三个文件

    • default.yaml
    # default
    token: my-token
    db:
      host: localhost
      port: 27017
      username: root
      password: 123456
    
    • development.yaml
    # development.yaml
    db:
      host: 127.0.0.1
      port: 7979
    
    • production.yaml
    # production
    db:
      host: www.wifi.com
      port: 8080
    
  • 执行脚本

    • npm run dev
    {
      host: '127.0.0.1',
      port: 7979,
      username: 'root',
      password: 123456
    }
    
    • npm run pro
    {
      host: 'www.wifi.com',
      port: 8080,
      username: 'root',
      password: 123456
    }
    

3. env 与 config 的优缺点

env适合简单配置,config适合嵌套复杂配置

四、@nestjs/config(在 nest 中使用官方 config 设置模块)

1. 安装

npm i @nestjs/config

2. 基本使用

2.1 user 模块分别导入

  • app.module.ts中:
import { Module } from "@nestjs/common";
import { UserModule } from "./user/user.module";
import { ConfigModule } from "@nestjs/config";

@Module({
  imports: [
    UserModule,
    // forRoot 读取根目录下的.env文件
    // 在app模块的controllers和servivice中都可以使用,而在在跨模块的user模块中无法使用
    ConfigModule.forRoot(),
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}
  • user.controller.ts中:
import { Controller, Get } from "@nestjs/common";
import { UserService } from "./user.service";
import { ConfigService } from "@nestjs/config";

@Controller("user")
export class UserController {
  constructor(
    private userService: UserService,
    private configService: ConfigService
  ) {}

  @Get("user")
  getUser(): Object {
    let db = this.configService.get("DB");
    console.log(db);
    return this.userService.getUser();
  }
}

这个时候console.log(db)会报错,需要在user.module.ts中进行导入:

import { Module } from "@nestjs/common";
import { UserController } from "./user.controller";
import { UserService } from "./user.service";
import { ConfigModule } from "@nestjs/config";

@Module({
  imports: [ConfigModule.forRoot()],
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}

这个时候就可以正常读取根目录下的.env文件了

2.2 user 模块全局导入

  • app.module.ts中:

ConfigModule.forRoot()中配置isGlobal: true,表示全局使用

import { Module } from "@nestjs/common";
import { UserModule } from "./user/user.module";
import { ConfigModule } from "@nestjs/config";

@Module({
  imports: [
    UserModule,
    ConfigModule.forRoot({
      // 配置isGlobal,可在全局使用
      isGlobal: true,
    }),
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}
  • user.controller.ts中:
import { Controller, Get } from "@nestjs/common";
import { UserService } from "./user.service";
import { ConfigService } from "@nestjs/config";

@Controller("user")
export class UserController {
  constructor(
    private userService: UserService,
    private configService: ConfigService
  ) {}

  @Get("user")
  getUser(): Object {
    let db = this.configService.get("DB");
    console.log(db);
    return this.userService.getUser();
  }
}

3. 进阶使用(env 文件)

3.1 envFilePath结合croess-env

  • app.module.ts
import { Module } from '@nestjs/common';
import { UserModule } from './user/user.module';
import { ConfigModule } from '@nestjs/config';

const envFilePath = `.env.${process.env.NODE_ENV}`

@Module({
  imports: [
    UserModule,
    ConfigModule.forRoot({
      // 配置isGlobal,可在全局使用
      isGlobal: true,
      // 加载指定路径的.env文件
      envFilePath: envFilePath
    })
  ],
  controllers: [],
  providers: [],
})
  • package.json
"scripts": {
  "start:dev": "cross-env NODE_ENV=development nest start --watch",
  "start:prod": "cross-env NODE_ENV=production node"
}

3.2 load结合dotenv使用

import { Module } from "@nestjs/common";
import { UserModule } from "./user/user.module";
import { ConfigModule } from "@nestjs/config";
import * as dotenv from "dotenv";

const envFilePath = `.env.${process.env.NODE_ENV}`;

@Module({
  imports: [
    UserModule,
    ConfigModule.forRoot({
      // 配置isGlobal,可在全局使用
      isGlobal: true,
      // 加载指定路径的.env文件
      envFilePath: envFilePath,
      // 读取.env文件作为公共配置文件
      load: [() => dotenv.config({ path: ".env" })],
    }),
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

4. 进阶使用(yaml 文件)

嵌套配置 yaml 文件读取

  • 安装js-yaml@types/js-yaml

js-yaml用于解析 yaml 文件,@types/js-yaml存放 js-yaml 文件类型声明

npm i js-yaml
npm i -D @types/js-yaml
  • 在根目录下新建config目录,在config目录下新建config.ymlconfig.development.ymlconfig.production.yml文件
# config.yml
db:
  mysql1:
    host: 127.0.0.1
    name: mysql-dev
    post: 3306

  mysql2:
    host: 127.0.0.2
    name: mysql-dev
    post: 3307
# config.development.yml
db:
  mysql1:
    name: mysql-dev1

  mysql2:
    name: mysql-dev2
# config.production.yml
db:
  mysql1:
    name: mysql-prod1

  mysql2:
    name: mysql-prod2
  • src目录下新建configuration.ts文件

安装lodash,用于拼接文件

npm i lodash
import { readFileSync } from "fs";
import * as yaml from "js-yaml";
import { join } from "path";
import * as _ from "lodash";

const YAML_COMMON_CONFIG_FILENAME = "config.yml";

// 读取yml文件路径
const filePath = join(__dirname, "../config", YAML_COMMON_CONFIG_FILENAME);
const envPath = join(
  __dirname,
  "../config",
  `config.${process.env.NODE_ENV}.yml`
);

const commonConfig = yaml.load(readFileSync(filePath, "utf-8"));
const envConfig = yaml.load(readFileSync(envPath, "utf-8"));

// 因为ConfigModule有一个load方法,需要传入一个函数
export default () => _.merge(commonConfig, envConfig);
  • app.module.ts
import { Module } from "@nestjs/common";
import { UserModule } from "./user/user.module";
import { ConfigModule } from "@nestjs/config";
import Configuration from "./configuration";

@Module({
  imports: [
    UserModule,
    ConfigModule.forRoot({
      isGlobal: true,
      load: [Configuration],
    }),
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}
  • user.controller.ts
import { Controller, Get } from "@nestjs/common";
import { UserService } from "./user.service";
import { ConfigService } from "@nestjs/config";

@Controller("user")
export class UserController {
  constructor(
    private userService: UserService,
    private configService: ConfigService
  ) {}

  @Get("user")
  getUser(): Object {
    let db = this.configService.get("db");
    console.log(db);

    return this.userService.getUser();
  }
}

执行npm run start:prodconsole.log(db);的结果为:

# 环境不同,字段已经进行合并和覆盖
{
  mysql1: { host: '127.0.0.1', name: 'mysql-prod1', post: 3306 },
  mysql2: { host: '127.0.0.2', name: 'mysql-prod2', post: 3307 }
}

5. 进阶使用:配置文件参数校验 Joi

  • 安装
npm i joi
  • 使用
import { Module } from "@nestjs/common";
import { UserModule } from "./user/user.module";
import { ConfigModule } from "@nestjs/config";
import * as Joi from "joi";

@Module({
  imports: [
    UserModule,
    ConfigModule.forRoot({
      // 环境参数校验
      validationSchema: Joi.object({
        // 默认3306,选择范围:3306、3309(只能是这两个)
        DB_PORT: Joi.number().default(3306).valid(3306, 3309),
        NODE_ENV: Joi.string()
          .valid("production", "development")
          .default("development"),
        DB_URL: Joi.string().domain(),
        DB_HOST: Joi.string().ip(),
      }),
    }),
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

五、数据库使用(TypeORM)

1. ORM 是什么

ORM:对象关系映射,其主要作用是在编程中,把面向对象的概念跟数据库中的概念对应起来。举例:
定义一个对象,那就对应着一张表,这个对象的实例,就对应着表中的一条记录。

2. 在 nest 中使用 TypeORM(基础)

  • 安装
npm i @nestjs/typeorm typeorm mysql2
  • app.module.ts中使用
import { Module } from "@nestjs/common";
import { UserModule } from "./user/user.module";
import { TypeOrmModule } from "@nestjs/typeorm";

@Module({
  imports: [
    UserModule,
    TypeOrmModule.forRoot({
      type: "mysql",
      host: "localost",
      port: 3306,
      username: "root",
      password: "123456",
      database: "nest_test",
      entities: [],
      // 同步本地的schema与数据库 => 初始化的时候去使用
      synchronize: true,
      // 日志等级
      logging: ["error"],
    }),
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

3. 在 nest 中使用 TypeORM(结合 env 进阶使用)

  • app.module.ts中使用
import { Module } from "@nestjs/common";
import { UserModule } from "./user/user.module";
import { TypeOrmModule, TypeOrmModuleOptions } from "@nestjs/typeorm";
import { ConfigModule, ConfigService } from "@nestjs/config";

@Module({
  imports: [
    UserModule,
    ConfigModule.forRoot({
      isGlobal: true,
    }),
    // 进阶写法 异步导入
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService], // 注入到useFactory中
      useFactory: (configService: ConfigService) =>
        ({
          type: "mysql",
          host: configService.get("DB_HOST"),
          port: 3306,
          username: "nest_test",
          password: "123456",
          database: "nest_test",
          entities: [],
          synchronize: true,
          logging: ["error"],
        } as TypeOrmModuleOptions),
    }),
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

4. 使用 TypeORM 创建多个实体

  • src/user目录下新建user.entity.ts文件
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";

@Entity()
export class User {
  // 主键
  @PrimaryGeneratedColumn()
  id: number;

  // 列
  @Column({ unique: true }) // unique: true 唯一数据
  username: string;

  @Column()
  password: string;
}
  • app.module.ts

配置TypeOrmModule.forRootAsyncentities

import { User } from "./user/user.entity";

TypeOrmModule.forRootAsync({
  imports: [ConfigModule],
  inject: [ConfigService], // 注入到useFactory中
  useFactory: (configService: ConfigService) =>
    ({
      type: "mysql",
      host: configService.get("DB_HOST"),
      port: 3306,
      username: "nest_test",
      password: "123456",
      database: "nest_test",
      // 配置
      entities: [User],
      synchronize: true,
      logging: ["error"],
    } as TypeOrmModuleOptions),
});

5. 一对一关系

需要有@JoinColumn(),建立表的关联

  • 新建profile.entity.ts文件
import {
  Column,
  Entity,
  JoinColumn,
  OneToOne,
  PrimaryGeneratedColumn,
} from "typeorm";
import { User } from "./user.entity";

@Entity()
export class Profile {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  gender: number;

  @Column()
  photo: string;

  @Column()
  address: string;

  // 使用函数是为了方便调用
  @OneToOne(() => User)
  // JoinColumn告诉TypeORM,在哪个表格去创建关联的字段
  // 默认生成userId字段,通过user表的主键和表名进行拼接。通过name,可以自定义关联字段名
  @JoinColumn({ name: "uid" })
  // 注入到user字段,返回的是user实体
  user: User;
}
  • user.entity.ts
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  username: string;

  @Column()
  password: string;

  // 将profile注入到user实体上
  @OneToOne(() => Profile, (profile) => profile.user)
  profile: Profile;
}
  • app.module.ts中进行导入
import { Module } from "@nestjs/common";
import { UserModule } from "./user/user.module";
import { TypeOrmModule, TypeOrmModuleOptions } from "@nestjs/typeorm";
import { ConfigModule, ConfigService } from "@nestjs/config";
import { User } from "./user/user.entity";
import { Profile } from "./user/profile.entity";
import { Roles } from "./roles/roles.entity";
import { Logs } from "./logs/logs.entity";

@Module({
  imports: [
    UserModule,
    ConfigModule.forRoot({
      isGlobal: true,
    }),
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (configService: ConfigService) =>
        ({
          type: "mysql",
          host: configService.get("DB_HOST"),
          port: 3306,
          username: "nest_test",
          password: "123456",
          database: "nest_test",
          // 导入的entity
          entities: [User, Profile, Roles, Logs],
          synchronize: true,
          logging: ["error"],
        } as TypeOrmModuleOptions),
    }),
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

6. 一对多关系

需要有@JoinColumn(),建立表的关联

  • 需要在app.module.ts中的entities中导入(省略)

  • user.entity.ts

import { Logs } from "src/logs/logs.entity";
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  username: string;

  @Column()
  password: string;

  // 一对多
  // 建立与数据库之间的关联关系,将查询出来的数据塞入logs属性中
  @OneToMany(() => Logs, (logs) => logs.user)
  logs: Logs[];
}
  • logs.entity.ts
import { User } from "src/user/user.entity";
import { Column, Entity, ManyToOne, JoinColumn, PrimaryGeneratedColumn } from "typeorm";

@Entity()
export class Logs {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  path: string;

  @Column()
  method: string;

  @Column()
  data: string;

  @Column()
  result: number;

  // 多对一
  @ManyToOne(() => User, (user) => user.logs) // 参数2:参数是user实体
  @JoinColumn()
  user: User;
}

7. 多对多关系

使用@JoinTable建立中间表

  • 需要在app.module.ts中的entities中导入(省略)

  • user.entity.ts

import { Logs } from "src/logs/logs.entity";
import {
  Column,
  Entity,
  JoinTable,
  ManyToMany,
  OneToMany,
  PrimaryGeneratedColumn,
} from "typeorm";
import { Roles } from "../roles/roles.entity";

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  username: string;

  @Column()
  password: string;

  @ManyToMany(() => Roles, (roles) => roles.users)
  @JoinTable({ name: "user_roles" })
  roles: Roles[];
}
  • roles.entity.ts
import { User } from "src/user/user.entity";
import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from "typeorm";

@Entity()
export class Roles {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @ManyToMany(() => User, (user) => user.roles)
  users: User[];
}

8. 旧项目已有数据库,用 TypeORM 生成对接

typeorm-model-generator生成器

  • 安装
npm i -D typeorm-model-generator
  • package.json中新增脚本
# typeorm-model-generator -h 地址 -p 端口号 -d 数据库名 -u 用户名 -x 密码 -e 数据库类型 -o 输出路径
typeorm-model-generator -h 127.0.0.1 -p 3306 -d nest_test -u root -x 123456 -e mysql -o .

9. CRUD 操作

9.1 基本使用

  • user.module.ts导入
import { Module } from "@nestjs/common";
import { UserController } from "./user.controller";
import { UserService } from "./user.service";
import { TypeOrmModule } from "@nestjs/typeorm";
import { User } from "./user.entity";

@Module({
  // 导入
  imports: [TypeOrmModule.forFeature([User])],
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}
  • user.service.ts中使用
import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { User } from "./user.entity";
import { Repository } from "typeorm";

@Injectable()
export class UserService {
  // 依赖注入
  constructor(
    @InjectRepository(User) private readonly userRepository: Repository<User>
  ) {}

  async getUser() {
    let res = await this.userRepository.find();
    return {
      code: 200,
      msg: "success",
      data: res,
    };
  }
}

9.2 查找

  1. find()
findAll() {
  return this.userRepository.find()
}
  1. findAll()
find(username: string) {
  return this.userRepository.findOne({ where: { username } })
}

9.3 新增

create()

create(user: User) {
  const userTmp = this.userRepository.create(user)
  return this.userRepository.save(userTmp)
}

9.4 更新

update()

update(id: number, user: Partial<User>) {
  return this.userRepository.update(id, user)
}

9.5 删除

delete()

delete(id: number) {
  return this.userRepository.delete(id)
}

9.6 联合查询:一对一

根据上面的 5 点(一对一关系),进行对表的一对一联合查询

findProfile(id: number) {
  return this.userRepository.findOne({
      where: {
          id
      },
      relations: {
          profile: true
      }
  })
}

9.6 联合查询:一对多

findUserLogs(id: number) {
  const user = this.userRepository.findOne({ where: { id } })
  return this.logsRepository.find({
      where: {
          user // logs返回的是user实体,具体查看上面第6点一对多
      },
      relations: {
          user: true // 返回关联的字段信息
      }
  })
}

9.9 QueryBuilder 高级查询

聚合,分页查询

findLogsByGroup(id: number) {
  // SELECT logs.result, COUNT(logs.result) from logs, user WHERE user.id = logs.userId AND user.id = 2 GROUP BY logs.result;
  return this.logsRepository.createQueryBuilder('logs')
    .select('logs.result') // logs.result是根据this.logsRepository.createQueryBuilder('logs')的logs来的
    .addSelect('COUNT(logs.result)', 'count')
    .leftJoinAndSelect('logs.user', 'user') // user对查询出来的字段取别名
    .where('user.id = :id', { id }) // :id这种写法是为了防止sql注入
    .groupBy('logs.result')
    .orderBy('result', 'DESC') // 对result字段进行倒序排序
    .addOrderby('count', 'DESC')
    .offset(2)
    .limit(10)
    .getRawMany();
}

9.10 TypeORM 使用原生 sql 查询

通过query()

this.userRepository.query("select * from logs");

9.11 remove()delete()的区别

建议使用:remove()

  1. remove可以一次性删除单个或者多个实例,并且remove可以触发BeforeRemoveAfterRemove钩子;
await repository.remove(user) // user实体
await repository.remove([user1, user2, user3])

钩子函数写在实体类上面:

user.entity.ts中:

import { Logs } from "src/logs/logs.entity";
import { AfterInsert, Column, Entity, PrimaryGeneratedColumn } from "typeorm";
import { Roles } from '../roles/roles.entity';
import { Profile } from "./profile.entity";

@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    username: string;

    @Column()
    password: string;

    // AfterRemove 钩子函数
    @AfterInsert()
    afterInsert() {
        console.log('afterInsert', this.id, this.username) // 可以拿到数据
    }
}
  1. delete可以一次性删除单个或多个id实例,或者给定条件,delete()是硬删除
await repository.delete(1) // id

10. 数据库代码重构

  1. 在项目根目录中创建ormconfig.ts
import { TypeOrmModuleOptions } from "@nestjs/typeorm";
import { User } from "./src/user/user.entity";
import { Logs } from "./src/logs/logs.entity";
import { Roles } from "./src/roles/roles.entity";
import { Profile } from "./src/user/profile.entity";
import * as fs from 'fs'
import * as dotenv from 'dotenv'

// 通过环境变量读取不同的.env文件
const getEnv = (env: string): Record<string, unknown> => {
    if (fs.existsSync(env)) {
        // 判断文件路径是否存在
        return dotenv.parse(fs.readFileSync(env))
    }
    return {}
}
// 通过dotenv来解析不同的配置
const buiildConnectionOptions = () => {
    const defaultConfig = getEnv('.env')
    const envConfig = getEnv(`.env.${process.env.NODE_ENV || 'development'}`)
    // 配置合并
    const config = { ...defaultConfig, ...envConfig }

    const entitiesDir =
        process.env.NODE_ENV === 'development'
            ? [__dirname + '/**/*.entity.ts']
            : [__dirname + '/**/*.entity{.js,.ts']

    return {
        type: "mysql",
        host: config['DB_HOST'],
        port: config['DB_PORT'],
        username: "nest_test",
        password: "123456",
        database: "nest_test",
        // entities: [User, Profile, Roles, Logs],
        // 如果导入文件过多,依次导入会非常麻烦(windows下路径会有问题)
        entities: entitiesDir,
        synchronize: true,
        logging: ["error"],
    } as TypeOrmModuleOptions
}

export const connectionParams = buiildConnectionOptions()
  1. app.module.ts
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { connectionParams } from "ormconfig";
import { UserModule } from "./user/user.module";

@Module({
  imports: [
    UserModule,
    TypeOrmModule.forRoot(connectionParams)
  ],
  controllers: [],
  providers: [],
})
export class AppModule { }

根据上面步骤,就将app.module.ts中的数据库配置文件提取到ormconfig.ts中了。

六、日志收集

1. 使用 NestJS 内置的日志模块

内置Logger实例

1.1 在main.ts全局日志

import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { Logger } from "@nestjs/common";

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    // logger: false // 关闭所有日志
    logger: ["error", "warn"], // 只打印错误和警告日志
  });
  app.setGlobalPrefix("/api/v1");
  const port = 3000;
  await app.listen(port);
  // 打印日志
  const logger = new Logger();
  logger.log(`服务允许在${port}端口`);
  logger.warn(`服务允许在${port}端口`);
  logger.error(`服务允许在${port}端口`);
}
bootstrap();
[Nest] 9232  - 2024/07/15 22:42:00     LOG 服务允许在3000端口 # 颜色为绿色
[Nest] 9232  - 2024/07/15 22:42:00    WARN 服务允许在3000端口 # 颜色为橙色
[Nest] 9232  - 2024/07/15 22:42:00   ERROR 服务允许在3000端口 # 颜色为红色

1.2 在user.controller.ts

import { Controller, Get, Logger } from "@nestjs/common";
import { UserService } from "./user.service";

@Controller("user")
export class UserController {
  private logger = new Logger(UserController.name); // 区分模块名称

  constructor(private userService: UserService) {
    this.logger.log("UserController Init");
  }

  @Get("user")
  findAll(): Object {
    this.logger.log(`请求findAll成功`);
    return this.userService.findAll();
  }
}
  • UserController初始化好后,会执行:
private logger = new Logger(UserController.name) // 区分模块名称
constructor(
    private userService: UserService
) {
    this.logger.log('UserController Init')
}
# LOG [UserController] 这个里的UserController就是new Logger(UserController.name)的UserController.name
[Nest] 9232  - 2024/07/15 22:42:00     LOG [UserController] UserController Init
  • 接口 log 日志
@Get('user')
findAll(): Object {
    this.logger.log(`请求findAll成功`)
    return this.userService.findAll()
}
[Nest] 9232  - 2024/07/15 22:54:03     LOG [UserController] 请求findAll成功

2. 集成第三方日志模块-Pino

2.1 安装

npm i nestjs-pino

2.2 使用

  1. user.module.ts中导入
import { Module } from "@nestjs/common";
import { UserController } from "./user.controller";
import { UserService } from "./user.service";
import { TypeOrmModule } from "@nestjs/typeorm";
import { User } from "./user.entity";
import { LoggerModule } from "nestjs-pino";

@Module({
  imports: [
    TypeOrmModule.forFeature([User]),
    // 导入pino日志模块
    LoggerModule.forRoot(),
  ],
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}
  1. user.controller.ts使用
import { Controller, Get } from "@nestjs/common";
import { UserService } from "./user.service";
import { Logger } from "nestjs-pino";

@Controller("user")
export class UserController {
  constructor(private userService: UserService, private logger: Logger) {
    this.logger.log("UserService Init");
  }

  @Get("user")
  findAll(): Object {
    this.logger.log("findAll接口请求成功");
    return this.userService.findAll();
  }
}
{"level":30,"time":1721055802833,"pid":34300,"hostname":"WKQT253","msg":"UserService Init"}
  • this.logger.log(‘findAll 接口请求成功’)
{"level":30,"time":1721056214051,"pid":9076,"hostname":"WKQT253","req":{"id":1,"method":"GET","url":"/api/v1/user/user","query":{},"params":{"0":"user/user"},"headers":{"host":"localhost:3000","connection":"keep-alive","cache-control":"max-age=0","sec-ch-ua":"\"Not/A)Brand\";v=\"8\", \"Chromium\";v=\"126\", \"Google Chrome\";v=\"126\"","sec-ch-ua-mobile":"?0","sec-ch-ua-platform":"\"Windows\"","upgrade-insecure-requests":"1","user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36","accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","sec-fetch-site":"none","sec-fetch-mode":"navigate","sec-fetch-user":"?1","sec-fetch-dest":"document","accept-encoding":"gzip, deflate, br, zstd","accept-language":"zh-CN,zh;q=0.9","if-none-match":"W/\"2-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w\""},"remoteAddress":"::1","remotePort":58082},"msg":"findAll接口请求成功"}
# 默认在接口请求的时候会打印一次
{"level":30,"time":1721056214078,"pid":9076,"hostname":"WKQT253","req":{"id":1,"method":"GET","url":"/api/v1/user/user","query":{},"params":{"0":"user/user"},"headers":{"host":"localhost:3000","connection":"keep-alive","cache-control":"max-age=0","sec-ch-ua":"\"Not/A)Brand\";v=\"8\", \"Chromium\";v=\"126\", \"Google Chrome\";v=\"126\"","sec-ch-ua-mobile":"?0","sec-ch-ua-platform":"\"Windows\"","upgrade-insecure-requests":"1","user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36","accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","sec-fetch-site":"none","sec-fetch-mode":"navigate","sec-fetch-user":"?1","sec-fetch-dest":"document","accept-encoding":"gzip, deflate, br, zstd","accept-language":"zh-CN,zh;q=0.9","if-none-match":"W/\"2-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w\""},"remoteAddress":"::1","remotePort":58082},"res":{"statusCode":304,"headers":{"x-powered-by":"Express","etag":"W/\"2-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w\""}},"responseTime":28,"msg":"request completed"}

2.3 pino-pretty 解决日志“丑”的方案

pino终端打印的日志不便于查看,使用pino-pretty中间件解决

  1. 安装
npm i pino-pretty
  1. 使用:在user.module.ts
imports: [
  LoggerModule.forRoot({
    pinoHttp: {
      transport: {
        // 无需用import导入,只用安装即可
        target: 'pino-pretty',
        options: {
          colorize: true
        }
      }
    }
  })
],
  • this.logger.log(‘UserService Init’)
[23:17:37.516] INFO (37496): UserService Init
  • 请求接口时
[23:24:59.120] INFO (31880): request completed
    req: {
      "id": 1,
      "method": "GET",
      "url": "/api/v1/user/user",
      "query": {},
      "params": {
        "0": "user/user"
      },
      "headers": {
        "host": "localhost:3000",
        "connection": "keep-alive",
        "cache-control": "max-age=0",
        "sec-ch-ua": "\"Not/A)Brand\";v=\"8\", \"Chromium\";v=\"126\", \"Google Chrome\";v=\"126\"",
        "sec-ch-ua-mobile": "?0",
        "sec-ch-ua-platform": "\"Windows\"",
        "upgrade-insecure-requests": "1",
        "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
        "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
        "sec-fetch-site": "none",
        "sec-fetch-mode": "navigate",
        "sec-fetch-user": "?1",
        "sec-fetch-dest": "document",
        "accept-encoding": "gzip, deflate, br, zstd",
        "accept-language": "zh-CN,zh;q=0.9",
        "if-none-match": "W/\"2-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w\""
      },
      "remoteAddress": "::1",
      "remotePort": 60401
    }
    res: {
      "statusCode": 304,
      "headers": {
        "x-powered-by": "Express",
        "etag": "W/\"2-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w\""
      }
    }
    responseTime: 29

2.4 pino-roll 用于生产环境(滚动日志)

pino-roll可以将日志输出到文件

  1. 安装
npm i pino-roll
  1. 使用:在user.module.ts
import { join } from "path";

LoggerModule.forRoot({
  pinoHttp: {
    transport: {
      // targets设置多个中间件
      targets: [
        {
          level: "info",
          target: "pino-pretty",
          options: {
            colorize: true,
          },
        },
        {
          level: "info",
          target: "pino-roll",
          options: {
            file: join("log", "log.txt"),
            frequency: "daily",
            size: "10m",
            mkdir: true,
          },
        },
      ],
    },
  },
});

2.5 生产环境,开发环境全局配置

app.module.ts

LoggerModule.forRoot({
  pinoHttp: {
    transport: {
      targets: [
        process.env.NODE_ENV === "development"
          ? {
              // 安装pino-pretty
              level: "info",
              target: "pino-pretty",
              options: {
                colorize: true,
              },
            }
          : {
              // 安装pino-roll
              level: "info",
              target: "pino-roll",
              options: {
                file: join("log", "log.txt"),
                frequency: "daily",
                size: "10m",
                mkdir: true,
              },
            },
      ],
    },
  },
});

3. 集成第三方日志模块-winston

3.1 安装

npm i nest-winston winston

3.2 使用

  1. main.ts
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
// 导入包
import { createLogger } from "winston";
import * as winston from "winston";
import { WinstonModule, utilities } from "nest-winston";

async function bootstrap() {
  const instance = createLogger({
    transports: [
      new winston.transports.Console({
        format: winston.format.combine(
          winston.format.timestamp(), // 日志时间
          utilities.format.nestLike()
        ),
      }),
    ],
  });

  const app = await NestFactory.create(AppModule, {
    // 重构nest的logger实例
    logger: WinstonModule.createLogger({
      instance: instance,
    }),
  });
  app.setGlobalPrefix("/api/v1");
  const port = 3000;
  await app.listen(port);
}
bootstrap();
  1. app.module.ts

如果要全局注册使用 logger,需要在app.module.ts中使用@Global()注解,使app模块变成全局模块,进行全局注册,使用exports将 logger 导出使用

import { Global, Logger, Module } from "@nestjs/common";
import { UserModule } from "./user/user.module";

@Global()
@Module({
  imports: [UserModule],
  controllers: [],
  // 从@nestjs/common进行导入。因为在main.ts中重构官方的logger实例
  // 全局提供logger
  providers: [Logger],
  exports: [Logger],
})
export class AppModule {}
  1. user.controller.ts进行使用
import { Controller, Get, Inject, Logger, LoggerService } from "@nestjs/common";

@Controller("user")
export class UserController {
  constructor(
    // 写法1:
    // @Inject(Logger) private readonly logger: LoggerService
    // 写法2:
    private readonly logger: Logger
  ) {
    this.logger.log("init");
  }
}

缺点:需要打印日志的时候,要手动导入 logger

3.3 winston-daily-rotate-file 滚动日志

  1. 安装
npm i winston-daily-rotate-file
  1. 使用:在main.ts
  • import 'winston-daily-rotate-file'文件全部导入

  • new winston.transports.DailyRotateFile记录日志

import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { createLogger } from "winston";
import * as winston from "winston";
import { WinstonModule, utilities } from "nest-winston";
import "winston-daily-rotate-file";

async function bootstrap() {
  const instance = createLogger({
    transports: [
      new winston.transports.Console({
        level: "info",
        format: winston.format.combine(
          // winston.format.timestamp() 日志时间
          winston.format.timestamp(),
          utilities.format.nestLike()
        ),
      }),
      // 将日志记录到文件中
      new winston.transports.DailyRotateFile({
        level: "info",
        format: winston.format.combine(
          // winston.format.timestamp() 日志时间
          winston.format.timestamp(),
          winston.format.simple()
        ),
        dirname: "log",
        filename: "application-%DATE%.log",
        datePattern: "YYYY-MM-DD-HH",
        zippedArchive: true, // 文件压缩
        maxSize: "20m",
        maxFiles: "15d", // 文件保存时间:15天
      }),
    ],
  });

  const app = await NestFactory.create(AppModule, {
    logger: WinstonModule.createLogger({
      instance: instance,
    }),
  });
  app.setGlobalPrefix("/api/v1");
  const port = 3000;
  await app.listen(port);
}
bootstrap();
  • 根据上面3.2节,即可正常保存日志到文件中

3.4 全局异常过滤器

  1. 异常过滤器-内置 http 异常Exception

https://nestjs.inode.club/exception-filters#%E5%86%85%E7%BD%AEhttp%E5%BC%82%E5%B8%B8

  1. 基本使用
import { NotFoundException } from "@nestjs/common";

@Controller("user")
export class UserController {
  @Get("user")
  findAll(): Object {
    throw new NotFoundException("用户不存在");
  }
}
  • 响应结果
{
  "message": "用户不存在",
  "error": "Not Found",
  "statusCode": 404
}
  1. 进阶使用:全局异常过滤器
  • src目录下新建filtters目录,新建http-exception.filtter.ts文件
// src/filtters/http-exception.filtter.ts
import {
  ArgumentsHost,
  Catch,
  ExceptionFilter,
  HttpException,
} from "@nestjs/common";

@Catch(HttpException) // 捕获HttpException错误,如果为空则捕获所有错误
export class HttpExceptionFiltter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    // host表示整个nest进程
    // switchToHttp 可以找到整个程序的上下文
    const ctx = host.switchToHttp();
    // 响应、请求对象
    const response = ctx.getResponse();
    const request = ctx.getRequest();
    // http状态码
    const status = exception.getStatus();

    // 返回给接口的数据
    response.status(status).json({
      code: status,
      timestamp: new Date().toISOString(),
      path: request.url,
      method: request.method,
      /**
       * 第3点,throw new NotFoundException("用户不存在");
       * 如果NotFoundException传入内容exception.message为传入的内容
       * 如果NotFoundException未传入内容,则默认为HttpException信息
       * */ 
      msg: exception.message || HttpException.name,
    });
  }
}
  • main.ts

useGlobalFilters全局过滤器

注意:全局过滤器app.useGlobalFilters只能有一个

import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { HttpExceptionFiltter } from "./filtters/http-exception.filtter";

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.setGlobalPrefix("/api/v1");
  // 将filtter配置成全局过滤器
  app.useGlobalFilters(new HttpExceptionFiltter());
  const port = 3000;
  await app.listen(port);
}
bootstrap();
  • user.controller.ts中使用
import {
  Controller,
  Get,
  NotFoundException,
} from "@nestjs/common";

@Controller("user")
export class UserController {

  @Get("user")
  findAll(): Object {
    // throw new NotFoundException()
    throw new NotFoundException("用户不存在");
  }
}
  • 请求接口返回的数据
{
    "code": 404,
    "timestamp": "2024-07-15T18:20:48.503Z",
    "path": "/api/v1/user/user",
    "method": "GET",
    "msg": "用户不存在"
}

3.5 全局异常过滤器配合 winston 记录日志

  • src/filtters/http-exception.filtter.ts
import { ArgumentsHost, Catch, ExceptionFilter, HttpException, LoggerService } from "@nestjs/common";

@Catch(HttpException)
export class HttpExceptionFiltter implements ExceptionFilter {

    // 依赖注入
    constructor(private logger: LoggerService) {}

    catch(exception: HttpException, host: ArgumentsHost) {
        const ctx = host.switchToHttp()
        const response = ctx.getResponse()
        const request = ctx.getRequest()
        const status = exception.getStatus()

        // 记录错误日志
        this.logger.error(exception.message, exception.stack)

        response.status(status).json({
            code: status,
            timestamp: new Date().toISOString(),
            path: request.url,
            method: request.method,
            msg: exception.message || HttpException.name
        })
    }
}
  • main.ts

app.useGlobalFilters(new HttpExceptionFiltter(logger))传入logger记录日志

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { createLogger } from 'winston';
import * as winston from 'winston'
import { WinstonModule, utilities } from 'nest-winston';
import 'winston-daily-rotate-file'
import { HttpExceptionFiltter } from './filtters/http-exception.filtter';

async function bootstrap() {

  const instance = createLogger({
    transports: [
      new winston.transports.Console({
        level: 'info',
        format: winston.format.combine(
          // winston.format.timestamp() 日志时间
          winston.format.timestamp(),
          utilities.format.nestLike()
        )
      }),
      new winston.transports.DailyRotateFile({
        level: 'info',
        format: winston.format.combine(
          // winston.format.timestamp() 日志时间
          winston.format.timestamp(),
          winston.format.simple()
        ),
        dirname: 'log',
        filename: 'application-%DATE%.log',
        datePattern: 'YYYY-MM-DD-HH',
        zippedArchive: true, // 文件压缩
        maxSize: '20m',
        maxFiles: '15d' // 文件保存时间:15天
      })
    ]
  })

  const logger = WinstonModule.createLogger({
    instance: instance
  })

  const app = await NestFactory.create(AppModule, {
    logger: logger
  });
  app.setGlobalPrefix('/api/v1');

  // 传入logger记录日志
  app.useGlobalFilters(new HttpExceptionFiltter(logger));
  const port = 3000;
  await app.listen(port);
}
bootstrap();

3.6 日志模块代码重构

通过上面的知识,在main.ts中的代码非常的臃肿,需要将不同的模块进行抽离,下面将讲述如何抽离日志模块的代码。

  1. 通过cli创建日志模块
nest g mo logs

会在src下新建logs目录,并且新建logs.module.ts文件

import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { WinstonModule, WinstonModuleOptions, utilities } from 'nest-winston';
import { Console } from 'winston/lib/winston/transports';
import * as winston from 'winston'
import * as DailyRotateFile from 'winston-daily-rotate-file';

const consoleTransPorts = new Console({
    level: 'info',
    format: winston.format.combine(
        // winston.format.timestamp() 日志时间
        winston.format.timestamp(),
        utilities.format.nestLike()
    )
})

const dailyTransPorts = new DailyRotateFile({
    level: 'info',
    format: winston.format.combine(
        // winston.format.timestamp() 日志时间
        winston.format.timestamp(),
        winston.format.simple()
    ),
    dirname: 'log',
    filename: 'application-%DATE%.log',
    datePattern: 'YYYY-MM-DD-HH',
    zippedArchive: true, // 文件压缩
    maxSize: '20m',
    maxFiles: '15d' // 文件保存时间:15天
})

@Module({
    imports: [
        // 异步导入
        WinstonModule.forRootAsync({
            imports: [ConfigModule],
            inject: [ConfigService],
            // configService主要用来读取环境变量的
            useFactory: (configService: ConfigService) => {
                return {
                    transports: [consoleTransPorts, dailyTransPorts]
                } as WinstonModuleOptions
            }
        })
    ]
})
export class LogsModule { }
  1. main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {

  });
  // 用winston的provider去替换nest的logger
  app.useLogger(app.get(WINSTON_MODULE_NEST_PROVIDER))
  app.setGlobalPrefix('/api/v1');
  const port = 3000;
  await app.listen(port);
}
bootstrap();
  1. user.controller.ts文件中使用
import { Controller, Get, Inject, LoggerService } from '@nestjs/common';
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';

@Controller('user')
export class UserController {
    constructor(
        // 注入logger
        @Inject(WINSTON_MODULE_NEST_PROVIDER) private readonly logger: LoggerService
    ) {
        this.logger.log('init')
    }

    @Get('user')
    findAll(): Object {
        this.logger.log('成功')
        return 'ok'
    }
}

打印结果:

# Nest
[Nest] 38236  - 2024/07/16 03:46:03     LOG [InstanceLoader] UserModule dependencies initialized +4ms
# Winston
[NestWinston] 38236 2024/7/16 03:46:03     LOG  init

3.7 局部过滤器

  • 使用cli创建文件

filtters目录下创建名为typeorm.filter.ts的文件

nest g f filtters/typeorm --flat --no-spec
import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common';
import { QueryFailedError, TypeORMError } from 'typeorm';

@Catch(TypeORMError) // 捕获TypeORM的错误
export class TypeormFilter implements ExceptionFilter {
  catch(exception: TypeORMError, host: ArgumentsHost) {
    const ctx = host.switchToHttp()
    // 响应对象
    const response = ctx.getResponse()
    let code = 500
    if (exception instanceof QueryFailedError) {
      code = exception.driverError.errno
    }

    response.status(500).json({
      code: code,
      message: exception.message
    })
  }
}
  • 在控制器中使用:user.controller.ts
import { Controller, Get, UseFilters } from '@nestjs/common';
import { TypeormFilter } from '../filtters/typeorm.filter';

@Controller('user')
// 使用局部过滤器
@UseFilters(new TypeormFilter())
export class UserController {
    @Get()
    getData() {
        
    }
}
  • 10
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值