自己动手编写Swagger枚举展示插件

王守钰 2022-01-21 15:01:50

背景

当前公司因为前后端文档交互使用swagger,swagger虽然解决了java文档标识的问题,但是在处理枚举交互的过程中并不能完全展示出key-value的结构,前后端交互带来麻烦,需要手动进行添加处理,这样对开发的成本进行增加。所以自己动手开发swagger的插件来解决前后端交互问题,以及减小开发的成本。

SwaggerDisplayEnum注解定义

定义注解的目的为了标识在枚举参数的列上,这样解析的话,直接可以通过添加注解的方式来进行解析。


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author 王守钰
 * @date 2021-12-27 09:35
 * @description swagger展示枚举
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SwaggerDisplayEnum {

    /**
     * 枚举键,默认值''
     * @return 枚举键
     */
    String key() default "";

    /**
     * 枚举值,默认值'value'
     * @return 枚举值
     */
    String value() default "value";

    /**
     * 枚举类,必须继承枚举类
     * @return 枚举类
     */
    Class<? extends Enum> clazz();
}

ModelPropertyBuilderPlugin插件配置

ModelPropertyBuilderPlugin为实体类参数的解析,通过继承来进行处理参数配置信息,通过判断类的列上是否标记了SwaggerDisplayEnum注解,通过SwaggerDisplayEnum进行解析枚举的数据拼接展示。

import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.google.common.base.Optional;
import org.springframework.util.ReflectionUtils;
import springfox.documentation.builders.ModelPropertyBuilder;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.schema.ModelPropertyBuilderPlugin;
import springfox.documentation.spi.schema.contexts.ModelPropertyContext;

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

/**
 * @author 王守钰
 * @date 2021-12-27 09:36
 * @description swagger枚举模型插件
 */
public class EnumModelPropertyBuilderPlugin implements ModelPropertyBuilderPlugin {

    /**
     * description列
     */
    private static final String DESCRIPTION_FIELD = "description";

    /**
     * 空字符串
     */
    private static final String EMPTY_STR = "";

    @Override
    public void apply(ModelPropertyContext context) {
        Optional<BeanPropertyDefinition> optional = context.getBeanPropertyDefinition();
        if (optional.isPresent()) {
            try{
                AnnotatedField annotatedField = optional.get().getField();
                Field field = annotatedField.getAnnotated();
                addDescForEnum(context, field);
            }catch (Exception e){}
        }
    }

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

    private void addDescForEnum(ModelPropertyContext context, Field field) {
        SwaggerDisplayEnum annotation = field.getAnnotation(SwaggerDisplayEnum.class);
        if (annotation != null) {
            String key = annotation.key();
            String value = annotation.value();
            Class<?> enumClass = annotation.clazz();
            Object[] enumConstants = enumClass.getEnumConstants();
            List<String> displayValues = Arrays.stream(enumConstants)
                    .filter(Objects::nonNull)
                    .map(item -> {
                        Object name = EMPTY_STR;
                        Class<?> currentClass = item.getClass();
                        if(null == key || EMPTY_STR.equals(key.trim())){
                            name = String.valueOf(item);
                        }else{
                            Field indexField = ReflectionUtils.findField(currentClass, key);
                            ReflectionUtils.makeAccessible(indexField);
                            name = ReflectionUtils.getField(indexField, item);
                        }

                        Field descField = ReflectionUtils.findField(currentClass, value);
                        ReflectionUtils.makeAccessible(descField);
                        Object desc = ReflectionUtils.getField(descField, item);
                        return name + ":" + desc;

                    }).collect(Collectors.toList());
            ModelPropertyBuilder builder = context.getBuilder();
            Field descField = ReflectionUtils.findField(builder.getClass(), DESCRIPTION_FIELD);
            ReflectionUtils.makeAccessible(descField);
            String joinText = ReflectionUtils.getField(descField, builder)
                    + " (" + String.join("; ", displayValues) + ")";
            builder.description(joinText);
        }
    }
}

启动类配置

import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.swagger2.configuration.Swagger2DocumentationConfiguration;

/**
 * @author 王守钰
 * @date 2021-12-27 09:40
 * @description swagger插件配置
 */
@Configuration
@ConditionalOnProperty(value = "plugin.swagger.enum-support", matchIfMissing = true)
@ConditionalOnBean(Swagger2DocumentationConfiguration.class)
public class SwaggerEnumPluginConfiguration {

    @Bean
    public EnumModelPropertyBuilderPlugin enumModelPropertyBuilderPlugin() {
        return new EnumModelPropertyBuilderPlugin();
    }
}

当配置中Swagger2DocumentationConfiguration类存在后,配置类自动配置我们的EnumModelPropertyBuilderPlugin插件,当然也可以通过plugin.swagger.enum-support参数来进行管控插件的开关。

META-INF配置

additional-spring-configuration-metadata.json配置

{
  "properties": [
    {
      "name": "plugin.swagger.enum-support",
      "type": "java.lang.Boolean",
      "description": "Enabled Swagger Enum Plugin.",
      "defaultValue": true,
      "deprecation": {
        "reason": "The swagger-enum-plugin auto-configuration is no longer customizable. Provide your own SwaggerPluginConfiguration bean instead.",
        "level": "error"
      }
    }
  ]
}

spring-configuration-metadata.json配置

{
  "properties": [
    {
      "name": "swagger.enum-support",
      "type": "java.lang.Boolean",
      "description": "Enabled Swagger Enum Plugin."
    }
  ]
}

spring.factories配置

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.plugin.swagger.SwaggerEnumPluginConfiguration

注意:com.plugin.swagger配置为自己项目相关的包信息。

示例效果

实体类

public class AccessRecordMobileDetailVO implements Serializable {
    /**
     * 拜访类型
     */
    @SwaggerDisplayEnum(clazz = AccessVisitTypeEnum.class, key = "type", value = "desc")
    @ApiModelProperty(value = "拜访类型", required = true, example = "ONLINE")
    private String accessVisitType;
}

枚举类

@Getter
@AllArgsConstructor
public enum AccessVisitTypeEnum {

    /**
     * 线上
     */
    ONLINE("ONLINE","线上"),

    /**
     * 线下
     */
    OFFLINE("OFFLINE","线下"),
    ;

    /**
     * 访问类型
     */
    private String type;

    /**
     * 描述
     */
    private String desc;
}

效果图

image

源码地址

https://gitee.com/wxquan/wsy-plugins

Maven仓库坐标

Maven Central

<dependency>
    <groupId>com.wangshouyu</groupId>
    <artifactId>wsy-plugins</artifactId>
    <version>${lastest.version}</version>
</dependency>