Springboot如何根據(jù)docx填充生成word文件并導(dǎo)出pdf
在項目中碰見一個需求,需要將.doc的合同,轉(zhuǎn)換為pdf實現(xiàn)打印與預(yù)覽功能。
將docx模板填充數(shù)據(jù)生成doc文件
1、依賴引入
填充docx模板,只需要引入一個pom依賴即可實現(xiàn)。
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.5.0</version>
</dependency>2、doc文件轉(zhuǎn)換docx,并標(biāo)注別名
用office或者wps,創(chuàng)建一個001.doc文件,繪制表格,保存。
更改后綴為.docx,確定后,在指定的位置,表示數(shù)據(jù)接受變量名稱。
如下圖所示:

3、編寫java代碼實現(xiàn)數(shù)據(jù)填充
import com.deepoove.poi.XWPFTemplate;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
/**
* word 填充測試
*/
public class TestWord {
public static void main(String[] args) throws IOException {
Map<String, Object> params = new HashMap<>();
params.put("username","xiangjiao1");
params.put("password","******");
params.put("age",22);
params.put("email","專注寫bug測試中文");
Resource resource = new ClassPathResource("templates_report/001.docx");
File file = resource.getFile();
// 數(shù)據(jù)填充
XWPFTemplate template = XWPFTemplate.compile(file).render(params);
String docOutPath = System.getProperty("user.dir")+File.separator+"springboot-poi"+File.separator+"pdf"+File.separator+ "1.doc";
OutputStream outputStream = new FileOutputStream(docOutPath);
template.write(outputStream);
}
}運行程序,查看結(jié)果。
測試項目結(jié)構(gòu)如下:

docx文件填充數(shù)據(jù)導(dǎo)出pdf(web)
1、依賴引入
向docx模板中填充數(shù)據(jù),并導(dǎo)出pdf類型的文件,除了上面的pom依賴之外,還需要引入其他的依賴信息,完整依賴如下所示:
<!-- docx 數(shù)據(jù)填充生成 doc文件 這個是主要 -->
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.5.0</version>
</dependency>
<!-- doc 轉(zhuǎn) pdf -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13</version>
</dependency>
<!-- docx4j docx2pdf -->
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j</artifactId>
<version>6.1.2</version>
</dependency>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-export-fo</artifactId>
<version>6.0.0</version>
</dependency>2、字體文件
在src\main\resources下創(chuàng)建一個font文件夾,其中放入simsun.ttc字體文件。
3、編寫工具類
思想很簡單
- 1、先使用上面的docx模板填充數(shù)據(jù)生成
臨時doc文件, - 2、再將doc文件轉(zhuǎn)換為pdf文件
- 3、刪除臨時文件
【注意:】
為了避免出現(xiàn)多人同時操作,導(dǎo)致文件誤刪的問題,需要盡可能地保證臨時文件名稱的唯一性。
import com.deepoove.poi.XWPFTemplate;
import com.itextpdf.text.*;
import com.itextpdf.text.Image;
import com.itextpdf.text.pdf.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.text.WordUtils;
import org.docx4j.Docx4J;
import org.docx4j.convert.out.FOSettings;
import org.docx4j.fonts.IdentityPlusMapper;
import org.docx4j.fonts.Mapper;
import org.docx4j.fonts.PhysicalFonts;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.springframework.stereotype.Component;
import java.io.*;
import java.util.Map;
import java.util.UUID;
import java.util.zip.ZipOutputStream;
/**
* pdf 導(dǎo)出工具類
*/
@Component
@Slf4j
public final class FreeMarkUtils {
/**
* 根據(jù)docx模板填充數(shù)據(jù) 并生成pdf文件
*
* @param dataMap 數(shù)據(jù)源
* @param docxFile docx模板的文件名
* @return 生成的文件路徑
*/
public static byte[] createDocx2Pdf(Map<String, Object> dataMap, String docxFile) {
//輸出word文件路徑和名稱 (臨時文件名,本次為測試,最好使用雪花算法生成,或者用uuid)
String fileName = UUID.randomUUID().toString() + ".docx";
// word 數(shù)據(jù)填充
// 生成docx臨時文件
final File tempPath = new File(fileName);
final File docxTempFile = getTempFile(docxFile);
XWPFTemplate template = XWPFTemplate.compile(docxTempFile).render(dataMap);
try {
template.write(new FileOutputStream(tempPath));
} catch (IOException e) {
e.printStackTrace();
}
// word轉(zhuǎn)pdf
final String pdfFile = convertDocx2Pdf(fileName);
return getFileOutputStream(new File(pdfFile)).toByteArray();
}
/**
* word(doc)轉(zhuǎn)pdf
*
* @param wordPath doc 生成的臨時文件路徑
* @return 生成的帶水印的pdf路徑
*/
public static String convertDocx2Pdf(String wordPath) {
OutputStream os = null;
InputStream is = null;
//輸出pdf文件路徑和名稱 (臨時文件 盡可能保證文件名稱的唯一性)
final String fileName = UUID.randomUUID().toString() + ".pdf";
try {
is = new FileInputStream(wordPath);
WordprocessingMLPackage mlPackage = WordprocessingMLPackage.load(is);
Mapper fontMapper = new IdentityPlusMapper();
fontMapper.put("隸書", PhysicalFonts.get("LiSu"));
fontMapper.put("宋體", PhysicalFonts.get("SimSun"));
fontMapper.put("微軟雅黑", PhysicalFonts.get("Microsoft Yahei"));
fontMapper.put("黑體", PhysicalFonts.get("SimHei"));
fontMapper.put("楷體", PhysicalFonts.get("KaiTi"));
fontMapper.put("新宋體", PhysicalFonts.get("NSimSun"));
fontMapper.put("華文行楷", PhysicalFonts.get("STXingkai"));
fontMapper.put("華文仿宋", PhysicalFonts.get("STFangsong"));
fontMapper.put("宋體擴展", PhysicalFonts.get("simsun-extB"));
fontMapper.put("仿宋", PhysicalFonts.get("FangSong"));
fontMapper.put("仿宋_GB2312", PhysicalFonts.get("FangSong_GB2312"));
fontMapper.put("幼圓", PhysicalFonts.get("YouYuan"));
fontMapper.put("華文宋體", PhysicalFonts.get("STSong"));
fontMapper.put("華文中宋", PhysicalFonts.get("STZhongsong"));
//解決宋體(正文)和宋體(標(biāo)題)的亂碼問題
PhysicalFonts.put("PMingLiU", PhysicalFonts.get("SimSun"));
PhysicalFonts.put("新細(xì)明體", PhysicalFonts.get("SimSun"));
// 字體文件
PhysicalFonts.addPhysicalFonts("SimSun", WordUtils.class.getResource("/font/simsun.ttc"));
mlPackage.setFontMapper(fontMapper);
os = new FileOutputStream(fileName);
//docx4j docx轉(zhuǎn)pdf
FOSettings foSettings = Docx4J.createFOSettings();
foSettings.setWmlPackage(mlPackage);
Docx4J.toFO(foSettings, os, Docx4J.FLAG_EXPORT_PREFER_XSL);
is.close();//關(guān)閉輸入流
os.close();//關(guān)閉輸出流
} catch (Exception e) {
e.printStackTrace();
} finally {
// 刪除docx 臨時文件
File file = new File(wordPath);
if (file != null && file.isFile() && file.exists()) {
file.delete();
}
try {
if (is != null) {
is.close();
}
if (os != null) {
os.close();
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
return fileName;
}
/**
* 文件轉(zhuǎn)字節(jié)輸出流
*
* @param outFile 文件
* @return
*/
public static ByteArrayOutputStream getFileOutputStream(File outFile) {
// 獲取生成臨時文件的輸出流
InputStream input = null;
ByteArrayOutputStream bytestream = null;
try {
input = new FileInputStream(outFile);
bytestream = new ByteArrayOutputStream();
int ch;
while ((ch = input.read()) != -1) {
bytestream.write(ch);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bytestream.close();
input.close();
log.info("刪除臨時文件");
if (outFile.exists()) {
outFile.delete();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return bytestream;
}
/**
* 獲取資源文件的臨時文件
* 資源文件打jar包后,不能直接獲取,需要通過流獲取生成臨時文件
*
* @param fileName 文件路徑 templates/xxx.docx
* @return
*/
public static File getTempFile(String fileName) {
final File tempFile = new File(fileName);
InputStream fontTempStream = null;
try {
fontTempStream = FreeMarkUtils.class.getClassLoader().getResourceAsStream(fileName);
FileUtils.copyInputStreamToFile(fontTempStream, tempFile);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fontTempStream != null) {
fontTempStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return tempFile;
}
/**
* 插入圖片水印
* @param srcByte 已生成PDF的字節(jié)數(shù)組(流轉(zhuǎn)字節(jié))
* @param destFile 生成有水印的臨時文件 temp.pdf
* @return
*/
public static FileOutputStream addWaterMark(byte[] srcByte, String destFile) {
// 待加水印的文件
PdfReader reader = null;
// 加完水印的文件
PdfStamper stamper = null;
FileOutputStream fileOutputStream = null;
try {
reader = new PdfReader(srcByte);
fileOutputStream = new FileOutputStream(destFile);
stamper = new PdfStamper(reader, fileOutputStream);
int total = reader.getNumberOfPages() + 1;
PdfContentByte content;
// 設(shè)置字體
//BaseFont font = BaseFont.createFont();
// 循環(huán)對每頁插入水印
for (int i = 1; i < total; i++) {
final PdfGState gs = new PdfGState();
// 水印的起始
content = stamper.getUnderContent(i);
// 開始
content.beginText();
// 設(shè)置顏色 默認(rèn)為藍色
//content.setColorFill(BaseColor.BLUE);
// content.setColorFill(Color.GRAY);
// 設(shè)置字體及字號
//content.setFontAndSize(font, 38);
// 設(shè)置起始位置
// content.setTextMatrix(400, 880);
//content.setTextMatrix(textWidth, textHeight);
// 開始寫入水印
//content.showTextAligned(Element.ALIGN_LEFT, text, textWidth, textHeight, 45);
// 設(shè)置水印透明度
// 設(shè)置筆觸字體不透明度為0.4f
gs.setStrokeOpacity(0f);
Image image = null;
image = Image.getInstance("url");
// 設(shè)置坐標(biāo) 絕對位置 X Y 這個位置大約在 A4紙 右上角展示LOGO
image.setAbsolutePosition(472, 785);
// 設(shè)置旋轉(zhuǎn)弧度
image.setRotation(0);// 旋轉(zhuǎn) 弧度
// 設(shè)置旋轉(zhuǎn)角度
image.setRotationDegrees(0);// 旋轉(zhuǎn) 角度
// 設(shè)置等比縮放 圖片大小
image.scalePercent(4);// 依照比例縮放
// image.scaleAbsolute(200,100);//自定義大小
// 設(shè)置透明度
content.setGState(gs);
// 添加水印圖片
content.addImage(image);
// 設(shè)置透明度
content.setGState(gs);
//結(jié)束設(shè)置
content.endText();
content.stroke();
}
} catch (IOException e) {
e.printStackTrace();
} catch (DocumentException e) {
e.printStackTrace();
} finally {
try {
stamper.close();
fileOutputStream.close();
reader.close();
} catch (DocumentException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
return fileOutputStream;
}
}4、編寫測試接口
import cn.xj.util.FreeMarkUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@RestController
@RequestMapping("/report")
public class ReportController {
@GetMapping("/doc2pdf")
public void doc2pdf(HttpServletResponse response) {
Map<String, Object> params = new HashMap<>();
params.put("username","xiangjiao1");
params.put("password","******");
params.put("age",22);
params.put("email","專注寫bug測試中文");
final byte[] data = FreeMarkUtils.createDocx2Pdf(params, "templates_report/001.docx");
String fileName = UUID.randomUUID().toString() + "_001_test.pdf";
generateFile(response, data, fileName);
}
/**
* 下載文件
* @param response 相應(yīng)
* @param data 數(shù)據(jù)
* @param fileName 文件名
*/
private void generateFile(HttpServletResponse response, byte[] data, String fileName) {
response.setHeader("content-Type", "application/octet-stream");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName);
try {
response.getOutputStream().write(data);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
response.getOutputStream().close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}請求測試
http://localhost/report/doc2pdf

docx4j 復(fù)雜docx文件轉(zhuǎn)pdf碰見的坑總結(jié)
轉(zhuǎn)pdf出現(xiàn)空格壓縮、中文縮減等問題,可以考慮將半角替換成全角,將模板中的空格使用全角空格替換。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
本地MinIO存儲服務(wù)Java遠程調(diào)用上傳文件的操作過程
MinIO是一款高性能、分布式的對象存儲系統(tǒng),它可以100%的運行在標(biāo)準(zhǔn)硬件上,即X86等低成本機器也能夠很好的運行MinIO,這篇文章主要介紹了本地MinIO存儲服務(wù)Java遠程調(diào)用上傳文件的操作過程,需要的朋友可以參考下2023-11-11
FeignClientFactoryBean創(chuàng)建動態(tài)代理詳細(xì)解讀
這篇文章主要介紹了FeignClientFactoryBean創(chuàng)建動態(tài)代理詳細(xì)解讀,當(dāng)直接進去注冊的方法中,一步步放下走,都是直接放bean的定義信息中放入值,然后轉(zhuǎn)成BeanDefinitionHolder,最后在注冊到IOC容器中,需要的朋友可以參考下2023-11-11
WebSocket實現(xiàn)系統(tǒng)后臺消息實時通知功能
在現(xiàn)代Web應(yīng)用中,提供實時通知對于改善用戶體驗至關(guān)重要,WebSocket技術(shù)允許建立雙向通信通道,從系統(tǒng)后臺將消息實時傳送給系統(tǒng)用戶,下面我們就來深入探討一下如何使用WebSocket來實現(xiàn)這一功能吧2023-10-10
Spring Boot + thymeleaf 實現(xiàn)文件上傳下載功能
最近同事問我有沒有有關(guān)于技術(shù)的電子書,我打開電腦上的小書庫,但是郵件發(fā)給他太大了,公司又禁止用文件夾共享,于是花半天時間寫了個小的文件上傳程序,部署在自己的Linux機器上,需要的朋友可以參考下2018-01-01
JavaWeb?Servlet實現(xiàn)文件上傳與下載功能實例
因自己負(fù)責(zé)的項目中需要實現(xiàn)文件上傳,所以下面下面這篇文章主要給大家介紹了關(guān)于JavaWeb?Servlet實現(xiàn)文件上傳與下載功能的相關(guān)資料,需要的朋友可以參考下2022-04-04

