SpringBoot v2.7.x+ 整合Swagger3入坑记?

news2025/1/17 0:18:24

目录

一、依赖

二、集成Swagger Java Config

三、配置完毕

四、解决方案

彩蛋


想尝鲜,坑也多,一起入个坑~

一、依赖

SpringBoot版本:2.7.14

Swagger版本:3.0.0

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
	<version>3.0.3</version>
</dependency>

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
	<version>3.0.0</version>
</dependency>

二、集成Swagger Java Config


@Value("${server.port:8080}")
private String port;

@Value("${server.servlet.context-path:}")
private String rootPath;

@Bean
Docket docket(SwaggerProperties properties) {
    Docket docket = new Docket(DocumentationType.OAS_30)
            .apiInfo(apiInfo(properties))
            .groupName(properties.getGroupName())
            .select()
            .apis(scanBasePackages(properties.getBasePackage()))
            .paths(PathSelectors.any())
            .build()
            .globalRequestParameters(globalRequestParameters(properties))
            .globalResponses(HttpMethod.POST, responses())
            .globalResponses(HttpMethod.GET, responses()).pathMapping("/");
    log.info("Swagger3 successfully started: http://{}:{}{}/doc.html", IPUtils.getLocalIP(), port, rootPath);
    return docket;
}

@Bean
public ModelPropertyBuilderPlugin modelPropertyBuilderPlugin() {
    return new DictPropertyPlugin();
}

/**
 * 构建响应状态码
 */
private List<Response> responses() {
    List<Response> responses = new LinkedList<>();
    responses.add(new ResponseBuilder().code("S").description("响应成功").build());
    responses.add(new ResponseBuilder().code("E").description("非'S'即为响应失败").build());
    return responses;
}

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

/**
 * 自定义请求参数
 *
 * @return - list
 */
private List<RequestParameter> globalRequestParameters(SwaggerProperties properties) {
    List<RequestParameter> params = new ArrayList<>();
    properties.getParams().forEach(e -> {
        RequestParameter parameter = new RequestParameterBuilder()
                .name(e.getName())
                .description(e.getDesc())
                .required(e.isRequired())
                .in(e.getParamType())
                .hidden(e.isHidden())
                .build();
        params.add(parameter);
    });
    return params;
}

/**
 * 多包扫描支持,扫描的包生成{@linkplain Predicate < RequestHandler >}
 *
 * @param basePackages - 扫描的包
 */
private Predicate<RequestHandler> scanBasePackages(final String... basePackages) {
    if (basePackages == null || basePackages.length == 0) {
        throw new IllegalArgumentException("basePackages不能为空");
    }
    Predicate<RequestHandler> predicate = null;
    for (int i = basePackages.length - 1; i >= 0; i--) {
        String strBasePackage = basePackages[i];
        if (StrUtil.isNotBlank(strBasePackage)) {
            Predicate<RequestHandler> tempPredicate = RequestHandlerSelectors.basePackage(strBasePackage);
            predicate = predicate == null ? tempPredicate : predicate.or(tempPredicate);
        }
    }
    if (predicate == null) {
        throw new IllegalArgumentException("basePackage配置不正确");
    }
    return predicate;
}
/**
 * swagger3 自定义展示枚举类型信息
 */
public class DictPropertyPlugin implements ModelPropertyBuilderPlugin {
    private final Logger log = LoggerFactory.getLogger(getClass());

    @Override
    public void apply(ModelPropertyContext ctx) {
        Optional<BeanPropertyDefinition> opt = ctx.getBeanPropertyDefinition();
        opt.ifPresent(bean -> {
            Class<?> cls = bean.getRawPrimaryType();
            if (IDict.class.isAssignableFrom(cls) && Enum.class.isAssignableFrom(cls)) {
                if (cls.getEnumConstants() == null) {
                    return;
                }
                try {
                    Field f = PropertySpecificationBuilder.class.getDeclaredField("description");
                    f.setAccessible(true);
                    String prefix = cls.getSimpleName() + "(" + f.get(ctx.getSpecificationBuilder()) + ")【";
                    StringJoiner join = new StringJoiner(",", prefix, "】");
                    for (IDict<?, ?> d : (IDict<?, ?>[]) cls.getEnumConstants()) {
                        join.add(d.getDesc() + "[" + d.getCode() + "]-" + ((Enum<?>) d).name());
                    }
                    ctx.getSpecificationBuilder().description(join.toString());
                } catch (Exception e) {
                    log.error("字典值处理失败:{}", cls.getName(), e);
                }
            }
        });
    }

    @Override
    public boolean supports(DocumentationType type) {
        return DocumentationType.OAS_30.equals(type);
    }
}

properties

package com.muchenx.common.swagger.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import springfox.documentation.service.ParameterType;

import java.util.ArrayList;
import java.util.List;

@ConfigurationProperties(prefix = "swagger")
public class SwaggerProperties {
    /**
     * swagger会解析的包路径
     **/
    private String basePackage = "com.muchenx";

    /**
     * 分组名
     */
    private String groupName = "default";

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

    /**
     * 描述
     **/
    private String description = "MuchenX Cloud Project supports by Spring Cloud Alibaba";

    /**
     * 版本
     **/
    private String version = "v1.0";

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

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

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

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

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

    /**
     * 自定义参数
     */
    private List<Param> params = new ArrayList<>();

    public String getGroupName() {
        return groupName;
    }

    public void setGroupName(String groupName) {
        this.groupName = groupName;
    }

    public String getBasePackage() {
        return basePackage;
    }

    public void setBasePackage(String basePackage) {
        this.basePackage = basePackage;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public String getLicense() {
        return license;
    }

    public void setLicense(String license) {
        this.license = license;
    }

    public String getLicenseUrl() {
        return licenseUrl;
    }

    public void setLicenseUrl(String licenseUrl) {
        this.licenseUrl = licenseUrl;
    }

    public String getTermsOfServiceUrl() {
        return termsOfServiceUrl;
    }

    public void setTermsOfServiceUrl(String termsOfServiceUrl) {
        this.termsOfServiceUrl = termsOfServiceUrl;
    }

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public Contact getContact() {
        return contact;
    }

    public void setContact(Contact contact) {
        this.contact = contact;
    }

    public List<Param> getParams() {
        return params;
    }

    public void setParams(List<Param> params) {
        this.params = params;
    }

    public static class Contact {
        /**
         * 联系人
         **/
        private String name = "Ian Geng";
        /**
         * 联系人url
         **/
        private String url = "www.muchenx.com";
        /**
         * 联系人email
         **/
        private String email = "gzhygz@gmail.com";

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getUrl() {
            return url;
        }

        public void setUrl(String url) {
            this.url = url;
        }

        public String getEmail() {
            return email;
        }

        public void setEmail(String email) {
            this.email = email;
        }
    }

    public static class Param {
        // 请求参数名
        private String name;
        // 请求参数描述
        private String desc;
        /**
         * 请求参数类型:QUERY("query"),HEADER("header"),PATH("path"),
         * COOKIE("cookie"),FORM("form"),FORMDATA("formData"),BODY("body");
         */
        private ParameterType paramType = ParameterType.HEADER;

        // 请求参数默认值
        private String defaultValue = "";
        // 是否必填
        private boolean required = false;

        // 是否隐藏
        private boolean hidden = false;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getDesc() {
            return desc;
        }

        public void setDesc(String desc) {
            this.desc = desc;
        }

        public ParameterType getParamType() {
            return paramType;
        }

        public void setParamType(ParameterType paramType) {
            this.paramType = paramType;
        }

        public String getDefaultValue() {
            return defaultValue;
        }

        public void setDefaultValue(String defaultValue) {
            this.defaultValue = defaultValue;
        }

        public boolean isRequired() {
            return required;
        }

        public void setRequired(boolean required) {
            this.required = required;
        }

        public boolean isHidden() {
            return hidden;
        }

        public void setHidden(boolean hidden) {
            this.hidden = hidden;
        }
    }
}

三、配置完毕

在启动类增加注解开起swagger:@springfox.documentation.oas.annotations.EnableOpenApi

此时控制台报错

Caused by: java.lang.NullPointerException: Cannot invoke "org.springframework.web.servlet.mvc.condition.PatternsRequestCondition.getPatterns()" because "this.condition" is null
	at springfox.documentation.spring.web.WebMvcPatternsRequestConditionWrapper.getPatterns(WebMvcPatternsRequestConditionWrapper.java:56) ~[springfox-spring-webmvc-3.0.0.jar:3.0.0]
	at springfox.documentation.RequestHandler.sortedPaths(RequestHandler.java:113) ~[springfox-core-3.0.0.jar:3.0.0]
	at springfox.documentation.spi.service.contexts.Orderings.lambda$byPatternsCondition$3(Orderings.java:89) ~[springfox-spi-3.0.0.jar:3.0.0]
	at java.base/java.util.Comparator.lambda$comparing$77a9974f$1(Comparator.java:473) ~[na:na]
	at java.base/java.util.TimSort.countRunAndMakeAscending(TimSort.java:355) ~[na:na]
	at java.base/java.util.TimSort.sort(TimSort.java:220) ~[na:na]
	at java.base/java.util.Arrays.sort(Arrays.java:1307) ~[na:na]
	at java.base/java.util.ArrayList.sort(ArrayList.java:1721) ~[na:na]
	at java.base/java.util.stream.SortedOps$RefSortingSink.end(SortedOps.java:392) ~[na:na]
	at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258) ~[na:na]
	at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258) ~[na:na]
	at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258) ~[na:na]
	at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258) ~[na:na]
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:510) ~[na:na]
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[na:na]
	at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921) ~[na:na]
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na]
	at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682) ~[na:na]

...........

原因是:主要出现在Spring Boot 2.6及以后,只要是Spring Boot 2.6引入的新PathPatternParser导致的。

四、解决方案

spring官方提及此issue:because "this.condition" is null · Issue #28794 · spring-projects/spring-boot · GitHub

但尚未解决,issue已关闭。

springfox社区活跃,已有大神解决此问题:Spring 5.3/Spring Boot 2.4 support · Issue #3462 · springfox/springfox · GitHub

适合的方案如下

 

1.Path匹配策略切换回ant_path_matcher(大多情况此方案可解决)

spring.mvc.pathmatch.matching-strategy=ant_path_matcher

2.若还是不能解决,添加如下配置

@Bean
ic WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier, ServletEndpointsSupplier servletEndpointsSupplier, ControllerEndpointsSupplier controllerEndpointsSupplier, EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties, WebEndpointProperties webEndpointProperties, Environment environment) {
    List<ExposableEndpoint<?>> allEndpoints = new ArrayList();
    Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
    allEndpoints.addAll(webEndpoints);
    allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
    allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
    String basePath = webEndpointProperties.getBasePath();
    EndpointMapping endpointMapping = new EndpointMapping(basePath);
    boolean shouldRegisterLinksMapping = this.shouldRegisterLinksMapping(webEndpointProperties, environment, basePath);
    return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes, corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath), shouldRegisterLinksMapping, null);
}


private boolean shouldRegisterLinksMapping(WebEndpointProperties webEndpointProperties, Environment environment, String basePath) {
    return webEndpointProperties.getDiscovery().isEnabled() && (StringUtils.hasText(basePath) || ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT));
}

涉及依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-actuator</artifactId>
    <version>2.7.14</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-actuator-autoconfigure</artifactId>
    <version>2.7.14</version>
    <scope>compile</scope>
</dependency>

配置完重启服务问题解决!

彩蛋

swagger3与springboot完整的集成方案已上架,欢迎查收:彩蛋~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/944248.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java面试之斐波那契数列(Fibonacci)及其应用:青蛙跳台阶问题

文章目录 一、斐波那契数列问题1.1 题目1.2 什么是斐波那契数列1.3 效率很低的解法&#xff1a;递归1.4 递归缺点分析 二、比较好的解决办法2.1 保存数列中间项2.2 从下往上计算 三、公式法四、青蛙跳台阶问题4.1 题目及分析4.2 代码实现 一、斐波那契数列问题 1.1 题目 写一…

制作鲜花商城小程序的详细步骤

如果你是一个新手商家&#xff0c;想要进入鲜花团购市场&#xff0c;但是不知道如何制作一个小程序商城&#xff0c;那么这篇文章就是为你准备的。以下是制作鲜花团购小程序商城的详细步骤&#xff1a; 1. 登录乔拓云平台后台&#xff0c;进入商城管理页面 首先&#xff0c;你需…

Shopee测评补单技巧:优化商品流量与权重

在Shopee平台上进行测评是一种低成本、高回报的推广方式&#xff0c;可以对商品的流量、转化率、质量分和权重等多个指标起到辅助作用。以下是一些Shopee测评的技巧和注意事项&#xff1a; 1. 测评原理与周期&#xff1a; Shopee的新品周期为7天&#xff0c;平台会在这段时间…

【管理运筹学】第 6 章 | 运输问题(4,表上作业法 |闭回路调整法以及特殊情况 | 产销不平衡的运输问题)

文章目录 引言二、表上作业法2.3 改进的方法 —— 闭回路调整法2.4 表上作业法中的特殊情况&#xff08;一&#xff09;无穷多最优解&#xff08;二&#xff09;退化 三、产销不平衡的运输问题3.1 产量大于销量3.2 销量大于产量 写在最后 引言 接下来我们学习表上作业法的最后…

图论算法基础:单源最短路径Dijkstra算法分析

文章目录 图的邻接矩阵 一.Dijkstra算法分析算法的核心逻辑要素算法的执行逻辑 二.Dijkstra算法接口实现邻接矩阵堆优化版本: 图的邻接矩阵 namespace Graph_Structure {//Vertex是代表顶点的数据类型,Weight是边的权值的数据类型,MAX_W是权值的上限值(表示不相两)//Direction…

项目 - 后端技术栈转型方案

前言 某开发项目的后端技术栈比较老了&#xff0c;现在想换到新的技术栈上。使用更好的模式、设计思想、更合理的架构等&#xff0c;为未来的需求迭代做铺垫。怎么办呢&#xff1f;假设系统目前在线上运行着的&#xff0c;直接整体换的话耗时太久&#xff0c;且中间还有新的需…

【嵌入式】Keil5自带JLink识别不到芯片(unkown to this version of the jlink software)的处理

目录 一 问题现象 二 原因分析 三 问题处理 一 问题现象 使用了一款新的嵌入式芯片&#xff0c;灵动微MM32SPIN27PF&#xff0c;安装了官方提供的J-Link Pack支持包。 【1】直接使用 JLink_V694a 可以正常烧写程序&#xff1b; 【2】使用Keil5烧写失败&#xff0c;显示报错“…

demo的改进和完善(首页添加介绍和图片)

1.在首页添加介绍 将布局→ 区域→ 静态内容拖拽到body中→ 标题 更改为自己想要的名称→ 源 写入html代码 《学习demo》是用来记录我的学习过程中遇到的问题及解决方案&#xff0c;见证了我的进步和成长 <hr> 相关内容可见<a href"https://blog.csdn.net/clove…

你的住宅安全吗?这个技能赶紧学学

随着城市化的不断加速和人口增长&#xff0c;住宅小区的管理和安全问题也愈发凸显出来。在这种背景下&#xff0c;门禁监控系统成为了一种既有效又实用的解决方案。 门禁监控系统不仅可以控制和管理出入小区的人员和车辆&#xff0c;还可以提供实时监控和记录&#xff0c;为小区…

视频监控/视频汇聚/视频云存储EasyCVR平台HLS流集成在小程序无法播放的问题排查

安防视频/视频云存储/视频集中存储EasyCVR视频监控综合管理平台可以根据不同的场景需求&#xff0c;让平台在内网、专网、VPN、广域网、互联网等各种环境下进行音视频的采集、接入与多端分发。在视频能力上&#xff0c;视频云存储平台EasyCVR可实现视频实时直播、云端录像、视频…

实现 Trie (前缀树)

题目链接 实现 Trie (前缀树) 题目描述 注意点 word 和 prefix 仅由小写英文字母组成 解答思路 首先要理解前缀树是什么&#xff0c;参照该篇文章【图解算法】模板变式——带你彻底搞懂字典树(Trie树)在了解前缀树是什么后&#xff0c;设计前缀树就会更加容易&#xff0c;…

java八股文面试[多线程]——主内存和工作内存的关系

JAVA内存模型&#xff08;JMM&#xff09;共享变量&#xff1a;如果一个变量在多个线程的工作内存中都存在副本&#xff0c;那么这个变量就是这几个线程的共享变量。 上面的工作内存其实是java内存模型抽象出来的概念&#xff0c;下面简要介绍一下java内存模型&#xff08;JMM&…

正中优配:股票经手费必须交吗?

在股票出资中&#xff0c;经手费是一个不可避免的要素。那么&#xff0c;股票经手费有必要交吗&#xff1f;从多个视点来看&#xff0c;这个问题需求进行必定的剖析。 法令视点&#xff1a;股票经手费有必要交 从法令视点来看&#xff0c;股票经手费有必要交。依据《证券法》的…

SQL 语句继续学习之记录三

一&#xff0c;数据的插入&#xff08;insert 语句的使用方法&#xff09; 使用insert语句可以向表中插入数据(行)。原则上&#xff0c;insert语句每次执行一行数据的插入。 列名和值用逗号隔开&#xff0c;分别扩在&#xff08;&#xff09;内&#xff0c;这种形式称为清单。…

Llama模型结构解析(源码阅读)

目录 1. LlamaModel整体结构流程图2. LlamaRMSNorm3. LlamaMLP4. LlamaRotaryEmbedding 参考资料&#xff1a; https://zhuanlan.zhihu.com/p/636784644 https://spaces.ac.cn/archives/8265 ——《Transformer升级之路&#xff1a;2、博采众长的旋转式位置编码》 前言&#x…

安科瑞风力发电场集中监控系统解决方案-安科瑞黄安南

作为清洁能源之一&#xff0c;风力发电场近几年装机容量快速增长。8月17日&#xff0c;国家能源局发布1-7月份全国电力工业统计数据。截至7月底&#xff0c;全国累计发电装机容量约27.4亿千瓦&#xff0c;同比增长11.5%。其中&#xff0c;太阳能发电装机容量约4.9亿千瓦&#x…

Oracle数据传输加密方法

服务器端“dbhome_1\NETWORK\ADMIN\”sqlnet.ora文件中添加 SQLNET.ENCRYPTION_SERVER requested SQLNET.ENCRYPTION_TYPES_SERVER (RC4_256) 添加后新的链接即刻生效&#xff0c;服务器无需重新启动。 也可以通过Net manager管理工具添加 各个参数含义如下&#xff1a; 是…

Web开发模式、API接口、restful规范、序列化和反序列化、drf安装和快速使用

一 Web开发模式 1. 前后端混合开发模式 前后端混合开发模式是一种开发方式&#xff0c;将前端和后端的开发工作结合在一起&#xff0c;以加快项目的开发速度和 提高协作效率。这种模式通常用于快速原型开发、小型项目或敏捷开发中。在前后端混合开发模式中&#xff0c;前端和…

【MyBatis】自定义resultMap三种映射关系

目录 一、一对一映射&#xff08;One-to-One&#xff09; 1.1 表关系 1.2 resultMap设置自定义映射 二、一对多映射&#xff08;One-to-Many&#xff09; 2.1 创建实体 2.2 级联方式处理映射关系 2.3 定义SQL 2.4 OrderMapper接口 2.5 编写业务逻辑层 2.6 Junit测试…

港联证券:游资爆炒中电环保,还有谁在蹭核污染防治概念?

8月28日&#xff0c;核污染防治概念股持续大涨&#xff0c;建工修复&#xff08;300958.SZ&#xff09;、捷强配备&#xff08;300875.SZ&#xff09;、东方园林&#xff08;002310.SZ&#xff09;、华盛昌&#xff08;002980.SZ&#xff09;等涨停。 中小市值的概念股成为游资…