最近工作上有一个小需求,需要将内部管控平台上的报表增加数据导出功能,工程内部没有找到现成可用的工具包,于是决定自己手动实现一个。本次实现主要借鉴了网上现有的一些excel导出的代码,并对其进行了一些改动,使其满足用于不需要去关心怎么在excel中填装对象,只需要为数据报表的行定义一个对象,将数据报表以列表的形式传参进去,就可以直接生成通用的excel格式的数据文件。
首先,我们需要导入依赖的包
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.12</version>
</dependency>
这里我们通过java的反射功能,从对象中获取它的字段,将字段的字段名作为excel的列名,然后遍历每个对象,通过反射将对象中的每个字段值取出来填进excel对象中,这里我们不对java的反射原理做过多的介绍,感兴趣的同学可以自行查阅相关的资料,这里直接给出相关的代码:
public class ExcelUtil<T> {
public static <T> HSSFWorkbook export(List<T> list, String sheetName){
if(CollectionUtils.isEmpty(list)){
return null;
}
T item = list.get(0);
Class clazz = item.getClass();
//取出所有的字段
List<Field> fieldList = Lists.newArrayList(clazz.getDeclaredFields());
//没有有效声明字段
if(CollectionUtils.isEmpty(fieldList)){
return null;
}
HSSFWorkbook wb = new HSSFWorkbook();
sheetName = sheetName == null ? clazz.getSimpleName() : sheetName;
HSSFSheet sheet = wb.createSheet(sheetName);
HSSFRow row = sheet.createRow(0);
//取字段名作为excel的列名
for(int i = 0; i < fieldList.size(); i++){
HSSFCell cell = row.createCell(i);
cell.setCellValue(fieldList.get(i).getName().toUpperCase());
sheet.autoSizeColumn(i);
}
//遍历行对象
for(int i = 0; i < list.size(); i++){
row = sheet.createRow(i + 1);
T obj = list.get(i);
//遍历字段,通过反射获取对象对应字段的值,填入excel对象中
for(int j = 0; j < fieldList.size();j++){
Field field = fieldList.get(j);
String value = "";
try{
field.setAccessible(true);
Object o = field.get(obj);
if(o == null){
value = "";
}else{
value = o.toString();
}
}catch (IllegalAccessException e){
LogHolder.RUN_LOG.error("[ExcelUtil] unhandled object, obj:" + obj.getClass());
}
row.createCell(j).setCellValue(value);
}
}
return wb;
}
}
这里list中的对象的字段类型只支持java的基本数据类型,不支持复杂的复合类型,在使用时,以excel的行作为对象的数据结果即可。
这里给出一个excel样例,报表的形式如下图:
对应的java数据类型如下:
public class SmsStatisExcelMeta {
private String msgDate;
private String msgTypeTitle;
private Integer sourceType;
private String sourceTypeName;
private Integer suc;
private Integer fail;
private Integer error;
private Integer sucPhoneNum;
private Integer failPhoneNum;
private Integer errorPhoneNum;
private Double fee;
}
在生成excel文件之后,我们还希望在前端可以直接一件点击下载,这里我们给出前后端的代码。
前端构建请求参数,发送请求:
dlexcel:function () {
var data = this.data;
var _add2 = {};
_add2.searchType = $("#searchType").val();
_add2.appType = $("#appType").val();
_add2.sourceType = $("#sourceType").val();
_add2.msgType = $("#msgType").val();
_add2.startDate = data.condition.startDate;
_add2.endDate = data.condition.endDate;
var url = "sms/export?"
var count = 0;
Object.keys(_add2).forEach(function(key){
if(_add2[key] != ""){
if(count == 0){
url = url + key + "=" + _add2[key];
}else{
url = url + "&" + key + "=" + _add2[key];
}
count++;
}
});
location.href = url;
}
后端接收请求,查库,生成excel文件,并返回文件:
@RequestMapping("export")
public void exportExcel(String searchType, Integer appType,
Integer sourceType, Integer msgType,
String startDate, String endDate,
HttpServletResponse response) throws Exception{
SmsStatisticsSearchParam param = createSearchParam(searchType,appType,sourceType,msgType,startDate,endDate);
ResultObj<List<SmsStatisticsSearchResult>> searchResult = smsStatisticsFacade.searchAll(param);
HSSFWorkbook wb;
if(!searchResult.hasException()){
List<SmsStatisticsSearchResult> list = searchResult.getObj();
List<SmsStatisExcelMeta> metas = convertExcelMeta(list);
wb = ExcelUtil.export(metas,null);
}else{
wb = new HSSFWorkbook();
wb.createSheet("smsStatis");
}
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-disposition", "attachment;filename=smsStatis.xls");
OutputStream ouputStream = response.getOutputStream();
wb.write(ouputStream);
ouputStream.flush();
ouputStream.close();
}