基于easypoi实现自定义模板导出excel

x33g5p2x  于2021-12-19 转载在 其他  
字(4.6k)|赞(0)|评价(0)|浏览(456)

项目中需要做一个统计报表功能,实现各种Excel报表数据导出。要求表头能够动态配置,表数据通过存储过程实现,也要求能够动态配置。

技术选型:
由于之前在项目中使用过easypoi,相对于原生apache poi,能够用很少的代码写出Excel导入、导出功能,且API清晰好理解。因此优先选择了使用easypoi,验证功能需求能否实现。easypoi是基于apache poi开发,在此基础上进行了封装和扩展,特别复杂的功能就需要使用基础poi来开发了。

开发指南:https://opensource.afterturn.cn/doc/easypoi.html

实现思路:
由于配置的报表多是复杂多级表头,而easypoi对于动态表头生成只支持两级,简单来说就是表头最多两行,所以这种方式就只能放弃。改选用配置动态模板的方式,先做好模板,然后配置到数据表里。

实现步骤:
Maven pom中引入jar包

<dependency>
	<groupId>cn.afterturn</groupId>
	<artifactId>easypoi-base</artifactId>
	<version>3.0.1</version>
</dependency>
<dependency>
	<groupId>cn.afterturn</groupId>
	<artifactId>easypoi-web</artifactId>
	<version>3.0.1</version>
</dependency>
<dependency>
	<groupId>cn.afterturn</groupId>
	<artifactId>easypoi-annotation</artifactId>
	<version>3.0.1</version>
</dependency>

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<String,Object>格式来接收
        List<Map<String, Object>> reportBodyList =  reportService.getReportBodyData(...);
        Map<String, Object> data = new HashMap<String, Object>();
        data.put("list", reportBodyList);
        // 获取模板文件路径
        // 这里有个很坑的地方,就是easypoi的API只能接收文件路径,无法读取文件流
        String filePath = 服务器上的某个路径或者项目中的某个路径
        // 设置模板路径
        params.setTemplateUrl(filePath);
        // 获取workbook
        Workbook workbook = ExcelExportUtil.exportExcel(params, data);
        // exportFileName代表导出的文件名称
	ReportUtils.export(response, workbook, exportFileName);

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<Map<String, Object>> reportBodyList =  new ArrayList<>();
  Map<String,Object> values = new HashMap<String,Object>();
  values.put(c1,总计);
  values.put(c2,10);
  values.put(c3,5);
  values.put(c4,8);
  values.put(c5,5);
  values.put(c6,8);
  values.put(c7,6);
  values.put(c8,3);
  reportBodyList.add(values);

导出的Excel结果如下:

到目前为止,已经可以实现需求了,但是实现的不够好,尤其是上面提到的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();
}

至此,代码实现较好的满足了动态配置的需要,如果大家有更好的方法,欢迎提出!

------------本文结束感谢您的阅读------------

相关文章

微信公众号

最新文章

更多