nestjs 使用总结
nestjs 使用总结
安装 swagger
- pnpm add -D @nestjs/swagger
nestjs 的设计模式 IOC 控制反转 DI 依赖注入
初始化项目
npm i -g @nestjs/cli
nest new project-name
- nest -h 查看命令
- nest g resource <name> //快速生成模板
配置接口版本号
在 main.ts 里面开启
app.enableVersioning({ type: VersioningType.URI });
在 controller 中使用版本
@Controller({ path: 'cat', version: '1' })
也可以给指定接口定义
@Version('1')
@Get('/xixi')
getCat() {....}
控制器
语法:@Controller()
常用的参数装
饰器:使用没有顺序要求
- @Query(id?:string) 获取 query 参数
- @Request() 获取请求体
- @Body(id?:string) 获取 body 参数
- @Param(id?:string) 获取 动态路由 参数
- @Session()
- @Headers()
常用的方法
饰器:使用没有顺序要求
- @Get(url?:string) 也可以加 :id 动态路由
- @Post(url?:string)
- @Patch(url?:string)
- @Delete(url?:string)
- @Put(url?:string)
注
控制器函数直接 return 的内容就是给客户端接收的 可以直接返回 json 或者 html 字符串他会做转换
提供者(provide)
语法:@Injectable() 使用: @Module({ providers: [...], }) 只有在模块中注入的服务才能在 controller 中使用他
例
//app.module.ts
@Module({
controllers: [UserController],
providers: [UserService],
})
export class AppModule {}
// user.service.ts
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}
}
别名使用
//app.module.ts
@Module({
controllers: [],
providers: [
{
provide: 'user',
useClass: UserService,
},
],
})
export class AppModule {}
// user.service.ts
@Controller('user')
export class UserController {
constructor(@Inject('user') private readonly userService: UserService) {}
}
注入变量
//app.module.ts
@Module({
controllers: [],
providers: [{ provide: 'auth', useValue: ['admin', 'user'] }],
})
export class AppModule {}
// user.service.ts
@Controller('user')
export class UserController {
constructor(@Inject('auth') private readonly auth: string[]) {}
@Get()
get() {
return this.auth
}
}
混合注入(重点)
//app.module.ts
@Module({
controllers: [],
providers: [
{
provide: 'he2',
inject: [HelloService],
useFactory(helloService: HelloService) {
console.log(helloService.fetch('cxn'))
return '我是混合'
},
// 异步也可以
/*
useFactory(helloService: HelloService) {
return new Promise((res) => {
setTimeout(() => {
res('我是混合');
}, 4000);
});
}, */
},
],
})
export class AppModule {}
// user.service.ts
@Controller('user')
export class UserController {
constructor(@Inject('he2') private readonly he2: string) {}
@Get()
get() {
return this.he2
}
}
模块
语法:@Module
模块一共分为三大类
- 局部模块 正常情况下自身模块引入的服务无法提供给其他模块 我们可以在模块中通过 export 暴露出去 语法:
// logger 模块
@Module({
providers: [LoggerService],
exports: [LoggerService],
})
export class LoggerModule implements NestModule {}
// app 根模块
@Module({
// 导入模块 然后就可以在controller中使用logger提供的服务
imports: [LoggerModule],
})
export class AppModule {}
- 全局模块 语法: @Global() 使用@Global 定义之后也需要导出模块,然后引入但是只需要引入一次就好,推荐在跟组件引入
// global module
@Global()
@Module({
imports: [],
controllers: [],
providers: [{ provide: 'global', useValue: ['admin', 'user'] }],
exports: [{ provide: 'global', useValue: ['admin', 'user'] }],
})
export class GlobalModule {}
// app 根模块
@Module({
imports: [GlobalModule],
})
export class AppModule {}
- 动态模块 动态模块主要是可以给模块传参 根据不同的参数使用不同的功能
// Datanse模块
import { Module, DynamicModule } from '@nestjs/common'
@Module({})
export class DatabaseModule {
static forRoot(options: string): DynamicModule {
return {
module: DatabaseModule,
providers: [{ provide: 'dynamic', useValue: '动态模块:' + options }],
exports: [{ provide: 'dynamic', useValue: '动态模块:' + options }],
// global: true, 是否是全局模块
}
}
}
// 使用
@Module({
imports: [DatabaseModule.forRoot('我是日志模块')],
controllers: [LoggerController],
providers: [LoggerService],
})
export class LoggerModule implements NestModule {}
中间件
中间件是在路由处理程序之前触发的,中间件函数可以访问请求和响应对象 中间件函数可以执行以下任务
- 执行任何代码
- 对请求和响应对象进行更改
- 结束请求响应周期
- 调佣堆栈中的下一个中间件函数
- 如果当前中间件没有结束请求响应周期,他必须调用 next()将控制权转移给下一个中间件上,否则请求挂起
定义一个中间件,中间件分为 class 中间件和函数中间件
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const { method, path } = req
console.log('method, path: ', method, path)
// 提前结束路由
res.send({ message: '日志中间件' })
// 也可以直接抛异常
// throw new HttpException(
// { message: '错误信息', code: 400 },
// HttpStatus.FORBIDDEN,
// );
// 跳到下一步
// next();
}
}
局部中间件
使用中间件一共有三种使用方式
- forRoutes 选择路由
- exclude 排除路由
- apply 应用中间件
@Module({})
export class LoggerModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggerMiddleware).forRoutes('logger')
consumer
.apply(LoggerMiddleware)
.forRoutes({ path: 'logger', method: RequestMethod.GET })
consumer.apply(LoggerMiddleware).forRoutes(LoggerController)
}
}
全局中间件
全局中间件需要在 main.ts 中使用
export function logger(req, res, next) {
const { method, path } = req
console.log('method, path: ', method, path)
next()
}
async function bootstrap() {
const app = await NestFactory.create(AppModule)
app.use(logger)
await app.listen(3000)
}
上传图片 和 静态目录
主要使用两个包
- @nestjs/platform-express nestJS 自带
- multer @types/multer 在 upload Module 中使用 MulterModule 注册存放图片目录 需要用到 multer 的 diskStorage 设置存放目录 extname 用来读取文件后缀 filenameyonlai 给文件重新命名
定义一个上传模块
import { UploadService } from './upload.service'
import { UploadController } from './upload.controller'
import { Module } from '@nestjs/common'
import { MulterModule } from '@nestjs/platform-express'
import { diskStorage } from 'multer'
import path from 'path'
@Module({
imports: [
MulterModule.register({
storage: diskStorage({
// 保存文件的目录
destination: path.join(process.cwd(), 'images'),
// 文件重新命名
filename: (_, file, callback) => {
const fileName = `${+new Date() + path.extname(file.originalname)}`
callback(null, fileName)
},
}),
}),
],
controllers: [UploadController],
providers: [UploadService],
})
export class UploadModule {}
在控制器中使用它
import { Controller, Post, UploadedFile, UseInterceptors } from '@nestjs/common'
import { FileInterceptor, FilesInterceptor } from '@nestjs/platform-express'
@Controller('upload')
export class UploadController {
@Post('album')
// 使用内置拦截器
@UseInterceptors(FileInterceptor('zp'))
upload(@UploadedFile() file) {
console.log('file: ', file)
return { filename: file.filename }
}
}
设置静态资源目录
// main.ts
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule)
app.useStaticAssets(path.join(process.cwd(), 'images'), { prefix: '/cxn' })
await app.listen(3000)
}
下载文件
普通下载
@Get('export')
downLoad(@Res() res) {
const url = path.join(process.cwd(), 'images', '1670083205150.jpg');
res.download(url);
}
流下载
import { zip } from 'compressing';
@Get('stream')
async down(@Res() res: Response) {
const tarStream = new zip.Stream();
const url = path.join(process.cwd(), 'images', '1670083205150.jpg');
await tarStream.addEntry(url);
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', 'attachment;filename=cxn');
tarStream.pipe(res);
}
// 前端
const downLoad = async () => {
const res = await axios.get('/api/upload/stream', { responseType: 'arraybuffer' })
const img = new Blob([res.data])
const url = URL.createObjectURL(img)
const a = document.createElement('a') as HTMLAnchorElement
a.href = url
a.download = 'cxn.zip'
console.log('a: ', a);
a.click()
a.remove()
}
rxjs
概念: RxJs 使用观察者模式,用来编写异步队列和处理程序 Observable 客观察的物件 Subscript 监听 Observable Operators 纯函数可以处理管道的数据如 map filter concat reduce 等
- 案例
import { Observable, interval, take, map, filter, of, retry } from 'rxjs'
// 管道传输
// interval(500)
// .pipe(take(5))
// .subscribe((e) => {
// console.log(e);
// });
// const subs = interval(500)
// .pipe(
// map((v) => ({ num: v })),
// filter((v) => v.num % 2 === 0),
// )
// .subscribe((e) => {
// console.log(e);
// if (e.num === 10) {
// subs.unsubscribe();
// }
// });
// retry 重试三次
const subs = of(4, 7, 8, 10, 6)
.pipe(
retry(3),
map((v) => ({ num: v })),
filter((v) => v.num % 2 === 0)
)
.subscribe((e) => {
console.log(e)
})
拦截器
拦截器具有一系列功能,这些功能受面向切片编程(AOP)技术的启发,它可以
- 在函数执行之前/之后绑定额外逻辑
- 转换从函数返回的结果
- 转换重函数抛出的异常
- 扩展基本函数行为
- 根据所选条件完全重写函数(例如 缓存目的)
import { CallHandler, NestInterceptor } from '@nestjs/common'
import { map, Observable } from 'rxjs'
interface Data<T> {
data: T
}
export class Response<T> implements NestInterceptor {
intercept(context, next: CallHandler): Observable<Data<T>> {
return next.handle().pipe(
map((data) => {
console.log('data: ', data)
return {
data,
status: 0,
message: 'cxn',
success: true,
}
})
)
}
}
// 使用 main.ts
app.useGlobalInterceptors(new Response())
过滤器
import {
ArgumentsHost,
Catch,
ExceptionFilter,
HttpException,
} from '@nestjs/common'
import { Request, Response } from 'express'
@Catch(HttpException)
export class HttpFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp()
const req = ctx.getRequest<Request>()
const res = ctx.getResponse<Response>()
const sts = exception.getStatus()
res.status(sts).json({
success: false,
time: new Date(),
data: exception.message,
status: sts,
url: req.url,
})
}
}
// 使用 main.ts
app.useGlobalFilters(new HttpFilter())
管道(Pipe)
管道有两个典型的应用场景:
转换:管道将输入数据转换为所需的数据输出(例如,将字符串转换为整数) 验证:对输入数据进行验证,如果验证成功继续传递; 验证失败则抛出异常
Nest
自带九个开箱即用的管道,即
ValidationPipe
ParseIntPipe
ParseFloatPipe
ParseBoolPipe
ParseArrayPipe
ParseUUIDPipe
ParseEnumPipe
DefaultValuePipe
ParseFilePipe
// 用法 我们使用以下构造来实现,并其称为在方法参数级别绑定管道
@Controller('pipe')
export class PipeController {
@Get(':id')
find(@Param('id', ParseIntPipe) id) {
console.log(typeof id)
console.log(id)
return id
}
}
Dto 校验
安装 pnpm add class-validator class-transformer
使用这两个库我们可以给参数进行管道校验
- 创建 loginDto
import { IsNotEmpty, IsString, Length, IsNumber } from 'class-validator'
export class CreateLoginDto {
@IsNotEmpty({ message: '不能为空' })
@IsString()
@Length(5, 10, { message: '不能超过10个字符' })
name: string
@IsNumber()
age: number
}
- 定义 pipe
import { HttpException, HttpStatus } from '@nestjs/common'
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common'
import { plainToInstance } from 'class-transformer'
import { validate } from 'class-validator'
@Injectable()
export class LoginPipe implements PipeTransform {
async transform(value: any, metadata: ArgumentMetadata) {
const dto = plainToInstance(metadata.metatype, value)
const err = await validate(dto)
if (err.length > 0) {
throw new HttpException(err, HttpStatus.BAD_REQUEST)
}
return value
}
}
- 在控制成中使用
@Controller('login')
export class LoginController {
constructor(private readonly loginService: LoginService) {}
@Post()
create(@Body(LoginPipe) createLoginDto: CreateLoginDto) {
return this.loginService.create(createLoginDto)
}
}
- 也可以使用 nestjs 提供的 pipe 来处理
// main.ts
app.useGlobalPipes(new ValidationPipe())
爬虫
我们主要使用两个库
- cheerio 是 jquery 核心功能的一个快速灵活又简洁的实现,主要是为了在用服务器需要对 Dom 操作的地方,让你在服务器端和 html 愉快的玩耍
- axios 网络请求库可以发送 http 请求
思路就是通过 axios 请求接口
然后通过 cheerio 操作 dom
守卫(guard)
守卫有一个单独的责任,他们根据运行时出现的某些条件(例如权限,角色,访问控制等)来确定给定的请求是否由路由处理程序处理,在传统的 Express 应用程序中,通常由中间件处理授权(以及认证相关)中间件是身份验证的良好选择,因为诸如 token 验证或者添加属性到 request 对象上与特定路由(及其元数据)没有强关联
这里引入了一个 SetMetadata 装饰器可以用来定义元数据
需要搭配 Reflector 来使用
注意
守卫在每个中间件之后执行,但在任何拦截器或管道之前执行
全局守卫
app.useGlobalGuards(new RoleGuard())
局部守卫
// 定义一个controller
import { RoleGuard } from './role.guard'
import { Controller, Get, SetMetadata, UseGuards } from '@nestjs/common'
@UseGuards(RoleGuard)
@Controller('guard/role')
export class RoleController {
@Get()
@SetMetadata('role', ['admin'])
find() {
return 'role'
}
}
// 定义守卫
/*
https://docs.nestjs.com/guards#guards
*/
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'
import { Reflector } from '@nestjs/core'
import { Request } from 'express'
import { Observable } from 'rxjs'
@Injectable()
export class RoleGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(
context: ExecutionContext
): boolean | Promise<boolean> | Observable<boolean> {
const admin = this.reflector.get<Array<string>>(
'role',
context.getHandler()
)
const req = context.switchToHttp().getRequest<Request>()
if (admin.includes(req.query.role as string)) {
return true
}
return false
}
}
自定义装饰器
定义装饰器
import {
applyDecorators,
createParamDecorator,
ExecutionContext,
SetMetadata,
} from '@nestjs/common'
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest()
return request.query.user
}
)
// 定义当前接口的数据
export function Auth(...roles: string[]) {
return applyDecorators(SetMetadata('role', roles))
}
使用
import { RoleGuard } from './role.guard'
import { Controller, Get, SetMetadata, UseGuards } from '@nestjs/common'
import { Auth, User } from './role.decorator'
@UseGuards(RoleGuard)
@Controller('guard/role')
export class RoleController {
@Get()
@Auth('admin')
find(@User() user: string) {
console.log('user: ', user)
return 'role'
}
}
Swagger
安装:pnpm add @nestjs/swagger
// 创建swagger
const swaggerOptions = new DocumentBuilder()
.setTitle('测试文档')
.setDescription('xxxxxxx')
.setVersion('1.0.0')
.addBearerAuth()
.build()
const doucment = SwaggerModule.createDocument(app, swaggerOptions)
// doc是文档路径
SwaggerModule.setup('doc', app, doucment)
常用装饰器
@ApiBearerAuth()
@ApiTags()
@ApiQuery()
@ApiResponse()
@ApiOperation()
@ApiProperty()
ORM(数据库连接)
pnpm add @nestjs/typeorm mysql2
// 注册
import { TestOrmModule } from './module/test-orm/test-orm.module'
import { TestingModule } from '@nestjs/testing'
import { AppService } from './app.service'
import { RoleModule } from './module/guard/role.module'
import { PipeModule } from './module/Pipe/pipe.module'
import { UploadModule } from './upload/upload.module'
import { DatabaseModule } from './global/database.module'
import { GlobalModule } from './global/global.module'
import { AppController } from './app.controller'
import { UserService } from './user/user.service'
import { UserController } from './user/user.controller'
import { HelloService } from './module/hello/hello.service'
import { CatService } from './cat/cat.service'
import { CatController } from './cat/cat.controller'
// import { LoggerMiddleware } from './common/middleware/logger.middleware';
// import { LoggerModule } from './module/logger/logger.module';
// import { HelloModule } from './module/hello/hello.module';
import { Module } from '@nestjs/common'
import { LoggerModule } from './module/logger/logger.module'
import { HelloModule } from './module/hello/hello.module'
import { LoginModule } from './login/login.module'
import { TypeOrmModule } from '@nestjs/typeorm'
@Module({
imports: [
// 使用数据库
TypeOrmModule.forRoot({
type: 'mysql',
username: 'root',
password: 'root',
host: 'localhost',
port: 3306,
database: 'type_orm',
// entities: [__dirname + '/**/*.entity{.ts,.js}'], // 实体文件目录
synchronize: true, // 是否自动同步实体类到数据库
retryDelay: 500, // 重试连接数据库
retryAttempts: 10, // 重试次数
autoLoadEntities: true, // 是否自动加载实体,forFeatuer()方法注册的每个实体都将自动添加在配置对象的实体
}),
TestOrmModule,
],
})
export class AppModule {}
子模块使用
import { TestOrm } from './entities/test-orm.entity'
import { Module } from '@nestjs/common'
import { TestOrmService } from './test-orm.service'
import { TestOrmController } from './test-orm.controller'
import { TypeOrmModule } from '@nestjs/typeorm'
@Module({
imports: [TypeOrmModule.forFeature([TestOrm])],
controllers: [TestOrmController],
providers: [TestOrmService],
})
export class TestOrmModule {}
实体文件
import {
Entity,
Column,
PrimaryGeneratedColumn,
CreateDateColumn,
Generated,
} from 'typeorm'
@Entity()
export class TestOrm {
@PrimaryGeneratedColumn('uuid')
id: number
@Column({ type: 'varchar', length: 255 })
name: string
// select:是否选中 nullable是否可以为空
@Column({ select: true, comment: '这是密码', nullable: null })
password: string
@Column()
age: number
@CreateDateColumn({ type: 'timestamp' })
create_time: Date
@Generated('uuid') //自动生成uuid
uuid: string
@Column({ type: 'enum', default: 1, enum: [1, 2, 3] })
type: number
@Column('simple-array')
names: string[]
@Column('simple-json')
json: { name: string; age: number }
}
发送邮件
@nest-modules/mailer
// 注册全局模块
MailerModule.forRootAsync({
useFactory: () => ({
transport: {
service: 'qq',
port: 465,
secureConnection: true,
host: 'smtp.qq.com',
auth: {
user: '*******',
pass: '*******',
},
}, // 邮件地址
defaults: {
form: 'fds', // 默认地址
},
template: {
dir: join(__dirname, 'template/email'),
adapter: new PugAdapter(),
options: {
strict: true,
},
},
}),
}),
// 使用
@Injectable()
export class EmailService {
constructor(private readonly mailerService: MailerService) {}
create(createEmailDto: CreateEmailDto) {
this.mailerService.sendMail({
to: '*******@qq.com',
from: '"测试呀" <*******@qq.com>',
subject: 'hello world',
template: 'welcome',
// html:''
});
return 'This action adds a new email';
}
}
注意
发送邮件的时候 我们可能找不到文件,我们需要在 nest-cli.json 配置静态文件的转移
"compilerOptions": {
"assets": [
"template/**/*"
]
}
配置集中管理
pnpm add nestjs-config
// 使用
import { ConfigModule, ConfigService } from 'nestjs-config'
@Module({
imports: [
ConfigModule.load(join(__dirname, 'config', '**/!(*.d).{te,js}')),
MailerModule.forRootAsync({
useFactory: (config: ConfigService) => config.get('email'),
inject: [ConfigService],
}),
],
})
export class AppModule {}
配置抽离
// src/config/email.ts
import { join } from 'path'
import { PugAdapter } from '@nest-modules/mailer'
export default {
transport: {
service: 'qq',
port: 465,
secureConnection: true,
host: 'smtp.qq.com',
auth: {
user: '1398675906@qq.com',
pass: 'bmtwebherkutijci',
},
}, // 邮件地址
defaults: {
form: 'fds', // 默认地址
},
template: {
dir: join(__dirname, '..', 'template/email'),
adapter: new PugAdapter(),
options: {
strict: true,
},
},
}
服务监控
pnpm add nest-status-monitor
// 使用
import statusMonitor from './config/statusMonitor'
@Module({
imports: [StatusMonitorModule.setUp(statusMonitor)],
})
export class AppModule {}
// src/config/statusMonitor
export default {
pageTitle: '测试项目',
port: 3000,
path: '/status',
ignoreStartsWith: '/healt/alive',
spans: [
{ interval: 1, retention: 60 },
{ interval: 5, retention: 60 },
{ interval: 15, retention: 60 },
],
chartVisibility: {
cpu: true,
mem: true,
load: true,
responseTime: true,
rps: true,
statusCodes: true,
},
healthChecks: [],
}
jwt 鉴权
pnpm add @nestjs/passport passport passport-local @nestjs/jwt passport-jwt pnpm add -D @types/passport-local @types/passport-jwt
local.strategy 生成 token
jwt.strategy 校验 token
定时任务
pnpm add @nestjs/schedule
// app.module.ts 注册
import { Module } from '@nestjs/common'
import { TaskModule } from './tasks/task.module'
import { ScheduleModule } from '@nestjs/schedule'
@Module({
imports: [AuthModule, TaskModule, ScheduleModule.forRoot()],
})
export class AppModule {}
// task.module.ts 使用
import { Cron, Interval, Timeout } from '@nestjs/schedule'
import { Injectable, Logger } from '@nestjs/common'
@Injectable()
export class TaskService {
private readonly logger = new Logger(TaskService.name)
@Cron('45 * * * * *')
handleCron() {
this.logger.debug('Called when the current second is 45')
}
@Interval(10000)
handleInterval() {
this.logger.debug('Called every 10 seconds')
}
@Timeout(5000)
handleTimeout() {
this.logger.debug('Called once after 5 seconds')
}
}
nest session
- pnpm add express-session
- pnpm add @types/express-session -D
案例请看 user 目录
附录
svg-captcha 可以生成验证码 cors 一键解决跨域 compressing 压缩文件
uuid 生成一个 uuid