Springfox 没有为 Spring MVC Rest 版本化 API 生成 Swagger 文档
Springfox not generating Swagger docs for Spring MVC Rest versioned API
我们面临 REST API 版本控制,在阅读了很多关于不同选项(URI 版本控制、mime 类型版本控制)的信息后,我们决定使用后一种方法。
我原以为 Springfox 会生成以下文档:
v1:
get /api/architecture/mails - application/vnd.arch.mails.v1+json
get /api/architecture/services - application/vnd.arch.service.v1+json
v2:
get /api/architecture/services - application/vnd.arch.service.v2+json
但是,在 v2 中我也得到了这个:
get /api/architecture/services - application/vnd.arch.service.v1+json
它不应该存在,因为我配置了 v2 Docklet
.produces(new HashSet<String>(Arrays.asList(new String[]{"application/vnd.arch.service.v2+json"})))
以便它根据版本控制的 mime 类型过滤服务。为什么不工作?
这是我们的 springfox 配置:
@Bean
public Docket arqV1Api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.regex("/api/architecture/.*"))
.build()
.apiInfo(new ApiInfo("Architecture Rest Api","Architecture REST Services","v1","","","",""))
.produces(new HashSet<String>(Arrays.asList(new String[]{"application/vnd.arch.service.v1+json","application/vnd.arch.mail.v1+json"})))
.securitySchemes(newArrayList(apiKey()))
.securityContexts(newArrayList(securityContext()))
.groupName("Arq v1 group");
}
@Bean
public Docket arqV2Api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.regex("/api/architecture/.*"))
.build()
.apiInfo(new ApiInfo("Architecture Rest Api","Architecture REST Services","v2","","","",""))
.produces(new HashSet<String>(Arrays.asList(new String[]{"application/vnd.arch.service.v2+json"})))
.securitySchemes(newArrayList(apiKey()))
.securityContexts(newArrayList(securityContext()))
.groupName("Arq v2 group");
}
这些是 REST 控制器:
private static final String serviceArqV1MediaType = "application/vnd.arch.service.v1+json";
private static final String serviceArqV2MediaType = "application/vnd.arch.service.v2+json";
private static final String mailsArqV1MediaType = "application/vnd.arch.mail.v1+json";
@ApiOperation(value = "Gets architecture services",
notes = "",
produces = serviceArqV1MediaType)
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Request OK"),
@ApiResponse(code = 400, message = "Bad Request")})
@RequestMapping(value = {"/services"}, method = RequestMethod.GET,
produces = serviceArqV1MediaType)
public List<ServicioArquitectura> getServices() {
return Arrays.asList(new ServiceArch[]{new ServicioArquitectura("Support"), new ServicioArquitectura("Kickoff")});
}
@ApiOperation(value = "Gets architecture services",
notes = "",
produces = serviceArqV2MediaType)
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Request OK"),
@ApiResponse(code = 400, message = "Bad Request")})
@RequestMapping(value = {"/services"}, method = RequestMethod.GET,
produces = {serviceArqV2MediaType})
public List<ServicioArquitecturaV2> getServicesV2() {
return Arrays.asList(new ServiceArchV2[]{new ServiceArchV2("Support", Boolean.TRUE), new ServiceArchV2("Kickoff", Boolean.FALSE)});
}
@ApiOperation(value = "Gets mails",
produces = mailsArqV1MediaType)
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Request OK"),
@ApiResponse(code = 400, message = "Bad Request")})
@RequestMapping(value = {"/mails"}, method = RequestMethod.GET,
produces = {mailsArqV1MediaType})
public List<String> getMails() {
return Arrays.asList(new String[]{"xxxcompany.com"});
}
我在 Springfox 的 github 中打开了一个 issue,他们指出了如何正确配置它。解决方法如下:
辅助静态方法:
public static Predicate<RequestHandler> withMediaType(final MediaType[] mediaTypes){
return new Predicate<RequestHandler>() {
@Override
public boolean apply(RequestHandler input) {
if(mediaTypes!=null){
ProducesRequestCondition producesCondition = input.getRequestMapping().getProducesCondition();
Set<MediaType> producibleMediaTypes = producesCondition.getProducibleMediaTypes();
for (MediaType mt : producibleMediaTypes) {
for (int i = 0; i < mediaTypes.length; i++) {
if(mt.equals(mediaTypes[i])){
return true;
}
}
}
}
return false;
}
};
}
public static Set<String> mediaTypesToStringSet(MediaType[] mediaTypes){
Set<String> mediaTypesSet = new HashSet<String>();
if(mediaTypes!=null){
for (int i = 0; i < mediaTypes.length; i++) {
mediaTypesSet.add(mediaTypes[i].toString());
}
}
return mediaTypesSet;
}
摘要定义:
@Bean
public Docket arqV1Api() {
MediaType[] validMediaTypes = new MediaType[]{new MediaType("application","vnd.arch.service.v1+json"),
new MediaType("application","vnd.arch.mails.v1+json")};
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(withMediaType(validMediaTypes))
.paths(PathSelectors.regex("/api/architecture/.*"))
.build()
.apiInfo(new ApiInfo("Architecture Rest Api","Architecture REST Services","v1","","","",""))
.produces(mediaTypesToStringSet(validMediaTypes))
.securitySchemes(newArrayList(apiKey()))
.securityContexts(newArrayList(securityContext()))
.groupName("Arq v1 group");
}
@Bean
public Docket arqV2Api() {
MediaType[] validMediaTypes = new MediaType[]{new MediaType("application","vnd.arch.service.v2+json")};
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(withMediaType(validMediaTypes))
.paths(PathSelectors.regex("/api/architecture/.*"))
.build()
.apiInfo(new ApiInfo("Architecture Rest Api","Architecture REST Services","v2","","","",""))
.produces(mediaTypesToStringSet(validMediaTypes))
.securitySchemes(newArrayList(apiKey()))
.securityContexts(newArrayList(securityContext()))
.groupName("Arq v2 group");
}
我们面临 REST API 版本控制,在阅读了很多关于不同选项(URI 版本控制、mime 类型版本控制)的信息后,我们决定使用后一种方法。
我原以为 Springfox 会生成以下文档:
v1:
get /api/architecture/mails - application/vnd.arch.mails.v1+json
get /api/architecture/services - application/vnd.arch.service.v1+json
v2:
get /api/architecture/services - application/vnd.arch.service.v2+json
但是,在 v2 中我也得到了这个:
get /api/architecture/services - application/vnd.arch.service.v1+json
它不应该存在,因为我配置了 v2 Docklet
.produces(new HashSet<String>(Arrays.asList(new String[]{"application/vnd.arch.service.v2+json"})))
以便它根据版本控制的 mime 类型过滤服务。为什么不工作?
这是我们的 springfox 配置:
@Bean
public Docket arqV1Api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.regex("/api/architecture/.*"))
.build()
.apiInfo(new ApiInfo("Architecture Rest Api","Architecture REST Services","v1","","","",""))
.produces(new HashSet<String>(Arrays.asList(new String[]{"application/vnd.arch.service.v1+json","application/vnd.arch.mail.v1+json"})))
.securitySchemes(newArrayList(apiKey()))
.securityContexts(newArrayList(securityContext()))
.groupName("Arq v1 group");
}
@Bean
public Docket arqV2Api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.regex("/api/architecture/.*"))
.build()
.apiInfo(new ApiInfo("Architecture Rest Api","Architecture REST Services","v2","","","",""))
.produces(new HashSet<String>(Arrays.asList(new String[]{"application/vnd.arch.service.v2+json"})))
.securitySchemes(newArrayList(apiKey()))
.securityContexts(newArrayList(securityContext()))
.groupName("Arq v2 group");
}
这些是 REST 控制器:
private static final String serviceArqV1MediaType = "application/vnd.arch.service.v1+json";
private static final String serviceArqV2MediaType = "application/vnd.arch.service.v2+json";
private static final String mailsArqV1MediaType = "application/vnd.arch.mail.v1+json";
@ApiOperation(value = "Gets architecture services",
notes = "",
produces = serviceArqV1MediaType)
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Request OK"),
@ApiResponse(code = 400, message = "Bad Request")})
@RequestMapping(value = {"/services"}, method = RequestMethod.GET,
produces = serviceArqV1MediaType)
public List<ServicioArquitectura> getServices() {
return Arrays.asList(new ServiceArch[]{new ServicioArquitectura("Support"), new ServicioArquitectura("Kickoff")});
}
@ApiOperation(value = "Gets architecture services",
notes = "",
produces = serviceArqV2MediaType)
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Request OK"),
@ApiResponse(code = 400, message = "Bad Request")})
@RequestMapping(value = {"/services"}, method = RequestMethod.GET,
produces = {serviceArqV2MediaType})
public List<ServicioArquitecturaV2> getServicesV2() {
return Arrays.asList(new ServiceArchV2[]{new ServiceArchV2("Support", Boolean.TRUE), new ServiceArchV2("Kickoff", Boolean.FALSE)});
}
@ApiOperation(value = "Gets mails",
produces = mailsArqV1MediaType)
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Request OK"),
@ApiResponse(code = 400, message = "Bad Request")})
@RequestMapping(value = {"/mails"}, method = RequestMethod.GET,
produces = {mailsArqV1MediaType})
public List<String> getMails() {
return Arrays.asList(new String[]{"xxxcompany.com"});
}
我在 Springfox 的 github 中打开了一个 issue,他们指出了如何正确配置它。解决方法如下:
辅助静态方法:
public static Predicate<RequestHandler> withMediaType(final MediaType[] mediaTypes){
return new Predicate<RequestHandler>() {
@Override
public boolean apply(RequestHandler input) {
if(mediaTypes!=null){
ProducesRequestCondition producesCondition = input.getRequestMapping().getProducesCondition();
Set<MediaType> producibleMediaTypes = producesCondition.getProducibleMediaTypes();
for (MediaType mt : producibleMediaTypes) {
for (int i = 0; i < mediaTypes.length; i++) {
if(mt.equals(mediaTypes[i])){
return true;
}
}
}
}
return false;
}
};
}
public static Set<String> mediaTypesToStringSet(MediaType[] mediaTypes){
Set<String> mediaTypesSet = new HashSet<String>();
if(mediaTypes!=null){
for (int i = 0; i < mediaTypes.length; i++) {
mediaTypesSet.add(mediaTypes[i].toString());
}
}
return mediaTypesSet;
}
摘要定义:
@Bean
public Docket arqV1Api() {
MediaType[] validMediaTypes = new MediaType[]{new MediaType("application","vnd.arch.service.v1+json"),
new MediaType("application","vnd.arch.mails.v1+json")};
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(withMediaType(validMediaTypes))
.paths(PathSelectors.regex("/api/architecture/.*"))
.build()
.apiInfo(new ApiInfo("Architecture Rest Api","Architecture REST Services","v1","","","",""))
.produces(mediaTypesToStringSet(validMediaTypes))
.securitySchemes(newArrayList(apiKey()))
.securityContexts(newArrayList(securityContext()))
.groupName("Arq v1 group");
}
@Bean
public Docket arqV2Api() {
MediaType[] validMediaTypes = new MediaType[]{new MediaType("application","vnd.arch.service.v2+json")};
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(withMediaType(validMediaTypes))
.paths(PathSelectors.regex("/api/architecture/.*"))
.build()
.apiInfo(new ApiInfo("Architecture Rest Api","Architecture REST Services","v2","","","",""))
.produces(mediaTypesToStringSet(validMediaTypes))
.securitySchemes(newArrayList(apiKey()))
.securityContexts(newArrayList(securityContext()))
.groupName("Arq v2 group");
}