SpringBoot2.6.5集成Swagger3和Knife3

一、源码

不废话,直接上代码

1.1 pom.xml

版本定义在父工程了

  • SpringBoot 2.6.5
  • springfox-boot-starter 3.0.0
  • knife4j-spring-boot-starter 3.0.3
	<!--提供SpringMVC支持-->
   <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
   </dependency>
   <!--监控、健康检查-->
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-actuator</artifactId>
   </dependency>

   <!--hibernate-validator-->
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-validation</artifactId>
   </dependency>
   <!--json模块-->
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-json</artifactId>
   </dependency>
  <!--swagger 依赖-->
   <dependency>
       <groupId>io.springfox</groupId>
       <artifactId>springfox-boot-starter</artifactId>
       <exclusions>
           <!--spring-plugin-core版本太低,排除掉-->
           <exclusion>
               <groupId>org.springframework.plugin</groupId>
               <artifactId>spring-plugin-core</artifactId>
           </exclusion>
           <exclusion>
               <artifactId>swagger-models</artifactId>
               <groupId>io.swagger</groupId>
           </exclusion>
           <exclusion>
               <artifactId>swagger-annotations</artifactId>
               <groupId>io.swagger</groupId>
           </exclusion>
       </exclusions>
   </dependency>
   <!--依赖更高版本的spring-plugin-core-->
   <dependency>
       <groupId>org.springframework.plugin</groupId>
       <artifactId>spring-plugin-core</artifactId>
   </dependency>
   <dependency>
       <groupId>com.github.xiaoymin</groupId>
       <artifactId>knife4j-spring-boot-starter</artifactId>
   </dependency>

1.2 SwaggerAutoConfiguration

@Slf4j
@Configuration
//开启swagger,当前版本为3,替代@EnableSwagger2,实测发现不加也行
@EnableOpenApi
//或者直接省略prefix,那么直接写为swagger.enabled,当配置中存在swagger.enabled生效,matchIfMissing = true默认生效
@ConditionalOnProperty(prefix = "swagger", name = "enabled", matchIfMissing = true)
public class SwaggerAutoConfiguration {

    //默认的排除路径,排除Spring Boot默认的错误处理路径和端点(在解析的url规则之上)  /*/error,由于服务通常加前缀,所以前面/*忽略前缀
    private static final List<String> DEFAULT_EXCLUDE_PATH = Arrays.asList("/error", "/actuator/**", "/*/error");

    //swagger会解析的url规则
    private static final String BASE_PATH = "/**";

    @Autowired
    private SwaggerProperties swaggerProperties;

    @Bean
    public Docket createRestApi() {

        // base-path处理
        if (swaggerProperties.getBasePath().isEmpty()) {
            swaggerProperties.getBasePath().add(BASE_PATH);
        }

        // exclude-path处理
        if (swaggerProperties.getExcludePath().isEmpty()) {
            swaggerProperties.getExcludePath().addAll(DEFAULT_EXCLUDE_PATH);
        }

        //需要排除的url
        List<Predicate<String>> excludePath = new ArrayList<>();
        swaggerProperties.getExcludePath().forEach(path -> excludePath.add(PathSelectors.ant(path)));

        ApiSelectorBuilder builder = new Docket(DocumentationType.OAS_30)
                .host(swaggerProperties.getHost())
                .apiInfo(apiInfo())
                .select()
                //此包路径下的类,才生成接口文档
                .apis(RequestHandlerSelectors.basePackage(swaggerProperties.getBasePackage()));

        swaggerProperties.getBasePath().forEach(p -> builder.paths(PathSelectors.ant(p)));

        swaggerProperties.getExcludePath().forEach(p -> builder.paths(PathSelectors.ant(p).negate()));

        return builder.build()
                .securitySchemes(Collections.singletonList(securitySchemes()))
                .securityContexts(Collections.singletonList(securityContexts())).pathMapping("/");

    }

    /**
     * 配置默认的全局鉴权策略的开关,通过正则表达式进行匹配;默认匹配所有URL
     */
    private SecurityContext securityContexts() {
        return SecurityContext.builder().securityReferences(Collections.singletonList(this.defaultAuth()))
                .forPaths(PathSelectors.regex("^(?!auth).*$")).build();
    }

    /**
     * 默认的全局鉴权策略
     */
    private SecurityReference defaultAuth() {
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[]{authorizationScope};
        return new SecurityReference("Authorization", authorizationScopes);
    }

    private ApiKey securitySchemes() {
        return new ApiKey("Authorization", "Authorization", "header");
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title(swaggerProperties.getTitle())
                .description(swaggerProperties.getDescription())
                .version(swaggerProperties.getVersion())
                .license(swaggerProperties.getLicense())
                .licenseUrl(swaggerProperties.getLicenseUrl())
                .termsOfServiceUrl(swaggerProperties.getTermsOfServiceUrl())
                .contact(new Contact(
                        swaggerProperties.getContact().getName(),
                        swaggerProperties.getContact().getUrl(),
                        swaggerProperties.getContact().getEmail()
                ))
                .build();
    }

    /**
     * 解决SpringBoot2.6.x与Swagger3、knife4j3的兼容问题,以下配置也是必须的
     * spring:
     *   mvc:
     *     pathmatch:
     *       matching-strategy: ant_path_matcher
     */
    @Bean
    public static BeanPostProcessor springFoxHandlerProviderBeanPostProcessor() {
        return new BeanPostProcessor() {

            @Override
            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                if (bean instanceof WebMvcRequestHandlerProvider) {
                    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);
                }
            }
        };
    }
}

1.3 SwaggerProperties

@Data
@Configuration
@ConfigurationProperties("swagger")
public class SwaggerProperties {

    /**
     * 是否开启swagger
     */
    private Boolean enabled;

    /**
     * swagger会解析的包路径
     **/
    private String basePackage = "";

    /**
     * swagger会解析的url规则
     **/
    private List<String> basePath = new ArrayList<>();

    /**
     * 在basePath基础上需要排除的url规则
     **/
    private List<String> excludePath = new ArrayList<>();

    /**
     * 需要排除的服务
     */
    private List<String> ignoreProviders = new ArrayList<>();

    /**
     * 标题
     **/
    private String title = "";

    /**
     * 描述
     **/
    private String description = "";

    /**
     * 版本
     **/
    private String version = "";

    /**
     * 许可证
     **/
    private String license = "";

    /**
     * 许可证URL
     **/
    private String licenseUrl = "";

    /**
     * 服务条款URL
     **/
    private String termsOfServiceUrl = "";

    /**
     * host信息
     **/
    private String host = "";

    /**
     * 联系人信息
     */
    private Contact contact = new Contact();

    /**
     * 全局统一鉴权配置
     **/
    private Authorization authorization = new Authorization();

    @Data
    @NoArgsConstructor
    public static class Contact {

        /**
         * 联系人
         **/
        private String name = "";

        /**
         * 联系人url
         **/
        private String url = "";

        /**
         * 联系人email
         **/
        private String email = "";

    }

    @Data
    @NoArgsConstructor
    public static class Authorization {

        /**
         * 鉴权策略ID,需要和SecurityReferences ID保持一致
         */
        private String name = "";

        /**
         * 需要开启鉴权URL的正则
         */
        private String authRegex = "^.*$";

        /**
         * 鉴权作用域列表
         */
        private List<AuthorizationScope> authorizationScopeList = new ArrayList<>();

        private List<String> tokenUrlList = new ArrayList<>();

    }

    @Data
    @NoArgsConstructor
    public static class AuthorizationScope {

        /**
         * 作用域名称
         */
        private String scope = "";

        /**
         * 作用域描述
         */
        private String description = "";

    }
}

1.4 application.yml

spring:
  application:
    name: user-center
  # 修改路径匹配基于AntPathMatcher,以使用swagger
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher
      
swagger:
  basePackage: xx.xx自定义,也可去掉,默认读取所有
  title: usercenter-api
  description: "用户中心api"
  license:  Powered By yex
  licenseUrl: https://demo.com/
  terms-of-service-url: https://demo.com/
  contact:
   email: xxx@qq.com
   url: https://demo.com/about.html
  #以下配置是在网关聚合多个微服务的api文档时用的
  authorization:
    name: auth-center
    auth-regex: ^.*$
    authorization-scope-list:
      - scope: server
        description: server all
    token-url-list:
      - http://${GATEWAY-HOST:gateway-center}:${GATEWAY-PORT:8081}/auth/oauth/token

1.5 Controller

使用@Api、@ApiOperation依然有效,也可以使用新版的@Tag、@Operation搭配,这块还是用旧的熟悉一点。

@Api(tags = "用户信息管理")
@RestController
@RequestMapping("/usercenter/user")
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/getUserById")
    @ApiOperation("根据ID获取用户")
    public UserVO getUserById(@RequestParam String id) {
        return userService.getUserById(id);
    }
}

二、遇到的问题

2.1 启动报错

启动报错,错误信息

Failed to start bean ‘documentationPluginsBootstrapper’; nested exception is java.lang.NullPointerException

解决方案:
参考Knife4j配置,代码已在上文给出,有很多文章说加配置spring.mvc.pathmatch.matching-strategy=ant_path_matcher就行,我遇到的情况就不行,查到资料说是因为依赖了spring-boot-starter-actuator,只能多配置一个Bean springfoxHandlerProviderBeanPostProcessor,配完后又出现依赖冲突,之后再解决冲突(pom排查冲突包),就正常启动了。

2.2 /swagger-ui/index.html报错

启动之后/swagger-ui/index.html报错,错误信息:

Unable to render this definition
The provided definition does not specify a valid version field.
Please indicate a valid Swagger or OpenAPI version field. Supported version fields are swagger: “2.0” and those that match openapi: 3.0.n (for example, openapi: 3.0.0).

解决方案:
ResponseBodyAdvice使用有讲

2.3 接口文档出不来

不报错之后发现接口文档出不来,反复测试发现spring.mvc.pathmatch.matching-strategy=ant_path_matcher这个配置也是必须的
在这里插入图片描述

三、正常情况

看似简单的集成,花了三天时间才搞定,记录一下踩过的坑,希望大家能避开
在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值