Spring Boot Swagger整合和增强


placeholder image
admin 发布于:2022-05-09 10:09:19
阅读:loading

背景介绍

Swagger相关的网站推荐为https://swagger.io/和https://github.com/swagger-api和http://springfox.github.io/springfox/,更多的了解可以翻阅相关网站的第一手介绍。本文主要讲述的是基于springfox-swagger项目的一款ui组件swagger-bootstrap-ui,该项目提供的swagger-ui主题界面与国人行为习惯并不十分相符,由业内大神萧明提供的基于bootstrap主题的风格重构,一开始是一个增强swagger-ui的皮肤项目,后来经过持续的集成完善就用了swagger-bootstrap-ui项目。注意了,该项目的最终版本1.9.6已经停止更新,作者在2017年重启的knife4j项目作为持续更新与维护的swagger项目,后续的应用应该基于knife4j进行集成与应用(该项目与微服务架构的契合度更高;后端Java代码与前端ui分离开来;页面更加优雅酷炫;功能更加强大;)。

Swagger是一款为开发人员提供在线API接口文档的组件,无需额外开销即可对您的API执行简单的功能测试和接口规范预览,被全球数以千计的团队广泛地使用。项目中集成Swagger可以依据代码的集成实现Restful接口的文档规范,无需手动编写和维护文档,省去频繁变更文档的繁琐,同时提供有Web页面,服务于接口的在线执行功能,值得拥有。接口集成的优势:

(1)接口文档页面支持的展示信息齐全,页面包含有接口描述、请求地址、作者信息、输入参数、输出参数等;

(2)接口包含运行页面,可在页面进行后台接口的调用执行,支持各种请求类型和请求方式的请求,同时参数传递支持普通文本参数、枚举类型参数、数组集合类型、JSON对象类型等;

应用痛点

本篇文章主要主角是swagger-ui-bootstrap,并不是介绍如何去集成和应用此项目,重点是介绍页面应用时的功能模块下拉框较多时的功能快速定位的实现(由于并未使用knife4j,未知此项目是否已经存在官方的解决方案),实际单位项目的开发过程中,下拉框中的功能模块近乎50个了,多个功能模块命名是以中文开头,而swagger-ui页面中的功能模块下拉框对于中文的排序并不支持,所以在功能较多时找起来功能模块就显得非常费劲,故而就有了本文,经过前端与后端代码的分析,发现前端页面的功能实现并不能优雅的(不修改原始代码)解决这个问题,逐渐将解决方案放置从后端代码分析中,经过最后的逻辑分析与源码调试,发现可以定义一个Aop拦截获取功能模块的集合方法,将功能模块的集合数据进行排序即可。

改造方式

基于上述的问题点的改造方式也是崇尚优雅的改造为第一优先,即我们不修改源代码的任何地方,所以本次的改造称之为组件的增强,增强的实现不局限于swagger-bootstrap-ui项目,对于原始的swagger-ui项目也同样生效,因为增强的功能逻辑为springfox项目提供的原始代码,故而本次增强的关键点就在于Aop的方法拦截(项目中已经应用许久了,可放心集成),参考如下代码:

package cn.chendd.configuration;

import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI;
import net.sourceforge.pinyin4j.PinyinHelper;
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;
import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;
import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.List;

/**
 * Swagger配置
 *
 * @author chendd
 * @date 2019/9/16 13:16
 */
@Configuration
@EnableSwagger2
@EnableSwaggerBootstrapUI
public class SwaggerConfiguration {

    /**
     * swagger 组名称增强Aop
     */
    @Component
    @Aspect
    public static class SwaggerGroupNameEnhanceAop {

        @Pointcut("execution(* springfox.documentation.swagger.web.InMemorySwaggerResourcesProvider.get())")
        public void pointcutDeclare() {

        }

        @Before("pointcutDeclare()")
        public void before(JoinPoint joinPoint) {

        }

        @Around("pointcutDeclare()")
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            Object returnValue;
            try {
                returnValue = joinPoint.proceed();
                this.sort(returnValue);
            } catch (Throwable e) {
                throw e;
            }
            return returnValue;
        }

        @After("pointcutDeclare()")
        public void after(JoinPoint joinPoint) {

        }

        /**
         * 排序逻辑实现,若仅仅实现按数字、英文字母、中文的顺序来排序,在功能模块过多时仍然存在一点点的识别难度,所以给模块名称前增加开头的字母。
         * 优势1:增加字母与-的分割,辨识度更高;
         * 优势2:在下拉框中可以直接点击英文或数字键,select下拉框会定位至开头匹配的option选项;
         * @param returnValue 数据
         * @noinspection unchecked
         */
        private void sort(Object returnValue) {
            if (returnValue == null) {
                return;
            }
            if (! List.class.isAssignableFrom(returnValue.getClass())) {
                return;
            }
            List<SwaggerResource> dataList = (List<SwaggerResource>) returnValue;
            //排序数据
            dataList.sort((before , after) -> {
                String beforeName = toPinyin(before.getName());
                String afterName = toPinyin(after.getName());
                return StringUtils.compareIgnoreCase(beforeName , afterName);
            });
            //将排序后的按格式重新输出
            for (SwaggerResource resource : dataList) {
                String name = resource.getName();
                String pinyinName = toPinyin(name);
                resource.setName(Character.toUpperCase(pinyinName.charAt(0)) + " - " + name);
            }
        }

        /**
         * 汉子转换拼音
         * @param text 文本
         * @return 转换后的拼音
         */
        static String toPinyin(String text) {
            if (StringUtils.isBlank(text)) {
                return text;
            }
            char[] cs = text.toCharArray();
            StringBuilder pinyinBuilder = new StringBuilder();
            HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
            //输出设置,大小写,音标方式等
            defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
            defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
            for (char c : cs) {
                //如果是中文
                if (c > 128) {
                    try {
                        String[] array = PinyinHelper.toHanyuPinyinStringArray(c, defaultFormat);
                        if (array != null) {
                            pinyinBuilder.append(array[0]);
                        } else {
                            pinyinBuilder.append(c);
                        }
                    } catch (BadHanyuPinyinOutputFormatCombination e) {
                        throw new RuntimeException(e);
                    }
                } else {
                    pinyinBuilder.append(c);
                }
            }
            return pinyinBuilder.toString();
        }
    }

}

顺便说一下增强功能点,给页面的功能下拉框提供了功能模块的拼音首字母,按照对应的首字母进行排序,排序规则默认使用Java的排序,特殊字符排第一、数字排第二、英文字母拍第三、中文排最后。所以本次除了增加Aop的方法拦截(环绕切面)外,还使用到了汉字转拼音,按照“字母大写 - 原始功能名称”的格式进行排序展示,参考增强后的运行效果截图:

image.png

(swagger-ui主题)

image.png

(swagger-bootstrap-ui主题)

由于切面的方法是springfox的代码,所以不仅swagger-bootstrap-ui的doc.html运行效果会生效,在swagger-ui的swagger-ui.html页面中同样生效。当然了,如果想实现功能模块前缀增加字母当然也可以有另外一种做法,就是在定义功能名称的时候手动增加字母的分割,当然了能否支持排序就不确定了,毕竟这种做法不是专业选手考虑事情事情的方式,自然也未尝验证。

特别说明

【2022/05/29补充】在后文中编写Spring Boot优雅的配置拦截器的时候,又将swagger中的【启用SwaggerBootstrapUi提供的增强功能】拿出来具体分析,后来发现无法开启的具体原因就是上述中增加了字母匹配的前缀导致,而该组件在进行实现的时候并为获取具体的值进行传递,而是将下拉框中的显示文本传递至后端导致无法正常的启用增强效果,故经过略微的分析后,于是准备在显示下拉框文本的时候显示格式化后的文本,而传递至Controller中时,再提供一个拦截器拦截处理增强请求的Controller方法,将方法的参数进行改写,改写完毕再通过AOP传递至具体的方法中来实现(注意:如下代码在swagger-bootstrap-ui分支中并不存在,可转至Interceptorji3后续的分支查看),增加@ConditionalOnBean注解,即当使用了Swagger的增强后才存在当前的方法过滤增强,详细参考如下代码所示:

/**
 * swagger 组名称修改,将传递过来的组名称还原为默认无前缀的名称
 */
@Component
@ConditionalOnBean(value = SwaggerGroupNameEnhanceAop.class)
@Aspect
public static class SwaggerGroupNameExtAop {

    @Pointcut("execution(* com.github.xiaoymin.swaggerbootstrapui.web.SwaggerBootstrapUiController.apiSorts(..))")
    public void pointcutDeclare() {

    }

    @Before("pointcutDeclare()")
    public void before(JoinPoint joinPoint) {

    }

    @Around("pointcutDeclare()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Object returnValue;
        try {
            Object[] args = joinPoint.getArgs();
            if (args[0] != null && args[0] instanceof String) {
                args[0] = args[0].toString().replaceAll("[A-Z] \\- " , "");
            }
            returnValue = joinPoint.proceed(args);
        } catch (Throwable e) {
            throw e;
        }
        return returnValue;
    }

    @After("pointcutDeclare()")
    public void after(JoinPoint joinPoint) {

    }
}

知识点介绍

(1)已经不更新了,除了使用最新版本1.9.6以外,可尝试使用knife4j新版本,官方网站:https://doc.xiaominfo.com;

(2)支持配置用户名和密码的授权验证,使得接口平台更加安全;

(3)支持API文档离线查看;

(4)支持全局参数设置,含query和header两种参数传递方式;

(5)支持个性化配置:国际化、请求参数缓存、分组tag显示、缓存已打开的api文档等;

(6)源码工程下载:源码下载.zip


 点赞


 发表评论

当前回复:作者

 评论列表


留言区