前言
在开发过程中,需要与前端核对接口,并且形成落地文档。这时候就可以有多个选择,其中后端常见的与代码绑定的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));
}
}
三 总结
代码是简单的代码,但是可以加深对策略枚举
实际应用的理解,也是不错滴。