Swagger3 版本动态分组
项目环境
springBoot 2.4.0
jdk1.8
swagger3
knife4j(Swagger生成Api文档的增强解决方案)
引入的包
<!-- springfox swagger3.x --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-boot-starter</artifactId> <version>3.0.0</version> </dependency> <!-- springfox swagger3.x 整合Knife4j --> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-boot-starter</artifactId> <version>3.0.2</version> <!-- 排除一些与swagger冲突的包 --> <exclusions> <exclusion> <artifactId>swagger-annotations</artifactId> <groupId>io.swagger</groupId> </exclusion> <exclusion> <artifactId>swagger-models</artifactId> <groupId>io.swagger</groupId> </exclusion> </exclusions> </dependency>
编写一个可在方法和类上添加的注解
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * ApiVersion * 自定义swagger接口上的版本分组注解 * 需要新分组时在下面的Version枚举类中新增一个常量即可 * * @author 七濑武 * @date 2021/4/16 16:50 */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD, ElementType.TYPE_USE}) public @interface ApiVersion { Version[] value(); enum Version { /** * 分组名称 */ DEFAULT("default"), v_1_1_0("1.1.0"); private final String display; Version(String display) { this.display = display; } public String getDisplay() { return display; } } }
配置Swagger3
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j; import com.github.xiaoymin.knife4j.spring.extension.OpenApiExtensionResolver; import com.google.common.collect.ImmutableList; import com.nanase.takeshi.annotion.ApiVersion; import com.nanase.takeshi.annotion.PassToken; import com.nanase.takeshi.constants.JwtConstant; import com.nanase.takeshi.util.enums.SysCodeEnum; import io.swagger.annotations.Api; import io.swagger.models.auth.In; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.http.HttpMethod; import org.springframework.plugin.core.OrderAwarePluginRegistry; import org.springframework.plugin.core.PluginRegistry; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.ResponseBuilder; import springfox.documentation.service.*; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spi.service.DocumentationPlugin; import springfox.documentation.spi.service.contexts.SecurityContext; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.spring.web.plugins.DocumentationPluginsManager; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; /** * Swagger3Config * * @author 七濑武 * @date 2021/4/16 16:50 */ @Primary //自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常。(只对接口的多个实现生效)覆盖swagger自己的配置 @Configuration //定义配置类 @EnableKnife4j //开启Knife4j public class Swagger3Config extends DocumentationPluginsManager { //yml文件中配置的name @Value("${spring.application.name}") private String applicationName; //注入Knife4j private final OpenApiExtensionResolver openApiExtensionResolver; //注入Knife4j @Autowired public Swagger3Config(OpenApiExtensionResolver openApiExtensionResolver) { this.openApiExtensionResolver = openApiExtensionResolver; } @Override public Collection<DocumentationPlugin> documentationPlugins() throws IllegalStateException { List<DocumentationPlugin> plugins = registry().getPlugins(); ensureNoDuplicateGroups(plugins); return plugins.isEmpty() ? Collections.singleton(this.defaultDocumentationPlugin()) : plugins; } private void ensureNoDuplicateGroups(List<DocumentationPlugin> allPlugins) throws IllegalStateException { Map<String, List<DocumentationPlugin>> plugins = allPlugins.stream().collect(Collectors.groupingBy((input) -> { return Optional.ofNullable(input.getGroupName()).orElse("default"); }, LinkedHashMap::new, Collectors.toList())); Iterable<String> duplicateGroups = plugins.entrySet().stream().filter((input) -> { return (input.getValue()).size() > 1; }).map(Map.Entry::getKey).collect(Collectors.toList()); if (StreamSupport.stream(duplicateGroups.spliterator(), false).count() > 0L) { throw new IllegalStateException(String.format("Multiple Dockets with the same group name are not supported. The following duplicate groups were discovered. %s", String.join(",", duplicateGroups))); } } private DocumentationPlugin defaultDocumentationPlugin() { return new Docket(DocumentationType.OAS_30); } private SwaggerPluginRegistry registry() { List<Docket> list = new ArrayList<>(); for (ApiVersion.Version version : ApiVersion.Version.values()) { Docket docket = new Docket(DocumentationType.OAS_30) // 指定构建api文档的详细信息的方法:apiInfo() .apiInfo(apiInfo()) .groupName(version.getDisplay()) .select() .apis(input -> { if (ApiVersion.Version.DEFAULT.equals(version)) { //指定扫描有Api注解的类 return input.findControllerAnnotation(Api.class).isPresent(); } //指定扫描有此版本的ApiVersion注解的方法 return input.findAnnotation(ApiVersion.class).filter(item -> Arrays.asList(item.value()).contains(version)).isPresent(); }) .paths(PathSelectors.any()) .build() //使Knife4j的增强配置生效 .extensions(openApiExtensionResolver.buildSettingExtensions()) // 支持的通讯协议集合 .protocols(Stream.of("https", "http").collect(Collectors.toSet())) // 授权信息设置,必要的header token等认证信息 .securitySchemes(securitySchemes()) // 授权信息全局应用 .securityContexts(securityContexts()); list.add(docket); } return new SwaggerPluginRegistry(list, new AnnotationAwareOrderComparator()); } private ApiInfo apiInfo() { return new ApiInfoBuilder() // 设置页面标题 .title(applicationName) // 设置接口描述 .description(applicationName + "通用框架接口") // 设置联系方式 .contact(new Contact("七濑武", null, null)) .build(); } /** * 设置授权信息 */ private List<SecurityScheme> securitySchemes() { //swagger3此处有个小坑(ApiKey中的参数name和keyname有问题,只有第一个参数name会生效,第二个参数keyname无效,此处就配置一样的字符串) return Collections.singletonList(new ApiKey("token", "token", In.HEADER.toValue())); } /** * 授权信息全局应用 */ private List<SecurityContext> securityContexts() { return ImmutableList.of(SecurityContext.builder() .securityReferences( Collections.singletonList( SecurityReference.builder() .scopes(new AuthorizationScope[0]) //此处需要配置的与ApiKey中的name一致才可以全局应用上 .reference("token") .build() ) ) //声明作用域,@PassToken注解的方法不在header中添加token,全局应用时不应用在有@PassToken注解上面 .operationSelector(o -> !o.findAnnotation(PassToken.class).isPresent()) .build()); } } /** * SwaggerPluginRegistry * * @author 七濑武 * @date 2021/4/16 16:55 */ class SwaggerPluginRegistry extends OrderAwarePluginRegistry<DocumentationPlugin, DocumentationType> implements PluginRegistry<DocumentationPlugin, DocumentationType> { protected SwaggerPluginRegistry(List<Docket> plugins, Comparator<? super DocumentationPlugin> comparator) { super(plugins, comparator); } @Override public List<DocumentationPlugin> getPlugins() { return super.getPlugins(); } }
下面是我用到的PassToken注解类
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 过滤token校验注解 * controller层方法上加上注解可不校验token * * @author 七濑武 * @date 2020/11/27 16:50 */ @Target({ ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface PassToken { boolean required() default true; }
下面是用到的yml配置
server: port: 8080 spring: application: name: NanaseTakeshi # knife4j配置 knife4j: basic: username: admin password: admin enable: true #开启认证,访问接口文档时需要用户名密码访问 enable: true # 开启增强配置 setting: # 增强的配置信息 enableOpenApi: false enableFooter: false
接口版本动态分组例子
//传入对应的版本即可 @ApiVersion(ApiVersion.Version.v_1_2_0) @ApiOperation("测试ApiVersion注解的方法") @GetMapping("/test") public String test(){ return "Hello World"; }