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

项目中需要做一个统计报表功能,实现各种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")@ResponseBodypublic 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);// 获取workbookWorkbook 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();}

模板样式:
template
模板以{{$fe:list 开头,以}}结尾,代表变遍历数据的意思,每个字段前面的t.前缀是easypoi指定的默认值。

获取的报表内容字段名称要与模板里的字段一一对应

  List<Map<String, Object>> reportBodyList =  new ArrayList<>();Map<String,Object> values = new HashMap<StringObject>();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结果如下:
excel result

到目前为止,已经可以实现需求了,但是实现的不够好,尤其是上面提到的easypoi无法读取文件流,只能从本地路径上获取文件模板,极大的限制了程序的灵活性。而生产环境中的项目大多都会使用文件存储服务器,比如fastdfs,而不是把模板上传到web服务器上的某个路径下。


还有别的解决办法吗?实在无法实现需求的话就只能使用apache poi了,但是这种方式改动太大,虽然可以灵活定制excel样式,但是实现要复杂的多。思考良久后,决定使用临时文件的方式解决这个问题。

实现思路:
从fastdfs中获取文件流后,写到本地临时目录,然后让easypoi从本地临时目录里读取模板文件,最后再删除临时文件。

关键代码如下:

	@RequestMapping("/exportExcel.html")@ResponseBodypublic 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);// 获取workbookWorkbook 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();
}

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


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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/556212.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vb6 datagrid表格垂直居中_老板不喜欢看你的Excel表格,学完这些美化技巧,早日升职加薪...

Excel报表是工作中经常要制作的&#xff0c;给老板看的表格越是简单明了越好&#xff0c;工作得到认可&#xff0c;给你升职加薪&#xff0c;如果你发给你老板的表格是这样的&#xff1a;对齐方式各种各样&#xff0c;数据看起来也很枯燥&#xff0c;仅需简单4点&#xff0c;轻…

typeof 数据类型转换

typeof 可以转换的数据类型有&#xff1a; number string boolean object undefined function null 原来是给对象占位的&#xff0c;代表空对象。后来JavaScript设计者也没改过来&#xff0c;所以 typeof 一直认为 null 的类型是 object const num null; num // object一、 …

Java接口修饰符详解

接口就是提供一种统一的”协议”&#xff0c;而接口中的属性也属于“协议”中的成员。它们是公共的&#xff0c;静态的&#xff0c;最终的常量。相当于全局常量。抽象类是不“完全”的类&#xff0c;相当于是接口和具体类的一个中间层。即满足接口的抽象&#xff0c;也满足具体…

python深度复制列表_Python-如何深层复制列表?

我对列表副本有一些问题&#xff1a;所以之后我得到了E0来自get_edge&#xff0c;我做的副本E0通过调用E0_copy list(E0)。我猜这里E0_copy是的较深的副本E0&#xff0c;我通过E0_copy了karger(E)。但是在主要功能上。为什么print E0[1:10]for循环之前的结果与for循环之后的结…

数据转换之 Number

看起来不是数组的转换为数字之后就不是数字&#xff08;NaN&#xff09; 只有 才会进行字符串拼接 - * / 都不会 let num Number(123); console.log(typeof(num) : num); // number : 123let demo true; let num Number(demo); console.log(typeof(num) : num); /…

sqlite3修改表内容python_Python sqlite3数据库模块使用攻略

Python作为数据科学主流语言&#xff0c;被广泛用于数据读存、处理、分析、建模&#xff0c;可以说是无所不能。数据一般存放在本地文件或者数据库里&#xff0c;之前介绍过如何使用python读取本地文件&#xff0c;也对# PyMySQL、cx_Oracle等数据库连接库做过简单的使用分享。…

parseInt(string, radix)

一、 变成 整形数字 二、 当字母和数字混杂的时候&#xff0c;从数字位开始看&#xff0c;一直看到非数字位为止&#xff0c;如果这个混杂的字符串不是以数字开头&#xff0c;那么就是 NaN 三、 第二个参数&#xff0c;表示将变量的值以多少进制&#xff08;范围2~32&#xff0…

查看论坛隐藏链接_软连接与硬链接的区别

点击上方蓝色“后端开发杂谈”关注我们, 专注于后端日常开发技术分享硬链接与软连接的联系与区别文件都有文件名和数据, 这在Linux上被分为两部分: 用户数据(user data) 与 元数据(metadata). 用户数据, 即文件数据块( data block), 数据块是记录文件真实内容的地方; 元数据是文…

parseFloat(string)

一、 将字符串转换为浮点数&#xff08;小数&#xff09; 二、 能正常输出小数 三、只看除了一个点以前的非数字位为止 let demo 100.1; let num parseFloat(demo); // 能正常输出小数 console.log(typeof(num) : num); // number : 100.1let demo 100.1.2; let num p…

python的合法语句_Python练习2

问答&#xff1a;1. 写出python中的几种分支结构&#xff0c;并解释其执行过程;(1) if&#xff1a;if 表达式: #条件语句 #输出(2)if...else...if 表达式: #条件语句 #输出else: #其他条件语句 #其他输出(3)if...elif...else...if 表达式: #条件1语…

java日志框架JUL、JCL、Slf4j、Log4j、Log4j2、Logback 一网打尽

为什么程序需要记录日志 我们不可能实时的24小时对系统进行人工监控&#xff0c;那么如果程序出现异常错误时要如何排查呢&#xff1f;并且系统在运行时做了哪些事情我们又从何得知呢&#xff1f;这个时候日志这个概念就出现了&#xff0c;日志的出现对系统监控和异常分析起着…

python继承问题_Python类的继承问题

Python相比较Java&#xff0c;C之类的高级语言是相对来说比较容易的&#xff0c;但能把Python学的很好的人却不多。虽然Python容易&#xff0c;但python的学习道路并不简单&#xff0c;可借助的资料不多&#xff0c;能把python讲的明白的人更是不多。作为一门高级语言&#xff…

String(min)

不管括号里面是啥&#xff0c;都尝试转换为字符串 let demo 1234.1; let num String(demo); console.log(typeof(num) : num); // string : 1234.1let demo undefined; let num String(demo); console.log(typeof(num) : num); // string : undefinedlet demo nu…

SpringBoot项目中Controller层代码编写规范整理

文章目录Controller层代码规范SpringMVC接口定义要注意以下常见的几种问题1. 返回格式不统一2. 没有考虑失败情况3. 出现和业务无关的输入参数Controller层代码规范 主要的内容是就是接口定义里面的内容&#xff0c;你只要遵循里面的规范&#xff0c;controller就问题不大&…

如何从一张图片里取出其中一部分_如何鉴别坑人的锌合金龙头

01.对大部分人而言&#xff0c;锌合金龙头是一个熟悉又陌生的词儿。当我们提起锌合金龙头时&#xff0c;很多人会一脸茫然的回答&#xff1a;啥&#xff1f;锌合金龙头&#xff1f;没听过&#xff01;不认识&#xff01;但在日常生活中锌合金龙头的出现率可不低&#xff01;不信…

Boolean()

一、 尝试把括号里面值转换为布尔值。 二、 只要是六个表示 false 之外的&#xff08;false / null / undefined / NaN / ""&#xff09;&#xff0c;都是 true let demo undefined; let num Boolean(demo); console.log(typeof(num) : num); // Boolean : f…

SpringBoot框架中各层(DTO、DAO、Service、Controller)理解

粗略理解 View层→Controller层&#xff08;响应用户请求&#xff09;→Service层&#xff08;接口→接口实现类&#xff09;→DAO层&#xff0c;即Mapper层&#xff08;抽象类&#xff1a;xxxMapper.java文件&#xff0c;具体实现在xxxMapper.xml&#xff09;→Model层&#…

verilog找不到模块_工欲善其事,必先利其器 verilog编辑器搭建

一款合适的编辑器能够大大提高我们代码的编写速度&#xff0c;而sublime就是一款非常强大的编辑器&#xff0c;它在拥有丰富的插件的同时&#xff0c;也具备非常美型的外观。sublime是一款免费的编辑器&#xff0c;虽然不进行购买的话会时不时地提示购买&#xff0c;但是无视就…

toString(radix)

一、一般用来做进制转换&#xff0c;括号里面就是填进制数 二、 把其它类型的变量转换字符串类型&#xff0c;少用&#xff0c;一般要转换为字符串拼接一个空字符串就可以了 let num 1 ; console.log(typeof(num) : num); // string : 1let demo 1234; let num demo.t…

Springboot中几个层的功能总结

具体的一个web项目中是&#xff1a;Controller层调用了Service层的具体功能方法&#xff0c;Service层调用Dao层的方法&#xff0c;其中调用的参数是使用Entity层进行传递的 下面我们来了解一下各个层的功能及作用: Service层&#xff1a; 业务层–>控制业务 ; service层…