赞
踩
概述:本篇文章是NestJS笔记,包括了Nest的基本使用、连接数据库、日志操作。
npm i -g @nestjs/cli
nest new [项目名]
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 │ └───────────────┴─────────────┴──────────────────────────────────────────────┘
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.
例如:
user
模块nest g module user
user
模块的controller
nest g controller user
项目目录结构
src
|—— app.controller.spec.ts # 测试文件
|—— app.controller.ts # 控制器:书写路由
|—— app.module.ts # 根模块
|—— app.service.ts # 书写逻辑部分
|—— main.ts # 入口文件
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";
}
}
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();
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(); } }
使用注解的方式获取请求的参数
请求路径:http://127.0.0.1:3000/api/v1/user/1
@Param()
@Get('/:id')
findParmas(@Param() params: any) {
console.log(params);
return 'success'
}
打印结果为:
{ id: '1' }
@Param('id')
@Get('/:id')
findParmas(@Param('id') id: any) {
console.log(id);
return 'success'
}
打印结果为:
1
请求路径: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' }
请求路径:
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' }
请求路径:
添加了一个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'
}
@Get()
getReq(@Request() req: any) {
console.log(req);
return 'success'
}
会打印请求对象所有信息
支持.env
文件
dotenv
npm i dotenv
.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'
}
支持yaml
、yml
、json
文件
config
npm i config
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'
}
config
目录下新建production.json
文件(文件名不可自定义){
"db": {
"host": "www.wifi.com",
"port": 8080
}
}
production
win:
set NODE_ENV=production
mac:
export NODE_ENV=production
这时候他会合并default.json
和production.json
的字段,以production.json
为准,打印结果为:
{
host: 'www.wifi.com',
port: 8080,
username: 'root',
password: '123456'
}
需要安装js-yaml
,也会自动合并字段
default.yaml
和production.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
}
可以在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
}
env
适合简单配置,config
适合嵌套复杂配置
npm i @nestjs/config
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
文件了
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(); } }
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"
}
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 {}
嵌套配置 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.yml
、config.development.yml
、config.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:prod
后console.log(db);
的结果为:
# 环境不同,字段已经进行合并和覆盖
{
mysql1: { host: '127.0.0.1', name: 'mysql-prod1', post: 3306 },
mysql2: { host: '127.0.0.2', name: 'mysql-prod2', post: 3307 }
}
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 {}
ORM:对象关系映射,其主要作用是在编程中,把面向对象的概念跟数据库中的概念对应起来。举例:
定义一个对象,那就对应着一张表,这个对象的实例,就对应着表中的一条记录。
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 {}
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 {}
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.forRootAsync
的entities
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), });
需要有@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 {}
需要有@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; }
使用@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[];
}
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 .
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, }; } }
find()
findAll() {
return this.userRepository.find()
}
findAll()
find(username: string) {
return this.userRepository.findOne({ where: { username } })
}
create()
create(user: User) {
const userTmp = this.userRepository.create(user)
return this.userRepository.save(userTmp)
}
update()
update(id: number, user: Partial<User>) {
return this.userRepository.update(id, user)
}
delete()
delete(id: number) {
return this.userRepository.delete(id)
}
根据上面的 5 点(一对一关系),进行对表的一对一联合查询
findProfile(id: number) {
return this.userRepository.findOne({
where: {
id
},
relations: {
profile: true
}
})
}
findUserLogs(id: number) {
const user = this.userRepository.findOne({ where: { id } })
return this.logsRepository.find({
where: {
user // logs返回的是user实体,具体查看上面第6点一对多
},
relations: {
user: true // 返回关联的字段信息
}
})
}
聚合,分页查询
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();
}
通过query()
this.userRepository.query("select * from logs");
remove()
和delete()
的区别建议使用:
remove()
remove
可以一次性删除单个或者多个实例,并且remove
可以触发BeforeRemove
,AfterRemove
钩子;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) // 可以拿到数据 } }
delete
可以一次性删除单个或多个id实例,或者给定条件,delete()
是硬删除await repository.delete(1) // id
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()
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
中了。
内置Logger
实例
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端口 # 颜色为红色
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
@Get('user')
findAll(): Object {
this.logger.log(`请求findAll成功`)
return this.userService.findAll()
}
[Nest] 9232 - 2024/07/15 22:54:03 LOG [UserController] 请求findAll成功
npm i nestjs-pino
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 {}
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"}
{"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"}
pino
终端打印的日志不便于查看,使用pino-pretty
中间件解决
npm i pino-pretty
user.module.ts
中imports: [
LoggerModule.forRoot({
pinoHttp: {
transport: {
// 无需用import导入,只用安装即可
target: 'pino-pretty',
options: {
colorize: true
}
}
}
})
],
[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
pino-roll
可以将日志输出到文件
npm i pino-roll
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, }, }, ], }, }, });
在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, }, }, ], }, }, });
npm i nest-winston winston
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();
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 {}
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
npm i winston-daily-rotate-file
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();
Exception
https://nestjs.inode.club/exception-filters#%E5%86%85%E7%BD%AEhttp%E5%BC%82%E5%B8%B8
import { NotFoundException } from "@nestjs/common";
@Controller("user")
export class UserController {
@Get("user")
findAll(): Object {
throw new NotFoundException("用户不存在");
}
}
{
"message": "用户不存在",
"error": "Not Found",
"statusCode": 404
}
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": "用户不存在"
}
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();
通过上面的知识,在main.ts
中的代码非常的臃肿,需要将不同的模块进行抽离,下面将讲述如何抽离日志模块的代码。
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 { }
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();
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
在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() {
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。