Nestjs中文文档链接
TypeORM 中文文档
小满视频
1. 安装Nest.js
- 安装脚手架
npm i -g @nestjs/cli
- 创建nestjs工程
nest new
- 工程目录
app.module.ts
根模块用于处理其他类的引用与共享。app.controller.ts
常见功能是用来处理http请求(处理请求的路径),以及调用service层的处理方法app.service.ts
封装通用的业务逻辑、与数据层的交互(例如数据库)、其他额外的一些三方请求
2. CURD生成器
nest g resource user
D:\vue\nestjs1>nest g resource user
// 使用哪种传输层协议
? What transport layer do you use? REST API
/*
REST API:选择 REST API 表示你将创建一个基于 HTTP 协议的 RESTful 服务。这种服务通常使用标准的 HTTP 方法(如 GET、POST、PUT、DELETE)来处理请求。
*/
? Would you like to generate CRUD entry points? Yes
/*
是否自动生成 CRUD(Create, Read, Update, Delete)操作
*/
- 自动生成以下文件
- nest g resource user 这条命令不仅生成了图中的文件,同时生成了
/user
路径 - 通过不同的请求会执行CURD操作
// controller文件
@Post() // post请求会新增数据
create(@Body() createUserDto: CreateUserDto) {
return this.userService.create(createUserDto);
}
@Get() // get请求获取全部数据
findAll() {
return this.userService.findAll();
}
// 请求的地址:http://localhost:3000/user/1
@Get(':id') // 获取id为1的数据
findOne(@Param('id') id: string) {
return this.userService.findOne(+id);
}
@Patch(':id') // 修改id为1的数据
update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
return this.userService.update(+id, updateUserDto);
}
@Delete(':id') // 删除id为1的数据
remove(@Param('id') id: string) {
return this.userService.remove(+id);
}
3. 版本控制
- 修改main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableVersioning({
type: VersioningType.URI, // 通过url传递版本信息
});
await app.listen(3000);
}
- 浏览器通过什么途径传递给服务器版本信息
export declare enum VersioningType {
URI = 0, // rul中传递
HEADER = 1, // 请求头中
MEDIA_TYPE = 2, // 通过 Content-Type HTTP 头中的媒体类型
CUSTOM = 3 // 自定义
}
- 通过
url
接收版本信息
- 浏览器中的版本信息写在端口后面,并且加字母
v
http://localhost:3000/v1/user
- 所有的请求都需要版本控制
@Controller({
version: '1',
path: 'user'
})
- 单独的某个请求需要版本控制
// 请求地址:http://localhost:3000/v1/user/1
@Get(':id')
@Version('1')
findOne(@Param('id') id: string) {
return this.userService.findOne(+id);
}
4. nest创建文件命令
nest g [命令] [文件名]
- 所有的命令
名称 | 简写 | 描述 | 翻译 |
---|---|---|---|
application | application | Generate a new application workspace | 生成新的应用程序工作区 |
class | cl | Generate a new class | 生成新类 |
configuration | config | Generate a CLI configuration file | 生成CLI配置文件 |
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 | 在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 | 生成GraphQL解析器 |
resource | res | Generate a new CRUD resource | 生成新的CRUD资源 |
service | s | Generate a service declaration | 生成服务 |
sub-app | app | Generate a new application within a monorepo | 在monorepo中生成新应用程序 |
5. 控制器(使用者) controller.ts
控制器负责处理传入的请求和向客户端返回响应,不做具体的逻辑处理
@Controller() 修饰的类就是控制器
路由
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}
@Post("login") // POST请求,路由:/user/login
create() {
//todo
return {message:"登录成功"} // nest推荐使用
}
@Get('code') // GET请求,路由 /user/code
createCode(@Response response) {
//todo
response.send({message:"返回的数据"})
}
响应
- 上面示例中当
return
的是基本类型 (string、number、boolean)直接发送值;如果return
的是对象和数组则发送JSON序列化;再加上200的状态码
- 参数中有
@Response
装饰器时,将 Nest 置于该处理函数的特定于库(Library-specific mode)的模式下,并负责管理响应。必须通过调用response
对象(例如,res.json(…)
或res.send(…)
)发出某种响应,否则 HTTP 服务器将挂起。
常用的装饰器
- 方法装饰器
装饰器 | 作用 | 使用 |
---|---|---|
@HttpCode() | 设置返回的状态码 | @HttpCode(500) //返回状态码500 |
@header() | 自定义响应头 | 可以使用 @header() 装饰器或@Res() res.header() |
@Redirect() | 重定向 | @Redirect(‘https://nestjs.com’, 301)或res.redirect() |
- 参数装饰器
装饰器 | 相当于 | 使用 |
---|---|---|
@Request(),@Req() | request | |
@Response(),@Res() | response | |
@Next() | ||
@Session() | req.session | |
@Param(key?: string) | req.params/req.params[key] | get请求获取浏览器传递的数据 |
@Body(key?: string) | req.body/req.body[key] | 发送post请求时接收数据 |
@Query(key?: string) | request.query/request.query[key] | 浏览器发送get请求,接收数据 |
@Headers(name?: string) | req.headers/req.headers[name] |
1. @Request():请求头
无论什么类型的请求,所有的信息全部都在请求头中
// 浏览器发送请求 :/user?id=1
// 服务器代码
@Get()
findAll(@Request() req) {
console.log(req.query);
return {code:200}
}
// 结果:{ id: '1' }
2. @Query(key?: string):get请求的数据
相当于request.query
// 浏览器发送请求 :/user?id=1&name=tom
@Get()
findAll(@Query() query) {
console.log(query); // { id: '1', name: 'tom' }
return {code:200}
}
@Get()
findAll(@Query("id") query) {
console.log(query); // 1
return {code:200}
}
3. @Body(key?: string):POST请求的数据
相当于request.body
/* 浏览器:/user
数据:Body 类型 : application/json
{
"id":1,
"name":"lili"
}
*/
//服务器代码
@Post()
create(@Body() body) {
console.log(body) // { id: 1, name: 'lili' }
return { code:200 }
}
@Post()
create(@Body("name") body) {
console.log(body) // lili
return { code:200 }
}
4. @Param(key?: string):浏览器使用/1
的方式传递数据
// 浏览器:/user/123
// 服务器代码
@Get(':id')
findOne(@Param('id') id: string) {
console.log(id) // 123
return { code:200 }
}
@Get(':id')
findOne(@Request() req ,@Param('id') id: string) {
console.log(req.params); //{ id: '123' }
console.log(id) // 123
return JSON.stringify({ code:200 })
}
5. @HttpCode:返回状态码
- 只要符合这条路由返回的状态码都是500
- 用于找不到某一条路由时重定向或者其他操作
@Get()
@HttpCode(500)
findAll() {
return this.userService.findAll();
}
6. 提供者service.ts
@Injectable()
修饰的类- 提供给控制器使用,用于逻辑处理
// 提供者 user.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class UserService {
findOne(id: number) {
return `This action returns a #${id} user`;
}
}
// 控制器 user.controller.ts
import { UserService } from './user.service'; // 导入提供者
@Controller('user')
export class UserController {
// 通过类构造函数注入提供者
constructor(private readonly userService: UserService) {} //
@Get(':id')
findOne(@Param('id') id: string) {
// 调用提供者中的方法
return this.userService.findOne(+id);
}
}
7. 模块 module
给controller和service拉皮条的
- @Module() 装饰器装饰的一个对象
@Module({
controllers: [AppController],
providers: [AppService],
imports: [UserModule, User1Module],
})
项目 | Value |
---|---|
providers | 由 Nest 注入器实例化的提供者,并且可以至少在整个模块中共享 |
controllers | 必须创建的一组控制器 |
imports | 导入模块的列表,这些模块导出了此模块中所需提供者 |
exports | 由本模块提供并应在其他模块中可用的提供者的子集。 |
module中的提供者providers
小满视频-提供者
官网链接
1. 标准提供者
(1)在 App.service.ts
中 @Injectable()
装饰器声明 AppService
类是一个可以由Nest IoC容器管理的类。
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}
(2)在 App.controller.ts
中通过构造函数声明了 AppService
注入的实例
constructor(private readonly appService: AppService) {}
(3)在 app.module.ts
中,我们将标记 AppService
为providers
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module';
import { User1Module } from './user1/user1.module';
@Module({
controllers: [AppController],
providers: [AppService],
imports: [UserModule, User1Module],
})
export class AppModule {}
(4)providers: [AppService],
的完整写法
providers: [
{
provide: AppService, // 名字可以随便起
useClass: AppService,
},
],
(5) provide为别名
providers: [
{
provide: "abc",
useClass: AppService,
},
],
- 控制器中使用
@Inject("abc")
constructor(@Inject("abc") private readonly appService: AppService) {}
2. 值提供者
- module
providers: [
AppService,
{
provide: 'AAA', // 提供者的名字
// 提供者提供的数据,数据可以是任何类型
useValue: { name: 'xiaoming', age: 18 },
},
],
- controller
import { Controller, Get, Inject } from '@nestjs/common';
import { AppService } from './app.service';
// 类型定义
type UserInfo = {
name: string;
age: number;
}
@Controller()
export class AppController {
constructor(private readonly appService: AppService,
// 声明 UserInfo的实例 userInfo
@Inject('AAA') private readonly userInfo: UserInfo) {}
@Get()
getHello(): string {
// 通过实例访问数据
return `name:${this.userInfo.name} age:${this.userInfo.age}`;
}
}
3. 工厂提供者
- module
providers: [
AppService,
{
provide: 'AAA',
useFactory: () => {
return 'AAA的工厂模式';
}
},
],
- controller
constructor(private readonly appService: AppService,
@Inject('AAA') private readonly abc:string) {}
4. 异步提供者
{
provide: 'ASYNC_CONNECTION',
useFactory: async () => {
const connection = await createConnection(options);
return connection;
},
}
共享模块
官网链接
- 在
user.module
文件中导出UserService
,则在所有imports: [UserModule]
的模块中都可以使用UserService
- 自己的话总结:
"当前module"
中导出service
,则其他任何导入"当前module"
的module
,都可以在controller
中使用"当前module"
的service
// 当前module:UseModule
@Module({
controllers: [UserController],
providers: [UserService],
exports: [UserService] // 导出 service
})
export class UserModule {}
// 其他module
@Module({
imports: [UserModule], // 导入了 UserModule
controllers: [AppController], // 导入以后,在控制器中就能使用UserModule中的service
providers: [AppService],
})
export class AppModule {}
全局模块@Global()
- 在根模块中引入全局模块,直接可以在子组件的
controller
中使用
- 自定义一个全局模块,路径:
/src/config/config.module.ts
import { Global, Module } from "@nestjs/common";
@Global() // 声明该模块为全局模块
@Module({
providers: [
{ provide: 'CONFIG_OPTIONS', useValue: { host: 'localhost', port: 77777 } },
],
exports: ['CONFIG_OPTIONS'],
})
export class ConfigModule {}
- 在根模块中引入
import { ConfigModule} from './config/config.module';
imports: [AAAModule, BBBModule, ConfigModule],
- 在其他模块的构造函数中使用
@Inject('CONFIG_OPTIONS') private readonly options
动态模块
使用类的静态方法,创建动态模块
- 调用时不带参数,使用静态模块中的数据
- 调用时带参数,使用动态模块中的数据
8. 中间件
1. 基本使用方法
- 创建中间件
nest g mi [路径/名字]
- 创建中间件文件
next()和res.send()这两个方法只能使用一个,不可同时使用
import { Injectable, NestMiddleware } from '@nestjs/common';
import {Request,Response,NextFunction} from 'express';
@Injectable()
export class UserMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
/* 可以执行以下任务
1. 执行任何代码
2. 对请求和响应对象进行更改
3. 调用堆栈中的下一个中间件函数
4. 如果在这里没有结束请求(发送response),必须调用next()
*/
console.log('user middleware');
next();
}
}
- 将中间件与路由绑定
import { Global, Module,NestModule,MiddlewareConsumer } from '@nestjs/common';
import { UserMiddleware} from './user.middleware';
import { UserService } from './user.service';
import { UserController } from './user.controller';
@Module({
controllers: [UserController],
providers: [UserService],
exports: [UserService]
})
// 使用中间件
export class UserModule implements NestModule {
// 通过绑定中间件与路由关系,实现全局中间件
configure(consumer: MiddlewareConsumer): any{
consumer
.apply(UserMiddleware) // 绑定中间件
.forRoutes('user'); // 绑定路由
};
}
- 实现的效果:只要访问
use
路由,这会在服务器端打印user middleware
2. 中间件绑定路由的其他方式
- 指定访问方式
import { RequestMethod } from '@nestjs/common';
.forRoutes({
path: 'user', // 绑定路由
method: RequestMethod.GET // 绑定访问方式
});
- 其中的RequestMethod 是枚举
export declare enum RequestMethod {
GET = 0,
POST = 1,
PUT = 2,
DELETE = 3,
PATCH = 4,
ALL = 5,
OPTIONS = 6,
HEAD = 7,
SEARCH = 8
}
-
直接绑定路由
.forRoutes('user');
,只能在访问/user
时调用中间件,对/user/code
不起作用 -
指定路由时使用通配符
.forRoutes({
path: 'user/*', // user路由下的所有路径
method: RequestMethod.GET // 绑定访问方式
});
- 指定控制器
consumer
.apply(UserMiddleware) // 绑定中间件
.forRoutes(UserController); // 指定控制器
};
3. 函数式中间件,并应用于全局
- 创建函数式中间件
// ./app.middleware.function
import {Request,Response,NextFunction} from 'express';
export const MiddleWareAll = (req:Request,res:Response,next:NextFunction)=>{
console.log("全局路由")
// 这里的主要使用场景是路由的白名单或黑名单
next()
}
- 在main.ts文件中引入并使用
import {MiddleWareAll} from './app.middleware.function';
app.use(MiddleWareAll)
4. 通过中间件插件允许跨域请求
- 安装cors插件
npm i cors -S
npm i @types/cors -D
- 在main.ts文件中配置
import * as cors from 'cors';
...
app.use(cors()) // 这条代码的位置必须在所有的中间件之前
9. 拦截器
官网
1. 创建拦截器
import { NestInterceptor,ExecutionContext,CallHandler } from '@nestjs/common';
import { Observable, tap } from 'rxjs';
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler):Observable<any>{
const req = context.switchToHttp().getRequest() // 获取请求对象
const res = context.switchToHttp().getResponse() // 获取响应对象
console.log('Before...');
const now = Date.now();
// 上面的代码执行完毕
// 再执行controller中的代码
//最后执行到下面的代码
return next.handle().pipe(
tap(()=>console.log(`After... ${Date.now() - now}ms`))
)
}
}
2. 拦截器的使用
- 在controller的类上使用
// 导入拦截器
import { UseInterceptors } from '@nestjs/common';
import { LoggingInterceptor } from '../Interceptors/Interceptor1';
@Controller('upload')
@UseInterceptors(LoggingInterceptor)
export class UploadController
- 在controller的方法上使用
@Get()
@UseInterceptors(LoggingInterceptor)
getFile(): string {...}
- 全局使用,main.ts
import {LoggingInterceptor} from './Interceptors/Interceptor1';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 直接使用拦截器的实例,没有使用依赖注入
app.useGlobalInterceptors(new LoggingInterceptor());
await app.listen(3000);
}
bootstrap();
- module中设置拦截器,可全局或者模块中使用
import { LoggingInterceptor } from './Interceptors/Interceptor1';
@Module({
controllers: [AppController],
providers: [AppService,{
provide:"APP_INTERCEPTOR",
useClass:LoggingInterceptor
}],
})
export class AppModule {}
3. 响应映射
- 使用map等运算符
使用运算符,则在controller中不能使用res返回信息
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
export interface Response<T> {
data: T;
}
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> {
return next.handle().pipe(map(data => ({ data })));
}
}
4. 异常拦截器
import { Catch,HttpException,ExceptionFilter,ArgumentsHost } from "@nestjs/common";
import { Request,Response } from "express";
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost){
const req:Request = host.switchToHttp().getRequest();
const res: Response = host.switchToHttp().getResponse();
// 获取异常时的状态码
const status: number = exception.getStatus();
// 链式调用status和json,status:设置状态码,json的参数是返回的数据
res.status(status).json({ // 此处不设置状态码,返回的状态码是 200
timestamp: new Date().toISOString(),
path: req.url,
error: exception.getResponse()
})
}
}
- 全局使用 main.ts
import { HttpExceptionFilter } from './Interceptors/filter';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.useGlobalFilters(new HttpExceptionFilter())
await app.listen(3000);
}
bootstrap();
10. 管道
管道有两个典型的应用场景:
- 转换:管道将输入数据转换为所需的数据输出(例如,将字符串转换为整数)
- 验证:对输入数据进行验证,如果验证成功继续传递; 验证失败则抛出异常
1. 简单数据类型的内置管道
ParseBoolPipe
布尔类型ParseFloatPipe
浮点数ParseEnumPipe
枚举ParseArrayPipe
数组ParseUUIDPipe
uuid
当使用上述类型时,会先转换数据的类型,如果转换不成功则抛出错误,
import { ParseIntPipe } from '@nestjs/common';
@Get(':id')
findOne(@Param('id',ParseIntPipe) id: string) { // 虽然已经在此定义了id的类型为string,使用管道后,转换为number
console.log(typeof(id)); // 输出:number
return this.pipeService.findOne(+id);
}
2. 自定义管道
① 在需要自定义管道的目录中创建文件
nest g pipe [文件名]
② 自定义管道的条件
import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';
@Injectable()
export class CustomPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
// 自定义条件来约束数据
// 这里的示例并不符合管道的职责,管道是验证数据的
if (value.age >= 18) {
value.age = 17
}
return value;
}
}
③在controller中绑定
import {CustomPipe} from './custom/custom.pipe'
@Controller('pipe')
export class PipeController {
constructor(private readonly pipeService: PipeService) {}
@Post()
create(@Body(CustomPipe) createPipeDto: CreatePipeDto) {
return createPipeDto
// return this.pipeService.create(createPipeDto);
}
3. 使用Joi做数据验证
- 安装joi
npm install --save joi
npm install --save-dev @types/joi
- 创建joi的验证文件
import { ArgumentMetadata, Injectable, PipeTransform,BadRequestException } from '@nestjs/common';
import { ObjectSchema } from 'joi';
import * as Joi from 'joi';
@Injectable()
export class JoiValidationPipe implements PipeTransform {
constructor(private schema: ObjectSchema) {}
transform(value: any, metadata: ArgumentMetadata) {
const { error } = this.schema.validate(value);
if (error) {
throw new BadRequestException('Validation failed');
}
return value;
}
}
// 验证规则
export const createPipeSchema = Joi.object({
// 假设 CreatePipeDto 包含 name 和 age 属性
name: Joi.string().min(3).max(30).required(),
age: Joi.number().integer().min(18).max(100).required(),
});
- 绑定验证
import { JoiValidationPipe,createPipeSchema } from './joi-validation/joi-validation.pipe'
@Post()
@UsePipes(new JoiValidationPipe(createPipeSchema))
create(@Body() createPipeDto: CreatePipeDto) {
return createPipeDto
// return this.pipeService.create(createPipeDto);
}
4. 类验证器
验证装饰器中文文档
- 安装
npm i --save class-validator class-transformer
- 生成CRUD资源模块时,同时生成两个数据类型文件
.dto.ts
- 插件
class-validator
提供了很多验证规则,使用装饰器就可对数据进行验证
// creat-pipe.dto.ts
import { IsString,IsNumber,IsNotEmpty,Length } from "class-validator";
export class CreatePipeDto {
@IsString({message:'name必须是字符串'})
@IsNotEmpty({message:'name不能为空'})
@Length(3, 10,{message:'name长度必须在3到10之间'})
name: string;
@IsNumber()
@IsNotEmpty()
age: number;
}
- 管道
// ./custom/custom.pipe 验证管道
import { ArgumentMetadata, Injectable, PipeTransform,BadRequestException } from '@nestjs/common';
import { plainToInstance } from 'class-transformer';
import { validate } from 'class-validator';
@Injectable()
export class CustomPipe implements PipeTransform {
async transform(value: any, metadata: ArgumentMetadata) {
// 在此可以修改value的数据,例如将null修改为""
// 将dto文件中的数据类型 和 接收到的数据 合并成一个实例
// value是前端发送的req.body,通过controller转发过来的
const DTO = plainToInstance(metadata.metatype,value)
// 验证DTO实例,errors 是DTO验证后的所有错误信息
const errors = await validate(DTO)
console.log(errors)
// 错误信息的条数大于1,说明数据有验证未通过
if (errors.length > 0) {
throw new BadRequestException(errors)
}
return value;
}
}
- 在绑定管道
import { CreatePipeDto } from './dto/create-pipe.dto';
import {CustomPipe} from './custom/custom.pipe'
@Post()
create(@Body(CustomPipe) createPipeDto: CreatePipeDto) {
return createPipeDto
// return this.pipeService.create(createPipeDto);
}
- 管道中返回的结果
POST /pipe {"name":"a","age":""}
{
"statusCode": 400,
"timestamp": "2024-09-13T14:43:14.775Z",
"path": "/pipe",
"message": "Bad Request Exception",
"error": {
"message": [
{
"target": {
"name": "a",
"age": ""
},
"value": "a",
"property": "name",
"children": [],
"constraints": {
"isLength": "name长度必须在3到10之间"
}
},
{
"target": {
"name": "a",
"age": ""
},
"value": "",
"property": "age",
"children": [],
"constraints": {
"isNotEmpty": "age should not be empty",
"isNumber": "age must be a number conforming to the specified constraints"
}
}
],
"error": "Bad Request",
"statusCode": 400
}
}
./custom/custom.pipe
管道代码中的metadata
,有三个属性,看图
5. 类验证器,全局设置
- 这种方式使用管道,只能验证数据类型,无法改变数据,例如把
null
改为""
- 全局使用Pipe,无需与controller绑定,也能返回错误信息
// main.ts
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.useGlobalPipes(new ValidationPipe())
await app.listen(3000);
}
bootstrap();
11. 守卫
官网中文文档
- 作用:鉴权
1. 基本使用
- 创建守卫
- 在需要守卫的模块中创建守卫文件
nest g gu [文件名]
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';
import { Reflector } from '@nestjs/core';
import type {Request} from 'express';
@Injectable()
export class RoleGuard implements CanActivate {
constructor(private reflector: Reflector){}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
// 获取controller中key=roles的SetMetadata装饰器中的参数
// 第一个参数是SetMetadata的key,第二个是SetMetadata修饰的函数体
const roles = this.reflector.get<string[]>('users', context.getHandler());
const req:Request = context.switchToHttp().getRequest();
// 获取通过query传入的权限
const user:string = req.query.user as string;
// 如果在roles中包含user则返回true否则返回false
if(roles.includes(user)){
return true
} else {
return false;
}
}
}
- 在controller的类使用UseGuards装饰器
import { UseGuards } from '@nestjs/common';
@Controller('guard')
@UseGuards(RoleGuard)
- 在controller类中的方法设置权限
- SetMetadata有两个参数:第一个参数是该条规则的名字,第二个就是可访问的角色,可以是多个角色
@Get()
@SetMetadata("users","admin","user")
2. 在全局中使用守卫
app.useGlobalGuards(new GlobaRulesGuard)
3. 自定义装饰器
nest g d [文件前缀名]
import { SetMetadata } from '@nestjs/common';
export const UserRoles = (...args: string[]) => SetMetadata('users', args);
- 在controller中使用
import { UserRoles } from './decorators/decorators.decorator';
@Get()
@UserRoles ("admin","guest")
常用的插件
1. express-session
- 安装express-session和类型声明
npm i express-session --save
npm i @types/express-session -D
- 在main.ts文件中引入
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as session from 'express-session' //
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(session({secret:"xm"})) //
await app.listen(3000);
}
bootstrap();
- 配置项
项目 | 用途 | 默认值 | 示例 |
---|---|---|---|
secret | 用于签名会话 ID 的 cookie。string类型 | 必选项 | |
resave | 是否在每次请求时重新保存会话,即使未修改 | true | |
saveUninitialized | 是否为尚未初始化的会话创建新的会话对象。 | true | |
cookie | 设置 cookie 的选项。 | { maxAge: 1000 * 60 * 60 * 24 * 30 } | |
name | 设置会话 ID 的 cookie 名称 | “connect.sid” | “session_id” |
store | 存储会话数据的对象。 | 使用 connect-mongo 连接 MongoDB | |
proxy | 是否信任代理服务器 | false | |
rolling | 是否在每次请求时刷新会话的过期时间 | false |
- cookie的子选项
项目 | 作用 |
---|---|
maxAge | 设置会话 cookie 的最大生存时间(毫秒)。例如,1000 * 60 * 60 * 24 * 30 表示 30 天。 |
httpOnly | 设置 cookie 的 httpOnly 标志,防止 JavaScript 访问,增加安全性。 |
secure | 设置 cookie 的 secure 标志,在生产环境中应设置为 true,确保 cookie 只能通过 HTTPS 传输。 |
sameSite | 设置 cookie 的 SameSite 属性,增加安全性。默认值“lax“;”strict“ 表示只能在相同站点下发送 cookie。 |
- 示例
app.use(session({
secret: "xm", // 加盐。必选配置项。用于签名会话 ID 的 cookie,确保会话数据的安全性
resave: false, // 是否在每次请求时重新保存会话,即使未修改(推荐设置为 false)
saveUninitialized: false, // 是否为尚未初始化的会话创建新的会话对象(推荐设置为 false)
cookie: {
maxAge: 1000 * 60 * 60 * 24 * 30, // 设置会话 cookie 的最大生存时间(30 天)
httpOnly: true, // 设置 cookie 的 httpOnly 标志,防止 JavaScript 访问(增加安全性)
secure: false, // 设置 cookie 的 secure 标志,在生产环境中应设置为 true(仅通过 HTTPS 传输)
sameSite: 'strict' // 设置 cookie 的 SameSite 属性,增加安全性(strict 表示只能在相同站点下发送 cookie)
},
name: 'session_id', // 设置会话 ID 的 cookie 名称,默认为 "connect.sid"
rolling: true, // 是否在每次请求时刷新会话的过期时间(滚动更新)
proxy: false // 是否信任代理服务器(在使用反向代理时设置为 true)
}));
2. 验证码svgCaptcha
svgCaptcha:svg图片
- 安装
npm i svg-captcha -S
- 使用
import * as svgCaptcha from 'svg-captcha';
const captcha = svgCaptcha.create({
size: 4, // 图片中有几个字符
fontSize: 50, // 字体大小
width: 100, // 图片宽度
height: 40, // 图片高度
background: '#cc9966' // 背景颜色
})
console.log(captcha)
- 结果
{
"text": "3X1i", // 图片中的字母
// data中就是svg图形
"data": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"100\" height=\"40\" viewBox=\"0,0,100,40\"><rect width=\"100%\" height=\"100%\" fill=\"#cc9966\"/><path d=\"M15 9 C68 38,51 13,91 39\" stroke=\"#4fd3d3\" fill=\"none\"/><path fill=\"#595959\" d=\"M14.06 ........12Z\"/></svg>"
}
技术
1. 上传文件
官网链接
为了处理文件上传,Nest 提供了一个内置的基于 multer 中间件包的 Express 模块。Multer 处理以 multipart/form-data 格式发送的数据
1. 基本使用
- 安装类型声明文件
npm i -D @types/multer
- 在控制器中使用
import { Controller, Get, Post, Body, Patch, Param, Delete, UseInterceptors, UploadedFile } from '@nestjs/common';
import { UploadService } from './upload.service';
import { FileInterceptor } from '@nestjs/platform-express';
import { Express } from 'express';
@Controller('upload')
export class UploadController {
constructor(private readonly uploadService: UploadService) {}
@Post('img')
@UseInterceptors(FileInterceptor('file'))
create(@UploadedFile() file: Express.Multer.File ) {
console.log(file)
}
}
- 结果
// 上传路径:/upload/img
{
fieldname: 'file',
originalname: '0bb9ad608ead12b4e8ead0d172fa6300.jpg',
encoding: '7bit',
mimetype: 'image/jpeg',
buffer: <Buffer ff d8 ff e0 00 10 4a 46 49 46 00 01 01 00 00 01 00 01 00 00 ff db 00 43 00 02 02 02 02 02 01 02 02 02 02 03 02 02 03 03 06 04 03 03 03 03 07 05 05 04 ... 68607 more bytes>,
size: 68657
}
2. 将上传文件保存到磁盘中
- 在module文件中,设置将上传的文件写入到磁盘中
import { Module } from '@nestjs/common';
import { UploadService } from './upload.service';
import { UploadController } from './upload.controller';
import { MulterModule} from '@nestjs/platform-express';
import { extname, join} from 'path';
import { diskStorage } from 'multer';
@Module({
imports:[MulterModule.register({
// 自定义存储
storage:diskStorage({ // diskStorage:保存到磁盘上
// 设置保存文件的路径
destination: join(__dirname,'../images'),
// 设置文件的文件名
filename:(req,file,callback)=>{
// extname(file.originalname):获取文件的后缀名
const filename = Date.now()+extname(file.originalname);
return callback(null,filename);
}
})
})],
controllers: [UploadController],
providers: [UploadService],
})
export class UploadModule {}
- 结果:文件上传后路径
dist/images
3. 访问服务器中的静态资源
- 修改main.ts文件
import { NestExpressApplication } from '@nestjs/platform-express';
import { join } from 'path';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
// 配置静态资源的路径
app.useStaticAssets(join(__dirname,'images'),{
// 设置虚拟路径,浏览器访问上面的静态资源时需要加 /img 路径
prefix: '/img',
})
await app.listen(3000);
}
bootstrap();
- 结果:浏览器访问:
http://localhost:3000/img/1726123176794.jpg
时返回一张图片
2. 下载文件
1. 直接下载
import { Response } from 'express';
import { join } from 'path';
@Controller('upload')
export class UploadController {
constructor(private readonly uploadService: UploadService) {}
@Get('export')
// 注意此处的Response是从express引入的
downLoad(@Res() res:Response) {
const url = join(__dirname,'../images/1726123176794.jpg')
res.download(url)
}
}
- 结果:浏览器:
http://localhost:3000/upload/export
下载图片文件
2. 通过流处理
官网
import { UploadService } from './upload.service';
import { Controller, Get, StreamableFile, Response } from '@nestjs/common';
import { createReadStream } from 'fs';
import { join } from 'path';
@Controller('upload')
export class UploadController {
constructor(private readonly uploadService: UploadService) {}
@Get()
getFile(@Response({ passthrough: true }) res): StreamableFile {
const file = createReadStream(join(__dirname, '../images/217.jpeg'));
console.log(file)
res.set({
'Content-Type': 'image/jpeg',
// inline:直接在浏览器中打开文件;attachment:下载文件
'Content-Disposition': 'inline; filename="217.jpeg"',
});
return new StreamableFile(file);
}
}
3. 压缩
小满视频中是压缩为zip包后再发送流文件
3. 数据库
官网链接
1. 连接数据库和实体类
- 安装nestjs与数据库的关系映射器
npm install --save @nestjs/typeorm typeorm mysql2
- 在app.moudel.ts中配置数据库
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module';
// 引入orm
import { TypeOrmModule } from '@nestjs/typeorm';
// 引入实体类
import { User } from './user/entities/user.entity';
@Module({
controllers: [AppController],
providers: [AppService],
imports: [
UserModule,
// 配置数据库
TypeOrmModule.forRoot({
type: "mysql", //数据库类型
username: "root", //账号
password: "******", //密码
host: "localhost", //host
port: 3306, //
database: "demo", //库名
// 载入实体类
// entities: [__dirname + '/**/*.entity{.ts,.js}'], // 通过路径指定实体类
// entities: [User], // 指定已经引入的实体类
autoLoadEntities:true, //在所有模块的module的forFeature()方法中注册实体都将自动添加到entities的数组中
// 生产环境中建议关闭synchronize
synchronize:true, //是否自动将实体类同步到数据库
retryDelay:500, //重试连接数据库间隔
retryAttempts:10,//重试连接数据库的次数
}),
],
})
export class AppModule {}
- user模块中的实体类
// ./entities/user.entity
import { Entity, Column, PrimaryGeneratedColumn,CreateDateColumn } from 'typeorm';
// 实体类的修饰符
@Entity()
export class User {
// 唯一且自增
@PrimaryGeneratedColumn()
id: number;
// @Column:定义列
@Column()
name: string;
@Column()
desc: string;
@CreateDateColumn({type:"timestamp"})
create_Time: Date;
}
- 在user模块的module中绑定实体类
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
@Module({
controllers: [UserController],
providers: [UserService],
exports: [UserService],
imports: [TypeOrmModule.forFeature([User])]
})
- 至此将会在数据库中自动创建user这张表
2. 定义实体类中各字段
小满
3. 在server中操作数据表
import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { Like, Repository } from 'typeorm';
@Injectable()
export class UserService {
// 实体类实例化
constructor(
@InjectRepository(User) private readonly userRepository: Repository<User>,
) {}
create(createUserDto: CreateUserDto) {
// 给实体类的属性赋值,通过sava方法保存到数据库中
const data = new User();
data.name = createUserDto.name;
data.desc = createUserDto.desc;
return this.userRepository.save(data);
}
// query传入的参数:关键字(当关键字为"",返回所有数据);第几页;每页几条数据
async findAll(query: { keyWord: string; currentPage: number; pageSize: number }) {
// find方法:按条件查询
const data = await this.userRepository.find({
where: { name: Like(`%${query.keyWord}%`) }, // where条件
order: { id: "ASC" }, // 按照id 升序排列
skip: (query.currentPage - 1) * query.pageSize, // 从第几条数据开始
take: query.pageSize, // 取几条数据
});
// 按条件查询,符合查询条件的数据总条数
const total = await this.userRepository.count({
where: { name: Like(`%${query.keyWord}%`) },
})
return {data,total}
}
update(id: number, updateUserDto: UpdateUserDto) {
// update方法,更新数据
return this.userRepository.update(id, updateUserDto);
}
remove(id: number) {
// delete方法,删除数据
return this.userRepository.delete(id);
}
}
4. 表的一对多关系
- User表和Tag表,一个User有多个Tag的一对多关系
user.entity.ts文件
import { Entity, Column, PrimaryGeneratedColumn,CreateDateColumn, OneToMany } from 'typeorm';
import { Tags } from './tags.entity';
// 实体类的修饰符
@Entity()
export class User {
// 唯一且自增
@PrimaryGeneratedColumn()
id: number;
// 列
@Column()
name: string;
@Column()
desc: string;
@CreateDateColumn({type:"timestamp"})
create_Time: Date;
// 一对多,主要体现在 装饰器 和 []
// 第一个参数指向关联的实体类Tags
// 第二个参数中的t是第一个参数指向的实体类,user是Tags实体类中列的名字
@OneToMany(()=>Tags,t=>t.user)
// 一对多,这里需要使用 []
tags:Tags[]
}
tags.entity.ts文件
import { Entity, Column, PrimaryGeneratedColumn,CreateDateColumn, ManyToOne } from 'typeorm';
import { User } from './user.entity'
// 实体类的修饰符
@Entity()
export class Tags {
// 唯一且自增
@PrimaryGeneratedColumn()
id: number;
// 列
@Column()
name:string
@ManyToOne(()=>User,u=>u.tags)
user:User
}
前端传来的数据
// 给id为2的user添加多个标签
{
"id": 2,
"tags": [
"red",
"green",
"blue"
]
}
// createTagsDto:前端传来的数据
async createTags(createTagsDto: CreateTagsDto) {
// 提取中其中的tags
const data:string[] = createTagsDto.tags
// 根据传来的id找到user
const user = await this.userRepository.findOne({
where:{id:createTagsDto.id}
})
// user表中的tags字段
const tagsList:Tags[] = []
// 遍历tags,保存到tag表中
for (const item of data){
const tags = new Tags()
tags.name = item
// tag表的user字段使用上面查询出来的user对象,不是只传user的id
tags.user = user
// 保存到tag表中。
await this.tagsRepository.save(tags)
tagsList.push(tags)
}
user.tags = tagsList
// 这里 sava 方法不会创建新的user
// 在sava时如果发现存在该id则会更新user表中的tags字段
await this.userRepository.save(user)
return true
}
5. 事务
export class TransferMoney{
fromId: number;
toId: number;
money: number;
}
async transfer(transferMoney: TransferMoney) {
try {
// 在数据库中找到要转账的两个账户
const accountFrom = await this.moneyRepository.findOneBy({
id: transferMoney.fromId,
});
const accountTo = await this.moneyRepository.findOneBy({
id: transferMoney.toId,
});
if (!accountFrom || !accountTo) {
return { message: '账户不存在' };
}
if (accountFrom.money < transferMoney.money) {
return { message: '余额不足' };
}
// 开启事务.manager.transaction(async (manager) => {})固定语法
await this.moneyRepository.manager.transaction(async (manager) => {
// 更新转出账户的余额
await manager.update(
Account,
{ id: transferMoney.fromId },
{ money: accountFrom.money - transferMoney.money },
);
// 更新转入账户的余额
await manager.update(
Account,
{ id: transferMoney.toId },
{ money: accountTo.money + transferMoney.money },
);
});
return { message: '转账成功' };
} catch (e) {
return { message: e };
}
}