Nest.js

news2024/11/16 7:31:14

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. 版本控制

  1. 修改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	// 自定义
}
  1. 通过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 [命令] [文件名]
  • 所有的命令
名称简写描述翻译
applicationapplicationGenerate a new application workspace生成新的应用程序工作区
classclGenerate a new class生成新类
configurationconfigGenerate a CLI configuration file生成CLI配置文件
controllercoGenerate a controller declaration生成控制器
decoratordGenerate a custom decorator生成自定义装饰器
filterfGenerate a filter declaration生成筛选器
gatewaygaGenerate a gateway declaration生成网关
guardguGenerate a guard declaration生成守卫
interceptoritcGenerate an interceptor declaration生成拦截器
interfaceitfGenerate an interface生成接口
librarylibGenerate a new library within a monorepo在monorepo中生成新库
middlewaremiGenerate a middleware declaration生成中间件
modulemoGenerate a module declaration生成模块
pipepiGenerate a pipe declaration生成管道
providerprGenerate a provider declaration生成提供者
resolverrGenerate a GraphQL resolver declaration生成GraphQL解析器
resourceresGenerate a new CRUD resource生成新的CRUD资源
servicesGenerate a service declaration生成服务
sub-appappGenerate 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:"返回的数据"})
  }

响应

  1. 上面示例中当return的是基本类型 (string、number、boolean)直接发送值;如果return的是对象和数组则发送JSON序列化;再加上200的状态码
  2. 参数中有@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 中,我们将标记 AppServiceproviders

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;
  },
}

共享模块

官网链接

  1. user.module文件中导出UserService,则在所有 imports: [UserModule]的模块中都可以使用UserService
  2. 自己的话总结:"当前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中使用
  1. 自定义一个全局模块,路径:/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 {}
  1. 在根模块中引入
import { ConfigModule} from './config/config.module';

imports: [AAAModule, BBBModule, ConfigModule],

  1. 在其他模块的构造函数中使用
    @Inject('CONFIG_OPTIONS') private readonly options

动态模块

使用类的静态方法,创建动态模块

  1. 调用时不带参数,使用静态模块中的数据
  2. 调用时带参数,使用动态模块中的数据

在这里插入图片描述

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. 中间件绑定路由的其他方式

  1. 指定访问方式
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
}

  1. 直接绑定路由.forRoutes('user');,只能在访问/user时调用中间件,对/user/code不起作用

  2. 指定路由时使用通配符

      .forRoutes({
        path: 'user/*', // user路由下的所有路径
        method: RequestMethod.GET // 绑定访问方式
      }); 
  1. 指定控制器
    consumer
      .apply(UserMiddleware) // 绑定中间件
      .forRoutes(UserController); // 指定控制器
  };

3. 函数式中间件,并应用于全局

  1. 创建函数式中间件
// ./app.middleware.function
import {Request,Response,NextFunction} from 'express';

export const  MiddleWareAll = (req:Request,res:Response,next:NextFunction)=>{
    console.log("全局路由")
    // 这里的主要使用场景是路由的白名单或黑名单
    next()
}   
  1. 在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做数据验证

  1. 安装joi
npm install --save joi
npm install --save-dev @types/joi
  1. 创建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(),

});
  1. 绑定验证
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. 基本使用

  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;
    }
  }
}
  1. 在controller的类使用UseGuards装饰器
import { UseGuards } from '@nestjs/common';

@Controller('guard')
@UseGuards(RoleGuard)
  1. 在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. 基本使用

  1. 安装类型声明文件
npm i -D @types/multer
  1. 在控制器中使用
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)
  }
}
  1. 结果
// 上传路径:/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. 连接数据库和实体类

  1. 安装nestjs与数据库的关系映射器
npm install --save @nestjs/typeorm typeorm mysql2
  1. 在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 {}
  1. 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;
}

  1. 在user模块的module中绑定实体类
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
@Module({
  controllers: [UserController],
  providers: [UserService],
  exports: [UserService],
  imports: [TypeOrmModule.forFeature([User])]
})
  1. 至此将会在数据库中自动创建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 };
    }
  }

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

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

相关文章

.net core8 使用JWT鉴权(附当前源码)

说明 该文章是属于OverallAuth2.0系列文章&#xff0c;每周更新一篇该系列文章&#xff08;从0到1完成系统开发&#xff09;。 该系统文章&#xff0c;我会尽量说的非常详细&#xff0c;做到不管新手、老手都能看懂。 说明&#xff1a;OverallAuth2.0 是一个简单、易懂、功能强…

焦虑拜拜!这些维生素是你的情绪小太阳✨,焦虑星人必看!

&#x1f33f; ‌维生素B群&#xff1a;情绪的调节大师‌ &#x1f3af; 说到缓解焦虑&#xff0c;怎能不提维生素B群&#xff1f;它可是个大家庭&#xff0c;包括B1、B2、B6、B12等&#xff0c;每一个都是调节神经系统的关键角色。维生素B群能够促进神经递质的合成&#xff0…

Prometheus监控k8s环境构建

传统架构中比较流行的监控工具有 Zabbix、Nagios 等&#xff0c;这些监控工具对于 Kubernetes 这类云平台的监控不是很友好&#xff0c;特别是当 Kubernetes 集群中有了成千上万的容器后更是如此&#xff0c;本章节学习下一代的云原生监控平台---Prometheus。 一、基于kuberne…

DNS解析域名详解

你有没有想过&#xff0c;当一个url传过来网络对它进行了哪些操作~DNS又是怎样对域名进行解析的~或者我们为什么要用到域名&#xff0c;为什么不直接使用ip地址~ 对于我们而言&#xff0c;面对长串的ip地址&#xff0c;我们更喜欢记忆较短的域名&#xff0c;但是对于路由器来说…

第二证券:降息升温!资金涌入港股,行情还能持续多久?

在美联储行将打开降息影响下&#xff0c;多国股指改写高点。 当时&#xff0c;商场环绕美联储是25个基点仍是50个基点的降息展开预期买卖&#xff0c;资金流向风险财物规划扩大显着。17日&#xff0c;澳大利亚S&P/ASX 200指数股指、印度孟买SENSEX30指数、新加坡富时海峡指…

MySQL函数:日期函数

先贴一张黑马程序员的听课截图 1.返回当前日期 CURDATE(); select CURDATE(); //获取当前日期2. 返回当前时间 CURTIME(); select CURTIME(); //获取当前时间3.返回当前日期和时间NOW() select NOW(); //获取当前日期和时间 4.获取指定date的年份YEAR(date) select YEAR…

力扣(LeetCode)每日一题 2848. 与车相交的点

题目链接https://leetcode.cn/problems/points-that-intersect-with-cars/description/?envTypedaily-question&envId2024-09-15 给你一个下标从 0 开始的二维整数数组 nums 表示汽车停放在数轴上的坐标。对于任意下标 i&#xff0c;nums[i] [starti, endi] &#xff0c;…

[Python]一、Python基础编程

F:\BaiduNetdiskDownload\2023人工智能开发学习路线图\1、人工智能开发入门\1、零基础Python编程 1. Python简介 Python优点: 学习成本低开源适应人群广泛应用领域广泛1.1 Python解释器 下载地址:Download Python | Python.org 1.2 Python开发IDE -- Pycharm 2. 基础语法…

人工智能(AI)的影响下人类的生活样子

讨论在人工智能(AI)的影响下人类的生活是什么样子 在21世纪的今天&#xff0c;人工智能&#xff08;AI&#xff09;已经不再是遥不可及的未来科技&#xff0c;而是悄然渗透到我们日常生活的每一个角落&#xff0c;以一种前所未有的方式改变着我们的生活方式、工作模式乃至社会…

使用 Python 绘制 BTC 期权的波动率曲面

波动率曲面&#xff08;Volatility Surface&#xff09;是期权交易中展示隐含波动率随行权价&#xff08;strike price&#xff09;和到期时间&#xff08;expiry time&#xff09;变化的一种三维图形。 本文尝试通过 Python&#xff0c;通过 ccxt 基于从交易所获取期权的指标…

远程连接MySQL并操作

配置MySQL开发环境 如果你使用的是基于Debian的系统&#xff08;如Ubuntu&#xff09;&#xff0c;可以在终端通过如下步骤安装MySQL开发包。 更新软件包列表 运行以下命令以确保你拥有最新的软件包列表。 sudo apt-get update安装libmysqlclient-dev开发包 执行以下命令以…

python-字符排列问题

题目描述 有 n 个字母&#xff0c;列出由该字母组成的字符串的全排列&#xff08;相同的排列只计一次&#xff09;。输入格式 第一行输入是字母个数 n 。 接下来一行输入的是待排列的 n 个字母。输出格式 计算出的 n 个字母的所有不同排列总数。样例输入输出样例输入 4 aacc样例…

道路驾驶视角人车检测数据集 16000张 带标注 voc yolo

随着智能驾驶技术和车辆辅助系统的快速发展&#xff0c;道路驾驶视角下的多目标检测成为了保障行车安全的关键技术之一。为了提高自动驾驶车辆以及辅助驾驶系统的性能&#xff0c;需要大量的高质量标注数据来训练这些系统。本数据集旨在为道路驾驶视角下的人车检测提供高质量的…

python画图|中秋到了,尝试画个月亮(球体画法)

学习了一段时间的画图&#xff0c;已经掌握了一些3D图的画法&#xff0c;部分链接如下&#xff1a; python画图|极坐标下的3D surface-CSDN博客 python画图|3D参数化图形输出-CSDN博客 我们今天尝试一下月亮的画法。 【1】官网教程 首先还是到达官网教程学习&#xff1a; …

java: 警告: 源发行版 17 需要目标发行版 17(100% 解决)

1. 问题说明 Idea启动Springboot服务报错&#xff1a;java: 警告: 源发行版 17 需要目标发行版 17 2. 解决方案 Project Structure指定jdk版本为我们当前使用的版本&#xff1b; Java Compiler指定jdk为我们当前使用的版本&#xff1b; Invalidate Caches重启Idea。 如果还…

NTC温度电阻--100K 10K

100K温度电阻分度表 粗精度直接用公式计算 细精度用厂家的传感器参数来计算 Y C1 C2* y C3 * y * y*y static float get_ntc_temp(double Rt) { double y log(Rt);//122.6us48M double val; static const double c1 0.0008314; static const double c2 0.00026178;…

Linux下vscode配置C++和python编译调试环境

Visual Studio Code (简称 VSCode) 是由微软开发的一款免费、开源、跨平台的代码编辑器。它支持 Windows、macOS 和 Linux 操作系统&#xff0c;并且内置对多种编程语言的支持&#xff0c;包括但不限于 C/C、Python、JavaScript、TypeScript、Java 和 Go 等。VSCode 主要用于编…

【C语言进阶】动态内存与柔性数组:C语言开发者必须知道的陷阱与技巧

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ ⏩收录专栏⏪&#xff1a;C语言 “ 登神长阶 ” &#x1f921;往期回顾&#x1f921;&#xff1a;C语言动态内存管理 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀C语言动态内存管理 &…

数据结构-链式二叉树-四种遍历

博客主页&#xff1a;【夜泉_ly】 本文专栏&#xff1a;【数据结构】 欢迎点赞&#x1f44d;收藏⭐关注❤️ 数据结构-链式二叉树-四种遍历 1.前言2.前、中、后序遍历2.1前序遍历2.1中、后序遍历 3.层序遍历3.1递归实现3.2队列实现关于在Pop之后为什么还能用tmp访问节点&#x…

Docker学习笔记(四)单主机网络

简介 Docker从容器中抽象除出了底层的主机连接网络&#xff0c;使得程序不用关心运行时的环境。连接到Docker网络的容器将获得唯一的地址&#xff0c;其他连接到同一Docker网络的容器也可以根据该IP找到目标容器并发送消息。   但是容器内运行的软件没法方便的确定主机IP地址…