如何用OpenAPI Spring代码生成器实现自定义请求头驱动的接口版本路由
解决方案:基于x-api-version请求头的路由实现(OpenAPI Spring生成器)
方法一:利用OpenAPI Spring扩展x-spring-headers
OpenAPI Spring代码生成器支持Spring专属扩展字段,直接在OpenAPI规范的操作节点中添加x-spring-headers,就能让生成的@RequestMapping自动包含headers属性,和原生Spring代码逻辑完全一致。
修改你的OpenAPI YAML中/places的GET操作:
/places: get: summary: Returns all places. operationId: getPlacesV2 # 添加Spring扩展,指定路由匹配的请求头条件 x-spring-headers: - X-API-VERSION=2 parameters: # 保留原有所有参数,包括x-API-VERSION - name: x-API-VERSION in: header description: Is used for versioning required: true style: simple explode: false schema: type: integer format: int32 # 其他参数...
生成后的控制器接口会自动生成带headers属性的@RequestMapping:
@RequestMapping( method = RequestMethod.GET, value = "/places", produces = {"application/json"}, headers = {"X-API-VERSION=2"} ) ResponseEntity<PlaceResponseV2> getPlacesV2( @RequestHeader(value = "Requestor", required = true) String requestor, @RequestHeader(value = "x-API-VERSION", required = true) Integer xAPIVERSION, // 其他参数... );
这样Spring就能通过请求头区分不同版本的控制器,解决Bean映射冲突,同时在进入控制器前完成路由切换。
方法二:自定义OpenAPI生成器模板
如果需要批量处理所有版本路由,可修改生成器的Mustache模板,自动将指定请求头转换为@RequestMapping的headers条件:
- 复制OpenAPI Spring生成器的默认
api.mustache模板到项目的src/main/resources/templates目录 - 找到模板中
@RequestMapping的定义部分,添加逻辑判断:如果操作包含x-API-VERSION参数,则自动添加headers属性
示例修改片段:@RequestMapping( method = {{{httpMethod}}}, value = {{{path}}}, {{#produces}}produces = {{{produces}}},{{/produces}} {{#consumes}}consumes = {{{consumes}}},{{/consumes}} {{#x-spring-headers}}headers = {{{x-spring-headers}}},{{/x-spring-headers}} // 新增逻辑:自动识别x-API-VERSION参数并生成headers条件 {{#parameters}} {{#isHeader}} {{#name}}{{#startsWith this "x-API-VERSION"}} headers = {"X-API-VERSION={{schema.default}}"}, {{/startsWith}}{{/name}} {{/isHeader}} {{/parameters}} ) - 在build.gradle中配置模板路径:
configOptions = [ interfaceOnly: "true", dateLibrary: "java8", templateDirectory: "$rootDir/src/main/resources/templates" ]
方法三:Spring层面自定义路由规则
如果不想修改OpenAPI规范,可通过Spring的RequestMappingHandlerMapping自定义请求匹配逻辑:
创建API版本匹配条件类:
public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> { private final int version; public ApiVersionCondition(int version) { this.version = version; } @Override public ApiVersionCondition combine(ApiVersionCondition other) { return new ApiVersionCondition(other.version); } @Override public ApiVersionCondition getMatchingCondition(HttpServletRequest request) { String versionHeader = request.getHeader("X-API-VERSION"); if (versionHeader != null && Integer.parseInt(versionHeader) == this.version) { return this; } return null; } @Override public int compareTo(ApiVersionCondition other, HttpServletRequest request) { return other.version - this.version; } }创建自定义注解标记控制器版本:
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface ApiVersion { int value(); }自定义
RequestMappingHandlerMapping,注入版本匹配条件:@Configuration public class ApiVersionConfig extends WebMvcConfigurationSupport { @Override protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() { return new RequestMappingHandlerMapping() { @Override protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) { ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class); return apiVersion != null ? new ApiVersionCondition(apiVersion.value()) : super.getCustomTypeCondition(handlerType); } }; } }在生成的控制器实现类上添加
@ApiVersion注解:@ApiVersion(2) @RestController public class PlacesControllerV2 implements PlacesApiV2 { // 实现方法... }
这样即使生成的代码没有headers属性,Spring也会通过自定义条件匹配请求头,完成路由切换。
内容的提问来源于stack exchange,提问作者Patrick
相关产品推荐
相关产品推荐

