如何经过 Freemark 优雅地生成那些花里胡哨的复杂样式 Excel 文件?

欢迎关注我的微信公众号: 小哈学Java, 文末 分享阿里 P8 高级架构师吐血总结的 《Java 核心知识整理&面试.pdf》资源连接!!

我的网站: https://www.exception.site/essay/how-to-create-complex-style-excel-with-freemarkhtml

freemark生成复杂样式excel

1、背景

小哈最近这段时间开始负责一个新的产品:下载中心。啥玩意这是?java

产品的目的其实就是统一管控各业务组文件下载功能(包括一些海量数据的导出,文件合并上传等),项目组不用本身再去实现各式各样的文件(PDF, Word, Excel)生成, 统一对接下载中心,由下载中心统一完成文件的生成、合并、上传、下载流程。git

问题来了,这里面包括一些复杂文件的生成,如带有复杂样式的 Excel 文件,好比下面这个样子的:程序员

复杂Excel

这种复杂样式的 Excel, 若是说放到各个业务线去实现仍是好办的,由于站在各个业务组的角度,场景变化不会太多,按照文件格式,代码写死便可。github

可是站在下载中心的角度,由于须要对接各个业务中心,每一个业务中心生成的样式都不同,不可能每一个业务组接进来,我都得定制的写一套生成代码吧!这显然也不合常理!面试

那么,有没有什么一劳永逸的办法呢?答案是确定的!spring

2、实现思路

要说实现方式,你的脑海里可能第一会想到传统的 Apache poi,jxl ,亦或者是阿里出品 EasyExcel 等等。数据库

PS: 关于阿里的 EasyExcel, 小哈以前有分享过 ,没看过的小伙伴们,能够看下《 7 行代码优雅地实现 Excel 文件生成&下载功能》

对于这种复杂样式,要是用 Apache poi, jxl, 阿里 EasyExcel 去实现,不可避免的,代码确定会很是繁琐。后端

有没有啥优雅(偷懒的)的方式呢?浏览器

其实咱们能够经过视图引擎 Freemark、Velocity 来帮咱们生成复杂样式 Excel 文件,无需关心花里胡哨的复杂样式,只关注于填充数据便可。接下来,咱们以 Freemark 做为示例来说解,如何生成这个复杂样式的 Excel 文件。

拓展阅读: 什么是 Freemark ?

FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员能够嵌入他们所开发产品的组件。

其实,对于Java 后端来讲,它更常被用来服务端动态渲染 html 页面返回给浏览器。前些年还比较火热,近些年由于先后端分离的火热,也开始慢慢淡出视野了。

3、快速上手

3.1 添加依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
注意: 小哈这里基于 Spring Boot 写的测试代码,版本号能够无需指定,不然,你须要手动指定好版本号。

3.2 导出 xml 模板文件

首先,将复杂样式的 Excel 文件另存为 .xml 视图模板,以下图所示:

打开 xml 模板文件,能够清晰的看到里面定义了各类节点,节点描述了整个 Excel 的样式结构, 以下图所示:

3.3 填充占位符

再回过头来看下以前那个复杂 Excel 文件, 观察一下哪些单元格的值须要动态设置:

图中用红色特地标注出来了。

在刚刚另存为的 xml 模板文件中填写 freemark 表达式,考虑到这里只是个示例 Demo, 仅仅选取几个示例单元格来填写占位符,以下所示:

订单标题:

其余须要动态填充的单元格:

PS: xml 文件中, <Row> 节点表明一行, <Cell>表明一个单元格。

在须要动态填充数据的地方,加上相关 freemark 表达式,如 ${commodity.name!},以下所示:

<Row ss:AutoFitHeight="0" ss:Height="54">
    <Cell ss:StyleID="s18"><Data ss:Type="String">1</Data></Cell>
    <Cell ss:StyleID="s18"><Data ss:Type="String">${commodity.name!}</Data></Cell>
    <Cell ss:StyleID="s18"/>
    <Cell ss:StyleID="s18"><Data ss:Type="String">${commodity.num!}</Data></Cell>
    <Cell ss:StyleID="s18"><Data ss:Type="String">${commodity.num1!}</Data></Cell>
    <Cell ss:StyleID="s18"><Data ss:Type="String">22</Data></Cell>
    <Cell ss:StyleID="s18"><Data ss:Type="String">44</Data></Cell>
    <Cell ss:StyleID="s18"><Data ss:Type="String">55</Data></Cell>
    <Cell ss:StyleID="s18"><Data ss:Type="String">盒</Data></Cell>
    <Cell ss:StyleID="s18"><Data ss:Type="String">${commodity.price!}</Data></Cell>
    <Cell ss:StyleID="s18"><Data ss:Type="String">${commodity.price2!}</Data></Cell>
    <Cell ss:StyleID="s18"><Data ss:Type="String">${commodity.price3!}</Data></Cell>
    <Cell ss:StyleID="s18"><Data ss:Type="String">${commodity.timestamp}</Data></Cell>
    <Cell ss:StyleID="s18"><Data ss:Type="String">${commodity.createTime?string('yyyy-MM-dd HH:mm:ss')}</Data></Cell>
    <Cell ss:StyleID="s18"/>
</Row>

按照服务端数据模型的定义,填写好相应的字段名称,再对照下后台 Commodity 商品类的定义:

这个商品类中,咱们定义了不一样类型的字段,如 String、int、Integer、Double、Float、金额类型 BigDecimal、日期类型 Date 等,用以测试对不一样数据类型的兼容性。

确认相关属性字段名无误后,再来看下 freemark 生成 Excel 的核心代码:

package site.exception.springbootfreemarkexcel;

import freemarker.template.Configuration;
import freemarker.template.Template;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import site.exception.springbootfreemarkexcel.entity.Commodity;

import java.io.File;
import java.io.FileWriter;
import java.math.BigDecimal;
import java.util.*;
/**
 * @author 犬小哈 (微信公众号: 小哈学Java)
 * @site www.exception.site
 * @date 2019/5/21
 * @time 上午10:57
 * @discription
 **/
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootFreemarkExcelApplicationTests {

    @Test
    public void createExcelByFreemark() throws Exception {

        Configuration configuration = new Configuration(Configuration.VERSION_2_3_28);

        // 设置模板文件的父目录
        configuration.setDirectoryForTemplateLoading(new File("/Users/a123123/Work/tmp_files"));

        // 加载模板文件
        Template template = configuration.getTemplate("/excel_template.xml", "UTF-8");

        // 数据准备,能够是从数据库中查询,这里为了方便演示,手动 new 了
        Map<String, Object> data = new HashMap<>();
        data.put("title", "测试标题1");

        List<Commodity> commodities = new ArrayList<>();
        Commodity commodity = Commodity.builder()
                .name("name1").num(11).num1(111)
                .price(new BigDecimal(11.1)).price2(new Double(11.11))
                .price3(new Float(11.1111))
                .createTime(new Date())
                .timestamp(System.currentTimeMillis())
                .build();

        commodities.add(commodity);

        // 生成 excel 文件
        template.process(data, new FileWriter("/Users/a123123/Work/tmp_files/excelByFreemark.xls"));
    }
}

能够看到生成复杂样式的 Excel 的代码很是简洁。关于每一行代码什么意思,注释已经说得很清楚了,这里就不加以说明了。

运行单元测试,看下效果:

完美,在须要填充内容的地方都已经动态设置上了内容。

4、多行数据如何生成?

如何作到动态生成多行呢?其实也很简单,从新打开刚刚修改的 xml 模板文件,在须要动态生成多行的地方,添加 freemark 循环表达式便可:

PS: 关于 Freemark 更多表达式的使用,小伙伴们能够自行在各大搜索引擎中搜索,由于如何使用 Freemark 不是本文关注的重点~

上图中,咱们对后台的 commodities 字段作了循环,因此对应的,后台代码也须要作相关修改:

咱们在 commodities 中添加了两个商品对象。赶快代码跑起来,看看效果!

别急,还有个地方须要作下修改,否则会报错!!

找到 <Table> 节点,有个属性叫 ExpandedRowCount, 它定义了表格行的总数,若是数值与实际的行数对应不上的话,会出问题。

这里咱们添加 Freemark 表达式,总行数为商品 commodites 集合的大小加上 16, 注意:16 为除了动态生成的行数外,固定不变的行数大小,小伙伴们若是使用的是不一样的 xml 模板,须要自行确认好这个数值的大小。

修改完了之后,再次运行单元测试,效果以下:

OK! 大功告成!

5、局限性

经过视图解析器来生成 Excel 的确很优雅(偷懒),同时兼具灵活性。可是它一样存在一些局限性!小伙伴们在技术选型时,须要结合实际的业务场景审视它是否适合。

  • 版本问题:
目前我的测试结果是,在 MAC 系统上仅支持生成 03 版本 Excel, 07 版本存在打不开的状况;
  • 没法写入大批量数据:
视图引擎生成文件没法往 Excel 里面追加数据,因此仅仅适用于数据量不大的个性化 Excel 生成,不然写入大批量数据时,存在内存溢出(OOM)的状况发生;
  • MAC 系统存在生成的 Excel 文件没法编辑保存的状况:
小哈在测试中发现,生成 excel 在 MAC 系统上存在编辑后,没法保存的状况;而 Windows 系统 Microsoft Excel 和 WPS 均可以正常编辑保存;

6、总结

本文中,小哈给你们介绍了如何经过视图引擎优雅的生成 Excel 文件,演示了相关示例代码,以及它的相关局限性,但愿你们看完本文后可以有所收获,下期见哟~

GitHub 示例代码

免费分享 | 面试&学习福利资源

最近在网上发现一个不错的 PDF 资源《Java 核心知识&面试.pdf》分享给你们,不光是面试,学习,你都值得拥有!!!

获取方式: 关注公众号: 小哈学Java, 后台回复资源,既可免费无套路获取资源连接,下面是目录以及部分截图:

关注微信公众号【小哈学Java】,回复【资源】,便可免费无套路领取资源连接哦

关注微信公众号【小哈学Java】,回复【资源】,便可免费无套路领取资源连接哦

关注微信公众号【小哈学Java】,回复【资源】,便可免费无套路领取资源连接哦

关注微信公众号【小哈学Java】,回复【资源】,便可免费无套路领取资源连接哦

关注微信公众号【小哈学Java】,回复【资源】,便可免费无套路领取资源连接哦

关注微信公众号【小哈学Java】,回复【资源】,便可免费无套路领取资源连接哦

重要的事情说两遍,关注公众号: 小哈学Java, 后台回复资源,既可免费无套路获取资源连接 !!!

欢迎关注微信公众号: 小哈学Java

关注微信公众号【小哈学Java】,回复【资源】,便可免费无套路领取资源连接哦

相关文章
相关标签/搜索