前言
最近公司在做一个功能,移动端给其他公司做,我们做后端。然后到了对接的环节,对面要我们给一个接口台账。以前我们自己内部对接一般都是用的 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 格式的