Techniques

版本控制

学习如何在 NestJS 应用中实现 API 版本控制,包括 URI 版本控制、Header 版本控制、Media Type 版本控制和自定义版本控制等多种方式。

版本控制

提示 本章节仅适用于基于 HTTP 的应用程序。

版本控制允许您在同一个应用程序中运行控制器或单个路由的不同版本。应用程序经常发生变化,在仍需要支持应用程序的先前版本时,您需要进行破坏性更改的情况并不少见。

支持 4 种类型的版本控制:

类型描述
URI Versioning版本将在请求的 URI 中传递(默认)
Header Versioning自定义请求头将指定版本
Media Type Versioning请求的 Accept 头将指定版本
Custom Versioning请求的任何方面都可以用来指定版本。提供自定义函数来提取所述版本。

URI 版本控制类型

URI 版本控制使用在请求 URI 中传递的版本,例如 https://example.com/v1/routehttps://example.com/v2/route

注意 使用 URI 版本控制时,版本将自动添加到 URI 中,位于全局路径前缀(如果存在)之后,以及任何控制器或路由路径之前。

要为您的应用程序启用 URI 版本控制,请执行以下操作:

@@filename(main)
const app = await NestFactory.create(AppModule);
// or "app.enableVersioning()"
app.enableVersioning({
  type: VersioningType.URI,
});
await app.listen(process.env.PORT ?? 3000);

注意 URI 中的版本默认会自动添加 v 前缀,但是可以通过将 prefix 键设置为您所需的前缀或 false(如果您希望禁用它)来配置前缀值。

提示 VersioningType 枚举可用于 type 属性,并从 @nestjs/common 包中导入。

Header 版本控制类型

Header 版本控制使用自定义的、用户指定的请求头来指定版本,其中头的值将是用于请求的版本。

Header 版本控制的示例 HTTP 请求:

要为您的应用程序启用 Header 版本控制,请执行以下操作:

@@filename(main)
const app = await NestFactory.create(AppModule);
app.enableVersioning({
  type: VersioningType.HEADER,
  header: 'Custom-Header',
});
await app.listen(process.env.PORT ?? 3000);

header 属性应该是将包含请求版本的头的名称。

提示 VersioningType 枚举可用于 type 属性,并从 @nestjs/common 包中导入。

Media Type 版本控制类型

Media Type 版本控制使用请求的 Accept 头来指定版本。

Accept 头中,版本将用分号 ; 与媒体类型分隔。然后它应该包含一个键值对,表示用于请求的版本,例如 Accept: application/json;v=2。在确定版本时,键被更多地视为前缀,将被配置为包含键和分隔符。

要为您的应用程序启用 Media Type 版本控制,请执行以下操作:

@@filename(main)
const app = await NestFactory.create(AppModule);
app.enableVersioning({
  type: VersioningType.MEDIA_TYPE,
  key: 'v=',
});
await app.listen(process.env.PORT ?? 3000);

key 属性应该是包含版本的键值对的键和分隔符。对于示例 Accept: application/json;v=2key 属性将设置为 v=

提示 VersioningType 枚举可用于 type 属性,并从 @nestjs/common 包中导入。

自定义版本控制类型

自定义版本控制使用请求的任何方面来指定版本(或多个版本)。传入的请求使用 extractor 函数进行分析,该函数返回字符串或字符串数组。

如果请求者提供了多个版本,提取器函数可以返回一个字符串数组,按从最大/最高版本到最小/最低版本的顺序排序。版本按从高到低的顺序与路由匹配。

如果从 extractor 返回空字符串或数组,则不匹配任何路由并返回 404。

例如,如果传入请求指定它支持版本 123,则 extractor 必须 返回 [3, 2, 1]。这确保首先选择最高可能的路由版本。

如果提取了版本 [3, 2, 1],但路由仅存在于版本 21,则选择匹配版本 2 的路由(版本 3 被自动忽略)。

注意 由于设计限制,基于从 extractor 返回的数组选择最高匹配版本在 Express 适配器中无法可靠工作。单个版本(字符串或 1 个元素的数组)在 Express 中工作得很好。Fastify 正确支持最高匹配版本选择和单版本选择。

要为您的应用程序启用 自定义版本控制,请创建一个 extractor 函数并将其传递到您的应用程序中,如下所示:

@@filename(main)
// Example extractor that pulls out a list of versions from a custom header and turns it into a sorted array.
// This example uses Fastify, but Express requests can be processed in a similar way.
const extractor = (request: FastifyRequest): string | string[] =>
  [request.headers['custom-versioning-field'] ?? '']
     .flatMap(v => v.split(','))
     .filter(v => !!v)
     .sort()
     .reverse()

const app = await NestFactory.create(AppModule);
app.enableVersioning({
  type: VersioningType.CUSTOM,
  extractor,
});
await app.listen(process.env.PORT ?? 3000);

使用方法

版本控制允许您对控制器、单个路由进行版本控制,还提供了某些资源选择退出版本控制的方法。无论您的应用程序使用哪种版本控制类型,版本控制的使用都是相同的。

注意 如果为应用程序启用了版本控制,但控制器或路由未指定版本,则对该控制器/路由的任何请求都将返回 404 响应状态。同样,如果收到包含没有相应控制器或路由的版本的请求,它也将返回 404 响应状态。

控制器版本

可以将版本应用于控制器,为控制器内的所有路由设置版本。

要向控制器添加版本,请执行以下操作:

@@filename(cats.controller)
@Controller({
  version: '1',
})
export class CatsControllerV1 {
  @Get('cats')
  findAll(): string {
    return 'This action returns all cats for version 1';
  }
}
@@switch
@Controller({
  version: '1',
})
export class CatsControllerV1 {
  @Get('cats')
  findAll() {
    return 'This action returns all cats for version 1';
  }
}

路由版本

可以将版本应用于单个路由。此版本将覆盖影响路由的任何其他版本,例如控制器版本。

要向单个路由添加版本,请执行以下操作:

@@filename(cats.controller)
import { Controller, Get, Version } from '@nestjs/common';

@Controller()
export class CatsController {
  @Version('1')
  @Get('cats')
  findAllV1(): string {
    return 'This action returns all cats for version 1';
  }

  @Version('2')
  @Get('cats')
  findAllV2(): string {
    return 'This action returns all cats for version 2';
  }
}
@@switch
import { Controller, Get, Version } from '@nestjs/common';

@Controller()
export class CatsController {
  @Version('1')
  @Get('cats')
  findAllV1() {
    return 'This action returns all cats for version 1';
  }

  @Version('2')
  @Get('cats')
  findAllV2() {
    return 'This action returns all cats for version 2';
  }
}

多个版本

可以将多个版本应用于控制器或路由。要使用多个版本,您需要将版本设置为数组。

要添加多个版本,请执行以下操作:

@@filename(cats.controller)
@Controller({
  version: ['1', '2'],
})
export class CatsController {
  @Get('cats')
  findAll(): string {
    return 'This action returns all cats for version 1 or 2';
  }
}
@@switch
@Controller({
  version: ['1', '2'],
})
export class CatsController {
  @Get('cats')
  findAll() {
    return 'This action returns all cats for version 1 or 2';
  }
}

版本"中性"

某些控制器或路由可能不关心版本,无论版本如何都具有相同的功能。为了适应这种情况,可以将版本设置为 VERSION_NEUTRAL 符号。

传入的请求将映射到 VERSION_NEUTRAL 控制器或路由,无论请求中发送的版本如何,以及请求是否根本不包含版本。

注意 对于 URI 版本控制,VERSION_NEUTRAL 资源在 URI 中不会有版本。

要添加版本中性控制器或路由,请执行以下操作:

@@filename(cats.controller)
import { Controller, Get, VERSION_NEUTRAL } from '@nestjs/common';

@Controller({
  version: VERSION_NEUTRAL,
})
export class CatsController {
  @Get('cats')
  findAll(): string {
    return 'This action returns all cats regardless of version';
  }
}
@@switch
import { Controller, Get, VERSION_NEUTRAL } from '@nestjs/common';

@Controller({
  version: VERSION_NEUTRAL,
})
export class CatsController {
  @Get('cats')
  findAll() {
    return 'This action returns all cats regardless of version';
  }
}

全局默认版本

如果您不想为每个控制器/或单个路由提供版本,或者如果您想为没有指定版本的每个控制器/路由设置特定版本作为默认版本,您可以按如下方式设置 defaultVersion

@@filename(main)
app.enableVersioning({
  // ...
  defaultVersion: '1'
  // or
  defaultVersion: ['1', '2']
  // or
  defaultVersion: VERSION_NEUTRAL
});

中间件版本控制

中间件也可以使用版本控制元数据来为特定路由的版本配置中间件。为此,请提供版本号作为 MiddlewareConsumer.forRoutes() 方法的参数之一:

@@filename(app.module)
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';
import { CatsController } from './cats/cats.controller';

@Module({
  imports: [CatsModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes({ path: 'cats', method: RequestMethod.GET, version: '2' });
  }
}

使用上面的代码,LoggerMiddleware 将仅应用于 /cats 端点的版本 '2'。

注意 中间件适用于本节中描述的任何版本控制类型:URIHeaderMedia TypeCustom