导出所涉及到的技能点
- servlet规范
- 面向接口编程
- 面向bean编程
- http协议的了解
- spring aop
- @Value注解的使用
- spring spel表达式引擎
- poi第三方依赖的api使用
- groovy 安全占位符(?.)
导出是一个很常见的功能,基本上每个后台管理的项目都会用到.
导出功能的实现一般会有三种方式的实现.
- 硬编码每个业务+poi代码,就是每个业务代码如果要有导出的需求,会有实施手动写代码与代码进行死耦合.
- 将导出部分的代码进行封装,每个业务调用公共类,实施写少量的代码进行硬编码.
- eap实现了导出部分的代码不需要写的地步,只需要写一些配置参数,eg: 导出文件名 文件头等等.
eap,进行导出,只需要在一个特定的表添加一行导出配置
INSERT INTO `t_system_system_export` (
`id`,
`content`, ##excel导出正文的字段申明
`file_name`, ##excel导出的文件名
`title`, ## excel的表头
`url`, ## 请求的uri
`data` ## 数据的位置
)
VALUES
(
'3',
'#root.id;#root.name;@exportG.yesNo(#root?.enabled);#root?.createdDate',
'角色列表',
'编号;角色名;状态;创建日期',
'/system/role/index.sp',
'#root[page]?.result'
) ;
这是一个角色表的导出配置. 导出后的结果大概是下面的样子
编号 | 角色名 | 状态 | 创建时间 |
---|---|---|---|
3 | 超级管理员 | 是 | 2017-02-02 12:00:00 |
然后在前台表单页面添加一个导出的按钮,导出这个功能就ok了.
以下开始介绍功能实现细节
浏览器发起一个请求,查询当前角色为正常的列表,这个请求的url可以是这样的
http://127.0.0.1:8085/system/role/index.sp?enabled=1
,
如果要将这页导出,只需要在请求后面追加一个请求参数.最终的url是
http://127.0.0.1:8085/system/role/index.sp?enabled=1&export=excel
↓
导出拦截器aop策略拦截所有带注解@RequestMapping的方法.同样会拦截到此请求.
↓
判断是否是导出请求,如果带export参数,根据请求的uri到库里查询对应的url是`system/role/index.sp` 的记录.
↓
这里预留了接口申明,ExportHelper,根据export=excel进行excel的实现类调用,以后可以添加csv的实现.
↓
ExportHelper exportHelper = new ExcelExportHelper();
↓
由于下载文件的特殊性,修改http response的MIME 申明application/vnd.ms-excel,使浏览器弹出下载确认框.添加文件名报文头,由于http协议的原因,需要将中文文件名转换为iso-8859-1编码.
↓
修改page的分页信息,进行库里数据循环批量查询
↓
执行被拦截的方法ProceedingJoinPoint.proceed(argNew) 返回结果ModelAndView.
↓
根据导出配置的data字段配置,从ModelAndView 取出数据集集合,根据字段的配置定义,取出对应的数据(spel),调用exportHelper.write()将数据写入到poi对象中.如果查到数据库里还有未导出的数据,再循环进行查询导出.
↓
将poi对象中的数据写入response流中.再flush一下,导出流程就结束了
所需要做的仅仅只是在数据库添加一行配置.需要掌握spel表达式.
比如,有的字段在库里存的是1234,但是显示的时候是不同的状态码. 比如角色的状态码 ,在库里 0 无效 1 有效 但是导出的时候不能是0和1 要进行必要的转换.
@exportG.yesNo(#root?.enabled)
这里@符号是调用spring bean进行处理看一下source code
@Component("exportG")
public class ExportTrasform {
@Value("${application.name:#{null}}")
public String APPLICATION_NAME;
@Autowired
private DefaultService defaultService;
public static String nothing(String value) {
return value;
}
public static String param(String value, String paramName) {
return QUtil.tran(value, paramName);
}
public static String yesNoontrary(String value) {
return "0".equals(value) ? "是" : "否";
}
public static String yesNo(String value) {
return "0".equals(value) ? "否" : "是";
}
}
html改造
在集成eap进行开发时,所需要的只需要拉一个导出组件到合适的位置.进行几个属性值的填充.开发框架会维护剩下的一切.
如果不使用页面编辑器,需要在页面添加一个按钮,绑定事件
$(".export").click(function () {
//找到导出按钮的所有form祖先里的第一个form祖先
var fm = $(this).closest("form");
if (!fm) return ;
var target = fm.attr("target");
var _action = fm.attr("action");
if (!_action) return;
var action = _action
if (action.indexOf('?')>-1)
action += "&";
else
action +="?";
action += "export=excel";
//修改target 新标签页打开
fm.attr("target","_blank");
fm.attr("action",action);
fm.submit();
setTimeout(function () {
if (target)
fm.attr("target",target);//之前修改了target属性,再修改回去
else
fm.removeAttr("target");
fm.attr("action",_action);
},100);
});
限制
- 表单元素里不能有input 的name为export,避免冲突
- 导出支持同步ModelAndView,暂时不支持异步的ajax,即添加@ReponseBody的方法,没有做兼容
- 导出按钮必须包含在form表单元素里面
- 跨行跨列不在考虑的范围内