使用Freemarker填充模板导出复杂Excel,其实很简单哒!
文章目录
- 1. 需求分析
- 2. 对象生成
- 3. 列表插值
- 4. 另存xml+格式化
- 5. ftl修改
- 6. 程序转化
- 7. 犯的错误
- 8. 总结
1. 需求分析
类似这样的一个表格
我们需要从数据库中查询对应的数据,将其汇总进该表格,并且可能还需要复制表格项,我这个案例中没有,只是一个动态列表,这时候我们可以分如下几个步骤。
我们以面向对象的思想来看, 编号一行 其实是一个列表,而剩余部分,则是表格的主题,这样构建对象的对象组成就是对象中包含一些属性,一个列表,其中一个列表再包含一些属性。
之前我对于这种固定模板,再套动态列表的导出,其实是有点懵的,但工作中碰到了,就要迎难而上,这篇文章就是来做一个梳理总结。
2. 对象生成
生成的对象如下所示
Invoice类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Invoice {// 客户名称private String customerName;// 客户编码private String customerCode;// 日期private Date date;// 总合计大写private String totalAmountInWords;// 合计private BigDecimal totalAmount;// 主管private String supervisor;// 财务private String finance;// 保管员private String custodian;// 经手人private String handler;List<InvoiceItem> invoiceItemList;
}
InvoiceItem类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class InvoiceItem {// 编号private String itemNumber;// 名称及规格private String itemNameAndSpecs;// 单位private String unit;// 数量private int quantity;// 单价private BigDecimal unitPrice;// 金额private BigDecimal amount;// 备注private String comments;}
3. 列表插值
然后先不要将表格转化为xml格式,我们可以先写freemarker表达式,在空位插值,否则等xml已转化,就很不直观了。
插值如下。
可以看到我把中间的空行删了,因为我们会遍历列表,所以那些空行是没有用的。
重要的事!此刻先确定好表格的样式,否则等你转换为ftl,写好循环什么的,再改样式将会变得很困难,特别是更复杂的表格。
4. 另存xml+格式化
接着将对应的表格另存为xml。
用文本打开会发现是这样的,去对应网站进行格式化。
xml格式化
5. ftl修改
我们可以将格式化后的代码复制到该文件 /templates/demo.ftl
你们随意命名即可。
我们找到worksheet这一行,下面的就是我们要更改的地方了。
因为我们只需要遍历列表即可,所以只需要在编号填充的上面引入列表即可,不要忘了在对应的地方结束循环。
6. 程序转化
service代码如下
@Service
@Slf4j
public class ExcelService {/*** 1.查询对象* 2.封装map* 3.指定模板与导出文件名称* 4.响应设置 不设置会导致乱码 文件格式出错等问题* 5.模板配置* 6.变量替换* 7.文件输出* @param id 商品id*/public void export(Integer id, HttpServletResponse response) {//这里假装查询对应的对象 我处于方便直接让ai生成了Invoice invoice = getById(1);Map<String, Object> model = new HashMap<>();//要与ftl中的对象一致model.put("invoice", invoice);// 指定FreeMarker模板文件的位置String templateFilePath = "demo.ftl";String excelName = "商品出库单" + ".xlsx";FileInputStream inputStream = null;ServletOutputStream outputStream = null;File file = null;// 使用FreeMarker加载模板文件try {response.reset();response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(excelName, "UTF8"));response.setCharacterEncoding("utf-8");response.setContentType("application/vnd.ms-excel;charset=utf-8");Configuration cfg = new Configuration(Configuration.getVersion());// 指定FreeMarker模板文件的位置String path = "/templates/";cfg.setClassForTemplateLoading(getClass(), path);Template template = cfg.getTemplate(templateFilePath);file = new File(excelName);FileWriter out = new FileWriter(excelName);// 变量替换template.process(model, out);// 将文件输出到response,返回给客户端inputStream = new FileInputStream(file);byte[] buffer = new byte[inputStream.available()];inputStream.read(buffer);inputStream.close();outputStream = response.getOutputStream();outputStream.write(buffer);outputStream.flush();outputStream.close();} catch (Exception e) {log.error("出库单导出失败", e);throw new RuntimeException(e);}}public static Invoice getById(Integer id) {// 创建一个 Invoice 对象Invoice invoice = new Invoice();invoice.setCustomerName("客户名称示例");invoice.setCustomerCode("客户编码示例");invoice.setDate(new Date());invoice.setTotalAmountInWords("总合计大写示例");invoice.setTotalAmount(new BigDecimal("1000.00"));invoice.setSupervisor("主管示例");invoice.setFinance("财务示例");invoice.setCustodian("保管员示例");invoice.setHandler("经手人示例");// 创建并填充 5 个 InvoiceItem 对象,然后添加到 Invoice 的列表中List<InvoiceItem> invoiceItems = new ArrayList<>();for (int i = 1; i <= 5; i++) {InvoiceItem item = new InvoiceItem();item.setItemNumber("编 号 " + i);item.setItemNameAndSpecs("名称及规格示例 " + i);item.setUnit("单位示例");item.setQuantity(10 * i);item.setUnitPrice(new BigDecimal("10.00"));item.setAmount(new BigDecimal("100.00"));item.setComments("备注示例 " + i);invoiceItems.add(item);}invoice.setInvoiceItemList(invoiceItems);return invoice;}}
controller 调用
@RestController
public class ExportController {@Autowiredprivate ExcelService excelService;@GetMapping("/export")public void export(Integer id, HttpServletResponse response) {excelService.export(1, response);}
}
生成的表格如下所示,可以看到,效果还不错的。
7. 犯的错误
但是!!!!!
你以为这就完了吗? 我之前做的表格比这复杂很多,导致遇到了很多错误。
比如文件路径错误,模板错误excel格式不支持错误。样式不整齐,乱码,读出错误。
现在一一列举下我犯的错,所以这不单单是一个教学贴,也是对我错误的一个总结。
1.文件路径错误。
在写这段代码的时候,由于路径藏的很深,且我以为第二个参数不能直接这么用,要填写全路径,导致文件一致读取失败,现在明白了,只需要直接填充根路径往下的内容即可。
// 指定FreeMarker模板文件的位置String path = "/templates/";cfg.setClassForTemplateLoading(getClass(), path);
2.对异常的不关注。
之前读取文件失败,还有个原因是ftl语法错误,但我打的异常并不明显,导致我一直没看到这个错误,计算把文件路径修复了,还是会报错,其实是找错了方向,提醒大家要注意异常,当时是日志太多了,确实忽略了这一点。
3.excel文件转化打不开。
其中一点,我们打开之前的xml文件,找到这两个字段
建议将这两个值改的大一点,否则文件格式会出问题,我这里是因为数据量少没出问题,但真实使用就不一定了,改个几百几千都可以。
还有一点,之前xml进行了格式化,但到了idea中,头文件内容错乱了,这点才导致文件无法打开,建议从之前的xml模板中复制头文件,在原基础上修改即可。
4.乱码
特别是中文命名的文件,一定要设置utf8格式,之前的response的设置可不是白加的。
5.freemarker空值问题
一旦某一个地方,比如日期,列表之类的为空,整个渲染就报错了,所以一定要提前判断某些地方会不会为null,提前设置默认值,一般来讲在表达式后面加!即可,当然只针对于字符串什么的。
6.遇到bug时的盲目
那时因为需求急,就算碰到了问题,也是一个劲的百度,cv大法,其实那时候静下心来,稍微放松一下,理清思路,也许很快就能解决问题,但知易行难,真正做起来才发现很有难度。
8. 总结
虽然我以前诟病freemarker很垃圾,只要一个语法出问题,就生成不了html页面了,但相比于用poi将复杂的excel导出,freemarker其实更好用,并且复杂的excel其实就是在原基础上做了更多的填充,字段复制罢了,原理不复杂,但对于没做过的人来说,可能会稍微有点恐惧,我的案例告诉你,只要咬牙迈过去就好了。
一个人最大的幸福莫过于在人生的中途、富有创造力的壮年,发现自己此生的使命。
相关文章:

使用Freemarker填充模板导出复杂Excel,其实很简单哒!
文章目录 1. 需求分析2. 对象生成3. 列表插值4. 另存xml格式化5. ftl修改6. 程序转化7. 犯的错误8. 总结 1. 需求分析 类似这样的一个表格 我们需要从数据库中查询对应的数据,将其汇总进该表格,并且可能还需要复制表格项,我这个案例中没有&a…...
windows环境下安装logstash同步数据,注册系统服务
windows环境下安装logstash同步数据,注册系统服务 此方法适用于Windows环境,同一个配置文件配置多个管道,并且配置系统服务,防止程序被杀进程 一、安装logstash (1)下载压缩包,解压后修改con…...

java服务内存说明及配置详解
java进程内存 JVM内存分布图: 【java进程内存】【堆外内存】 【jvm堆内存】 【堆外内存】 【Metaspace】 【Direct Memory】【JNI Memory】【code_cache】 … 堆外内存泄漏的排查在于【本地内存(Native Memory)】【Direct Memory】【JNI Memory】 一般…...

Mybatis-MyBatis的缓存
Mybatis-MyBatis的缓存 一、MyBatis的一级缓存二、MyBatis的二级缓存二级缓存的相关配置 三、MyBatis缓存查询的顺序 一、MyBatis的一级缓存 一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就 会从…...

计算机组成原理之硬件的基本组成,深入介绍两大计算机结构体系,从底层出发认识计算机。
大家好,欢迎阅读《计算机组成原理》的系列文章,本系列文章主要的内容是从零学习计算机组成原理,内容通俗易懂,大家好好学习吧!!! 更多的优质内容,请点击以下链接查看哦~~ ↓ ↓ ↓ …...

二十五、MySQL事务的四大特性和常见的并发事务问题
1、事务的四大特性 2、常见的并发事务问题 (1)并发事务问题分类: (2)脏读: 一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致的状态;…...
辨析常见的医学数据分析(相关性分析回归分析)
目录 1 常见的三种分类结果? 2 什么是相关性分析? 相关性分析的结果怎么看? 3 什么是回归分析? 1)前提 2)常见的回归模型 4 对于存在对照组实验的医学病例如何分析? 1)卡方检验…...
SpringBoot项目中只执行一次的任务写法
SpringBoot项目中只执行一次的任务写法 有时候我们需要进行初始化工作,就说明只要进行一次的工作,那么,在Springboot项目中如何做到任务只进行一次呢 利用定时任务 在Spring Boot项目中,你可以使用Spring框架提供的Scheduled注解…...
TCK、TMS、TDI、TDO的含义
这四个信号是JTAG(Joint Test Action Group)界面的一部分。JTAG是一种用于测试和验证集成电路和印刷电路板的技术,也用于进行设备编程和调试。这四个信号分别是: TCK (Test Clock): 意义:测试时钟ÿ…...

R语言RSTAN MCMC:NUTS采样算法用LASSO 构建贝叶斯线性回归模型分析职业声望数据...
全文链接:http://tecdat.cn/?p24456 如果你正在进行统计分析:想要加一些先验信息,最终你想要的是预测。所以你决定使用贝叶斯(点击文末“阅读原文”获取完整代码数据)。 相关视频 但是,你没有共轭先验。你…...

【PowerShell】PowerShell的Core版本的额外配置
在PowerShell 7.1 安装完成后,默认情况下打开PowerShell 会直接进入到系统内置的PowerShell,如果希望通过远程连接或者PowerShell Web Access 进入到PowerShell 7环境的界面,就需要进行环境的再配置才能实现PowerShell 7.1 的环境连接。需要为外部的环境提供连接的话需要按照…...

数据结构----链式栈
目录 前言 链式栈 操作方式 1.存储结构 2.初始化 3.创建节点 4.判断是否满栈 5.判断是否空栈 6.入栈 7.出栈 8.获取栈顶元素 9.遍历栈 10.清空栈 完整代码 前言 前面我们学习过了数组栈的相关方法,(链接:线性表-----栈(栈…...

实在智能携手40+央企,探索财务大模型及数智化实践与应用
“这次培训给我一个最大的感触就是,过去以为AI智能化、大模型技术是很高深的事情。但现在,我们通过RPA等数字化工具,自主根据自己的工作岗位,完成业务自动化流程的开发和设计。AI技术没有想象中的那么难入门。” 这是一位参加了“…...

upload-labs文件上传1-5关
第一关 编写一句话木马1.php,编写完成后将后缀名修改为png 将1.png上传,上传时使用bp抓包 抓包后将后缀名修改为png 连接蚁剑 第二关 上传1.php,显示文件类型不正确 使用bp抓包发送重发器,修改文件后缀名后点击发送,…...
git的基本使用
查看当前分支 git branch //查看本地分支 git branch -a // 查看本地和远程的分支切分支 git checkout -b 分支的名字从当前分支切换到其他分支 拉取远程分支到本地 拉取远程develop分支代码到本地develop分支 git checkout -b develop origin/developgit merge B分支合并…...

Mac台式电脑内存清理方法教程
对于一些小白用户,如果觉得以上的清理方法比较复杂却又想要更好的优化Mac电脑内存,专业的系统清理软件是一个不错的选择。比起花几个小时时间浏览文件夹、删除临时文件、缓存和卸载残留。Cleanmymac X,只需单击几下即可完成所有内存清理工作&…...

FL Studio怎么破解?2023年最新FL Studio 21图文安装激活教程?FL 21中文版下载 v21.1.1.3750 汉化 版
fl studio21中文解锁特别破解版是一款功能强大的编曲软件,也就是众所熟知的水果软件。它可以编曲、剪辑、录音、混音,让您的计算机成为全功能录音室。除此之外,这款软件功能非常强大,为用户提供了许多音频处理工具,包含…...
Zookeeper高级_四字命令
之前使用stat命令来验证ZooKeeper服务器是否启动成功,这里的stat命令就是ZooKeeper 中最为典型的命令之一。ZooKeeper中有很多类似的命令,它们的长度通常都是4个英文字母,因此我们称之为“四字命令”。 添加配置 vim zoo.cfg 4lw.commands…...

/usr/bin/ld: cannot find -lmysqlcllient
文章目录 1. question: /usr/bin/ld: cannot find -lmysqlcllient2. solution 1. question: /usr/bin/ld: cannot find -lmysqlcllient 2. solution 在 使用编译命令 -lmysqlclient时,如果提示这个信息。 先确认一下 有没有安装mysql-devel 执行如下命令 yum inst…...

折线图geom_line()参数选项
往期折线图教程 图形复现| 使用R语言绘制折线图折线图指定位置标记折线图形状更改 | 绘制动态折线图跟着NC学作图 | 使用python绘制折线图 前言 我们折线的专栏推出一段时间,但是由于个人的原因,一直未进行更新。那么今天,我们也参考《R语…...
Axios请求超时重发机制
Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式: 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...
聊一聊接口测试的意义有哪些?
目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开,首…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...

使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...

处理vxe-table 表尾数据是单独一个接口,表格tableData数据更新后,需要点击两下,表尾才是正确的
修改bug思路: 分别把 tabledata 和 表尾相关数据 console.log() 发现 更新数据先后顺序不对 settimeout延迟查询表格接口 ——测试可行 升级↑:async await 等接口返回后再开始下一个接口查询 ________________________________________________________…...

三分算法与DeepSeek辅助证明是单峰函数
前置 单峰函数有唯一的最大值,最大值左侧的数值严格单调递增,最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值,最小值左侧的数值严格单调递减,最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...
WebRTC从入门到实践 - 零基础教程
WebRTC从入门到实践 - 零基础教程 目录 WebRTC简介 基础概念 工作原理 开发环境搭建 基础实践 三个实战案例 常见问题解答 1. WebRTC简介 1.1 什么是WebRTC? WebRTC(Web Real-Time Communication)是一个支持网页浏览器进行实时语音…...

通过 Ansible 在 Windows 2022 上安装 IIS Web 服务器
拓扑结构 这是一个用于通过 Ansible 部署 IIS Web 服务器的实验室拓扑。 前提条件: 在被管理的节点上安装WinRm 准备一张自签名的证书 开放防火墙入站tcp 5985 5986端口 准备自签名证书 PS C:\Users\azureuser> $cert New-SelfSignedCertificate -DnsName &…...
学习一下用鸿蒙DevEco Studio HarmonyOS5实现百度地图
在鸿蒙(HarmonyOS5)中集成百度地图,可以通过以下步骤和技术方案实现。结合鸿蒙的分布式能力和百度地图的API,可以构建跨设备的定位、导航和地图展示功能。 1. 鸿蒙环境准备 开发工具:下载安装 De…...