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:等价写法1userService: 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-urlencodedusername=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: localhostport: 27017username: rootpassword: 123456
# production.yaml
db:host: www.wifi.comport: 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: localhostport: 27017username: rootpassword: 123456
    
    • development.yaml
    # development.yaml
    db:host: 127.0.0.1port: 7979
    
    • production.yaml
    # production
    db:host: www.wifi.comport: 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.1name: mysql-devpost: 3306mysql2:host: 127.0.0.2name: mysql-devpost: 3307
# config.development.yml
db:mysql1:name: mysql-dev1mysql2:name: mysql-dev2
# config.production.yml
db:mysql1:name: mysql-prod1mysql2: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",// 导入的entityentities: [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 completedreq: {"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-prettylevel: "info",target: "pino-pretty",options: {colorize: true,},}: {// 安装pino-rolllevel: "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实例// 全局提供loggerproviders: [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的loggerapp.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 = 500if (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() {}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/46688.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用 JavaScript 实现图片上传

首先&#xff0c;我们需要创建一个包含用于显示图片预览和文件输入的 HTML 页面。 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-sc…

Assignments

目录 1、 Assignments.cshtml 1.1、 Content body start 1.2、 <!-- row --> 1.3、 Content body end Assignments.cshtml@{

C++写一个线程池

C写一个线程池 文章目录 C写一个线程池设计思路测试数据的实现任务类的实现线程池类的实现线程池构造函数线程池入口函数队列中取任务添加任务函数线程池终止函数 源码 之前用C语言写了一个线程池&#xff0c;详情请见&#xff1a; C语言写一个线程池 这次换成C了&#xff01;…

获取淘宝商品详情app原数据item_get_app响应参数解析(二):商品标题、销量、sku、价格、视频

API名&#xff1a;item_get_app 功能说明&#xff1a;通过传入商品id获取该商品的详情页原数据。因为是原数据&#xff0c;响应参数较多。 请求示例&#xff08;curl&#xff09;&#xff1a; -- 请求示例 url 默认请求参数已经URL编码处理 curl -i "https://api-服…

Java 在PDF中替换文字(详解)

目录 使用工具 Java在PDF中替换特定文字的所有实例 Java在PDF中替换特定文字的第一个实例 Java在PDF中使用正则表达式替换特定文字 其他替换条件设置 可能出现的问题及解决方案 PDF文档中的信息随时间的推移可能会发生变化&#xff0c;比如产品价格、联系方式等。为了确保…

2024可信数据库发展大会|存算分离架构驱动电信数据平台革新

7 月 16 日 - 17 日&#xff0c;由中国通信标准化协会和中国信息通信研究院主办&#xff0c;大数据技术标准推进委员会承办&#xff0c;InfoQ 联合主办的「2024 可信数据库发展大会」&#xff08;TDBC&#xff09;在北京召开。 酷克数据解决方案架构师吴昊受邀参与“电信行业数…

算法-计数质数

题目&#xff1a; 给定整数 n &#xff0c;返回 所有小于非负整数 n 的质数的数量 。 思路&#xff1a; 使用埃式筛法 当n大于等于2时&#xff0c;如果当前遍历的数 i 是质数&#xff0c;那么从 i*i 开始&#xff0c;直到 n 为止&#xff0c;把 i 的倍数都标记为合数 代码&a…

python数据挖掘---机器学习模型

机器学习模型 数据 算法 算法 Clustering (聚类)&#xff0c;旨在将数据根据相似性进行分组&#xff0c;不需要事先知道每个组的具体定义或标签。简单地说就是把相似的东西分到一组&#xff08;簇&#xff09;&#xff0c;聚类的时候&#xff0c;我们并不关心某一类是什么&am…

为什么提示词写作技能会让生成式AI工程师更上一层楼?

欢迎来到云闪世界。 “为什么我的经理对我的文章吹毛求疵&#xff1f;把措辞从 X 改为 Y 有什么区别&#xff1f;” 当你看到你的经理在你的文档中提出了无数建议时&#xff0c;你可能发现自己有这样的想法&#xff1b;我知道我有过。事实上&#xff0c;我曾经认为写作是数据科…

29、js中this指向

this就是一个使用在作用域内的关键字 1.普通函数调用——》指向window function fn(){console.log(this) //Window } fn() 2.对象调用——》指向对象名 var obj {a: fn,};console.log(obj.a()); 3.定时器函数调用——》指向window setTimeout(function () {console.log(thi…

Flutter 教程实战笔记

Flutter 实战教程笔记&#xff0c;个人觉得不错&#xff0c;特此整理&#xff0c;需要的小伙伴可以看看 01. 初学者须知02. 初识 Flutter03. Flutter 开发环境搭建 Windows 版04. 创建 Flutter 项目05. Flutter 编写一个 HelloWorld 程序06. Flutter Text Widget 文本组件的使…

npm安装依赖包报错,npm ERR! code ENOTFOUND

一、报错现象&#xff1a; npm WARN registry Unexpected warning for https://registry.npmjs.org/: Miscellaneous Warning ETIMEDOUT: request to https://registry.npmjs.org/vue failed, reason: connect ETIMEDOUT 104.16.23.35:443 npm WARN registry Using stale data…

“点点通“餐饮点餐小程序-计算机毕业设计源码11264

"点点通"餐饮点餐小程序 XXX专业XX级XX班&#xff1a;XXX 指导教师&#xff1a;XXX 摘要 随着中国经济的飞速增长&#xff0c;消费者的智能化水平不断提高&#xff0c;许多智能手机和相关的软件正在得到更多的关注和支持。其中&#xff0c;微信的餐饮点餐小程序更…

《Exploring Aligned Complementary Image Pair for Blind Motion Deblurring》

这篇论文的标题《Exploring Aligned Complementary Image Pair for Blind Motion Deblurring》可以翻译为《探索对齐的互补图像对用于盲运动去模糊》。从标题可以推断,论文的焦点在于开发一种算法或技术,利用成对的图像来解决运动模糊问题,特别是在不知道模糊核(即造成模糊…

wifi preamble code, 前导码

WiFi packets 很神奇&#xff0c;自从802.11 协议诞生以来 就对信息传输 产生了无尽的影响&#xff0c; 闲来无事 看看wireless 空口包 发现 前导码 非常奇怪。 为什么前导码有两部分组成&#xff0c; 1. radiotap header 2. 802.11 radio information 并且 radiotap 是有在…

AIGC工具:IPAdapter和ControlNet 指导控制生成工具

ControlNet强调对生成过程的直接控制,如通过线条、边缘、形状等信息;而IPAdapter侧重于风格迁移和内容的间接引导。 IPAdapter 它专注于通过迁移图片风格来生成新的图像内容。IPAdapter的强项在于能够将一张图片的风格迁移到另一张图片上,实现风格融合,甚至可以进行多图风格…

CS110L(Rust)

1.Rust 语法总结 数值类型 有符号整数: i8, i16, i32, i64无符号整数: u8, u16, u32, u64 变量声明 声明变量: let i 0; // 类型推断let n: i32 1; // 显式类型声明 可变变量: let mut n 0; n n 1; 字符串 注意&#xff0c;let s: str "Hello world";…

React@16.x(62)Redux@4.x(11)- 中间件2 - redux-thunk

目录 1&#xff0c;介绍举例 2&#xff0c;原理和实现实现 3&#xff0c;注意点 1&#xff0c;介绍 一般情况下&#xff0c;action 是一个平面对象&#xff0c;并会通过纯函数来创建。 export const createAddUserAction (user) > ({type: ADD_USER,payload: user, });这…

WEB前端07-DOM对象

DOM模型 1.DOM概念 文档对象模型属于BOM的一 部分&#xff0c;用于对BOM中的核心对象document进行操作&#xff0c;它是一种与平台、语言无关的接口&#xff0c;允许程序和脚本动态地访问或更新HTML、XML文档的内容、结构和样式&#xff0c;且提供了一系列的函数和对象来实现…

工作边界感

工作边界 **明确工作边界****尊重他人的工作边界**&#xff1a;**建立有效的沟通机制**&#xff1a;**制定明确的规则和流程**&#xff1a;**保持开放和包容的心态**&#xff1a;**寻求专业支持**&#xff1a; 在程序员的日常工作中&#xff0c;会遇到很多边界问题。如果这些边…