在上一篇博客中主要是使用SpringBoot+Apache POI实现了BOM物料清单Excel表格导出,详见以下博客:
Spring Boot + Apache POI 实现 Exc()el 导出:BOM物料清单生成器(支持中文文件名、样式美化、数据合并)
目录
引言
项目结构
源代码展示
1.WordController
2.WordUtil工具类
3.FreeMarker模版
4.POM依赖
WordController类深度解析
1.类结构
2.main方法
3.generateWordFile方法
4.addTestData方法
WordUtil类深度解析
1.类结构和静态成员
2.静态初始化块
3.私有构造函数
4.exportMillCertificateWord方法
5.createDoc方法
6.WordUtil类总结
FreeMarker模板深度解析
1.文档结构和样式
2.表格结构和动态数据插入
总结
在电缆行业,生成供货清单是一项常见但繁琐的任务。本教程将介绍如何使用现代Java技术栈自动化这一过程,大幅提高工作效率和准确性。我们将使用SpringBoot作为框架,Apache POI处理Word文档,以及FreeMarker作为模板引擎来实现这一功能!
让我们先了解一下这个问题的背景:
为了解决这个问题,我们提出了一个技术方案,结合了以下几个关键技术:
这个方案的主要优势包括:
通过阅读这篇博客,您将学习如何实现这个解决方案,从而帮助您或您的团队简化工作流程,提高生产效率。
效果图:
src/
├── main/
│ ├── java/
│ │ └── com/
│ │ └── pw/
│ │ ├── WordController.java #负责生成测试数据并调用WordUtil工具类来生成Word文档
│ │ └── utils/
│ │ └── WordUtil.java #这个工具类封装了使用FreeMarker生成Word文档的核心功能
│ └── resources/
│ └── templates/
│ └── template.ftl #模版定义了Word文档的结构和样式,使用HTML和CSS来格式化内容
1.WordController类:这个类是我们应用的入口点,负责生成测试数据并调用WordUtil来生成Word文档。
2.WordUtil类:这个工具类封装了使用FreeMarker生成Word文档的核心逻辑。
3.FreeMarker模版(template.ftl):这个模版定义了Word文档的结构和样式,使用HTML和CSS来格式化内容。
import com.pw.utils.WordUtil;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class WordController {
public static void main(String[] args) throws IOException {
// 指定保存Word文件的目录
String filePath = "F:\\Poi2Word\\src\\main\\resources\\output"; // 更改为您希望的目录
new WordController().generateWordFile(filePath);
}
public void generateWordFile(String directory) throws IOException {
List
package com.pw.utils;
import freemarker.template.Configuration;
import freemarker.template.Template;
import java.io.*;
import java.util.Map;
public class WordUtil {
private static Configuration configuration = null;
// 模板文件夹路径
private static final String templateFolder = WordUtil.class.getResource("/templates").getPath();
static {
configuration = new Configuration();
configuration.setDefaultEncoding("utf-8");
try {
System.out.println(templateFolder);
configuration.setDirectoryForTemplateLoading(new File(templateFolder)); // 设置模板加载路径
} catch (IOException e) {
e.printStackTrace();
}
}
private WordUtil() {
throw new AssertionError(); // 防止实例化
}
/**
* 导出Word文档
* @param map Word文档中参数
* @param wordName 模板的名字,例如xxx.ftl
* @param fileName Word文件的名字 格式为:"xxxx.doc"
* @param outputDirectory 输出文件的目录路径
* @param name 临时的文件夹名称,作为Word文件生成的标识
* @throws IOException
*/
public static void exportMillCertificateWord(String outputDirectory, Map map, String wordName, String fileName, String name) throws IOException {
Template freemarkerTemplate = configuration.getTemplate(wordName); // 获取模板文件
File file = null;
try {
// 调用工具类的createDoc方法生成Word文档
file = createDoc(map, freemarkerTemplate, name);
// 确保输出目录存在
File dir = new File(outputDirectory);
if (!dir.exists()) {
dir.mkdirs(); // 如果目录不存在则创建
}
// 定义完整的文件路径
File outputFile = new File(outputDirectory, fileName);
// 重命名并移动文件到指定目录
file.renameTo(outputFile);
System.out.println("文件成功生成在: " + outputFile.getAbsolutePath());
} finally {
if (file != null && file.exists()) {
file.delete(); // 删除临时文件
}
}
}
private static File createDoc(Map, ?> dataMap, Template template, String name) {
File f = new File(name);
try {
// 使用OutputStreamWriter来指定编码,防止特殊字符出问题
Writer w = new OutputStreamWriter(new FileOutputStream(f), "utf-8");
template.process(dataMap, w); // 使用FreeMarker处理模板
w.close();
} catch (Exception ex) {
ex.printStackTrace();
throw new RuntimeException(ex);
}
return f; // 返回生成的文件
}
}
${company}送货清单
${company}送货清单
序号
供货单号
产品名称
规格型号
数量
单位
备注
<#assign totalQuantity = 0>
<#assign totalItems = 0>
<#assign sortedList = qdList?sort_by("model")>
<#assign currentModel = "">
<#assign subtotalQuantity = 0>
<#assign subtotalItems = 0>
<#list sortedList as item>
<#if item.model != currentModel>
<#if currentModel != "">
小计:${subtotalQuantity}${sortedList[0].unit} ${subtotalItems}轴
${subtotalQuantity}
${sortedList[0].unit}
#if>
<#assign currentModel = item.model>
<#assign subtotalQuantity = 0>
<#assign subtotalItems = 0>
#if>
${item?counter}
${item.danhao}
${item.name}
${item.model}
${item.num}
${item.unit}
${item.remark}
<#assign itemNum = item.num?replace(",", "")?number>
<#assign subtotalQuantity = subtotalQuantity + itemNum>
<#assign subtotalItems = subtotalItems + 1>
<#assign totalQuantity = totalQuantity + itemNum>
<#assign totalItems = totalItems + 1>
#list>
<#if currentModel != "">
小计:${subtotalQuantity}${sortedList[0].unit} ${subtotalItems}轴
${subtotalQuantity}
${sortedList[0].unit}
#if>
合计:${totalQuantity}${qdList[0].unit} ${totalItems}轴
${totalQuantity}
${qdList[0].unit}
发货联系人:${contacts}
联系电话:${contactsPhone}
日期:${date}
收货人(签字):_______________
联系电话:_______________
${customer}
org.springframework.boot
spring-boot-starter-freemarker
org.apache.poi
poi
5.0.0
org.apache.poi
poi-ooxml
5.0.0
org.apache.poi
ooxml-schemas
1.4
WordController类是整个应用的核心控制器,负责协调数据生成和文档创建的过程。让我们逐步分析它的主要组成部分:
public class WordController {
// 方法定义...
}
这个类没有继承任何其他类,也没有实现任何接口,是一个独立的控制器类。
public static void main(String[] args) throws IOException {
String filePath = "F:\\Poi2Word\\src\\main\\resources\\output";
new WordController().generateWordFile(filePath);
}
generateWordFile
方法。而当进入到正式开发环节时,有几个关键要点务必落实:
其一,需要引入数据库集成功能,将当前所使用的测试数据全面替换为从数据库中精准查询获取的真实数据,以此确保数据的准确性与时效性;
其二,要对控制器进行优化改造,摒弃现有的演示模式,将其转换为遵循标准规范的请求接口实现方式,进而满足实际业务需求,提升系统的稳定性与可扩展性。
此方法的只要目的是生成Word文件,首先需要先收集和存储测试数据,存储表格数据是将一条数据存储在Map集合中,再将每一条数据存储到List集合中。将其他数据存储到单独的一个Map集合中。然后确保输出目录存在,最后调用WordUtil中的exportMillCertificateWord方法生成文件,并输出文件的生成位置。
// 生成 Word 文件的方法
public void generateWordFile(String directory) throws IOException {
// 存储测试数据的列表,每个元素都是一个 Map,存储了具体的信息
List> listMap = new ArrayList<>();
// 添加测试数据,调用 addTestData 方法添加一条记录
addTestData(listMap, "4600025747", "绝缘导线", "AC10kV,JKLYJ,300", 1500, "米", "盘号:A1");
//... 可以继续调用 addTestData 方法添加更多测试数据...
// 存储最终要填充到 Word 模板的数据的 Map,包含各种信息
HashMap map = new HashMap<>();
// 将测试数据列表添加到 map 中,键为 "qdList"
map.put("qdList", listMap);
// 联系人信息
map.put("contacts", "张三");
// 联系人电话
map.put("contactsPhone", "13988887777");
// 日期信息
map.put("date", "2025年01月18日");
// 公司名称
map.put("company", "新电缆科技有限公司");
// 客户名称
map.put("customer", "国网北京市电力公司");
// Word 模板文件的名称
String wordName = "template.ftl";
// 生成的 Word 文件的名称,使用当前时间戳保证文件名的唯一性
String fileName = "供货清单" + System.currentTimeMillis() + ".doc";
// 名称信息,具体含义可能根据实际情况而定
String name = "name";
// 创建一个文件对象,用于表示输出目录
File directoryFile = new File(directory);
// 检查输出目录是否存在,如果不存在则创建目录
if (!directoryFile.exists()) {
directoryFile.mkdirs();
}
// 调用 WordUtil 的 exportMillCertificateWord 方法生成 Word 文件
// 传入目录、数据 Map、模板名称、生成的文件名称和名称信息
WordUtil.exportMillCertificateWord(directory, map, wordName, fileName, name);
// 打印生成文件的成功信息
System.out.println("文件成功生成在:" + directory + fileName);
}
这个方法完成以下任务:
这个方法用于创建单个供货项目的数据
// 添加一条测试数据到 listMap 中
private void addTestData(List> listMap, String danhao, String name, String model, int num, String unit, String remark) {
// 创建一个新的 HashMap,用于存储每一条数据
Map item = new HashMap<>();
// 将数据项依次放入 HashMap 中,"serNo" 表示序号,使用 listMap 的大小+1 生成序号
item.put("serNo", listMap.size() + 1); // 序号是当前列表的大小 + 1
item.put("danhao", danhao); // 供货单号
item.put("name", name); // 产品名称
item.put("model", model); // 规格型号
item.put("num", String.valueOf(num)); // 数量,将整数转为字符串
item.put("unit", unit); // 单位
item.put("remark", remark); // 备注
// 将该条数据项添加到 listMap 列表中
listMap.add(item);
}
这个方法完成以下任务:
serNo
)基于当前列表的大小。WordUtil类是整个文档生成过程的核心,它封装了FreeMarker模板引擎的配置和使用逻辑。让我们逐步分析它的主要组成部分:
public class WordUtil {
private static Configuration configuration = null;
private static final String templateFolder = WordUtil.class.getResource("/templates").getPath();
// 其他方法...
}
configuration:这是FreeMarker的核心配置对象,用于设置模版加载路径。
templateFolder:定义了模版文件的存储路径。使用getResource()方法确保在不同环境下都能正确找到模版文件。
这段代码的作用是初始化FreeMarker的Configuration对象,设置模版加载目录以及编码格式,以便FreeMarker后续能够正确加载和处理模版文件。
// 静态初始化块,用于初始化 FreeMarker 配置
static {
// 创建一个 FreeMarker 配置对象,用于后续模板处理
configuration = new Configuration();
// 设置 FreeMarker 配置对象的默认编码为 "utf-8"
configuration.setDefaultEncoding("utf-8");
try {
// 输出模板文件夹路径,帮助调试
System.out.println(templateFolder);
// 设置模板加载目录为 templateFolder 指定的路径,模板文件会从该目录加载
configuration.setDirectoryForTemplateLoading(new File(templateFolder));
} catch (IOException e) {
// 如果加载模板目录时出现异常,打印错误堆栈信息
e.printStackTrace();
}
}
这个静态初始化块在类加载时执行,主要完成以下任务:
这个构造函数防止类被实例化,确保WordUtil只能通过其静态方法使用。
private WordUtil() {
throw new AssertionError();
}
私有构造函数的好处包括:
当类的构造函数被声明为private时,外部代码无法直接创建该类的实例。这就意味着该类只能公国静态方法访问,确保类的功能是全局共享的。
在一些设计模式中,例如单例模式,类只允许有一个实例,私有构造函数确保了这一点。通过private构造函数,我们可以控制类的实例化过程,并确保只有一个实例被创建。
私有构造函数可以帮助隐藏类的具体实现细节,外部代码不需要关心如何创建类的实例,只需要使用类提供的静态方法即可。这增加了类的封装性,降低了与外部代码的耦合度。
由于无法实例化类,每次调用静态方法时,都会使用已有的类实例,这可以避免无意义的对象创建,节省内存和资源。
这个方法的主要功能是通过加载指定的 FreeMarker 模板生成一个临时的 Word 文档,确保输出目录存在后,将临时文件重命名并保存到指定的位置,同时在过程结束后清理临时文件,并打印文件生成的成功消息。
// 导出 Word 文档的方法
public static void exportMillCertificateWord(String outputDirectory, Map map, String wordName, String fileName, String name) throws IOException {
// 获取 FreeMarker 模板文件
Template freemarkerTemplate = configuration.getTemplate(wordName);
// 初始化一个 File 对象,用于存储生成的临时文件
File file = null;
try {
// 使用模板和数据创建 Word 文档,返回临时文件
file = createDoc(map, freemarkerTemplate, name);
// 创建目标目录的 File 对象
File dir = new File(outputDirectory);
// 如果目录不存在,则创建该目录
if (!dir.exists()) {
dir.mkdirs(); // 创建目录及其父目录
}
// 定义最终输出文件的完整路径(包括目录和文件名)
File outputFile = new File(outputDirectory, fileName);
// 将临时生成的文件重命名为目标文件,并将其移动到指定目录
file.renameTo(outputFile);
// 打印输出文件的绝对路径,a通知文件生成成功
System.out.println("文件成功生成在: " + outputFile.getAbsolutePath());
} finally {
// 最后,无论是否成功生成文件,都确保临时文件被删除
if (file != null && file.exists()) {
file.delete(); // 删除临时文件
}
}
}
这个方法是文档导出的主要入口,主要实现了以下功能:
这个方法是创建文档的核心方法,主要是通过创建一个临时文件,使用指定的FreeMarker模版和数据模型将内容填充到文件中,并确保文件使用UTF-8编码进行写入。该方法在执行过程中捕获异常并打印堆栈信息,确保发生错误时能够正确处理。最后。方法返回生成的文件对象,以便后续操作或保存。
// 创建文档的方法,使用 FreeMarker 模板生成内容并写入文件
private static File createDoc(Map, ?> dataMap, Template template, String name) {
// 创建一个新的 File 对象,表示生成的文档文件,文件名由参数 "name" 提供
File f = new File(name);
try {
// 使用 OutputStreamWriter 创建一个写入文件的 Writer 对象,设置编码为 "utf-8"
Writer w = new OutputStreamWriter(new FileOutputStream(f), "utf-8");
// 使用 FreeMarker 模板将数据填充到文件中
template.process(dataMap, w);
// 关闭 Writer,确保所有内容写入文件
w.close();
} catch (Exception ex) {
// 捕获异常并打印错误堆栈信息
ex.printStackTrace();
// 抛出 RuntimeException,确保错误被传播到调用者
throw new RuntimeException(ex);
}
// 返回生成的文件对象
return f;
}
这个方法是实际创建文档的核心,主要实现以下功能:
创建一个临时文件。
使用OutputStreamWriter设置UTF-8编码,确保正确处理所有字符。
调用FreeMarker的template.process()
方法,将数据模型(dataMap)应用到模板上。
关闭写入器。
如果过程中发生异常,打印堆栈跟踪并抛出RuntimeException。
返回生成的文件对象。
WordUtil
类通过封装 FreeMarker 模板引擎的配置和文件操作,提供了一个简洁的文档生成工具。它加载指定模板,使用数据模型填充内容,创建临时文件,并确保文件按照指定路径保存。该类通过静态方法确保全局共享功能,使用 UTF-8 编码处理字符,捕获异常并清理临时文件,确保文档生成过程的稳定性和高效性。
FreeMarker模板是整个文档生成过程的核心,它定义了最终Word文档的结构和样式。让我们来逐步分析模板的主要组成部分
${company}送货清单
${company}送货清单
这段代码通过HTML和内嵌CSS定义了页面布局和样式:
动态公司名称:
字体和表格样式:
小计和总计行样式:为小计行加粗字体,并为总计行加粗且增大字体,突出显示重要数据。
序号
供货单号
产品名称
规格型号
数量
单位
备注
<#assign totalQuantity = 0>
<#assign totalItems = 0>
<#assign sortedList = qdList?sort_by("model")>
<#assign currentModel = "">
<#assign subtotalQuantity = 0>
<#assign subtotalItems = 0>
<#list sortedList as item>
<#if item.model != currentModel>
<#if currentModel != "">
小计:${subtotalQuantity}${sortedList[0].unit} ${subtotalItems}轴
${subtotalQuantity}
${sortedList[0].unit}
#if>
<#assign currentModel = item.model>
<#assign subtotalQuantity = 0>
<#assign subtotalItems = 0>
#if>
${item?counter}
${item.danhao}
${item.name}
${item.model}
${item.num}
${item.unit}
${item.remark}
<#assign itemNum = item.num?replace(",", "")?number>
<#assign subtotalQuantity = subtotalQuantity + itemNum>
<#assign subtotalItems = subtotalItems + 1>
<#assign totalQuantity = totalQuantity + itemNum>
<#assign totalItems = totalItems + 1>
#list>
<#if currentModel != "">
小计:${subtotalQuantity}${sortedList[0].unit} ${subtotalItems}轴
${subtotalQuantity}
${sortedList[0].unit}
#if>
合计:${totalQuantity}${qdList[0].unit} ${totalItems}轴
${totalQuantity}
${qdList[0].unit}
表格结构:
标签创建表格,并通过 定义表头,包含7列:序号、供货单号、产品名称等。
动态数据插入:
- 使用 FreeMarker
<#list>
遍历排序后的清单数据,并通过 ${item.属性名}
动态插入每项数据,如 ${item.danhao}
插入供货单号。
小计和总计计算:
- 通过
<#assign>
定义变量如 totalQuantity
和 subtotalQuantity
,在循环中累加数量。
- 使用
<#if>
判断条件,插入小计行,并在循环结束后插入总计行。
数据处理:
- 使用
sortedList = qdList?sort_by("model")
按型号对清单数据进行排序。
- 处理数量
itemNum = item.num?replace(",", "")?number
,移除逗号并转换为数字,确保计算正确。
格式化输出:
- 小计和总计行使用
colspan
属性合并单元格,确保表格显示整洁。
- 使用 CSS 类
subtotal
和 total
为小计和总计行应用加粗和突出显示的样式。
总结:此表格通过 FreeMarker 动态插入数据、计算小计和总计,并通过合适的排序和格式化样式,确保清单展示清晰且易于阅读。
最后,模板还包括了一些额外信息:
发货联系人:${contacts}
联系电话:${contactsPhone}
日期:${date}
收货人(签字):_______________
联系电话:_______________
${customer}
这部分添加了额外的联系信息和签名区域,进一步完善了文档的实用性。
总的来,这个FreeMarker模板展示了如何结合HTML、CSS和FreeMarker的模板语法来创建一个复杂、动态且格式良好的文档。它不仅能够准确地呈现数据,还能执行必要的计算和格式化,从而生成一个专业的供货清单文档。
总结
通过使用SpingBoot、Apache POI和FreeMarker,我们成功自动化了电缆供货清单的生成过程。这不仅提高了效率,还减少了人为错误。本解决方案的模块化设计使其易于维护和扩展。
希望本教程能够帮助您理解如何使用Java技术来解决实际业务问题。
你可能感兴趣的:(java,apache,freemarker)