Java根据Freemarker模板生成Word文件 - 废物大师兄
source link: https://www.cnblogs.com/cjsblog/p/16715294.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Java根据Freemarker模板生成Word文件
1. 准备模板
模板 + 数据 = 模型
1、将准备好的Word模板文件另存为.xml文件(PS:建议使用WPS来创建Word文件,不建议用Office)
2、将.xml文件重命名为.ftl文件
3、用文本编辑器打开.ftl文件,将内容复制出来,格式化一下,再覆盖原来的内容
(PS:格式化一下是为了方便查找并设置变量/占位符,当然设置好模板参数变量以后可以再压缩后再写会.ftl文件)
另外,强烈不建议在word文件中去编辑设置模板变量,因为.docx文件在另存为.xml文件后,原先好好的一个变量可能就被拆开了,建议另存为之后再用文本编辑器打开去编辑。
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("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
欢迎各位转载,但必须在文章页面中给出作者和原文链接!
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK