nestjs之策略模式的应用

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

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

策略模式的优点

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

举例说明

假设我们正在开发一个导航系统,需要支持多种路线规划算法,例如最短路径、最少时间和避开高速。

当使用 TypeScript 来实现策略模式时,基本的模式和概念与 Java 类似,只是语法稍有不同。以下是使用 TypeScript 实现策略模式的示例:

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

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

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

typescript 复制代码
// 上下文类
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)。

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

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

typescript 复制代码
import { SetMetadata } from '@nestjs/common';

export const Roles = (...roles: Role[]) => SetMetadata('roles', roles);
步骤 3:实现 RolesGuard

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

typescript 复制代码
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 并应用到具体的路由上。

typescript 复制代码
// 在模块中
@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),它描述了日志服务应该实现的方法。

typescript 复制代码
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

typescript 复制代码
@Injectable()
class ConsoleLogger implements LoggerService {
  log(message: string) { console.log(message); }
  // ...实现其他方法
}

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

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

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

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

步骤 4:使用日志服务

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

typescript 复制代码
@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 异常。

typescript 复制代码
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:注册异常过滤器

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

  • 全局注册:

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

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

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

typescript 复制代码
@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 的拦截器。

typescript 复制代码
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:注册拦截器

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

  • 全局注册:

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

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

    typescript 复制代码
    @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 的方法。

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

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

    typescript 复制代码
    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 响应。

相关推荐
css趣多多15 分钟前
案例自定义tabBar
前端
林的快手2 小时前
CSS列表属性
前端·javascript·css·ajax·firefox·html5·safari
匹马夕阳2 小时前
ECharts极简入门
前端·信息可视化·echarts
bug总结2 小时前
新学一个JavaScript 的 classList API
开发语言·javascript·ecmascript
网络安全-老纪2 小时前
网络安全-js安全知识点与XSS常用payloads
javascript·安全·web安全
API_technology2 小时前
电商API安全防护:JWT令牌与XSS防御实战
前端·安全·xss
yqcoder3 小时前
Express + MongoDB 实现在筛选时间段中用户名的模糊查询
java·前端·javascript
十八朵郁金香3 小时前
通俗易懂的DOM1级标准介绍
开发语言·前端·javascript
GDAL4 小时前
HTML 中的 Canvas 样式设置全解
javascript
m0_528723814 小时前
HTML中,title和h1标签的区别是什么?
前端·html