1. 前言
最近工作中需要到Knife4j, 加上自己的项目充电鸭上本来好的Knife4j文档突然不好使了,遇到的问题正好记录一下
2.个人网站遇到的问题
个人网站本来的事还挺好用的,经过我得一阵折腾之后,每次启动的时候都会报错空指针。Springboot版本是2.3.
具体问题如下
: null
at (:131) ~[springfox-core-3.0.!/:3.0.0]
at (:59) ~[na:1.8.0_201]
at (:132) ~[springfox-core-3.0.!/:3.0.0]
at (:635) ~[na:1.8.0_201]
at (:612) ~[na:1.8.0_201]
at (:220) ~[na:1.8.0_201]
at $3ReducingSink.accept(:169) ~[na:1.8.0_201]
at $3$1.accept(:193) ~[na:1.8.0_201]
at $2$1.accept(:175) ~[na:1.8.0_201]
at $3$1.accept(:193) ~[na:1.8.0_201]
at $ArrayListSpliterator.forEachRemaining(:1382) ~[na:1.8.0_201]
at (:481) ~[na:1.8.0_201]
at (:471) ~[na:1.8.0_201]
at $ReduceOp.evaluateSequential(:708) ~[na:1.8.0_201]
at (:234) ~[na:1.8.0_201]
at (:499) ~[na:1.8.0_201]
at (:93) ~[springfox-spring-web-3.0.!/:3.0.0]
at (:144) ~[springfox-spring-web-3.0.!/:3.0.0]
at (:72) ~[springfox-spring-web-3.0.!/:3.0.0]
at $new$0(:43) ~[springfox-spring-web-3.0.!/:3.0.0]
at (:1127) ~[na:1.8.0_201]
at (:48) ~[springfox-spring-web-3.0.!/:3.0.0]
at (:72) ~[springfox-spring-web-3.0.!/:3.0.0]
at (:169) ~[springfox-spring-web-3.0.!/:3.0.0]
at (:67) ~[springfox-spring-web-3.0.!/:3.0.0]
at (:96) [springfox-spring-web-3.0.!/:3.0.0]
at (:82) [springfox-spring-web-3.0.!/:3.0.0]
at (:100) [springfox-spring-web-3.0.!/:3.0.0]
at (:182) [spring-context-5.2.!/:5.2.]
at $200(:53) [spring-context-5.2.!/:5.2.]
at $LifecycleGroup.start(:360) [spring-context-5.2.!/:5.2.]
at (:158) [spring-context-5.2.!/:5.2.]
at (:122) [spring-context-5.2.!/:5.2.]
at (:895) [spring-context-5.2.!/:5.2.]
at (:554) [spring-context-5.2.!/:5.2.]
at (:143) [spring-boot-2.3.!/:2.3.]
at (:758) [spring-boot-2.3.!/:2.3.]
at (:750) [spring-boot-2.3.!/:2.3.]
at (:397) [spring-boot-2.3.!/:2.3.]
at (:315) [spring-boot-2.3.!/:2.3.]
at (:1237) [spring-boot-2.3.!/:2.3.]
at (:1226) [spring-boot-2.3.!/:2.3.]
at (:30) [classes!/:1.0-SNAPSHOT]
at .invoke0(Native Method) ~[na:1.8.0_201]
at (:62) ~[na:1.8.0_201]
at (:43) ~[na:1.8.0_201]
at (:498) ~[na:1.8.0_201]
at (:48) [charge_duck-1.:1.0-SNAPSHOT]
at (:87) [charge_duck-1.:1.0-SNAPSHOT]
at (:50) [charge_duck-1.:1.0-SNAPSHOT]
at (:58) [charge_duck-1.:1.0-SNAPSHOT]
最开始以为是springfox版本的问题,然后将springfox从knife4j中排除,引入了新的springfox,结果问题依然存在
<!--knife swagger UI库-->
<dependency>
<groupId></groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.3</version>
<exclusions>
<exclusion>
<groupId></groupId>
<artifactId>springfox-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId></groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
项目是也能正常启动,但是打开/就是会空白,什么都不展示,前端也会提示有一个undefined
最后在一个****老哥的文章下找到了问题,说是@ApiModelProperty
中的value在同一个类或者父类里边有重复的了,
文章暂时找不到了,等我想起来我在贴一下链接,最后把所有的@ApiModelProperty
都注释了先,启动一下果然能用了。
3.公司项目遇到的问题
公司项目整合Knife4j,权限框架用的是SpringSecurity,CAS认证具体是啥我还没明白哈哈哈,Springboot版本
然后我就开开心心的放行了资源 2.6.6
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class CasSecurityConfig extends WebSecurityConfigurerAdapter {
// knife4j 资源
String[] IGNORE_URL_ARRAY = {"/swagger-resources", "/v2/api-docs", "/",
"/webjars/**", "/"};
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().httpBasic().disable()
.addFilterBefore(tokenBasedAuthenticationFilter(casProperties), AnonymousAuthenticationFilter.class)
.authorizeRequests().antMatchers(IGNORE_URL_ARRAY).permitAll().anyRequest().authenticated().and()
.formLogin().disable();
}
}
美滋滋的打开localhost:8000/一看 /swagger-resources
和/swagger-resources/xxx(具体忘了)
404了,
然后更改一下放行资源为/swagger-resources/**
直接/swagger-resources/xxx(403)
,/swagger-resources(404)
了
又是一阵折腾之后发现 Springboot2.6.0版本以后需要修改配置文件才行,不然会出现各种奇怪的问题
具体的报错信息已经忘记了,下次有记录更新,改成下边这种形式就好了
spring:
mvc:
pathmatch:
# 切勿更改此配置,否则knife4j将报错404
matching-strategy: ant_path_matcher
4. 新项目遇到的新问题
项目环境依赖来瞅一眼,
SpringBoot 2.6.4
、SpringCloud 2021.0.7
、knife4j 3.0.3
<parent>
<groupId></groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<packaging>jar</packaging>
<properties>
<>1.8</>
<!--<>Greenwich.SR2</>-->
<>2021.0.7</>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId></groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--spring相关-->
<dependency>
<groupId></groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<!--knife swagger UI库-->
<dependency>
<groupId></groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
</dependencies>
因为已经有了之前的经验,SpringBoot 2.6.0
版本以后就需要改一下配置
spring:
mvc:
pathmatch:
matching-strategy: ant_path_matcher
然鹅,启动之后华丽的报了个错 ,最主要的就是这个了Cannot invoke "()" because "" is null
: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is : Cannot invoke "()" because "" is null
at (:181) ~[spring-context-5.3.:5.3.16]
at $200(:54) ~[spring-context-5.3.:5.3.16]
at $LifecycleGroup.start(:356) ~[spring-context-5.3.:5.3.16]
at /(:75) ~[na:na]
at (:155) ~[spring-context-5.3.:5.3.16]
at (:123) ~[spring-context-5.3.:5.3.16]
at (:935) ~[spring-context-5.3.:5.3.16]
at (:586) ~[spring-context-5.3.:5.3.16]
at (:145) ~[spring-boot-2.6.:2.6.4]
at (:740) ~[spring-boot-2.6.:2.6.4]
at (:415) ~[spring-boot-2.6.:2.6.4]
at (:303) ~[spring-boot-2.6.:2.6.4]
at (:1312) ~[spring-boot-2.6.:2.6.4]
at (:1301) ~[spring-boot-2.6.:2.6.4]
at (:21) ~[classes/:na]
Caused by: : Cannot invoke "()" because "" is null
at (:56) ~[springfox-spring-webmvc-3.0.:3.0.0]
at (:113) ~[springfox-core-3.0.:3.0.0]
at $byPatternsCondition$3(:89) ~[springfox-spi-3.0.:3.0.0]
at /$comparing$77a9974f$1(:473) ~[na:na]
at /(:355) ~[na:na]
at /(:220) ~[na:na]
at /(:1307) ~[na:na]
at /(:1721) ~[na:na]
at /$RefSortingSink.end(:392) ~[na:na]
at /$ChainedReference.end(:258) ~[na:na]
at /$ChainedReference.end(:258) ~[na:na]
at /$ChainedReference.end(:258) ~[na:na]
at /$ChainedReference.end(:258) ~[na:na]
at /(:510) ~[na:na]
at /(:499) ~[na:na]
at /$ReduceOp.evaluateSequential(:921) ~[na:na]
at /(:234) ~[na:na]
at /(:682) ~[na:na]
at (:81) ~[springfox-spring-webmvc-3.0.:3.0.0]
at /$3$1.accept(:197) ~[na:na]
at /$ArrayListSpliterator.forEachRemaining(:1625) ~[na:na]
at /(:509) ~[na:na]
at /(:499) ~[na:na]
at /$ReduceOp.evaluateSequential(:921) ~[na:na]
at /(:234) ~[na:na]
at /(:682) ~[na:na]
at (:107) ~[springfox-spring-web-3.0.:3.0.0]
at (:91) ~[springfox-spring-web-3.0.:3.0.0]
at (:82) ~[springfox-spring-web-3.0.:3.0.0]
at (:100) ~[springfox-spring-web-3.0.:3.0.0]
at (:178) ~[spring-context-5.3.:5.3.16]
... 14 common frames omitted
然后根据 qq_21480329的帖子解决了,解决方案如下
package net.lesscoding.handmade.config;
import lombok.extern.slf4j.Slf4j;
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;
/**
* @author eleven
* @date 2023/6/28 10:05
* @apiNote
*/
@Slf4j
@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);
}
}
};
}
}
X. 番外,Knife4j添加全局权限认证
package net.lesscoding.config;
import cn.hutool.core.util.StrUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import io.swagger.models.auth.In;
import springfox.documentation.service.*;
import springfox.documentation.spi.service.contexts.SecurityContext;
import java.util.ArrayList;
import java.util.List;
/**
* @author eleven
* @date 2023/6/20 14:07
* @apiNote
*/
@Configuration
public class Knife4jConfig {
@Value("${}")
private String active;
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(new ApiInfoBuilder()
.description("双重预防数智化管控平台接口文档")
.termsOfServiceUrl("")
.contact(new Contact("双重预防数智化管控平台", "","safeinfo@"))
.version("1.0")
.build())
//分组名称
.groupName("1.0.0版本")
.groupName("1.0.1版本")
.select()
//这里指定Controller扫描包路径
.apis(RequestHandlerSelectors.basePackage(""))
// Knife4j 配置只扫描带有@ApiOperatgion注解的方法
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
// Knife4j 配置只扫描带有@Api注解的类
.apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
.paths(PathSelectors.any())
.build()
// 添加全局的权限认证请求头
.securityContexts(securityContexts())
.securitySchemes(securitySchemes())
// 只有在非prod环境才启用
.enable(!StrUtil.equals(active, "prod"))
;
}
/**
* 安全模式,这里指定token通过Authorization头请求头传递
*/
private List<SecurityScheme> securitySchemes() {
// 设置请求头信息
List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>();
apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue()));
return apiKeyList;
}
/**
* 安全上下文
*/
private List<SecurityContext> securityContexts() {
// 设置需要登录的认证路径
List<SecurityContext> securityContexts = new ArrayList<>();
securityContexts.add(
SecurityContext.builder()
.securityReferences(defaultAuth())
.operationSelector(o -> o.requestMappingPattern().matches("/.*"))
.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("Authorization", authorizationScopes));
return securityReferences;
}
}
X + 1 生产环境配置过滤掉 Knife4j
参考链接knife4j生产环境资源屏蔽
# knife4j的增强配置
knife4j:
enable: true
# 开启生产环境屏蔽,一定要先开启knife4j增强才会生效
production: true