项目环境
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";
}