通过调用公共接口导出列表excel

通过调用公共接口导出列表excel

这篇文章不讲述如何使用easypoi导出excel,主要讲述的是如何通过公共接口调用其他接口拿数据。

现在系统有个需求是 对所有列表 都增加一个导出excel功能,为了不对现有的业务带来倾入性 以及 对导出方法方便管理。设计为一个公共接口,前端列表 统一调用该接口,传参 有 要导出的列,要导出的数据(导出当前页数据,导出选中的数据,全部数据则传入数据接口的URL),导出excel集成的是easypoi,这个设置好表头List modelList = new ArrayList<>()和对应的数据List<Map<String, Object>> dataList = new ArrayList<>(),再调用ExcelExportUtil.exportExcel(exportParams, entities, list)就好了。codeSet 代码集是用于这个系统数据转换的。

//表头
List<ExcelExportEntity> modelList = new ArrayList<>();
//数据行
List<Map<String, Object>> dataList = new ArrayList<>();
//代码集
Map<String,Map<String,String>> codeSet = new HashMap<>();
//设置表头
setTableTitle( tableDefinition, modelList, codeSet, requestWrapper, response);
//设置对应数据
setTableData(rowList, tableDefinition, dataList, codeSet, requestWrapper, response);
log.debug("=====>组装数据完成:"+(System.currentTimeMillis()-start));
//组装下载excel文件
ExcelUtils.exportExcel(modelList,dataList,"导出列表.xls",response);
//testExcel(tableDefinition,rowList,response);
log.debug("=====>导出全部数据完成:"+(System.currentTimeMillis()-start));

以为很简单,但是遇到不少坑啊,现在就将遇到的问题做一个记录。

主要问题是导出所有数据,如何通过前台传入的url调用指定的接口拿数据。我是通过在后台维护了一套url和方法对象的映射关系。通过下面的方法:

@Slf4j
@Configuration
public class ActionManager {

	public static Map<String, MethodEntity> actionMap = new HashMap();

	/**
	 * 扫描包
	 */
	@PostConstruct
	private void scan() {
		doAction();
	}

	public void doAction() {
		// 包名且不可忘记,不然扫描全部项目包,包括引用的jar,填写所在的包名称!!!!
		Reflections reflections = new Reflections("cn.***.***.app");
		// 获取带Service注解的类
		Set<Class<?>> typesAnnotatedWith = reflections.getTypesAnnotatedWith(RestController.class);
		//遍历restcontroller类中的url映射方法方法
		searchUrlMethod(typesAnnotatedWith);
		Set<Class<?>> typesAnnotatedWith2 = reflections.getTypesAnnotatedWith(Controller.class);
		//遍历controller类中的url映射方法方法
		searchUrlMethod(typesAnnotatedWith2);
	}

	private void searchUrlMethod(Set<Class<?>> typesAnnotatedWith) {
		for (Class clazz : typesAnnotatedWith) {
			String[] clazzReq = null;
			if(clazz.isAnnotationPresent(RequestMapping.class)){
				clazzReq = ((RequestMapping) clazz.getAnnotation(RequestMapping.class)).value();
			}
			String clazzUrl = "";
			if(clazzReq!=null&&clazzReq.length>0){
				clazzUrl = clazzReq[0];
			}
			Map<String, MethodEntity> methodMap = new HashMap<>();
			Method[] methods = clazz.getDeclaredMethods();
			for (Method method : methods) {
				String[] methUrl = null;
				if (method.isAnnotationPresent(RequestMapping.class)) {
					methUrl = ((RequestMapping) method.getAnnotation(RequestMapping.class)).value();
				}else if(method.isAnnotationPresent(PostMapping.class)){
					methUrl = ((PostMapping) method.getAnnotation(PostMapping.class)).value();
				}else if(method.isAnnotationPresent(GetMapping.class)){
					methUrl = ((GetMapping) method.getAnnotation(GetMapping.class)).value();
				}else{
					continue;
				}
				if (methUrl.length > 0) {
					try {
						//执行方法记录
						MethodEntity methodEntity = MethodEntity.builder().annotation(methUrl[0]).classpath(clazz.getName()).classname(clazz.getSimpleName()).methodname(method.getName()).build();
						actionMap.put(clazzUrl+methUrl[0], methodEntity);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
		}
	}
}

这个方法中 @PostConstruct 表示初始化bean时执行一次注解的方法,扫描指定的包 cn.*.*.app ,找到包含 @RestController@Controller 注解的类,然后遍历方法,将方法的信息封装到 MethodEntity 中,

@Data
@Builder
public class MethodEntity {

	/**
	 * 类名称
	 */
	private String classname;

	/**
	 * 类路径
	 */
	private String classpath;
	/**
	 * 方法名称
	 */
	private String methodname;
	/**
	 * 注解写的value
	 */
	private String annotation;
}

最后将拼接的 url 和封装好的 MethodEntity 放入actionMap 中。
然后就是通过前台传入 url 调用对应的接口了,直接上代码

@Slf4j
public class CallUrlUtil {
	//前台没有传入类似json参数
	public static Object getResultByAction(HttpServletRequest request, HttpServletResponse response) {
		return getResultByAction(request,response,null);
	}

	//前台有传入类似json参数 通过JSONObject[]接收
	public static Object getResultByAction(HttpServletRequest request, HttpServletResponse response, JSONObject[] requestbody) {
		String actionName = RequestUtil.getString(request, "actionName");
		if (StringUtils.isBlank(actionName)) {
			actionName = (String) request.getAttribute("actionName");
		}
		if (StringUtils.isBlank(actionName)) {
			throw new BusinessException("actionName is null!", BaseStatusCode.SYSTEM_ERROR);
		}
		String beanName = "";
		String methodName = "";
		Class<?> clazz = null;
		try {
			//通过指定url找到对应的方法
			MethodEntity methodEntity = ActionManager.actionMap.getOrDefault(actionName, null);
			if (methodEntity == null) {
				return null;
			}
			//获取方法所在类的bean名称
			beanName = LowerUpperCaseFirstLetter.lowerCaseFirstLetter(methodEntity.getClassname());
			//方法名称
			methodName = methodEntity.getMethodname();
			//方法所在的类的字节码对象
			//clazz = SpringContextUtil.getBean(beanName).getClass(); //此方法获取的字节码是代理过的 没含有编译保留的信息
			clazz = Class.forName(methodEntity.getClasspath());
			String finalMethodName = methodName;
			//找到对应的方法对象
			Method mathod = Arrays.stream(clazz.getMethods()).filter(method -> method.getName().equals(finalMethodName)).findAny().get();
			//装配参数
			//获取方法上的参数
			Parameter[] parameters = mathod.getParameters();
			if (parameters.length == 0) {
				return mathod.invoke(SpringContextUtil.getBean(beanName));
			} else {
				List<Object> args = new ArrayList<>(parameters.length);
				assembleParam(request, response, requestbody, parameters, args);
				return mathod.invoke(SpringContextUtil.getBean(beanName), args.toArray());
			}
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		log.debug("============>call controller interface encounter error:" + actionName);
		return null;
	}

	//装配参数
	private static void assembleParam(HttpServletRequest request, HttpServletResponse response, JSONObject[] requestbody, Parameter[] parameters, List<Object> args) {
		for (Parameter parameter : parameters) {
			if (parameter.getType().getSimpleName().equals("HttpServletRequest")) {
				args.add(request);
			} else if (parameter.getType().getSimpleName().equals("HttpServletResponse")) {
				args.add(response);
			} else {
				//string || 基本数据类型
				if (parameter.getType().getSimpleName().equals("String") || parameter.getType().isPrimitive()) {
					String value = "";
					//优先尝试获取参数注解里的值
					RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
					if (requestParam != null && StringUtils.isNoneBlank(requestParam.value())) {
						value = request.getParameter(requestParam.value());
					}
					if(StringUtils.isBlank(value)){
						value = request.getParameter(parameter.getName());
					}
					args.add(value);
				} else {
					try {
						//非基本数据类型尝试装配复杂数据类型
						//判断是否是通过@RequestBody注解
						if (parameter.isAnnotationPresent(RequestBody.class)) {
							if (requestbody == null || requestbody.length == 0) {
								//如果是简单的pojo则尝试通过requestParameter注入
								if(!parameter.getType().getSimpleName().equals("List")){
									args.add(PackObject.getObject(request, parameter.getType()));
								}else{
									args.add(null);
								}
								continue;
							}
							//list的复杂类型
							if (parameter.getType().getSimpleName().equals("List")) {
								List<Object> list = new ArrayList<>();
								//获取list的泛型类型
								ParameterizedType parameterizedType = (ParameterizedType) parameter.getParameterizedType();
								Class<?> actualTypeArgument = (Class<?>) parameterizedType.getActualTypeArguments()[0];
								//通过requestbody构造list
								for (JSONObject jsonObject : requestbody) {
									list.add(JSONObject.parseObject(jsonObject.toJSONString(), actualTypeArgument));
								}
								args.add(list);
							} else {
								//简单的pojo类型
								args.add(JSONObject.parseObject(requestbody[0].toJSONString(), parameter.getType()));
							}
						} else {
							args.add(PackObject.getObject(request, parameter.getType()));
						}
					} catch (Exception e) {
						args.add(null);
					}
				}
			}
		}
	}

}

这里面有个问题是 获取方法所在的类的字节码对象,使用 clazz = Class.forName(methodEntity.getClasspath()); 而不能用 clazz = SpringContextUtil.getBean(beanName).getClass(); 因为此方法获取的字节码是代理过的 没含有编译保留的信息,后面获取方法参数时 Parameter[] parameters = mathod.getParameters(); 则获取到的参数名是没有实际意义的 为arg0,arg1 这样就没办法绑定对应的参数数据了。

最麻烦的就是绑定参数数据这一块了,情况太多肯定没考虑周全,后面再优化了……

最后还有一个 PackObject.getObject(request, parameter.getType()) 这个方法是通过request 封装指定class的对象,代码如下:

public class PackObject<T> {
    Class<T> c;

    public static <T> T getObject(HttpServletRequest request, Class<T> c) {

        T t = null;
        try {
            t = c.newInstance(); // 实例化参数对象
        } catch (InstantiationException e1) {
            e1.printStackTrace();
        } catch (IllegalAccessException e1) {
            e1.printStackTrace();
        }

        @SuppressWarnings("rawtypes")
        Enumeration e = request.getParameterNames(); // 所有请求参数

        Method[] methods = c.getDeclaredMethods(); // 参数对象的所有方法

        // 根据对象的set方法的参数类型去将请求的值做相应转换
        while (e.hasMoreElements()) {
            String paramName = e.nextElement().toString();
            String setParamName = reverseParamName(paramName); //将参数名字转换成set方法名字,如:id 转换成 setId
            
            for (Method method : methods) {
                if (setParamName.equals(method.getName())) {
                    try {
                        Class<?> paramType = (method.getParameterTypes())[0]; //得到set方法参数类型
                        String value = request.getParameter(paramName); 
                        adapter(t, method, paramType, value); //通过适配器将值注入进POJO里面
                    } catch (IllegalArgumentException e1) {
                        e1.printStackTrace();
                    } catch (IllegalAccessException e1) {
                        e1.printStackTrace();
                    } catch (InvocationTargetException e1) {
                        e1.printStackTrace();
                    } catch (SecurityException e1) {
                        e1.printStackTrace();
                    }
                }
            }
        }
        return t;
    }

    private static String reverseParamName(String paramName) {
        char firstChar = paramName.charAt(0);
        char toUpper = Character.toUpperCase(firstChar);
        String setParamName = "set" + String.valueOf(toUpper)
                + paramName.substring(1);
        return setParamName;
    }

    private static <T> void adapter(T t, Method method, Class<?> paramType,
            String value) throws IllegalAccessException,
            InvocationTargetException {
        if (paramType == String.class) {
            method.invoke(t, value);
        } else if (paramType == Integer.class || paramType == int.class) {
            method.invoke(t, Integer.parseInt(value));
        } else if (paramType == Long.class || paramType == long.class) {
            method.invoke(t, Long.parseLong(value));
        } else if (paramType == Boolean.class || paramType == boolean.class) {
            method.invoke(t, Boolean.parseBoolean(value));
        } else if (paramType == Short.class || paramType == short.class) {
            method.invoke(t, Short.parseShort(value));
        } else if (paramType == Float.class || paramType == float.class) {
            method.invoke(t, Float.parseFloat(value));
        } else if (paramType == Double.class || paramType == double.class) {
            method.invoke(t, Double.parseDouble(value));
        } else if (paramType == Character.class || paramType == char.class) {
            char[] cs = value.toCharArray();
            if (cs.length > 1) {
                throw new IllegalArgumentException("参数长度太大");
            }
            method.invoke(t, value.toCharArray()[0]);
        }
    }
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值