参考
https://juejin.cn/post/7249173717749940284
近期由于升级到springboot2.6X,所以服务端很多组件都需要重新导入以及解决依赖问题。
下面就是一个很经典的问题了,
springboot2.6与knife4j的整合。
版本对应
springboot2.6与knife4j 3.0.3
坑
- 两者路径匹配模式不同
由于Springfox使用的路径匹配是基于AntPathMatcher,而Spring Boot 2.6.X使用的是PathPatternMatcher,所以将MVC的路径匹配规则改成 AntPathMatcher。具体配置如下:
--- application.yml:
spring:
mvc:
pathmatch:
#Springfox使用的路径匹配是基于AntPathMatcher的,而Spring Boot 2.6.X使用的是PathPatternMatcher 解决knife4j匹配boot2.6.7 boot版本过高的问题
matching-strategy: ant_path_matcher
-- 或者:application.properties:
spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER
- springboot加载springfox过程中失败:
第一个点很多文章都提到了,但是我主要遇到的是第二个点,表现在:
"org.springframework.web.servlet.mvc.condition.PatternsRequestCondition.getPatterns()" because "this.condition" is null
这时候你可以
新增一个配置类,注意配置类不能放到swagger的配置类下,测试无效。配置Bean可以放在启动类下或者重新新增一个配置Bean,代码如下:
package net.w2p.AppBase.fixBugs;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider;
import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
import java.lang.reflect.Field;
import java.util.List;
import java.util.stream.Collectors;
/**
* Springboot整合Knife4j问题修正
*/
@Configuration
public class BeanPostProcessorConfig {
@Bean
public BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
return new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
}
return bean;
}
private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
List<T> copy = mappings.stream()
.filter(mapping -> mapping.getPatternParser() == null)
.collect(Collectors.toList());
mappings.clear();
mappings.addAll(copy);
}
@SuppressWarnings("unchecked")
private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
try {
Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
field.setAccessible(true);
return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
};
}
}
- 配置demo
package net.w2p.AppBase;
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import io.swagger.annotations.ApiOperation;
import net.w2p.WebExt.config.AppENV;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import javax.servlet.http.HttpSession;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import static springfox.documentation.builders.PathSelectors.regex;
/*
* @Api: 修饰整个类,描述Controller的作用
* @ApiOperation: 描述一个类的一个方法,或者说一个接口
* @ApiParam: 单个参数描述
* @ApiModel: 用对象来接收参数
* @ApiProperty: 用对象接收参数时,描述对象的一个字段
* @ApiResponse: HTTP响应其中1个描述
* @ApiResponses: HTTP响应整体描述
* @ApiIgnore: 使用该注解忽略这个API
* @ApiError :发生错误返回的信息
* @ApiImplicitParam: 一个请求参数
* @ApiImplicitParams: 多个请求参数
* doc.html
*/
@EnableSwagger2
@Configuration
@EnableKnife4j
public class Swagger2Configuration implements WebMvcConfigurer {
private String accessToken="token";
private String title;
private String description;
private String version;
private String termsOfServiceUrl;
private String name;
private String url;
private String email;
@Bean
public Docket createRestApi() {
boolean isEnableSwagger=true;
if(!"test".equalsIgnoreCase(AppENV.getEnv())&&!"dev".equalsIgnoreCase(AppENV.getEnv())){
isEnableSwagger=false;
}
if("dev".equalsIgnoreCase(AppENV.getEnv())){
isEnableSwagger=true;
}else{
isEnableSwagger=false;
}
String scanPaths = "net.w2p.AppBase.controller.api;" +
"net.w2p.AppBase.controller.common;" ;
return new Docket(DocumentationType.SWAGGER_2).enable(isEnableSwagger)
.apiInfo(apiInfo())
// .enable(enableSwagger)
.directModelSubstitute(Timestamp.class, Long.class)//将Timestamp类型全部转为Long类型
.directModelSubstitute(Date.class, Long.class)//将Date类型全部转为Long类型
.forCodeGeneration(true)
.select()
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
// .apis(RequestHandlerSelectors.any())
.apis(basePackage(scanPaths))
// .paths(PathSelectors.ant("/api/**"))
// .paths(regex("^.*(?<!error)$"))
.paths(PathSelectors.any())
.build()
// 添加忽略类型
.ignoredParameterTypes(HttpSession.class)
.securitySchemes(securitySchemes())
.securityContexts(securityContexts());
}
private List<SecurityScheme> securitySchemes() {
List<SecurityScheme> apiKeyList= new ArrayList();
apiKeyList.add(new ApiKey("token", "token", "header"));
return apiKeyList;
}
private List<SecurityContext> securityContexts() {
List<SecurityContext> securityContexts=new ArrayList<>();
securityContexts.add(
SecurityContext.builder()
.securityReferences(defaultAuth())
.forPaths(regex("^(?!auth).*$"))
.build());
return securityContexts;
}
private List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
List<SecurityReference> securityReferences = new ArrayList<>();
securityReferences.add(new SecurityReference(accessToken, authorizationScopes));
return securityReferences;
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("api文档")
.description("综合前台网站")
.termsOfServiceUrl("http://www.baidu.com")
.contact(new Contact("freeLife","baidu.com","1000@qq.com"))
.version("2.9.2")
.build();
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
System.out.println("================> add doc.html -url");
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
// registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
// 定义分隔符
private static final String splitor = ";";
/**
* 声明基础包
*
* @param basePackage 基础包路径
* @return
*/
public static Predicate<RequestHandler> basePackage(final String basePackage) {
return input -> declaringClass(input).transform(handlerPackage(basePackage)).or(true);
}
/**
* 校验基础包
*
* @param basePackage 基础包路径
* @return
*/
private static Function<Class<?>, Boolean> handlerPackage(final String basePackage) {
return input -> {
for (String strPackage : basePackage.split(splitor)) {
boolean isMatch = input.getPackage().getName().startsWith(strPackage);
if (isMatch) {
return true;
}
}
return false;
};
}
/**
* 检验基础包实例
*
* @param requestHandler 请求处理类
* @return
*/
@SuppressWarnings("deprecation")
private static Optional<? extends Class<?>> declaringClass(RequestHandler requestHandler) {
return Optional.fromNullable(requestHandler.declaringClass());
}
}
运行结果
正常运行。