nestjs之策略模式的应用

策略模式(Strategy Pattern)是一种软件设计模式,它定义了算法族,分别封装起来,使它们可以互相替换。策略模式让算法的变化独立于使用算法的客户。这种模式涉及到三个角色:

  1. 上下文(Context):持有一个策略类的引用,用来与策略类交互。
  2. 策略接口(Strategy Interface):定义了每个策略或算法必须遵循的接口。
  3. 具体策略(Concrete Strategies):实现策略接口的类,提供具体的算法实现。

策略模式的优点

  • 分离算法:策略模式通过分离算法和上下文来提高内聚性和灵活性。
  • 易于扩展:可以定义新的策略而不影响到其他的代码。
  • 避免条件语句:策略模式避免了使用多重条件选择语句。

举例说明

假设我们正在开发一个导航系统,需要支持多种路线规划算法,例如最短路径、最少时间和避开高速。
当使用 TypeScript 来实现策略模式时,基本的模式和概念与 Java 类似,只是语法稍有不同。以下是使用 TypeScript 实现策略模式的示例:

首先,定义策略接口和具体策略类:

// 策略接口
interface RouteStrategy {buildRoute(pointA: string, pointB: string): string;
}// 具体策略类 - 最短路径策略
class ShortestPathStrategy implements RouteStrategy {buildRoute(pointA: string, pointB: string): string {return `最短路径从 ${pointA}${pointB}`;}
}// 具体策略类 - 最少时间策略
class MinTimeStrategy implements RouteStrategy {buildRoute(pointA: string, pointB: string): string {return `最少时间路径从 ${pointA}${pointB}`;}
}// 具体策略类 - 避开高速策略
class AvoidHighwaysStrategy implements RouteStrategy {buildRoute(pointA: string, pointB: string): string {return `避开高速的路径从 ${pointA}${pointB}`;}
}

接下来,创建上下文类和使用策略模式:

// 上下文类
class NavigationContext {private strategy: RouteStrategy;setRouteStrategy(strategy: RouteStrategy): void {this.strategy = strategy;}buildRoute(pointA: string, pointB: string): string {return this.strategy.buildRoute(pointA, pointB);}
}// 使用策略模式
const context = new NavigationContext();// 选择最短路径策略
context.setRouteStrategy(new ShortestPathStrategy());
console.log(context.buildRoute("起点", "终点"));// 切换到最少时间策略
context.setRouteStrategy(new MinTimeStrategy());
console.log(context.buildRoute("起点", "终点"));// 切换到避开高速策略
context.setRouteStrategy(new AvoidHighwaysStrategy());
console.log(context.buildRoute("起点", "终点"));

在这个例子中,RouteStrategy 是策略接口,ShortestPathStrategyMinTimeStrategyAvoidHighwaysStrategy 是实现了不同路线规划算法的具体策略类。NavigationContext 是上下文,负责接受不同的策略并使用它们来构建路线。这样,当需要改变路线规划算法时,只需更换不同的策略类即可,无需修改 NavigationContext 的代码。

理解和运用这些设计模式可以帮助你更有效地使用 NestJS 构建可维护和可扩展的应用程序。

在 NestJS 中,策略模式主要用于以下几个领域:

Authentication

在 NestJS 中实现策略模式主要是通过 Guards 来完成的,特别是在处理授权(Authorization)时。我们可以通过定义不同的 Guards 来实现不同的授权策略,例如基于角色的授权(RBAC)或者是基于权限的授权。

示例:基于角色的访问控制(RBAC)

假设我们有一个简单的应用程序,它有两种用户角色:普通用户(User)和管理员(Admin)。我们想要实现的是,某些操作只能由管理员执行。

步骤 1:定义角色

首先,我们定义一个角色枚举(Enum)。

export enum Role {User = 'user',Admin = 'admin',
}
步骤 2:创建 Roles 装饰器

接下来,我们创建一个自定义装饰器来标记特定的路由需要特定的角色。

import { SetMetadata } from '@nestjs/common';export const Roles = (...roles: Role[]) => SetMetadata('roles', roles);
步骤 3:实现 RolesGuard

然后,我们实现一个 RolesGuard,它将检查用户是否具有访问特定路由所需的角色。

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';@Injectable()
export class RolesGuard implements CanActivate {constructor(private reflector: Reflector) {}canActivate(context: ExecutionContext): boolean {const roles = this.reflector.get<Role[]>('roles', context.getHandler());if (!roles) {return true;}const request = context.switchToHttp().getRequest();const user = request.user;return roles.some(role => user.roles?.includes(role));}
}

在这个 Guard 中,我们使用 Reflector 来获取与当前路由处理程序相关联的角色。然后,我们检查当前用户是否具有这些角色之一。

步骤 4:应用 RolesGuard

最后,我们需要在模块中注册这个 Guard 并应用到具体的路由上。

// 在模块中
@Module({providers: [{provide: APP_GUARD,useClass: RolesGuard,},],
})
export class AppModule {}// 在控制器中
@Controller('items')
export class ItemsController {@Get()@Roles(Role.Admin) // 只有管理员可以访问findAll() {// ...}
}

在这个例子中,RolesGuard 作为一个策略,用于控制对特定路由的访问。通过简单地更改 @Roles 装饰器中的参数,我们可以轻松地改变访问控制的策略,而无需修改其他业务逻辑。

Logging

在 NestJS 中实现日志策略模式通常涉及到自定义日志服务。这样,你可以根据需要切换或扩展不同的日志策略,例如输出日志到控制台、文件、远程服务器等。

示例:自定义日志服务

假设我们需要实现两种日志策略:一种是简单地将日志输出到控制台,另一种是将日志记录到文件中。

步骤 1:定义日志接口

首先,我们定义一个日志接口(LoggerService),它描述了日志服务应该实现的方法。

interface LoggerService {log(message: string): void;error(message: string, trace: string): void;warn(message: string): void;debug(message: string): void;verbose(message: string): void;
}
步骤 2:实现具体的日志策略

接下来,我们实现两个具体的日志策略,分别是 ConsoleLoggerFileLogger

@Injectable()
class ConsoleLogger implements LoggerService {log(message: string) { console.log(message); }// ...实现其他方法
}@Injectable()
class FileLogger implements LoggerService {log(message: string) {// 将消息写入文件}// ...实现其他方法
}
步骤 3:动态选择日志策略

然后,我们可以根据需要在应用中动态选择使用哪个日志策略。

@Module({providers: [{provide: 'LoggerService',useClass: process.env.NODE_ENV === 'development' ? ConsoleLogger : FileLogger,},],
})
export class AppModule {}

在这个模块中,我们根据环境变量来决定使用 ConsoleLogger 还是 FileLogger

步骤 4:使用日志服务

在应用的其他部分,我们可以注入并使用 LoggerService

@Controller('items')
export class ItemsController {constructor(@Inject('LoggerService') private logger: LoggerService) {}@Get()findAll() {this.logger.log('Fetching all items');// ...业务逻辑}
}

在这个控制器中,我们通过构造函数注入了 LoggerService。不论底层使用的是哪种日志策略,我们都可以通过相同的方式记录日志。

Exception Handling

在 NestJS 中,异常处理通常通过异常过滤器(Exception Filters)来实现,这可以被视为一种策略模式的应用。异常过滤器允许你定义不同的处理策略来处理不同类型的异常。以下是一个详细的例子,展示如何在 NestJS 中使用异常过滤器来实现异常处理的策略模式。

示例:自定义异常过滤器

假设我们的应用需要特定的处理方式来处理数据库异常和 HTTP 异常。

步骤 1:创建自定义异常过滤器

首先,我们创建两个异常过滤器,一个用于处理数据库异常,另一个用于处理 HTTP 异常。

import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {catch(exception: HttpException, host: ArgumentsHost) {const ctx = host.switchToHttp();const response = ctx.getResponse();const status = exception.getStatus();response.status(status).json({statusCode: status,timestamp: new Date().toISOString(),message: exception.message,});}
}@Catch(DatabaseException)
export class DatabaseExceptionFilter implements ExceptionFilter {catch(exception: DatabaseException, host: ArgumentsHost) {// 处理数据库异常的逻辑}
}
步骤 2:注册异常过滤器

接下来,我们需要在应用中注册这些异常过滤器。你可以全局注册或针对特定控制器或路由注册。

  • 全局注册:

    @Module({// ...providers: [{provide: APP_FILTER,useClass: HttpExceptionFilter,},{provide: APP_FILTER,useClass: DatabaseExceptionFilter,},],
    })
    export class AppModule {}
    
  • 针对特定控制器的注册:

    @Controller('users')
    @UseFilters(new HttpExceptionFilter(), new DatabaseExceptionFilter())
    export class UsersController {// ...
    }
    
步骤 3:触发异常

在应用的任何地方抛出异常,对应的过滤器将会捕获并处理它。

@Controller('users')
export class UsersController {@Get(':id')findOne(@Param('id') id: string): string {throw new HttpException('User not found', HttpStatus.NOT_FOUND);}
}

在这个例子中,当 findOne 方法抛出 HttpException 时,HttpExceptionFilter 会被触发,并按照其逻辑处理异常。

Request Processing

在 NestJS 中,请求处理(Request Processing)通常涉及拦截器(Interceptors),这些拦截器可以被视为一种策略模式的实现。拦截器允许你在请求处理流程中插入自定义逻辑,比如日志记录、响应转换、错误处理等。接下来我将通过一个详细的例子来说明如何在 NestJS 中使用拦截器实现请求处理的策略。

示例:响应转换拦截器

假设我们需要一个拦截器来统一格式化所有 API 响应。这个拦截器将拦截出站响应,并将其转换为一个标准的格式。

步骤 1:创建拦截器

首先,我们创建一个名为 TransformInterceptor 的拦截器。

import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';@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,timestamp: new Date().toISOString(),path: context.switchToHttp().getRequest().url,})));}
}interface Response<T> {data: T;timestamp: string;path: string;
}

在这个拦截器中,我们通过 rxjsmap 操作符来转换处理函数返回的数据。我们把数据包装成一个对象,其中包含数据、时间戳和请求路径。

步骤 2:注册拦截器

接下来,我们需要在应用中注册这个拦截器。有两种方式可以注册拦截器:全局注册和针对特定路由的注册。

  • 全局注册:

    @Module({// ...providers: [{provide: APP_INTERCEPTOR,useClass: TransformInterceptor,},],
    })
    export class AppModule {}
    
  • 针对特定路由的注册:

    在特定控制器或者处理函数上使用拦截器:

    @Controller('items')
    @UseInterceptors(TransformInterceptor)
    export class ItemsController {// ...
    }
    
步骤 3:使用拦截器

一旦拦截器被注册,它就会自动应用于你的请求处理流程。在上面的例子中,任何通过 ItemsController 的响应都会被 TransformInterceptor 拦截并格式化。

Validation and Transformation

从源码层面详细分析 NestJS 中的管道(Pipes)是一个涉及到多个文件和类的复杂过程,但我会尽量简化并解释关键部分。

管道的基本原理

在 NestJS 中,管道(Pipes)是负责处理输入数据的中间件,它们在控制器处理函数执行之前运行。管道可以执行数据转换或数据验证。当你在控制器的参数前使用管道,NestJS 会在将请求传递给处理函数之前执行这些管道。

核心类:ValidationPipe

ValidationPipe 为例,这是一个内置的管道,通常用于 DTO(Data Transfer Object)验证。我们将从它的源码开始分析。

  1. ValidationPipe的定义

    ValidationPipe 是一个实现了 PipeTransform 接口的类。PipeTransform 接口要求实现一个名为 transform 的方法。

    export class ValidationPipe implements PipeTransform<any> {async transform(value, metadata: ArgumentMetadata) {// ...验证逻辑}
    }
    
  2. 使用 Class Validator 进行验证

    ValidationPipetransform 方法使用 class-validator 库来验证输入数据。如果数据不符合 DTO 定义的规则,它会抛出异常。

    import { validate } from 'class-validator';async transform(value, { metatype }): Promise<any> {if (!metatype || !this.toValidate(metatype)) {return value;}const object = plainToClass(metatype, value);const errors = await validate(object);if (errors.length > 0) {throw new BadRequestException('Validation failed');}return value;
    }
    
  3. 调用管道

    当请求到达控制器时,NestJS 会根据控制器方法的装饰器(如 @Body)来确定需要应用哪个管道。

    @Body(new ValidationPipe()) 的情况下,NestJS 会创建一个 ValidationPipe 实例,并调用它的 transform 方法,将传入的请求体作为参数。

  4. 异常处理

    如果验证失败,ValidationPipe 会抛出一个 BadRequestException。NestJS 捕获这个异常,并根据异常类型生成相应的 HTTP 响应。

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

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

相关文章

YOLOv8改进 | 检测头篇 | 辅助特征融合检测头FASFFHead (增加额外目标检测层,独家创新)

一、本文介绍 本文给大家带来的改进机制是由我独家创新的FASFFHead检测头,我根据ASFFHead检测头(只能用于三头检测)的基础上进行二次创新,解决由于跨尺度融合的特征丢失情况,同时本文的内容全网无第二份,非常适合大家拿来发表论文,该检测头为四头版本,增加小目标检测层或…

通过代理服务器的方式解决跨域问题

学习源码可以看我的个人前端学习笔记 (github.com):qdxzw/frontlearningNotes 觉得有帮助的同学&#xff0c;可以点心心支持一下哈 这里以本地访问https://heimahr.itheima.net/api/sys/permission接口为列子 Node.js 代理服务器 (server.js) 本次考虑使用JSONP或CORS代理来…

助力医疗数字化转型,贝锐x医百科技案例解析

在医疗数字化这个历史进程的大浪潮中&#xff0c;医药企业扮演着重要的角色&#xff0c;其重要程度恐怕仅次于医疗机构本身。同时&#xff0c;数字化转型对于医药企业的赋能作用也是十分明显的&#xff0c;尤其在营销端&#xff0c;一系列的数字化管理、数字化推广方案已经成为…

安装IIS及搭建asp.net程序遇到的问题

一、安装IIS 在服务器管理中选择IIS&#xff0c;右键选择添加角色和功能 在服务器角色中&#xff0c;应用程序开发中要选择ASP.NET3.5、ASP.NET4.6功能。 单击下一步执行安装即可。 二、常见问题 问题1. HTTP 错误404.17 - Not Found 解决方法&#xff1a; 出现以上问题没有…

幻兽帕鲁Docker服务端搭建

幻兽帕鲁Docker服务端搭建 各种命令 https://bbs.saraba1st.com/2b/thread-2168983-1-1.html 存档恢复 这里直接看这个工程的readme就行&#xff1a;https://github.com/yoko-murasame/palworld-host-save-fix 其他参考&#xff1a;https://forum.gamer.com.tw/C.php?bsn7…

SpringBoot使用Swagger2生成接口文档

一、导入依赖 <!-- knife4j--><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId><version>2.0.7</version></dependency> 二、配置类 通过一下配置&am…

USRP相关报错解决办法

文章目录 前言一、本地环境二、相关报错信息二、解决办法1、更换电脑操作系统2、升级最新版固件 前言 在进行 USRP 开发时遇到了一些报错&#xff0c;这里做个记录解决问题的方法。 一、本地环境 电脑操作系统&#xff1a;Windows11MATLAB 版本&#xff1a;MATLAB 2021aUSRP …

rabbitmq基础-java-2、work模型

1、简介 工作队列模式&#xff08;Work Queue Mode&#xff09;&#xff1a;在这个模型中&#xff0c;生产者同样将消息发送到队列&#xff0c;但多个消费者可以从队列中获取消息并发处理。这意味着不同的消费者可以独立地处理各自的任务&#xff0c;从而提高效率。 2、消息发送…

基于springboot+vue的小徐影城管理系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

C语言进阶——数据结构之链表

前言 hello&#xff0c;大家好呀&#xff0c;我是Humble 在之前的两篇博客&#xff0c;我们学完了数据结构中的顺序表&#xff0c;还对它进行了一个应用&#xff0c;做了一个通讯录的小项目 那今天我们再来学习一个新的数据结构——链表 引入 我们来回忆一下顺序表 对于顺…

小程序技术实践:快速开发适配鸿蒙的App

今年&#xff0c;在中国&#xff0c;被各大媒体和开发者称为“鸿蒙元年”。 在2023年底就有业内人士透露&#xff0c;华为明年将推出不兼容安卓的鸿蒙版本&#xff0c;未来IOS、鸿蒙、安卓将成为三个各自独立的系统。 果不其然&#xff0c;执行力超强的华为&#xff0c;与202…

Elasticsearch8安装Fleet Server

由于业务需求&#xff0c;我们需要在7个不同的业务平台安装ES-Agent以收取其数据。我们的策略是在每个平台上安装一个Fleet Server&#xff0c;用于管理各平台的ES-Agent。 Configure SSL/TLS for self-managed Fleet Servers | Fleet and Elastic Agent Guide [8.12] | Elast…

1、【vue篇】vue框架快速上手

注意事项&#xff1a; methods必须要加s 导入vue&#xff1a;<script src"https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>导入Axios:<script src"https://unpkg.com/axios/dist/axios.min.js"></script> 简单Vue程序…

轻松上手:通过阿里云PAI QuickStart微调部署Qwen-72B-Chat模型

作者&#xff1a;熊兮、求伯、一耘 引言 通义千问-72B&#xff08;Qwen-72B&#xff09;是阿里云研发的通义千问大模型系列的720亿参数规模模型。Qwen-72B的预训练数据类型多样、覆盖广泛&#xff0c;包括大量网络文本、专业书籍、代码等。Qwen-72B-Chat是在Qwen-72B的基础上…

05-使用结构体构建相关数据

上一篇&#xff1a; 04-了解所有权 结构体&#xff08;struct&#xff09;是一种自定义数据类型&#xff0c;可以将多个相关值打包命名&#xff0c;组成一个有意义的组。如果你熟悉面向对象的语言&#xff0c;那么结构体就像是对象的数据属性。在本章中&#xff0c;我们将对元组…

C# Wpf MVVM 框架下的线程并发与异步编程

在软件开发中&#xff0c;我们经常需要执行耗时的操作&#xff0c;如文件读写、网络请求等。这些操作可能会阻塞主线程&#xff0c;导致用户界面无响应。为了避免这种情况&#xff0c;我们可以使用线程并发和异步编程技术。本文将详细解释这两种技术的区别以及它们各自的优点&a…

新年钜惠|泰迪智能科技免费协助企业完成3个行业AI案例建模

在新年到来之际&#xff0c;为了感谢各企业一直以来对泰迪智能科技的支持&#xff0c;日前我们推出了新年钜惠活动即&#xff1a;免费协助企业完成3个行业AI案例建模。我们希望通过这样的活动&#xff0c;可以帮助更多的企业迈出AI应用的第一步&#xff0c;同时我们也希望能够为…

Spring Boot整合webservice

Spring Boot整合webservice 前言1.整合依赖2.建立暴露接口2.实现类 3.发布服务4.查看打完收工&#xff01; 前言 工作中遇到的问题&#xff0c;由于下游系统属于第三方系统&#xff0c;使用的是soap webservice&#xff0c;同时也在开发&#xff0c;虽然也发布了一套webservic…

R语言【taxa】——roots(),stems(),subtaxa(),supertaxa():获取根节点、茎节点、子类群和父类群

roots(x, subset NULL) 在 taxonomy 中查找根节点类群的索引值。 > x <- taxonomy(c(Carnivora, Felidae, Panthera, Panthera leo,Panthera tigris, Ursidae, Ursus, Ursus arctos),supertaxa c(NA, 1, 2, 3, 3, 1, 6, 7))> roots(x) [1] 1> roots(x, subset …

如何在阿里云提交使用工单

有时候大家在使用阿里云的服务时候&#xff0c;可能会遇到一些问题&#xff0c;或许是云服务器如何升级了如何改套餐啊之类的&#xff0c;亦或者是域名ICP备案啊看进度啊等等问题&#xff0c;遇到问题怎么办不要慌。我们可以使用阿里云的工单系统&#xff0c;阿里云工单系统可以…