承接上篇文章 《一站式解决使用枚举的各种痛点》 文章最后提到:在使用 swagger 来编写接口文档时,需要告诉前端枚举类型有哪些取值,每次增加取值之后,不仅要改代码,还要找到对应的取值在哪里使用了,然后修改 swagger 文档。反正小黑我觉得这样做很不爽,那有没有什么办法可以让 swagger 框架来帮我们自动列举出所有的枚举数值呢?

这期小黑同学就来讲讲解决方案。

先来看一下效果,有一个感性的认识

请注意哦,这里是课程类型不是我们手动列举出来的,是swagger框架帮我们自动列举的。对应的代码如下:

那么,这是怎么做到的呢?

简单描述一下实现:

1、自定义 SwaggerDisplayEnum 注解,注解中有两个属性,这两个属性是用来干什么的呢?小黑我先不说,大家往下阅读,相信就能明白啦~

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SwaggerDisplayEnum {
String index() default "index"; String name() default "name"; }

2、在我们的自定义枚举类中标记 @SwaggerDisplayEnum 注解

@Getter
@AllArgsConstructor
@SwaggerDisplayEnum(index = "type", name = "desc")
public enum CourseType { /**
* 图文
*/
PICTURE(102, "图文"),
/**
* 音频
*/
AUDIO(103, "音频"),
/**
* 视频
*/
VIDEO(104, "视频"),
/**
* 外链
*/
URL(105, "外链"),
; @JsonValue
private final int type;
private final String desc; private static final Map<Integer, CourseType> mappings; static {
Map<Integer, CourseType> temp = new HashMap<>();
for (CourseType courseType : values()) {
temp.put(courseType.type, courseType);
}
mappings = Collections.unmodifiableMap(temp);
} @EnumConvertMethod
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
@Nullable
public static CourseType resolve(int index) {
return mappings.get(index);
} }

3、实现 ModelPropertyBuilderPlugin 接口,扩展 swagger,实现在文档中列举所有的枚举值。

public class EnumModelPropertyBuilderPlugin implements ModelPropertyBuilderPlugin {

    @Override
public void apply(ModelPropertyContext context) {
Optional<BeanPropertyDefinition> optional = context.getBeanPropertyDefinition();
if (!optional.isPresent()) {
return;
} final Class<?> fieldType = optional.get().getField().getRawType(); addDescForEnum(context, fieldType);
} @Override
public boolean supports(DocumentationType delimiter) {
return true;
} private void addDescForEnum(ModelPropertyContext context, Class<?> fieldType) {
if (Enum.class.isAssignableFrom(fieldType)) {
SwaggerDisplayEnum annotation = AnnotationUtils.findAnnotation(fieldType, SwaggerDisplayEnum.class);
if (annotation != null) {
String index = annotation.index();
String name = annotation.name(); Object[] enumConstants = fieldType.getEnumConstants(); List<String> displayValues =
Arrays.stream(enumConstants)
.filter(Objects::nonNull)
.map(item -> {
Class<?> currentClass = item.getClass(); Field indexField = ReflectionUtils.findField(currentClass, index);
ReflectionUtils.makeAccessible(indexField);
Object value = ReflectionUtils.getField(indexField, item); Field descField = ReflectionUtils.findField(currentClass, name);
ReflectionUtils.makeAccessible(descField);
Object desc = ReflectionUtils.getField(descField, item);
return value + ":" + desc; }).collect(Collectors.toList()); ModelPropertyBuilder builder = context.getBuilder();
Field descField = ReflectionUtils.findField(builder.getClass(), "description");
ReflectionUtils.makeAccessible(descField);
String joinText = ReflectionUtils.getField(descField, builder)
+ " (" + String.join("; ", displayValues) + ")"; builder.description(joinText).type(context.getResolver().resolve(Integer.class));
}
} }
}

4、实现 ParameterBuilderPluginOperationBuilderPlugin 接口,列举枚举参数的所有取值。

public class EnumParameterBuilderPlugin implements ParameterBuilderPlugin, OperationBuilderPlugin {

    private static final Joiner joiner = Joiner.on(",");

    @Override
public void apply(ParameterContext context) {
Class<?> type = context.resolvedMethodParameter().getParameterType().getErasedType();
if (Enum.class.isAssignableFrom(type)) {
SwaggerDisplayEnum annotation = AnnotationUtils.findAnnotation(type, SwaggerDisplayEnum.class);
if (annotation != null) { String index = annotation.index();
String name = annotation.name();
Object[] enumConstants = type.getEnumConstants();
List<String> displayValues = Arrays.stream(enumConstants).filter(Objects::nonNull).map(item -> {
Class<?> currentClass = item.getClass(); Field indexField = ReflectionUtils.findField(currentClass, index);
ReflectionUtils.makeAccessible(indexField);
Object value = ReflectionUtils.getField(indexField, item); Field descField = ReflectionUtils.findField(currentClass, name);
ReflectionUtils.makeAccessible(descField);
Object desc = ReflectionUtils.getField(descField, item);
return value.toString(); }).collect(Collectors.toList()); ParameterBuilder parameterBuilder = context.parameterBuilder();
AllowableListValues values = new AllowableListValues(displayValues, "LIST");
parameterBuilder.allowableValues(values);
}
}
} @Override
public boolean supports(DocumentationType delimiter) {
return true;
} @Override
public void apply(OperationContext context) {
Map<String, List<String>> map = new HashMap<>();
List<ResolvedMethodParameter> parameters = context.getParameters();
parameters.forEach(parameter -> {
ResolvedType parameterType = parameter.getParameterType();
Class<?> clazz = parameterType.getErasedType();
if (Enum.class.isAssignableFrom(clazz)) {
SwaggerDisplayEnum annotation = AnnotationUtils.findAnnotation(clazz, SwaggerDisplayEnum.class);
if (annotation != null) {
String index = annotation.index();
String name = annotation.name();
Object[] enumConstants = clazz.getEnumConstants(); List<String> displayValues = Arrays.stream(enumConstants).filter(Objects::nonNull).map(item -> {
Class<?> currentClass = item.getClass(); Field indexField = ReflectionUtils.findField(currentClass, index);
ReflectionUtils.makeAccessible(indexField);
Object value = ReflectionUtils.getField(indexField, item); Field descField = ReflectionUtils.findField(currentClass, name);
ReflectionUtils.makeAccessible(descField);
Object desc = ReflectionUtils.getField(descField, item);
return value + ":" + desc; }).collect(Collectors.toList()); map.put(parameter.defaultName().or(""), displayValues); OperationBuilder operationBuilder = context.operationBuilder();
Field parametersField = ReflectionUtils.findField(operationBuilder.getClass(), "parameters");
ReflectionUtils.makeAccessible(parametersField);
List<Parameter> list = (List<Parameter>) ReflectionUtils.getField(parametersField, operationBuilder); map.forEach((k, v) -> {
for (Parameter currentParameter : list) {
if (StringUtils.equals(currentParameter.getName(), k)) {
Field description = ReflectionUtils.findField(currentParameter.getClass(), "description");
ReflectionUtils.makeAccessible(description);
Object field = ReflectionUtils.getField(description, currentParameter);
ReflectionUtils.setField(description, currentParameter, field + " , " + joiner.join(v));
break;
}
}
});
}
}
});
}
}

这篇文章比较枯燥,小黑我也不知道该怎么去讲述,只是将源码附录了出来。如果有读者看了之后还是不清楚的话,可以给我留言,我会一一解答。感谢你的阅读~~

相关源码已经上传到了 github:https://github.com/shenjianeng/solution-for-enums

真香警告!扩展 swagger支持文档自动列举所有枚举值的更多相关文章

  1. JAVA中自定义扩展Swagger的能力,自动生成参数取值含义说明,提升开发效率

    大家好,又见面了. 在JAVA做前后端分离的项目开发的时候,服务端需要提供接口文档供周边人员做接口的对接指导.越来越多的项目都在尝试使用一些基于代码自动生成接口文档的工具来替代由开发人员手动编写接口文 ...

  2. springboot+swagger接口文档企业实践(上)

    目录 1.引言 2.swagger简介 2.1 swagger 介绍 2.2 springfox.swagger与springboot 3. 使用springboot+swagger构建接口文档 3. ...

  3. .net core的Swagger接口文档使用教程(二):NSwag

    上一篇介绍了Swashbuckle ,地址:.net core的Swagger接口文档使用教程(一):Swashbuckle 讲的东西还挺多,怎奈微软还推荐了一个NSwag,那就继续写吧! 但是和Sw ...

  4. .net core的Swagger接口文档使用教程(一):Swashbuckle

    现在的开发大部分都是前后端分离的模式了,后端提供接口,前端调用接口.后端提供了接口,需要对接口进行测试,之前都是使用浏览器开发者工具,或者写单元测试,再或者直接使用Postman,但是现在这些都已经o ...

  5. 添加swagger api文档到node服务

    swagger,一款api测试工具,详细介绍参考官网:http://swagger.io/ ,这里主要记录下怎么将swagger api应用到我们的node服务中: 1.任意新建node api项目, ...

  6. springboot成神之——swagger文档自动生成工具

    本文讲解如何在spring-boot中使用swagger文档自动生成工具 目录结构 说明 依赖 SwaggerConfig 开启api界面 JSR 303注释信息 Swagger核心注释 User T ...

  7. springboot+swagger接口文档企业实践(下)

    目录 1.引言 2. swagger接口过滤 2.1 按包过滤(package) 2.2 按类注解过滤 2.3 按方法注解过滤 2.4 按分组过滤 2.4.1 定义注解ApiVersion 2.4.2 ...

  8. Swagger API文档

    Swagger API文档集中化注册管理   接口文档是前后端开发对接时很重要的一个组件.手动编写接口文档既费时,又存在文档不能随代码及时更新的问题,因此产生了像swagger这样的自动生成接口文档的 ...

  9. [BI项目记]-配置Sharepoint2013支持文档版本管理笔记

    做开发或者做方案,写文档是很重要的一个工作,我们经常需要知道文档被修改的次数,谁在什么时间修改的文档,以及在某一个版本中,都修改了哪些内容,以及不同版本的文档之间有什么差别. 如何对文档进行版本管理, ...

随机推荐

  1. Python玩转人工智能最火框架 TensorFlow应用实践 学习 教程

    随着 TensorFlow 在研究及产品中的应用日益广泛,很多开发者及研究者都希望能深入学习这一深度学习框架.而在昨天机器之心发起的框架投票中,2144 位参与者中有 1441 位都在使用 Tenso ...

  2. NTSTATUS code 和 Windows 系统错误码 的对应关系

    出处:https://github.com/dokan-dev/dokany/blob/master/dokan/ntstatus.i case EPT_S_CANT_CREATE: return E ...

  3. js基石之---易读、易复用、易重构的 JavaScript 代码规范

    易读.易复用.易重构的 JavaScript 代码规范 1.变量命名规范有意义 Bad: const yyyymmdstr = moment().format("YYYY/MM/DD&quo ...

  4. Codeforces Round #628 (Div. 2) 题解

    人闲桂花落,夜静春山空. 月出惊山鸟,时鸣春涧中.--王维 A. EhAb AnD gCd You are given a positive integer x. Find any such 2 po ...

  5. java中FutureTask的使用

    文章目录 FutureTask简介 Callable和Runnable的转换 以Runnable运行 java中FutureTask的使用 FutureTask简介 FutureTask是java 5 ...

  6. vue2.x学习笔记(二十六)

    接着前面的内容:https://www.cnblogs.com/yanggb/p/12682137.html. 单文件组件 介绍 在很多的vue项目中,我们都是使用[Vue.component]来定义 ...

  7. linux rpm包

    rpm包,软件包,程序包,以.rpm结尾的包 我们刚开始安装的Linux系统是最小化安装(minimol),只安装系统,不安装不必要的软件包 刚开始vim,ifconfig,tree等命令都没有,当然 ...

  8. CodeForces-259B]Little Elephant and Magic Square

      Little Elephant loves magic squares very much. A magic square is a 3 × 3 table, each cell contains ...

  9. P4720【模板】扩展卢卡斯,P2183 礼物

    扩展卢卡斯定理 最近光做模板了 想了解卢卡斯定理的去这里,那题也有我的题解 然而这题和卢卡斯定理并没有太大关系(雾 但是,首先要会的是中国剩余定理和exgcd 卢卡斯定理用于求\(n,m\)大,但模数 ...

  10. docker overlay network

    下载binary consul wget https://releases.hashicorp.com/consul/1.2.2/consul_1.2.2_linux_amd64.zip unzip ...