真香警告!擴展 swagger支持文檔自動列舉所有枚舉值

承接上篇文章 《一站式解決使用枚舉的各種痛點》 文章最后提到:在使用 swagger 來編寫接口文檔時艾猜,需要告訴前端枚舉類型有哪些取值,每次增加取值之后捻悯,不僅要改代碼箩朴,還要找到對應的取值在哪里使用了,然后修改 swagger 文檔秋度。反正小黑我覺得這樣做很不爽炸庞,那有沒有什么辦法可以讓 swagger 框架來幫我們自動列舉出所有的枚舉數(shù)值呢?

這期小黑同學就來講講解決方案荚斯。

先來看一下效果埠居,有一個感性的認識


swagger

請注意哦,這里是課程類型不是我們手動列舉出來的事期,是swagger框架幫我們自動列舉的滥壕。對應的代碼如下:

代碼

那么,這是怎么做到的呢兽泣?

簡單描述一下實現(xiàn):

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狂票、實現(xiàn) ModelPropertyBuilderPlugin 接口,擴展 swagger熙暴,實現(xiàn)在文檔中列舉所有的枚舉值闺属。

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、實現(xiàn) ParameterBuilderPluginOperationBuilderPlugin 接口周霉,列舉枚舉參數(shù)的所有取值掂器。

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;
                            }
                        }
                    });
                }
            }
        });
    }
}

這篇文章比較枯燥,小黑我也不知道該怎么去講述俱箱,只是將源碼附錄了出來唉匾。如果有讀者看了之后還是不清楚的話,可以給我留言匠楚,我會一一解答巍膘。感謝你的閱讀~~

相關源碼已經(jīng)上傳到了 github:https://github.com/shenjianeng/solution-for-enums

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市芋簿,隨后出現(xiàn)的幾起案子峡懈,更是在濱河造成了極大的恐慌,老刑警劉巖与斤,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肪康,死亡現(xiàn)場離奇詭異,居然都是意外死亡撩穿,警方通過查閱死者的電腦和手機磷支,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來食寡,“玉大人雾狈,你說我怎么就攤上這事〉种澹” “怎么了善榛?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長呻畸。 經(jīng)常有香客問我移盆,道長,這世上最難降的妖魔是什么伤为? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任咒循,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘叙甸。我一直安慰自己颖医,他們只是感情好,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布蚁署。 她就那樣靜靜地躺著便脊,像睡著了一般蚂四。 火紅的嫁衣襯著肌膚如雪光戈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天遂赠,我揣著相機與錄音久妆,去河邊找鬼。 笑死跷睦,一個胖子當著我的面吹牛筷弦,可吹牛的內容都是我干的。 我是一名探鬼主播抑诸,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼烂琴,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蜕乡?” 一聲冷哼從身側響起奸绷,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎层玲,沒想到半個月后号醉,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡辛块,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年畔派,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片润绵。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡线椰,死狀恐怖,靈堂內的尸體忽然破棺而出尘盼,到底是詐尸還是另有隱情士嚎,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布悔叽,位于F島的核電站莱衩,受9級特大地震影響,放射性物質發(fā)生泄漏娇澎。R本人自食惡果不足惜笨蚁,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧括细,春花似錦伪很、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至览濒,卻和暖如春呆盖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背贷笛。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工应又, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人乏苦。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓株扛,卻偏偏與公主長得像,于是被迫代替她去往敵國和親汇荐。 傳聞我的和親對象是個殘疾皇子洞就,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355