参考
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 {@Beanpublic BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {return new BeanPostProcessor() {@Overridepublic 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
@EnableKnife4jpublic 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;@Beanpublic 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();}@Overridepublic 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());}
}
运行结果
正常运行。