【API 导出工具】将 API 导出到 Excel 的工具类

223 阅读10分钟

前言

最近公司在做一个功能,移动端给其他公司做,我们做后端。然后到了对接的环节,对面要我们给一个接口台账。以前我们自己内部对接一般都是用的 yapi 对接的,直接使用 swagger 同步过去。所以我们公司就要求接口规范必须符合 swagger 的规范。

问题来了,yapi 很坑的地方,导出来的一个样式丑,不好看

第二个有时候这里缺一点,那里缺一点,就很坑

而且对面要求最好有一个接口台账,用一个 Excel 表格列出所有的接口,接口名称,路径,参数等

我:前端我也帮你们写完并且接完好不好…………………………

没办法,那边有可能会临时加一些离谱的要求,使用工具是没办法满足了,干脆自己写一个 API 接口导出的工具吧,还能自己定制化。说干就干

正文

前提条件

使用这个工具有几个条件

  • 项目符合 swagger 规范
  • 好像没了

引入依赖

  • 导出我使用的是阿里的 easyExcel,如果还没有用过的或者公司还有传统的 poi 的,强烈推荐,谁用谁爽。讲个玩笑话,我进来之前公司就用传统的 poi,一个导出的接口看得我头皮发麻,我就跟主管商量能不能引用 easyExcel,然后真香。那些 poi 屎山一样的代码谁改谁死。后来主管又找我,后面排期不是很紧的时候,你看看能不能把之前的导入导出改成 easyExcel 的方式,看看工作量大不大。我……
  • 还有工具用的是 hutool 的工具,这个虽然是第三方工具,如果公司没有特别限制的话,还是很好用的
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>easyexcel</artifactId>
			<version>3.1.1</version>
		</dependency>
		<dependency>
			<groupId>cn.hutool</groupId>
			<artifactId>hutool-all</artifactId>
			<version>5.8.0</version>
		</dependency>
		<dependency>
			<groupId>org.reflections</groupId>
			<artifactId>reflections</artifactId>
			<version>0.10.2</version>
		</dependency>

导出实体

@Data
@AllArgsConstructor
@NoArgsConstructor
@ContentRowHeight(30)
@HeadRowHeight(40)
@ColumnWidth(25)
public class ControllerInfo {

    @ExcelProperty("接口模块")
    private String controllerName;

    @ExcelProperty("接口名称")
    private String apiName;

    @ExcelProperty("请求方式")
    private String requestMethod;

    @ExcelProperty("请求路径")
    private String requestUrl;

    @ExcelProperty("请求参数")
    private String requestParams;

    @ExcelProperty("响应参数")
    private String responseParams;
}

导出工具

import cn.hutool.core.map.MapUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.merge.OnceAbsoluteMergeStrategy;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.spring.ldj.common.vo.ControllerInfo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.ClassUtils;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.reflections.Reflections;
import org.springframework.stereotype.Controller;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;

import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;


public class ControllerInfoExporter {

    // 忽略特殊字段
    private static List<String> ignoreFieldList = Arrays.asList("serialVersionUID");

    // 已经解析过的并且完全解析完的对象类型进行缓存,一级缓存
    private static Map<String, Object> cache = new HashMap<>();
    // 循环依赖的缓存
    private static Map<Class<?>, Class<?>> cycleCache = new HashMap<>();
    // 引用关系缓存 如果 A 对象引用了 B 对象,则 A 作为 key,B 作为 value
    private static Map<Class<?>, Set<Class<?>>> referenceCache = new HashMap<>();

    public static void main(String[] args) {
        String packageName = "com.spring.ldj"; // 你的包名
        export(packageName);
    }

    public static void export(String pageName){
        // 清除缓存
        cacheClear();
        // 合并下标
        List<Integer[]> mergeIndexList = new ArrayList<>();
        // 获取导出的 api 数据
        List<ControllerInfo> controllerInfoList = getControllerInfoList(pageName,mergeIndexList);
        // 合并策略
        List<OnceAbsoluteMergeStrategy> mergeStrategies = getMergeStrategies(mergeIndexList);
        // 指定导出的文件路径和文件名
        String exportFilePath = "controller_info" + System.currentTimeMillis() + ".xlsx";
        ExcelWriterBuilder excelWriter = EasyExcel.write(exportFilePath, ControllerInfo.class);
        for (OnceAbsoluteMergeStrategy strategy : mergeStrategies) {
            excelWriter.registerWriteHandler(strategy);
        }
        excelWriter.registerWriteHandler(getCellStyleStrategy());
        excelWriter.sheet("Sheet1").doWrite(controllerInfoList);

        System.out.println("Controller 接口信息已成功导出到 " + exportFilePath);
        // 清除缓存
        cacheClear();
    }

    private static void cacheClear() {
        cache.clear();
        cycleCache.clear();
        referenceCache.clear();
    }

    private static List<ControllerInfo> getControllerInfoList(String pageName, List<Integer[]> mergeIndexList) {
        List<ControllerInfo> controllerInfoList = new ArrayList<>();
        final int[] currRowIndex = {1};
        // 获取使用 @RestController 注解标注的所有 Controller 类
        Reflections reflections = new Reflections(pageName);
        Set<Class<?>> controllers = reflections.getTypesAnnotatedWith(Controller.class);
        controllers.addAll(reflections.getTypesAnnotatedWith(RestController.class));
        controllers.addAll(reflections.getTypesAnnotatedWith(Controller.class));
        controllers.forEach(clazz -> {
            System.out.println(clazz.getSimpleName() + "数据获取中……");
            if (clazz.getSimpleName().equals("AppSchoolController")){
                System.out.println("312");
            }
            // 获取 Controller 类的名称
            String controllerName = getControllerName(clazz);
            // 获取 controller 的 @RequestMapping
            String requestMappingValue = getRequestMappingValue(clazz);
            // 获取 Controller 类中的所有方法
            Method[] methods = clazz.getMethods();
            // 合并开始下标
            int mergeStartIndex = currRowIndex[0];
            for (Method method : methods) {
                // 获取接口 API
                Map<String, String> requestMethodMap = getRequestMappingValue(method);
                if (CollectionUtils.isEmpty(requestMethodMap)) {
                    continue;
                }
                String apiName = getApiName(method);
                AtomicReference<String> requestMethod = new AtomicReference<>("");
                AtomicReference<String> requestPath = new AtomicReference<>("");
                String requestParams = getRequestParams(method);
                String responseParams = getResponseParams(method);
                requestMethodMap.forEach((key, value) -> {
                    requestMethod.set(key);
                    requestPath.set(requestMappingValue + value);
                });
                // get 请求拼接在 url后面
                if (method.isAnnotationPresent(GetMapping.class)) {
                    requestPath.set(requestPath.get() + requestParams);
                    requestParams = "";
                }
                controllerInfoList.add(new ControllerInfo(controllerName, apiName, requestMethod.get(), requestPath.get(), requestParams,responseParams));
                currRowIndex[0]++;
            }
            // 合并结束下标
            mergeIndexList.add(new Integer[]{mergeStartIndex, currRowIndex[0] - 1});
            System.out.println(clazz.getSimpleName() + "数据获取完毕……");
        });
        return controllerInfoList;
    }

    private static String getControllerName(Class<?> clazz) {
        if (clazz.isAnnotationPresent(ApiModel.class)){
            ApiModel apiModel = clazz.getAnnotation(ApiModel.class);
            return apiModel.value() != null ? apiModel.value() : clazz.getSimpleName();
        }
        if (clazz.isAnnotationPresent(Api.class)){
            Api api = clazz.getAnnotation(Api.class);
            return api.value() != null ? api.value() : clazz.getSimpleName();
        }
        return clazz.getSimpleName();
    }


    private static void dependencyAssign(Map<String, Object> paramMap) {
        // 循坏依赖问题重新赋值
        // cycleCache
        if (cycleCache != null && paramMap != null && cache != null) {
            Iterator<Map.Entry<Class<?>, Class<?>>> iterator = cycleCache.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<Class<?>, Class<?>> entry = iterator.next();
                Class<?> fieldClazz = entry.getKey();
                Class<?> relyClazz = entry.getValue();
                Field[] fieldList = relyClazz.getDeclaredFields();
                for (Field field : fieldList) {
                    if (!field.getType().equals(fieldClazz)) {
                        continue;
                    }
                    String typeName = fieldClazz.getTypeName();
                    paramMap.forEach((k,v) -> {
                        if (v instanceof Map){
                            // 重新赋值
                            reAssign(v, typeName);
                        }
                    });
                    iterator.remove();
                }
            }
        }
    }

    private static void reAssign(Object v, String typeName){
        Map<String, Object> sonMap = (Map<String, Object>) v;
        sonMap.forEach((k1,v1) -> {
            if ("循环依赖".equals(v1)){
                sonMap.put(k1, JSONUtil.parseObj(cache.get(typeName)));
            }
            // response 多一层
            if (v1 instanceof Map){
                Map<String, Object> sonMap1 = (Map<String, Object>) v1;
                sonMap1.forEach((k2,v2) -> {
                    if ("循环依赖".equals(v2)){
                        sonMap1.put(k2, JSONUtil.parseObj(cache.get(typeName)));
                    }
                });
            }
        });
    }
    //获取方法的返回值。如果是对象类型,则将对象的所有字段的 name 作为 key,类型作为 value,组成一个 Map,并转换为 JSON 字符串返回。
    private static String getResponseParams(Method method) {
        // 获取响应参数的泛型类型
        Class<?> typeArgClass = getResponseTypeArgClass(method);
        // 对象类型
        if (isObjectType(method.getReturnType())) {
            Field[] fields = method.getReturnType().getDeclaredFields();
            Map<String, Object> paramMap = new HashMap<>();
            for (Field field : fields) {
                // 我这里的 content 作为返回体的主要内容,所以要获取 content 的泛型类型
                // 根据具体项目灵活调整
                if (field.getName().equals("data") && typeArgClass != null){
                    Object objectParams = getObjectParams(typeArgClass);
                    cache.put(typeArgClass.getTypeName(), objectParams);
                    paramMap.put(field.getName(), objectParams);
                }else {
                    paramMap.put(field.getName(), getParams(field));
                }
            }
            dependencyAssign(paramMap);
            return JSONUtil.toJsonStr(paramMap);
        } else {
            // 基本数据类型
            String type = method.getReturnType().getName();
            // 取最后一个点之后的字符串
            type = type.substring(type.lastIndexOf(".") + 1);
            return type;
        }
    }

    private static Class<?> getResponseTypeArgClass(Method method) {
        Class<?> typeArgClass = null;
        Type returnType = method.getGenericReturnType();
        if(returnType instanceof ParameterizedType){
            ParameterizedType type = (ParameterizedType) returnType;
            Type[] typeArguments = type.getActualTypeArguments();
            for(Type typeArgument : typeArguments){
                try {
                    if(typeArgument instanceof Class<?>){
                        // 泛型类型
                        typeArgClass = (Class<?>) typeArgument;
                    }else {
                        ParameterizedType listType = (ParameterizedType) typeArgument;
                        // 获取 List 中的泛型
                        Type[] listActualTypeArguments = listType.getActualTypeArguments();
                        for (Type listActualTypeArgument : listActualTypeArguments) {
                            typeArgClass = (Class<?>) listActualTypeArgument;
                        }
                    }
                }catch (Exception e){
                    System.err.println(method.getName() + "数据获取有误,跳过部分数据");
                    e.printStackTrace();
                }
            }
        }
        return typeArgClass;
    }

    /**
     * 获取对象类型的字段的 name 作为 key,类型作为 value,组成一个 Map,并转换为 JSON 字符串返回。
     *
     * @param typeArgClass 类型arg类
     * @return {@link Object}
     */
    private static Object getObjectParams(Class<?> typeArgClass) {
        // 跳过 java 内部类
        if (typeArgClass.getName().contains("java")){
            return null;
        }
        // 缓存该对象包含的所有对象类型
        cacheContainObject(typeArgClass);
        Field[] fields = typeArgClass.getDeclaredFields();
        Map<String, Object> paramMap = new HashMap<>();
        // 先从缓存获取
        Object paramObject = cache.get(typeArgClass.getTypeName());
        if (paramObject != null){
            return paramObject;
        }
        for (Field field : fields) {
            // 忽略特殊字段
            if (ignoreFieldList.contains(field.getName())){
                continue;
            }
            Class<?> fieldClazz = field.getType();
            // 获取对象的字段还是对象,则继续递归获取
            if (isObjectType(fieldClazz)) {
                // 处理 泛型 类型
                Type genericType = field.getGenericType();
                // fieldClazz.getTypeName().equals("java.util.List")
                if (fieldClazz.getTypeName().equals("java.util.List")){
                    Object listParams = getListParams(field, typeArgClass);
                    paramMap.put(field.getName(), listParams);
                }else if (isCircularReference(fieldClazz, typeArgClass)) {
                    // 是否是循环依赖
                    paramMap.put(field.getName(), "循环依赖");
                    cycleCache.put(fieldClazz, typeArgClass);
                }else {
                    // 缓存该对象包含的所有对象类型
                    cacheContainObject(fieldClazz);
                    Object objectParams = getObjectParams(fieldClazz);
                    cache.put(fieldClazz.getTypeName(), objectParams);
                    paramMap.put(field.getName(), objectParams);
                }
            } else {
                // 基本数据类型
                String type = field.getType().getName();
                // 取最后一个点之后的字符串
                type = type.substring(type.lastIndexOf(".") + 1);
                if (field.isAnnotationPresent(ApiModelProperty.class)){
                    type = type + " " + field.getAnnotation(ApiModelProperty.class).value();
                }
                paramMap.put(field.getName(), type);
            }
        }
        return paramMap;
    }

    private static Object getListParams(Field field, Class<?> typeArgClass) {
        // 获取 List 的泛型字段
        Type genericType = field.getGenericType();
        if(genericType instanceof ParameterizedType) {
            ParameterizedType type = (ParameterizedType) genericType;
            // 获取 List 中的泛型
            Type[] actualTypeArguments = type.getActualTypeArguments();
            try {
                for (Type actualTypeArgument : actualTypeArguments) {
                    Class<?> fieldClazz = (Class<?>) actualTypeArgument;
                    if (isCircularReference(fieldClazz, typeArgClass)) {
                        // 是否是循环依赖
                        cycleCache.put(fieldClazz, typeArgClass);
                        return "循环依赖";
                    } else if (isObjectType(fieldClazz)) {
                        // 对象
                        Object objectParams = getObjectParams(fieldClazz);
                        JSONArray jsonArray;
                        if (objectParams instanceof JSONArray) {
                            jsonArray = (JSONArray) objectParams;
                        } else {
                            jsonArray = JSONUtil.createArray();
                            jsonArray.add(objectParams);
                        }
                        cache.put(fieldClazz.getTypeName(), jsonArray);
                        return jsonArray;
                    } else {
                        // 基本数据类型
                        return MapUtil.of(fieldClazz.getTypeName(), fieldClazz.getTypeName());
                    }
                }
            }
            catch (Exception e){
                System.err.println(typeArgClass.getName() + "数据获取有误,跳过部分数据");
                e.printStackTrace();
            }
        }
        return null;
    }

    private static boolean isCircularReference(Class<?> fieldClazz, Class<?> typeArgClass) {
        Set<Class<?>> classSet = referenceCache.get(fieldClazz);
        return (classSet != null && classSet.contains(typeArgClass)) || fieldClazz.equals(typeArgClass);
    }

    /**
     * 缓存包含对象
     *
     * @param typeArgClass 类型arg类
     */
    private static void cacheContainObject(Class<?> typeArgClass) {
        // 缓存该对象包含的所有对象类型
        Field[] fields = typeArgClass.getDeclaredFields();
        Set<Class<?>> cSet = new HashSet<>();
        for (Field field : fields) {
            Class<?> fieldClazz = field.getType();
            if (isObjectType(fieldClazz)) {
                cSet.add(fieldClazz);
            }
        }
        if (cSet.size() > 0){
            referenceCache.put(typeArgClass, cSet);
        }
    }

    // 是否是对象类型
    private static boolean isObjectType(Class<?> typeArgClass) {
        if (typeArgClass.isPrimitive()) {
            return false;
        }
        if (typeArgClass.getTypeName().contains("String")){
            return false;
        }
        if (typeArgClass.getTypeName().contains("Date")){
            return false;
        }
        if (ClassUtils.isPrimitiveOrWrapper(typeArgClass)){
            return false;
        }
        if (typeArgClass.isEnum()){
            return false;
        }
        return true;
    }

    private static Object getParams(Field field) {
        Class<?> fieldClazz = field.getType();
        if (!isObjectType(fieldClazz)) {
            String type = fieldClazz.getName();
            // 取最后一个点之后的字符串
            type = type.substring(type.lastIndexOf(".") + 1);
            return type;
        } else {
            Object objectParams = getObjectParams(fieldClazz);
            cache.put(fieldClazz.getTypeName(), objectParams);
            return objectParams;
        }
    }

    // 获取方法的入参
    private static String getRequestParams(Method method) {
        if (method.isAnnotationPresent(GetMapping.class)){
            // 获取 get 请求参数
            return getGetReqParams(method);
        }else {
            // 获取 post 请求参数
            return getPostReqParams(method);
        }
    }

    private static String getPostReqParams(Method method) {
        JSONObject paramJson = JSONUtil.createObj();
        Parameter[] parameters = method.getParameters();
        int i = 0;
        for (Parameter parameter : parameters) {
            // url 后面的参数不处理 如 user/{id}
            if (parameter.isAnnotationPresent(PathVariable.class)){
                continue;
            }
            // 忽略特殊字段
            if (ignoreFieldList.contains(parameter.getName())){
                continue;
            }
            // 对象类型
            if (isObjectType(parameter.getType())) {
                Object objectParams = getObjectParams(parameter.getType());
                // 处理第一个对象参数
                if (i == 0){
                    paramJson = JSONUtil.parseObj(objectParams);
                }else {
                    paramJson.set(parameter.getName(), objectParams);
                }
                cache.put(parameter.getType().getTypeName(), objectParams);
            } else {
                // 基本数据类型
                String type = parameter.getType().getName();
                // 取最后一个点之后的字符串
                type = type.substring(type.lastIndexOf(".") + 1);
                if (parameter.isAnnotationPresent(ApiModelProperty.class)){
                    type = type + " " + parameter.getAnnotation(ApiModelProperty.class).value();
                }
                paramJson.set(parameter.getName(), type);
            }
            i++;
        }
        // 处理循环依赖
        Map<String, Object> paramMap = JSONUtil.parseObj(paramJson);
        dependencyAssign(paramMap);
        return JSONUtil.toJsonStr(paramMap);
    }

    private static String getType(Method method, Parameter parameter){
        String type = method.getReturnType().getName();
        // 取最后一个点之后的字符串
        type = type.substring(type.lastIndexOf(".") + 1);
        if (parameter.isAnnotationPresent(ApiModelProperty.class)){
            type = type + " " + parameter.getAnnotation(ApiModelProperty.class).value();
        }
        return type;
    }

    private static String getGetReqParams(Method method) {
        StringBuilder sb = new StringBuilder("?");
        Parameter[] parameters = method.getParameters();
        for (Parameter parameter : parameters) {
            // url 后面的参数不处理 如 user/{id}
            if (parameter.isAnnotationPresent(PathVariable.class)){
                continue;
            }
            // 忽略特殊字段
            if (ignoreFieldList.contains(parameter.getName())){
                continue;
            }
            // 基本数据类型
            String type = parameter.getType().getName();
            // 取最后一个点之后的字符串
            type = type.substring(type.lastIndexOf(".") + 1);
            sb.append(parameter.getName()).append("=").append(type).append("&");
        }
        return sb.substring(0, sb.length() - 1);
    }

    private static List<OnceAbsoluteMergeStrategy> getMergeStrategies(List<Integer[]> mergeIndexList) {
        List<OnceAbsoluteMergeStrategy> strategies = new ArrayList<>();
        // 根据你的需求添加合并策略
        // 例如:strategies.add(new OnceAbsoluteMergeStrategy(2, 3, 5));
        mergeIndexList.forEach(mergeIndex -> {
            if (!Objects.equals(mergeIndex[0], mergeIndex[1])) {
                OnceAbsoluteMergeStrategy strategy = new OnceAbsoluteMergeStrategy(mergeIndex[0], mergeIndex[1], 0, 0);
                strategies.add(strategy);
            }
        });
        return strategies;
    }


    private static HorizontalCellStyleStrategy getCellStyleStrategy() {
        WriteCellStyle headCellStyle = new WriteCellStyle();
        headCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
        headCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
        headCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        headCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);

        WriteCellStyle contentCellStyle = new WriteCellStyle();
        contentCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
//        contentCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);

        return new HorizontalCellStyleStrategy(headCellStyle, contentCellStyle);
    }

    private static String getApiName(Method method) {
        // 获取接口名称
        if (method.isAnnotationPresent(ApiOperation.class)) {
            ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
            return apiOperation.value();
        }
        if (method.isAnnotationPresent(ApiModelProperty.class)) {
            ApiModelProperty apiModelProperty = method.getAnnotation(ApiModelProperty.class);
            return apiModelProperty.value();
        }
        return null;
    }

    private static String getRequestMappingValue(Class<?> clazz) {
        // 获取 @RequestMapping
        if (clazz.isAnnotationPresent(RequestMapping.class)) {
            RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);
            return requestMapping.value().length > 0? formatPath(requestMapping.value()[0]) : "";
        }
        return "";
    }

    private static Map<String, String> getRequestMappingValue(Method method) {
        // 获取 @RequestMapping/@GetMapping/@PostMapping/@PutMapping/@DeleteMapping 注解的值
        Map<String, String> requestMappingMap = new HashMap<>();
        if (method.isAnnotationPresent(RequestMapping.class)) {
            RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
            requestMappingMap.put("requestMapping", requestMapping.value().length > 0? formatPath(requestMapping.value()[0]) : "");
        }
        if (method.isAnnotationPresent(GetMapping.class)) {
            GetMapping getMapping = method.getAnnotation(GetMapping.class);
            requestMappingMap.put("get", getMapping.value().length > 0? formatPath(getMapping.value()[0]) : "");;
        }
        if (method.isAnnotationPresent(PostMapping.class)) {
            PostMapping postMapping = method.getAnnotation(PostMapping.class);
            requestMappingMap.put("post", postMapping.value().length > 0? formatPath(postMapping.value()[0]) : "");
        }
        return requestMappingMap;
    }

    // 格式化路径
    private static String formatPath(String path) {
        if (!path.startsWith("/")) {
            path = "/" + path ;
        }
        return path;
    }

}

使用注意

  • main 方法修改包名

  • 我这里只写了requestMapping、get、post请求方式,如果需要加其他方式的可以在这个方法加getRequestMappingValue

  • getResponseParams这个获取响应参数时,因为一般公司都是有通用的返回实体的,这里的data 根据公司返回实体自行调整。为什么要这样写死不动态获取呢,因为这个泛型编译期无法获取具体的泛型类型。网上查了一个好像 Google 有一个工具是可以获取,我后面有时间再看看吧

    // 我这里的 content 作为返回体的主要内容,所以要获取 content 的泛型类型
    // 根据具体项目灵活调整
    if (field.getName().equals("content") && typeArgClass != null){
        Object objectParams = getObjectParams(typeArgClass);
        cache.put(typeArgClass.getTypeName(), objectParams);
        paramMap.put(field.getName(), objectParams);
    }else {
        paramMap.put(field.getName(), getParams(field));
    }
    
    // 如
    public class CommonResult <T> implements Serializable {
    
        private T content;
    
        private boolean success;
    
        private String message;
    
        private int code;
    
        private ResultCode resultCode;
    }  
    
  • 在写接口时,尽量规范吧,我这里测试有几种是无法获取的。如

        @ApiOperation("修改商品")
        @PostMapping("/update")
    		// 直接一个? 我也??? (我们公司就很多这种接口……)
        public ResultForm<?> update(@RequestBody Goods goods){
    
        }
    
    		@ApiOperation("修改商品")
        @PostMapping("/update")
    		// 嵌套几个泛型类型
        public ResultForm<UserVo<User>> update(@RequestBody Goods goods){
    
        }
    
  • 好像暂时想到这么多,如果有朋友路过可以复制过去玩一下,好用的话点个赞。或者有问题的话评论区提出来,在线测试改 bug。或者有什么好的建议的,分享一下

总结

  • 这个东西其实很早之前就想写了,刚好这次有机会,也刚好周末下暴雨,没事做写来玩一下,然后必须写个记录。踩过的坑都怀疑人生
  • 本来以为挺简单的,在我的学习项目很快实现了,然后搬到公司项目一测,一堆 bug
  • 坑一:循环引用。这也是我一直在优化的地方,然后翻了一下 spring 的源码,三级缓存解决循环依赖的问题,借鉴了一下。这也是我写的最没眼看的一坨代码,如果有大佬可以给我优化一下或者给个建议就好了。反正就是功能大致实现了,就是代码不灵活
  • 坑二:一个项目一个规范,一个人一个规范。原本以为挺简单的,但是要解决的情况实在太多了,单单入参方式,get、post请求不同。路径参数,body 参数等都有很多种情况。真的吐血。正常来说响应参数 Result不就行了,非要搞一个 Result<?>、Result<List>
  • 坑三:getObjectParams这个方法,最让我头痛,因为这里获取对象的具体字段,但凡有个特殊一点的,就动不动内存溢出了。比如自己引用自己导致死循环的(其实还是循环依赖的问题),然而这个方法不断加限制,不断加判断,而且还有很多 java 内部的类引用到了,然后就一直无限循环
  • 本以来很快解决了,没想到这个周末就这样搭进去了。难受。兄弟们在使用的时候肯定会有各种各样的 bug 的,提出来我有时间就修复
  • 现在只是导出到 Excel,打算后面如果完善好了的话,看看再处理导出到 word 以及 md 格式的