swagger展示枚举类型

2023-11-03

文章首发于个人博客,欢迎访问关注:https://www.lin2j.tech

需求场景

在书写 swagger 文档的时候,有些字段是对应一个枚举的。在处理这类字段时,如果在@ApiModelProperty 中手动添加枚举值,可能会出现漏写、错写的情况。

接下来就展示一种swagger 中处理枚举类型的方法。示例源码在文章底部,有需要的自取。

思路

通过拦截 swagger 生成文档的过程,查看字段是否对应某个枚举类,将枚举类的值按照自定义的形式添加到字段描述中。

Springfox相关的类

ModelPropertyBuilderPlugin

内含 void apply(ModelPropertyContext context)boolean supports(S delimiter) 方法。

support用来判断该文档类型要不要使用插件。

apply 方法是真正做拦截工作的方法,ModelPropertyContext 可以给我们提供字段的信息

ModelPropertyContext

字段的上下文信息,主要介绍下面两个字段。

ModelPropertyBuilder builder:包含了字段详细信息。下图是某个字段的信息。

ModelPropertyBuilder

TypeResolver resolver:用来处理泛型的信息,其 ResolvedType resolve(Type type, Type... typeParameters) 返回一个 ResolvedType 对象,通过 ResolvedType 可以用简单的 API 访问类的信息。可以看看 com.fasterxml.classmate.ResolvedType 声明的方法,看看这些简单的 API

Demo 中自定义的类

项目结构

swagger-enum-demo-项目结构

pom.xml 主要的依赖
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
    </parent>

    <properties>
        <springfox-swagger2.version>2.9.2</springfox-swagger2.version>
        <swagger-bootstrap-ui.version>1.9.3</swagger-bootstrap-ui.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <!-- swagger -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>${springfox-swagger2.version}</version>
        </dependency>
        <!-- swagger-ui -->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>swagger-bootstrap-ui</artifactId>
            <version>${swagger-bootstrap-ui.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
@SwaggerDisplayEnum
/**
 * 标记注解,没有字段,仅是标记作用,
 * 标记到的枚举类才能在 swagger 文档中展示
 *
 * @author linjinjia
 * @date 2021/4/5 16:18
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SwaggerDisplayEnum {
}
UserController
/**
 * 方便展示效果
 * 
 * @author linjinjia
 * @date 2021/4/5 10:38
 */
@Api(tags = "用户管理接口")
@RestController
@RequestMapping("user")
public class UserController {

    @ApiOperation("获取用户信息")
    @GetMapping
    public UserVo query() {
        UserVo vo = new UserVo();
        vo.setName("jia");
        vo.setGender(GenderEnum.MALE.getCode());
        return vo;
    }
}
GenderEnum
/**
 * @author linjinjia
 * @date 2021/4/5 10:21
 */
@SwaggerDisplayEnum
@Getter
public enum GenderEnum {

    MALE(0, "男"),
    FEMALE(1, "女"),
    UNKNOWN(2, "未知")
    ;

    private final Integer code;
    private final String desc;

    GenderEnum(Integer code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    /**
     * 单个枚举的展示
     */
    @Override
    public String toString() {
        return code + "-" + desc;
    }
}
UserVo
/**
 * @author linjinjia
 * @date 2021/4/5 10:10
 */
@Data
public class UserVo {

    @ApiModelProperty("姓名")
    private String name;
    
    /**
     * notes 是对应枚举类的全限定名
     */
    @ApiModelProperty(value = "性别",notes = "com.jia.swaggerenum.enums.GenderEnum")
    private Integer gender;
}
SwaggerConfig ⭐⭐

做拦截工作的关键类。

package com.jia.swaggerenum.config;

import com.fasterxml.classmate.ResolvedType;
import com.google.common.base.Optional;
import com.jia.swaggerenum.annotation.SwaggerDisplayEnum;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.StringUtils;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ModelPropertyBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.Annotations;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.schema.ModelPropertyBuilderPlugin;
import springfox.documentation.spi.schema.contexts.ModelPropertyContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger.schema.ApiModelProperties;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 关键类
 * 
 * @author linjinjia
 */
@Slf4j
@Configuration
@EnableSwagger2
public class SwaggerConfig implements ModelPropertyBuilderPlugin {

    @Value("${swagger.title}")
    private String swaggerTitle;

    @Value("${swagger.description}")
    private String swaggerDescription;

    @Value("${swagger.version}")
    private String swaggerVersion;

    @Value("${swagger.enable}")
    private Boolean swaggerEnable;

    /**
     * 添加摘要信息(Docket)
     */
    @Bean
    public Docket controllerApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(new ApiInfoBuilder()
                        .title(swaggerTitle)
                        .description(swaggerDescription)
                        .contact(new Contact("林锦佳", null, "linjinjia047@163.com"))
                        .version(swaggerVersion)
                        .licenseUrl("/api-doc")
                        .build()
                )
                .select()
                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                .paths(PathSelectors.any())
                .build();
    }

    @Override
    public void apply(ModelPropertyContext context) {
        //如果不支持swagger的话,直接返回
        if (!swaggerEnable) {
            return;
        }

        //获取当前字段的类型
        final Class fieldType = context.getBeanPropertyDefinition().get().getField().getRawType();

        //为枚举字段设置注释
        descForEnumFields(context, fieldType);
    }

    /**
     * 为枚举字段设置注释
     */
    private void descForEnumFields(ModelPropertyContext context, Class fieldType) {
        Optional<ApiModelProperty> annotation = Optional.absent();

        // 找到 @ApiModelProperty 注解修饰的枚举类
        if (context.getAnnotatedElement().isPresent()) {
            annotation = annotation
                    .or(ApiModelProperties.findApiModePropertyAnnotation(context.getAnnotatedElement().get()));
        }
        if (context.getBeanPropertyDefinition().isPresent()) {
            annotation = annotation.or(Annotations.findPropertyAnnotation(
                    context.getBeanPropertyDefinition().get(),
                    ApiModelProperty.class));
        }

        //没有@ApiModelProperty 或者 notes 属性没有值,直接返回
        if (!annotation.isPresent() || StringUtils.isEmpty((annotation.get()).notes())) {
            return;
        }

        //@ApiModelProperties中的notes指定的class类型
        Class rawPrimaryType;
        try {
            rawPrimaryType = Class.forName((annotation.get()).notes());
        } catch (ClassNotFoundException e) {
            //如果指定的类型无法转化,直接忽略
            return;
        }

        Object[] subItemRecords = null;
        SwaggerDisplayEnum swaggerDisplayEnum = AnnotationUtils
                .findAnnotation(rawPrimaryType, SwaggerDisplayEnum.class);
        // 判断是否存在 @SwaggerDisplayEnum 注解,并且 rawPrimaryType 是枚举
        if (null != swaggerDisplayEnum && Enum.class.isAssignableFrom(rawPrimaryType)) {
            // 拿到枚举的所有的值
            subItemRecords = rawPrimaryType.getEnumConstants();
        }
        if (null == subItemRecords) {
            return;
        }

        final List<String> displayValues =
                Arrays.stream(subItemRecords)
                        .filter(Objects::nonNull)
                        // 调用枚举类的 toString 方法
                        .map(Object::toString)
                        .filter(Objects::nonNull)
                        .collect(Collectors.toList());

        String joinText = " (" + String.join("; ", displayValues) + ")";
        try {
            // 拿到字段上原先的描述
            Field mField = ModelPropertyBuilder.class.getDeclaredField("description");
            mField.setAccessible(true);
            // context 中的 builder 对象保存了字段的信息
            joinText = mField.get(context.getBuilder()) + joinText;
        } catch (Exception e) {
            log.error(e.getMessage());
        }

        // 设置新的字段说明并且设置字段类型
        final ResolvedType resolvedType = context.getResolver().resolve(fieldType);
        context.getBuilder().description(joinText).type(resolvedType);
    }

    @Override
    public boolean supports(DocumentationType documentationType) {
        return true;
    }
}

效果截图

swagger-enum-demo-效果截图

至此,整个过程就算结束了。

示例源码

点击下载

小技巧

@ApiModelPropertynotes 中类的全限定名称可以不用自己一个一个打上去,Idea 提供了复制类的全限定名称的功能。

copy-reference

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

swagger展示枚举类型 的相关文章

  • nodejs 读取文件中的几行

    最近遇到了这样的问题 如何每次从一个文件里面依次读里面的几行 对nodejs提供的readline和一些三方npm例如lineReader不满意 就自己实现一个简易版本 var fs require fs var data fs readF
  • Spark服务启动的一些总结

    1 我理解常用的Spark部署方式有三种 1 本地服务 就是所谓的local 在IDE上本地跑程序 用于调试 2 Standalone 使用自己的master worker进行服务的调度 脱离yarn的资源管理 3 Spark on yar
  • Java 转 C++ 那些事

    前提纪要 虽说编程语言只是承载思想的一种媒介 但是每种编程语言有自己的设计哲学 所以在实现自己思想的时候 也需要遵循该门语言的理念才行 截止 2022 年 07 月 12 日本人最大的体验是 Java 这门语言存在大量的过度封装 所以能封装
  • c#复习题2(含答案及解析)

    1 单选题 ArrayList集合的 属性表示集合中实际包含的元素数 A Capacity B Count C Total D Length 正确答案 B 解析 ArrayList集合的 Capacity属性表示它们所封装的Object 数
  • 【密码学】破解RSA密码(Python代码实现)

    题目 已知有人写了如下的代码 并将生成的 n e c 以及 n2 e2 c2 p2 1 q2 1 输出 from Crypto Util number import def ef p getPrime 512 q getPrime 512
  • 云开发初探 —— 更简便的小程序开发模式

    欢迎大家前往腾讯云 社区 获取更多腾讯海量技术实践干货哦 本文由heyli发表于云 社区专栏 小程序诞生以来 业界关注小程序前端的技术演进较多 因此众多小程序前端的框架 工具也应运而生 前端开发效率大大提高 而后台的开发技术则关注不多 痛点
  • js 解密

    Ai Chat sbaliyun com 在分析接口的时候 我们看到请求做了加密 查看事件我们发信啊了方法 callCHATGPT 得到 callCHATGPT 的代码 async function callCHATGPT var resp
  • 在概念堆里理解什么是智能SOA

    今年在继7月北京成功举办SOA与企业成长高峰论坛之后 在这个初冬的季节 IBM再次携众位专家11月15号在上海隆重举行了 IBM 2007 SOA创新高峰论坛 并且在这次峰会上首发了基于全球5700家SOA客户实施经验之上总结出的一套指导客
  • 实时查询与检测域名是否被微信封杀的核心代码和原理

    微信官方在对微信中推广活动的第三方网页内容管控越来越严格 如果活动效果稍微好一些 自己的网址域名可能就会被封杀 用户就打不开分享页面 很多人就是因为没有及时知道域名在微信中的状况 不知道什么时候被封的 然后导致损失惨重 在网上搜索了很多相关
  • 【Java基础教程】(三十六)常用类库篇 · 第六讲:数学运算类——全面讲解Java数学计算支持类库,BigDecimal、Math、Random、DecimalFormat...~

    Java基础教程之常用类库 数学运算类 1 概念 数学运算类汇总 2 优势和缺点 3 使用 3 1 各数学计算支持类使用案例 3 2 Math类 3 3 BigDecimal类 3 4 Random类 3 5 DecimalFormat类
  • Thymeleaf 对象空值处理

    前端代码 单例实体对象空值处理 div div class form group col md 6 div div

随机推荐

  • 小程序picker 多列选择详解

    需求 选择左边的选项 右边会显示对应的数据 如果你的数据和官方文档一样 数组套数组的形式 那直接复制复制就行了 这篇文章主要讲 数据是数组里面套对象的这种情况 我的数据结构如下 这就用用到 range key 属性 首先 通过 range
  • 【ubuntu】【rabbitmq】ubuntu 安装 rabbitmq

    文章目录 一 安装 erlang 二 添加公钥 三 更新软件包 可选 四 安装 RabbitMQ 五 RabbitMQ 状态管理 六 安装 web 插件 七 远程登录 web端 八 添加自定义 VHOST 及分配权限 一 安装 erlang
  • Java 使用LocalDate获取上周时间、本月时间等

    获取当前时间 LocalDate currentDate LocalDate now 获取当前日期所在的星期几 DayOfWeek currentDayOfWeek currentDate getDayOfWeek 计算需要减去的天数 in
  • 迟到的Pairwork工作总结 - by Glede

    迟到的总结 By Glede 队友连昭鹏的总结 http www cnblogs com lzplzp archive 2012 10 22 2732946 html 我们一开始交流的时候 就决定基本模仿生活中的电梯运行过程来设计程序 生活
  • 多列堆积柱形图怎么做_Excel两组数据做对比,用左右对比图才好看,操作简单又美观...

    我们都知道在Excel中 展示多组据的时候 可以使用图表来进行演示 Excel图表展示数据既直观 看起来也显得非常的大气 而一般人通常都是用柱形图 折线图等图表来展示 今天我们教大家一种特殊的图表 如何运用左右对比图来展示2组数据 如上图所
  • 老卫带你学---华为机试(17.坐标移动)

    华为机试 17 坐标移动 问题 题目描述 开发一个坐标计算工具 A表示向左移动 D表示向右移动 W表示向上移动 S表示向下移动 从 0 0 点开始移动 从输入字符串里面读取一些坐标 并将最终输入结果输出到输出文件里面 输入 合法坐标为A 或
  • 定制Android12系统:源码下载、编译、刷机(通俗易懂版本)

    定制系统步骤 一 下载源码 二 编译源码 三 刷机 一 源码下载 1 安装Git 2 安装Repo 3 新建目录 4 下载代码 1 Git是最常用的开源版本控制系统 git安装示例如下 2 Repo则是一个Google在git基础之上构建的
  • 你的眼界有多大,格局就有多大

    记得曾经有一次在公众号上看到一篇这样的文章 一个人格局越来越大的两个迹象 本文提出了重要的两点 格局大的人不计较小事的得失 他们只将时间和精力放在有意义的事情上 对错是非 我颇有感触 的确 我们日常的琐事比比皆是 只要是人与人之间进行交流
  • hadoop3.0.3高可用(ZK;DN)

    server1 5五台配置好apache的hadoop nfs utils rpcbind 将hadoopserver1挂载 gt 2 5 清空环境 sbin stop yarn sh sbin stop dfs sh rm fr tmp
  • yolov5 网络结构和后处理结构

    转自 https www 163 com dy article G07PMVPO0511ABV6 html 作者 gloomyfish 新智元导读 本文从原始的三个输出层解析实现了boxes classes nms等关键C 代码输出 实现了
  • 用js动态返回各类文本框的值

    1 返回单行文本输入框的值 示例 用户名
  • SpringBoot整合缓存框架(jetcache、memcached、mykit-cache)

    目录 1 缓存简介 2 应用场景 3 memcached 3 1 简介 3 2 特征 3 3 docker安装 3 3 1 拉取镜像 3 4 linux安装 4 jetcache 4 1 简介 4 1 2 引入依赖 4 1 3 jetcac
  • TCP关闭过程

    状态迁移 1 SO LINGER SO REUSEADDR TCP正常的关闭过程如下 四次握手过程 FIN WAIT 1 A FIN gt B CLOSE WAIT FIN WAIT 2 A lt ACK B CLOSE WAIT TIME
  • 软件外包开发UI管理工具

    软件在开发前需要设计UI界面 UI界面是产品经理和开发人员 测试人员之间的交流工具 因此项目中会有多人的工作涉及到的UI界面 这就需要有个好的工具协调相互之间的工作 今天和大家分享一些常用到的工具 希望对大家的工作有所帮助 北京木奇移动技术
  • [架构之路-198] - 功能需求与分析:1张图、 4个阶段、16个步骤,系统分析问题与彻底解决问题的方法:问题界定、原因分析、方案确认、落实执行

    目录 前言 一张图 一 界定问题 找到真正的问题 问题就解决了一大半 1 发现异常 2 优先顺序 3 描述现状 4 确定目标 二 要因分析 透过表象 直达问题本质 才能彻底解决问题 5 列举要素 6 结构细化 7 内部归因 8 选择重点 三
  • Anaconda常用命令

    conda常用命令 conda list 查看安装了哪些包 conda env list 或 conda info e 查看当前存在哪些虚拟环境 conda update n base c defaults conda 检查更新conda
  • 形式语言与自动机总结笔记

    形式语言与自动机 MOOC 形式语言与自动机理论 GitHub课件资源 gzn00417 2020Spring Formal Languages and Automata 教学大纲 正则语言 2 有穷自动机 2 1 确定的有穷自动机 2 2
  • SSM+果园信息统计管理系统 毕业设计源码021103

    SSM果园信息统计管理系统 摘 要 随着互联网大趋势的到来 社会的方方面面 各行各业都在考虑利用互联网作为媒介将自己的信息更及时有效地推广出去 而其中最好的方式就是建立网络管理系统 并对其进行信息管理 由于现在网络的发达 果园信息统计管理系
  • 第三十八章、PyQt输入部件:QKeySequenceEdit快捷键输入部件使用案例

    专栏 Python基础教程目录 专栏 使用PyQt开发图形界面Python应用 专栏 PyQt入门学习 老猿Python博文目录 老猿学5G博文目录 一 功能简介 Key Sequence Edit输入部件是用于输入快捷键序列的一个部件 输
  • swagger展示枚举类型

    文章首发于个人博客 欢迎访问关注 https www lin2j tech 需求场景 在书写 swagger 文档的时候 有些字段是对应一个枚举的 在处理这类字段时 如果在 ApiModelProperty 中手动添加枚举值 可能会出现漏写