Java根据Freemarker模板生成Word文件
1. 准备模板
模板 + 数据 = 模型
1、将准备好的Word模板文件另存为.xml文件(PS:建议使用WPS来创建Word文件,不建议用Office)
2、将.xml文件重命名为.ftl文件
3、用文本编辑器打开.ftl文件,将内容复制出来,格式化一下,再覆盖原来的内容
(PS:格式化一下是为了方便查找并设置变量/占位符,当然设置好模板参数变量以后可以再压缩后再写会.ftl文件)
4、设置模板参数(变量/占位符)
2. 代码实现
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo920</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo920</name>
<description>demo920</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.8.7</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13.3</version>
</dependency>
<!--<dependency>
<groupId>com.aspose</groupId>
<artifactId>aspose-words</artifactId>
<version>22.9</version>
<classifier>jdk17</classifier>
</dependency>-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
写个类测试一下
package com.example.demo920;
import com.example.demo920.domain.LoanReceipt;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.math.BigDecimal;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
@SpringBootTest
class Demo920ApplicationTests {
private DateTimeFormatter DTF = DateTimeFormatter.ofPattern("yyyyMMddHHmmss", Locale.CHINA);
@Test
void contextLoads() {
}
@Test
void testGenerateWord() throws Exception {
Configuration configuration = new Configuration(Configuration.VERSION_2_3_31);
configuration.setDefaultEncoding("UTF-8");
configuration.setClassForTemplateLoading(this.getClass(), "/templates");
Template template = configuration.getTemplate("借条.ftl");
Path path = Paths.get("tmp","contract");
File fileDir = path.toFile();
if (!fileDir.exists()) {
fileDir.mkdirs();
}
String filename = "借条" + "_" + LocalDateTime.now().format(DTF) + ".docx";
filename = path.toFile() + File.separator + filename;
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filename)));
// template.process(getDataMap(), writer);
template.process(getData(), writer);
writer.flush();
writer.close();
}
Map<String, Object> getDataMap() {
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("borrowerName", "李白");
dataMap.put("borrowerIdCard", "421302199001012426");
dataMap.put("lenderName", "杜甫");
dataMap.put("amount", 100);
dataMap.put("amountInWords", "壹佰");
dataMap.put("startDate", "2022年8月15日");
dataMap.put("endDate", "2022年11月11日");
dataMap.put("borrowingMonths", 3);
dataMap.put("interestRate", "1.23");
dataMap.put("guarantorName", "白居易");
dataMap.put("guarantorIdCard", "421302199203152412");
return dataMap;
}
LoanReceipt getData() {
LoanReceipt receipt = new LoanReceipt();
receipt.setBorrowerName("狄仁杰");
receipt.setBorrowerIdCard("421302198710121234");
receipt.setBorrowingMonths(6);
receipt.setLenderName("李元芳");
receipt.setAmount(new BigDecimal("101"));
receipt.setAmountInWords("壹佰零壹");
receipt.setInterestRate(new BigDecimal("0.6"));
receipt.setStartDate("2022年1月1日");
receipt.setEndDate("2022年6月30日");
receipt.setGuarantorName("武则天");
receipt.setGuarantorIdCard("421302199101014567");
return receipt;
}
}
最主要的是下面两行
// 加载模板
Template template = configuration.getTemplate("借条.ftl");
// 填充数据
template.process(getData(), writer);
数据可以是Map也可以是一个对象
改进一下,将生成文件的操作单独写成一个工具方法
package com.example.demo920.util;
import cn.hutool.core.io.IoUtil;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import java.io.*;
public class FreemarkerUtils {
/**
* 生成Word
* @param templateDir 模板所在的目录
* @param templateName 模板文件名称
* @param filename 生成的文件(含路径)
* @param dataModel 模板参数数据
*/
public static void generateWord(File templateDir, String templateName, String filename, Object dataModel) {
BufferedWriter writer = null;
Configuration configuration = new Configuration(Configuration.VERSION_2_3_31);
configuration.setDefaultEncoding("UTF-8");
try {
configuration.setDirectoryForTemplateLoading(templateDir);
Template template = configuration.getTemplate(templateName);
writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filename)));
template.process(dataModel, writer);
writer.flush();
} catch (IOException e) {
throw new RuntimeException(e);
} catch (TemplateException e) {
throw new RuntimeException(e);
} finally {
IoUtil.close(writer);
}
}
}
再测试一下
package com.example.demo920;
import cn.hutool.core.io.IoUtil;
import com.example.demo920.util.FreemarkerUtils;
import com.example.demo920.util.PdfUtils;
import org.junit.jupiter.api.Test;
import org.springframework.util.ResourceUtils;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
public class WordTest {
/**
* 1、从文件服务器下载模板文件
* 2、根据业务类型获取需要填充模板的数据
* 3、模板+数据 再经过处理生成新的文件
* 4、将生成后的文件上传到文件服务器,并返回一个文件ID
* 5、业务可以保存这个文件ID或者文件的路径
*/
@Test
void testGenerateWordV1() throws Exception {
Path tempPath = Paths.get("tmp", "contract2");
File path = tempPath.toFile();
if (!path.exists()) {
path.mkdirs();
}
File tempFile = Files.createTempFile(tempPath, "qiantiao", ".docx").toFile();
System.out.println(tempFile.getParent());
System.out.println(tempFile.getName());
FileOutputStream fos = new FileOutputStream(tempFile);
File templateFile = ResourceUtils.getFile("classpath:templates/借条.ftl");
FileInputStream fis = new FileInputStream(templateFile);
IoUtil.copy(fis, fos);
String filename = "借条" + "_" + System.currentTimeMillis() + ".docx";
filename = "tmp/contract" + File.separator + filename;
FreemarkerUtils.generateWord(new File(tempFile.getParent()), tempFile.getName(), filename, getDataMap());
}
/**
* 获取数据
*/
Map<String, Object> getDataMap() {
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("borrowerName", "李白2");
dataMap.put("borrowerIdCard", "421302199001012426");
dataMap.put("lenderName", "杜甫");
dataMap.put("amount", 100);
dataMap.put("amountInWords", "壹佰");
dataMap.put("startDate", "2022年8月15日");
dataMap.put("endDate", "2022年11月11日");
dataMap.put("borrowingMonths", 3);
dataMap.put("interestRate", "1.23");
dataMap.put("guarantorName", "白居易");
dataMap.put("guarantorIdCard", "421302199203152412");
return dataMap;
}
@Test
void testGenerateWord2() throws Exception {
File templateDir = ResourceUtils.getFile(ResourceUtils.CLASSPATH_URL_PREFIX + "templates");
String templateName = "借条.ftl";
String destFilename = "借条" + System.currentTimeMillis() + ".docx";
Map<String, Object> data = getDataMap();
FreemarkerUtils.generateWord(templateDir, templateName, destFilename, data);
}
}
3. PDF文件加水印
有时候,生成或者从服务器下载的文件是需要加水印的,比如标识这个文件是谁下载的之类的
pdf加水印还是比较方便的,用itext组件可以轻松实现
另外,如果最终需要pdf文件,建议直接生成pdf文件,跳过word转pdf的步骤
package com.example.demo920.util;
import cn.hutool.core.io.IoUtil;
import com.aspose.words.Document;
import com.aspose.words.SaveFormat;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Image;
import com.itextpdf.text.pdf.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.time.LocalDateTime;
/**
* @author chengjiansheng
* @date 2022/09/21
*/
public class PdfUtils {
/**
* Word转PDF
* https://www.aspose.com/
* 注意:Aspose.Words 这个组件是收费的,如果购买的话生成的PDF会有水印。
* 可以去找相应的破解版本,但是我感觉完全可以跳过Word直接生成PDF。
* 比如,可以通过Freemarker直接生成PDF,或者利用iText通过模板生成PDF
* @param src
* @param dest
*/
public static void wordToPdf(String src, String dest) {
File file = new File(src);
if (!file.exists()) {
throw new RuntimeException("文件不存在");
}
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
Document wpd = new Document(fis);
wpd.save(dest, SaveFormat.PDF);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
IoUtil.close(fis);
}
}
/**
* 加水印
* @param src 源文件
* @param dest 目标文件
* @param text 文字
* @param imagePath 图片地址
*/
public static void addWatermark(String src, String dest, String text, String imagePath) {
try {
// 待加水印的文件
PdfReader reader = new PdfReader(src);
// 加完水印的文件
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
// 字体
BaseFont baseFont = BaseFont.createFont(BaseFont.HELVETICA_BOLD, BaseFont.CP1252, BaseFont.NOT_EMBEDDED);
baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
// 透明度
PdfGState gs = new PdfGState();
gs.setFillOpacity(0.4f);
// PDF文件总页数
int total = reader.getNumberOfPages() + 1;
// 循环对每一页都加水印
PdfContentByte content;
for (int i = 1; i < total; i++) {
// 水印在文本之上
content = stamper.getOverContent(i);
content.setGState(gs);
if (null != imagePath) {
Image image = Image.getInstance(imagePath);
// image.setAbsolutePosition(150, 150);
// image.scaleToFit(300,300);
// content.addImage(image);
for (int x = 0; x < 700; x = x + 300) {
for (int y = 0; y < 900; y = y + 200) {
image.setAbsolutePosition(x+50, y+50);
image.scaleToFit(100,100);
content.addImage(image);
}
}
}
if (null != text) {
content.beginText();
content.setColorFill(BaseColor.RED);
content.setFontAndSize(baseFont, 20);
// content.showTextAligned(Element.ALIGN_CENTER, text, 50, 50, 45);
for (int x = 0; x < 700; x = x + 300) {
for (int y = 0; y < 900; y = y + 200) {
//水印内容和水印位置
content.showTextAligned(Element.ALIGN_CENTER, "哈哈哈哈哈", x - 20, y + 10, 30);
content.showTextAligned(Element.ALIGN_CENTER, LocalDateTime.now().toString(), x, y, 30);
}
}
content.endText();
}
}
stamper.close();
reader.close();
} catch (IOException e) {
throw new RuntimeException(e);
} catch (DocumentException e) {
throw new RuntimeException(e);
}
}
}
跑一下
@Test
void testWatermark() {
String src2 = "D:\\借条_2.pdf";
String dest2 = "D:\\借条_3.pdf";
String imagePath = "D:\\1.jpg";
PdfUtils.addWatermark(src2, dest2, "哈哈哈哈哈", imagePath);
}
加完水印后效果如图
最后,示例项目结构如图
参考:
https://blog.csdn.net/weixin_45103378/article/details/118395284
Java根据Freemarker模板生成Word文件的更多相关文章
- java通过FreeMarker模板生成Excel文件之.ftl模板制作
关于怎么通过freemarker模板生成excel的文章很多,关键点在于怎么制作模板文件.ftl 网上的办法是: (1)把Excel模板的格式调好,另存为xml文件 (2)新建一个.ftl文件,把xm ...
- 利用html模板生成Word文件(服务器端不需要安装Word)
利用html模板生成Word文件(服务器端不需要安装Word) 由于管理的原因,不能在服务器上安装Office相关组件,所以只能采用客户端读取Html模板,后台对模板中标记的字段数据替换并返回给客户端 ...
- 【java】Freemarker 动态生成word(带图片表格)
1.添加freemarker.jar 到java项目. 2.新建word文档. 3.将文档另存为xml 格式. 4.将xml格式化后打开编辑(最好用notepad,有格式),找到需要替换的内容,将内容 ...
- freemarker根据模板生成word文件实现导出功能
一.准备工作 1.创建一个03的word文档,动态的数据用占位符标志占位(如testname).然后另存为word2003的xml文件. 2.格式化xml文件,占位符的位置用${testname}代替 ...
- java使用freemarker模板导出word(带有合并单元格)文档
来自:https://blog.csdn.net/qq_33195578/article/details/73790283 前言:最近要做一个导出word功能,其实网上有很多的例子,但是我需要的是合并 ...
- 根据指定Word模板生成Word文件
最近业务需要批量打印准考证信息 1.根据Table数据进行循环替换,每次替换的时候只替换Word中第一个Table的数据, 2.每次替换之后将Word中第一个Table数据进行复制,将复制Table和 ...
- Java 使用模板生成 Word 文件---基于 Freemarker 模板框架
Java项目引入 Freemarker 插件自行完成. 步骤如下: .编写 Word 模板,并将模板中要用代码动态生成数据用 Freemarker 变量取代,即${变量名},如${username}: ...
- java 根据html模板生成html文件
1.代码部分 import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test. ...
- 使用freemarker模板生成word文档
项目中最近用到这个东西,做下记录. 如下图,先准备好一个(office2003)word文档当做模板.文档中图片.姓名.性别和生日已经使用占位符代替,生成过程中将会根据实际情况进行替换. 然后将wor ...
随机推荐
- 业务可视化-让你的流程图"Run"起来
前言 最近在研究业务可视化的问题,在日常的工作中,流程图和代码往往是分开管理的. 一个被维护多次的系统,到最后流程图和代码是否匹配这个都很难说. 于是一直有一个想法,让程序直接读流程图,根据流程图的配 ...
- springboot的@ConditionalOnBean注解
上篇文章中分析了springboot的自动注入的原理,可在文章后面的推荐阅读中温习哦.在自动注入的原理那篇文章中提到了@ConditionalOnXX注解,今天来看下springboot中的@Co ...
- p_b_p_b 杂题选讲
[ARC119F] AtCoder Express 3 [ARC117F] Gateau 考虑二分答案,对前缀和建差分约束 \(\text{check}\) ,但是用 \(\text{spfa}\) ...
- .Net下极限生产力之efcore分表分库全自动化迁移CodeFirst
.Net下极限生产力之分表分库全自动化Migrations Code-First ## 介绍 本文ShardinfCore版本x.6.x.x+ 本期主角: - [`ShardingCore`](htt ...
- Collections集合工具类的方法_sort(List)和sort(List,Comparator)方法
Comparator比较器 我们还是先研究这个方法 public static <T> void sort(List<T> list):将集合中元素按照默认规则排序. 不过这次 ...
- 查询postgresql表结构和索引
通过系统数据字典查询表结构 selectcol.table_schema,col.table_name,col.ordinal_position,col.column_name,col.data_ty ...
- 论文阅读 A Data-Driven Graph Generative Model for Temporal Interaction Networks
13 A Data-Driven Graph Generative Model for Temporal Interaction Networks link:https://scholar.googl ...
- elastsearch整合springboot
文档地址: https://www.baeldung.com/elasticsearch-java
- 项目配置yaml
springboot的一些配置 #当循环调用时,就会报错 spring.main.allow-circular-references=true #配置mvc是需要使用一个@EnableWebMvc,不 ...
- css基础03
就近原则执行粉色.而不是全覆盖,只有样式冲突的地方才会覆盖. 会执行粉色和12px.后来者居上 高度宽度内外边距这些不会继承. 子元素会继承行高, 1.5是行高是字体大小的1.5倍的意思. 有了!im ...