项目中需要做一个统计报表功能,实现各种Excel报表数据导出。要求表头能够动态配置,表数据通过存储过程实现,也要求能够动态配置。
技术选型:
由于之前在项目中使用过easypoi,相对于原生apache poi,能够用很少的代码写出Excel导入、导出功能,且API清晰好理解。因此优先选择了使用easypoi,验证功能需求能否实现。easypoi是基于apache poi开发,在此基础上进行了封装和扩展,特别复杂的功能就需要使用基础poi来开发了。
开发指南:https://opensource.afterturn.cn/doc/easypoi.html
实现思路:
由于配置的报表多是复杂多级表头,而easypoi对于动态表头生成只支持两级,简单来说就是表头最多两行,所以这种方式就只能放弃。改选用配置动态模板的方式,先做好模板,然后配置到数据表里。
实现步骤:
Maven pom中引入jar包
cn.afterturn
easypoi-base
3.0.1
cn.afterturn
easypoi-web
3.0.1
cn.afterturn
easypoi-annotation
3.0.1
ReportController类: 如下代码仅显示主要步骤:
@RequestMapping("/exportExcel.html")
@ResponseBody
public void exportExcel(HttpServletResponse response, HttpSession session) {
// 获取报表配置 ReportResultVo主要存储了 标题行数、模板路径位置、导出文件名称等
ReportResultVo config = reportService.getReportConfig(id);
TemplateExportParams params = new TemplateExportParams();
// 标题开始行
params.setHeadingStartRow(0);
// 标题行数
params.setHeadingRows(config.getHeadRowNum());
// 设置sheetName,若不设置该参数,则使用得原本得sheet名称
params.setSheetName("数据统计");
// 获取报表内容
// 因为表数据是根据存储过程来实现的,不同的报表有不同的配置,
// 所以使用Map格式来接收
List
ReportUtils类:
// Excel 导出 通过浏览器下载的形式
public static void export(HttpServletResponse response, Workbook workbook, String fileName) throws IOException {
response.setHeader("Content-Disposition",
"attachment;filename=" + new String(fileName.getBytes("UTF-8"), "iso8859-1"));
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
BufferedOutputStream bufferedOutPut = new BufferedOutputStream(response.getOutputStream());
workbook.write(bufferedOutPut);
bufferedOutPut.flush();
bufferedOutPut.close();
}
模板样式:
模板以{{$fe:list
开头,以}}
结尾,代表变遍历数据的意思,每个字段前面的t.
前缀是easypoi指定的默认值。
获取的报表内容字段名称要与模板里的字段一一对应
List
到目前为止,已经可以实现需求了,但是实现的不够好,尤其是上面提到的easypoi无法读取文件流,只能从本地路径上获取文件模板,极大的限制了程序的灵活性。而生产环境中的项目大多都会使用文件存储服务器,比如fastdfs,而不是把模板上传到web服务器上的某个路径下。
还有别的解决办法吗?实在无法实现需求的话就只能使用apache poi了,但是这种方式改动太大,虽然可以灵活定制excel样式,但是实现要复杂的多。思考良久后,决定使用临时文件的方式解决这个问题。
实现思路:
从fastdfs中获取文件流后,写到本地临时目录,然后让easypoi从本地临时目录里读取模板文件,最后再删除临时文件。
关键代码如下:
@RequestMapping("/exportExcel.html")
@ResponseBody
public void exportExcel(HttpServletResponse response, HttpSession session) {
......
try{
// 从fastDfs上获取文件流 (fileStorage.readFile自己封装的API)
InputStream inputStream = fileStorage.readFile(filepath);
// 模板临时目录
String rootPath = session.getServletContext().getRealPath(“template_temp/”);
// 临时文件路径名
String filePath = rootPath + "_" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + filename;
tempFile = new File(filePath);
// 保存到临时文件
ReportUtils.saveTempFile(inputStream, tempFile);
// 设置模板路径
params.setTemplateUrl(filePath);
// 获取workbook
Workbook workbook = ExcelExportUtil.exportExcel(params, data);
// exportFileName代表导出的文件名称
ReportUtils.export(response, workbook, exportFileName);
} catch (Exception e) {
throw new GeneralException(ErrorCode.REPORT_EXPORT_EXCEPTION);
} finally {
// 删除临时文件
if (tempFile.exists()) {
tempFile.delete();
}
}
}
ReportUtils类:
// 保存到临时目录
public static void saveTempFile(InputStream inputStream, File tempFile) throws IOException {
if(!tempFile.getParentFile().exists()){ //如果文件的目录不存在
tempFile.getParentFile().mkdirs(); //创建目录
}
OutputStream os = new FileOutputStream(tempFile);
byte[] b = new byte[2048];
int length;
while ((length = inputStream.read(b)) > 0) {
os.write(b, 0, length);
}
os.flush();
os.close();
inputStream.close();
}
至此,代码实现较好的满足了动态配置的需要,如果大家有更好的方法,欢迎提出!