概述
AOP实现统一下载报表导出实现过程
- 前言
- ■ 需求
- ■ 解析
- 分解步骤
- 开切
- ■ 1.织入切点
- ①设定一个注解
- ②设定一个切面
- ■ 2.整理参数与数据
- ■ 3.得到报表数据-上
- ① 实体类配置注解
- ②前端传递查询字段(不推荐)
- ③ 后台配置方案(力荐)
- ■ 4.得到报表数据-下
- 最后
前言
注:之前博客的功能都是在业余时间开发,所以博客有带些代码,这个功能思路在业余构造,而后台配置方案源码在上班期间编写,故不作代码开源,以避免面对牢狱编程。
对于开发而言,报表流是最不愿意碰的,无趣,没有逻辑难度,却又极其繁琐。(这话有点得罪专门搞工作流的)不过,无论做哪个行业多多少少都会涉及报表流,躲也躲不掉的。在此,我通过切面思想实现统一报表导出功能,避免单一重复的报表接口编写——原来做报表也能这么有趣。
■ 需求
支持导出本页和导出所有数据接口
■ 解析
看到这个需求
一类码畜——CV开发;先是暗自吐槽一波,然后列出所有需要导出的报表,接着就立马下键盘,一个一个的CV,删删改改,好不辛苦,一问工资2500。这么搞,开发的繁琐不说,后期的新报表估计也要归你编写,而且一旦有新变动,就得一个个改;
二类码畜——抽象开发;嗯?先抽离出共同点,写个工具类,然后一个个写接口,调用工具类。高级一点的就选用某种设计模式,只用一个接口就可供应各种报表下载,抽离出了共同点,但仍然需要写接口,然后引用工具类,后期新报表也需要开发人员进行编写。这么搞,简单了不少,但新报表需求仍然需要进行少量代码编写。后续导出报表都落你手上,只有少量CV,还好说,但落同事手上,又得浪费开发资源咯,因为你写的工具类,同事不一定会用,也不一定会采用。
三类码畜——切面开发;我认为万物皆可切,不滥用切面编程的前提下,切面开发是一个非常棒的解决问题的思想。观摩有报表导出需求的页面,从而提取这些报表导出的共同点——都是数据查询、有分页。
先找切面——嗯。。不就是对数据查询做个增强吗,改数据查询显示前端为数据查询存入Excel;
再找切点——我最早的设想是设定一个注解,把注解作为切入点,标记注解的接口进行织入,对返回的json数据进行解析。接着思考如何区分接口查数据和导出报表,用request带参数?给查询接口加个后缀?思考了很久,一直在想怎样低侵入性嵌入这个参数,直到第二天,突然灵感一闪,嗯?分页,对,分页对象,切service层的特征点返回值分页对象,有的时候懵了,休息一会儿,跳出来看才发现自己钻牛角尖了。这样切的准确,切到的基本都是需求点,并且切面返回值还更接近原始数据格式(分页对象的list属性正是报表所需要的参数),更完美的是新增新报表基本不需要改动代码。
分解步骤
①、织入分页查询
②、获取分页返回值的list
③、筛选字段,进行数据处理
④、返回数据
开切
■ 1.织入切点
①设定一个注解
设定一个注解,供特殊情况织入使用(不一定使用,作预留)
/**
* @author bbq
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface XXAnnotation {
String value() default "";
}
注:元注解
@Target —— ElementType.METHOD,ElementType.TYPE 表示可打在类和方法上
@Retention —— RetentionPolicy.RUNTIME jvm加载class文件之后,仍然存在
@Inherited —— 作用于类,注解会被子类继承
@Documented —— 标识生成的javadoc
②设定一个切面
设定一个切面,切点为service层并且返回值为分页对象的方法 或标有 ①注解的方法
/**
* @author bbq
*/
@Aspect
public class XXAspect {
public static final int MAX_EXPORT_COUNT = 10_000;
// 切点 为service层并且返回值为分页对象的方法 或标有 ①注解的方法
@Around(value = "@annotation(XXAnnotation) || " +
"execution (包名.Page 包名.*.service层.*.*(..))")
public Object XX(ProceedingJoinPoint joinPoint) throws Throwable {
try {
Object[] args = joinPoint.getArgs();
// 判断切点方法的参数,找到参数分页对象,判断请求参数是查询数据还是下载报表
for (int i = 0; i < args.length; i++) {
if (args[i] != null && Page.class.isAssignableFrom(args[i].getClass()) && "1".equals(((Page<T>)args[i]).getIsExportExcel())) {
return excute(joinPoint, (Page<T>) args[i]);
}
}
} catch (Exception e) {
。。。
}
return joinPoint.proceed();
}
public Object excute(...){...}
}
■ 2.整理参数与数据
如果是下载报表进入该方法。
public Object excute(ProceedingJoinPoint joinPoint, Page<T> page) throws Throwable {
// 判断是否下载全部,查询全部则设置查询上限(防止报表行数太多)。
if(page.getPageSize() == -1) {
page.setPageSize(MAX_EXPORT_COUNT);
page.setPageNo(0);
}
// 然后才执行分页查询,并可得出list列表。
Page pageResult = (Page)joinPoint.proceed();
String fileName = "占位符" + DateUtils.getDate("yyyyMMdd-HHmmss") + ".xlsx";
try {
// list有数据即可通过反射获取数据的类,没有数据返回空json,前端作无数据弹窗
Class format = Object.class;
if(pageResult != null && pageResult.getList() != null && pageResult.getList().size() > 0) {
format = pageResult.getList().get(0).getClass();
}
new ExportExcel(format).setDataList(pageResult.getList()).write(page.getResponse(), fileName).dispose();
} catch (IllegalArgumentException e) {
。。。
}
return null;
}
■ 3.得到报表数据-上
得出list列表后,即可调用报表导出工具类,然后就是判断需要导出哪些字段,提供3种方法供你选择。
① 实体类配置注解
这个方法被广泛应用,基本开源报表功能都有带这个功能,大同小异,就是在属性或方法上打注解,然后报表导出工具类就可获取字段信息,一般都是设定注解不可重复,给出可重复注解方法
定义一个新注解ExcelFields
/**
* @author bbq
*/
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelFields {
ExcelField[] value();
}
然后在原注解ExcelField加上这句即可实现多注解。就可在类上放打上一堆注解来映射报表字段
@Repeatable(ExcelFields.class)
再定义一个注解供获取标题使用
/**
* @author bbq
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelTitle {
String value();
}
②前端传递查询字段(不推荐)
我曾考虑过这种导出的字段有前端传参决定,后来认为不太合理便放弃了。
③ 后台配置方案(力荐)
最后我选用了后台配置方案,开发虽然麻烦了点,但一次解决,后期不用作代码改动。在权限最高的后台管理页面加上这个报表配置功能。两个配置管理,这有一个主子表关系,
主表设有以下字段——全路径类名,报表文件名,版本号;
子表设有——字段名,映射的报表标签名,排序。
字段可通过类名反射获取,可抓字段也可抓方法,我抓的是字段和方法都有的,如果抓到的属性是基本类型作为节点返回,抓到的是类则作为父节点返回,注意需要设为懒加载树,如果递归的话会陷入死循环。
如果嫌一个一个配置麻烦的话,可在导出报表切面中增加一个方法,用于通过接口自动生成配置字段信息(得织到json数据上),配置人员只需要勾选需要导出的字段,编写标签名和排序即可。
如果报表导出频繁的话,可以考虑把这些信息加入redis缓存。
■ 4.得到报表数据-下
在之前的切面中,通过反射获取分页对象,即可得list,list有数据即可通过反射获取数据的类。然后就可以通过反射获得的类去寻找我们配置的类,即可获取配置的一切信息,如果一个类不止对应一个报表的话,可引入版本号机制来区分。
有了标题名,字段,字段对应的标签,字段排序等信息,一个报表的配置也就完成了。
通过配置的信息利用报表导出工具类即可导出对应报表。
最后
这样就可通过aop实现一个低维护成本的统一导出简单报表的功能。
最后
以上就是孝顺招牌为你收集整理的AOP实现统一导出报表——原来做报表也能这么有趣的全部内容,希望文章能够帮你解决AOP实现统一导出报表——原来做报表也能这么有趣所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复