通过swagger注解,生成YAPI文档

372 阅读4分钟

前言

在开发过程中,需要与前端核对接口,并且形成落地文档。这时候就可以有多个选择,其中后端常见的与代码绑定的swagger就是首选。

但是如果swagger不能够开放给外组件查看的情况下,YApi接口管理平台也是一个不错的选择。并且能够通过添加备注的方式,对接口进一步说明。

yapi有提供从swagger导入的接口,这里就不再说明。为什么会有这个工具的诞生呢,肯定是因为方便的东西有一定的局限性(才不是因为无聊瞎搞的)

一 设计

通过yapi上面的Raw字符串,将接口生成符合要求的内容,然后粘贴进去。再在YApi上面,点击JSON,进行转换成可视化内容。

其中常见的格式有三种:对象列表分页。本文也是对这三种情况进行研究开发。(其他情况可以参考自行扩展)

二 编码

1.YApi的字段类型

这里可以将 array 类型也考虑进来。添加 private Object item; 字段

package com.cah.project.yapi;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 功能描述: 字段参数类 <br/>
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class FieldParam {

    /** yapi字段类型 */
    private String type;

    /** yapi描述 */
    private String description;

    /** yapi对象 */
    private Object properties;

    /** 获取字符串类型 */
    public static FieldParam getString(String description) {
        return new FieldParam("string", description, null);
    }

    /** 获取整型类型 */
    public static FieldParam getInteger(String description) {
        return new FieldParam("integer", description, null);
    }

    /** 获取数值类型 */
    public static FieldParam getNumber(String description) {
        return new FieldParam("number", description, null);
    }

    /** 获取字布尔值类型 */
    public static FieldParam getBoolean(String description) {
        return new FieldParam("boolean", description, null);
    }

    /** 获取对象类型 */
    public static FieldParam getObject(String description, Object properties) {
        return new FieldParam("object", description, properties);
    }

}

2.生成类型枚举

这里使用策略枚举的设计模式,方便扩展。并且相互之间也相对独立。

生成的YApi上面需要的字符串主要分为两种,请求参数响应参数。目前编写是对象的形式。

如果说,之后有 树类型的,可以加一个 TREE 枚举值,并实现响应的代码即可。

package com.cah.project.yapi.enums;

import com.cah.project.yapi.FieldParam;
import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 功能描述: 生成类型枚举(可自定义修改) <br/>
 */
@Getter
@AllArgsConstructor
public enum GenTypeEnum {

    PAGE("分页数据") {
        @Override
        public Object getResponseData(String desc, Class<?> clazz) {
            Map<String, Object> dataMap = new LinkedHashMap<>();
            // 分页对象,根据自己项目进行修改 {@link com.cah.project.core.domain.out.PageInfo}
            Map<String, Object> pageMap = new LinkedHashMap<>(4);
            pageMap.put("recordCount", "总数量");
            pageMap.put("pageCount", "总页数");
            pageMap.put("pageSize", "每页最大数量");
            pageMap.put("currentNumber", "当前页码");
            dataMap.put("pageInfo", pageMap);
            // 添加数据列表
            dataMap.put("list", LIST.getResponseData(clazz));
            return dataMap;
        }

        @Override
        public Object getRequestData(String desc, Class<?> clazz) {
            return OBJECT.getRequestData(desc, clazz);
        }
    },

    LIST("数据列表") {
        @Override
        public Object getResponseData(String desc, Class<?> clazz) {
            Map<String, Object> listMap = new LinkedHashMap<>();
            listMap.put("type", "array");
            listMap.put("description", desc);
            listMap.put("item", OBJECT.getResponseData(clazz));
            return listMap;
        }

        @Override
        public Object getRequestData(String desc, Class<?> clazz) {
            return getResponseData(desc, clazz);
        }
    },

    OBJECT("数据对象") {
        @Override
        public Object getResponseData(String desc, Class<?> clazz) {
            return FieldParam.getObject(desc, FieldUtil.getClassFieldParam(clazz));
        }

        @Override
        public Object getRequestData(String desc, Class<?> clazz) {
            return getResponseData(desc, clazz);
        }
    },
    ;

    private final String desc;

    /** 获取YApi响应数据 */
    public Object getResponseData(Class<?> clazz) {
        return getResponseData(getDesc(), clazz);
    }

    /** 获取YApi响应数据 */
    public abstract Object getResponseData(String desc, Class<?> clazz);

    /** 获取YApi请求数据 */
    public Object getRequestData(Class<?> clazz) {
        return getRequestData(getDesc(), clazz);
    }

    /** 获取YApi请求数据 */
    public abstract Object getRequestData(String desc, Class<?> clazz);

}

3.字段反射工具类

通过类的反射,将字段组装成YApi上需要的格式。

这里有一个需要根据实际情况考虑的。是否需要将父类,父类的父类级联去调用查询其中的字段。比较麻烦

package com.cah.project.yapi.enums;

import com.cah.project.core.dict.annotation.Dict;
import io.swagger.annotations.ApiModelProperty;

import java.lang.reflect.Field;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 功能描述: 字段工具 <br/>
 */
public class FieldUtil {

    /** 通过类,获取字段信息 */
    public static Map<String, Object> getClassFieldParam(Class<?> clazz) {
        // 暂时不考虑父类的父类,递归往上,后续补充
        Class<?> superclass = clazz.getSuperclass();
        Field[] superFields = new Field[0];
        if(superclass != null && !"Object".equals(superclass.getSimpleName())) {
            // 存在父类,获取父类字段
            superFields = superclass.getDeclaredFields();
        }
        Field[] fields = clazz.getDeclaredFields();
        Map<String, Object> fieldMap = new LinkedHashMap<>(fields.length + superFields.length);
        for (Field field : fields) {
            // 这里对字段进行过滤,不需要的剔除
            if("serialVersionUID".equals(field.getName())) {
                continue;
            }
            fieldMap.put(field.getName(), FieldTypeEnum.getObjectParam(field.getType().getSimpleName(), getDescription(field), field));
        }
        for (Field field : superFields) {
            // 这里对字段进行过滤,不需要的剔除
            if("serialVersionUID".equals(field.getName())) {
                continue;
            }
            fieldMap.put(field.getName(), FieldTypeEnum.getObjectParam(field.getType().getSimpleName(), getDescription(field), field));
        }
        return fieldMap;
    }

    /** 通过swagger注解,获取字段描述 */
    public static String getDescription(Field field) {
        ApiModelProperty apiModeProperty = field.getAnnotation(ApiModelProperty.class);
        String desc = "";
        if(apiModeProperty != null) {
            desc = apiModeProperty.value();
            // 可以做一些其他处理,比如说如果有字典的话,拼接字典内容
            // 例如依赖 project-dict框架
            Dict dict = field.getAnnotation(Dict.class);
            if(dict != null) {
                desc += "(" + dict.type() + ", " + dict.desc() + ")";
            }
        }
        return desc;
    }

}

4.字段类型映射枚举

同样的使用策略枚举的模式,将所有场景分开。如果有不同需要。则添加枚举值,并实现即可。

package com.cah.project.yapi.enums;

import com.cah.project.yapi.FieldParam;
import lombok.AllArgsConstructor;
import lombok.Getter;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.stream.Stream;

/**
 * 功能描述: 类字段类型枚举(字段类型需要再补充) <br/>
 */
@Getter
@AllArgsConstructor
public enum FieldTypeEnum {

    INT("int") {
        @Override
        public Object getFieldParam(String desc, Field field) {
            return FieldParam.getInteger(desc);
        }
    },
    INTEGER("Integer") {
        @Override
        public Object getFieldParam(String desc, Field field) {
            return FieldParam.getInteger(desc);
        }
    },
    LONG("long") {
        @Override
        public Object getFieldParam(String desc, Field field) {
            return FieldParam.getNumber(desc);
        }
    },
    LONG_UP("Long") {
        @Override
        public Object getFieldParam(String desc, Field field) {
            return FieldParam.getNumber(desc);
        }
    },
    DOUBLE("double") {
        @Override
        public Object getFieldParam(String desc, Field field) {
            return FieldParam.getNumber(desc);
        }
    },
    DOUBLE_UP("Double") {
        @Override
        public Object getFieldParam(String desc, Field field) {
            return FieldParam.getNumber(desc);
        }
    },
    FLOAT("float") {
        @Override
        public Object getFieldParam(String desc, Field field) {
            return FieldParam.getNumber(desc);
        }
    },
    FLOAT_UP("Float") {
        @Override
        public Object getFieldParam(String desc, Field field) {
            return FieldParam.getNumber(desc);
        }
    },
    BIG_DECIMAL("BigDecimal") {
        @Override
        public Object getFieldParam(String desc, Field field) {
            return FieldParam.getNumber(desc);
        }
    },
    BOOLEAN("boolean") {
        @Override
        public Object getFieldParam(String desc, Field field) {
            return FieldParam.getBoolean(desc);
        }
    },
    BOOLEAN_UP("Boolean") {
        @Override
        public Object getFieldParam(String desc, Field field) {
            return FieldParam.getBoolean(desc);
        }
    },
    STRING("String") {
        @Override
        public Object getFieldParam(String desc, Field field) {
            return FieldParam.getString(desc);
        }
    },
    LIST("List") {
        @Override
        public Object getFieldParam(String desc, Field field) {
            return GenTypeEnum.LIST.getResponseData(FieldUtil.getDescription(field),
                    // 获取泛型比较麻烦
                    (Class<?>) ((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0]);
        }
    },
    MAP("Map") {
        @Override
        public Object getFieldParam(String desc, Field field) {
            // 用Map返回的,直接打死吧
            return null;
        }
    },
    OBJECT("") {
        @Override
        public Object getFieldParam(String desc, Field field) {
            return GenTypeEnum.OBJECT.getResponseData(FieldUtil.getDescription(field), field.getType());
        }
    },;

    private final String type;

    /** 获取字段返回 */
    public abstract Object getFieldParam(String desc, Field field);

    /** 获取字段返回 */
    public static Object getObjectParam(String type, String desc, Field field) {
        return indexOf(type).getFieldParam(desc, field);
    }

    /** 获取字段类型枚举 */
    public static FieldTypeEnum indexOf(String type) {
        return Stream.of(values()).filter(e -> e.getType().equals(type)).findFirst().orElse(OBJECT);
    }

}

5.工具主类

将上面编写的内容在最外围包装一下,对外提供两个方法即可。

package com.cah.project.yapi;

import cn.hutool.json.JSONUtil;
import com.cah.project.module.standard.domain.vo.in.DictDataQuery;
import com.cah.project.yapi.enums.GenTypeEnum;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 功能描述: yapi 生成raw工具 <br/>
 */
public class YApiGenRawUtil {

    public static void main(String[] args) {
        requestBody(DictDataQuery.class, GenTypeEnum.PAGE);
    }

    /** 功能描述: 请求对象json字符串 */
    private static void requestBody(Class<?> clazz, GenTypeEnum type) {
        // 添加固定头
        System.out.println("\n");
        System.out.println(JSONUtil.toJsonStr(type.getRequestData(clazz)));
        System.out.println("\n");
        System.out.println(JSONUtil.toJsonPrettyStr(type.getRequestData(clazz)));
    }

    /**
     * 功能描述: 响应对象Raw字符串 <br/>
     */
    private static void responseBody(Class<?> clazz, GenTypeEnum type) {
        Map<String, Object> resMap = new LinkedHashMap<>();
        // 添加固定头
        resMap.put("$schema", "http://json-schema.org/draft-04/schema#");
        resMap.put("type", "object");
        // 添加统一返回对象,根据项目不同,返回不同 {@link com.cah.project.core.domain.out.CommonResult}
        Map<String, Object> commonResultMap = new LinkedHashMap<>();
        commonResultMap.put("code", "错误码");
        commonResultMap.put("msg", "错误码描述");
        commonResultMap.put("data", type.getResponseData(clazz));
        resMap.put("properties", commonResultMap);
        // 添加必填字段,如果有需要,自行开发
        resMap.put("required", new ArrayList<>());
        System.out.println("\n");
        System.out.println(JSONUtil.toJsonStr(resMap));
        System.out.println("\n");
        System.out.println(JSONUtil.toJsonPrettyStr(resMap));
    }

}

三 总结

代码是简单的代码,但是可以加深对策略枚举实际应用的理解,也是不错滴。

四 代码地址

生成工具

感谢关注(微信小程序)

柒宅mini二维码.jpg