在開(kāi)發(fā)中,我們不免會(huì)遇到需要把數(shù)據(jù)生成pdf 文件,其中使用的方法也有多種咪笑,這里我們主要介紹documents4j浸剩,documents4j 是一個(gè)跨平臺(tái)的文檔轉(zhuǎn)換庫(kù)钾军,并且可以在 Linux 上進(jìn)行 Word 轉(zhuǎn) PDF 的操作。這個(gè)比較推薦绢要,開(kāi)源而且轉(zhuǎn)換后也不會(huì)有格式錯(cuò)誤(推薦)吏恭。
那么接下來(lái)這里將帶你實(shí)現(xiàn)documents4j 根據(jù)模板生成pdf文件。
注意
linux操作系統(tǒng)要安裝libreoffice6重罪,原因是documents4j調(diào)用的是office的API樱哼,建議安裝6.4版本以上,否則表格中的樣式無(wú)法被解析到剿配。
如果依賴無(wú)法加載搅幅,參照最后導(dǎo)入的依賴
1、模板準(zhǔn)備
該文件是一個(gè).docx的模板文件呼胚,我們做的是先填充word 再轉(zhuǎn)換為pdf 文件導(dǎo)出茄唐。模板數(shù)據(jù)我們一般使用 {{}},如果是對(duì)象包含列表的數(shù)據(jù)格式蝇更,那么我們就是先用{{list}}放在需要循環(huán)的表單上沪编,里面的各個(gè)字段用[deptId]。
2.上代碼:
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.colorful.common.config.ColorfulConfig;
import com.colorful.common.core.controller.BaseController;
import com.colorful.common.utils.AmountTransformUtils;
import com.colorful.common.utils.StringUtils;
import com.colorful.ict.commonUtils.FreeMarkUtils;
import com.colorful.ict.domain.IctBuyConsumable;
import com.colorful.ict.domain.IctBuyDevice;
import com.colorful.ict.domain.IctScene;
import com.colorful.ict.domain.vo.IctBuyVo;
import com.colorful.ict.mapper.IctSceneMapper;
import com.colorful.ict.service.IIctBuyService;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.config.ConfigureBuilder;
import com.deepoove.poi.policy.HackLoopTableRenderPolicy;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@GetMapping(value = "/exportPdf")
public void getUserPdf(HttpServletResponse response) throws Exception {
Map<String, Object> params = new HashMap<>();
//基礎(chǔ)信息
params.put("name", "張三");
params.put("age", 18);
params.put("idCard", "5023657200126584785");
List<SysDept> deptsList = new ArrayList<>();
SysDept sysDept = new SysDept();
sysDept.setDeptName("研發(fā)中心");
sysDept.setDeptId(1l);
deptsList.add(sysDept);
sysDept = new SysDept();
sysDept.setDeptName("人事部");
sysDept.setDeptId(2l);
deptsList.add(sysDept);
params.put("list", deptsList);
//對(duì)象里如果存在多個(gè)列表屬性年扩,那么接著綁定
//Configure.newBuilder().bind("aList", new HackLoopTableRenderPolicy()).bind("BList",
//new HackLoopTableRenderPolicy());
ConfigureBuilder configureBuilder = Configure.newBuilder().bind("list", new HackLoopTableRenderPolicy());
Configure config = configureBuilder.build();
//模板位置
InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("templates" +
"/customerConfirmForm.docx");
// 數(shù)據(jù)填充
XWPFTemplate template = XWPFTemplate.compile(inputStream, config).render(params);
// 上傳文件路徑
String docOutPath = "C:/upload/userPdf/user.docx";
OutputStream outputStream = new FileOutputStream(docOutPath);
template.write(outputStream);
outputStream.close();
template.close();
//word 轉(zhuǎn)pdf
FreeMarkUtils.wordToPdf(docOutPath);
//修改輸出的文件格式為pdf
String docOutPathPdf = docOutPath.replace(".docx", ".pdf");
// 讀取文件內(nèi)容到字節(jié)數(shù)組
File file1 = new File(docOutPathPdf);
byte[] fileBytes = Files.readAllBytes(file1.toPath());
response.getOutputStream().write(fileBytes);
FreeMarkUtils.generateFile(response, fileBytes, file1.getName());
}
pdf 導(dǎo)出工具類
import com.documents4j.api.DocumentType;
import com.documents4j.api.IConverter;
import com.documents4j.job.LocalConverter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
/**
* pdf 導(dǎo)出工具類
*/
@Component
@Slf4j
public final class FreeMarkUtils {
/**
* word轉(zhuǎn)pdf
* @param filePath
* @throws IOException
*/
public static void wordToPdf(String filePath) throws IOException {
//如果為文檔類型 則生成同樣的文件名的PDF
//創(chuàng)建word文件流
FileInputStream fileInputStreamWord = null;
FileOutputStream os = null;
try{
File upFile = new File(filePath);
fileInputStreamWord =new FileInputStream(upFile);
File pdfFile=new File(filePath.substring(0, filePath.lastIndexOf(".")) + ".pdf");
if (!pdfFile.exists()){
pdfFile.createNewFile();
}
os = new FileOutputStream(pdfFile);
String systemOS = System.getProperty("os.name").toLowerCase();
log.info("convertWordToPdf 當(dāng)前操作系統(tǒng):{}", systemOS);
if (systemOS.contains("win")) {
// Windows操作系統(tǒng)
windowsWordToPdf(fileInputStreamWord, os);
} else if (systemOS.contains("nix") || systemOS.contains("nux") || systemOS.contains("mac")) {
// Unix/Linux/Mac操作系統(tǒng)
linuxWordToPdf(fileInputStreamWord, os);
} else {
// 未知操作系統(tǒng)
throw new RuntimeException("不支持當(dāng)前操作系統(tǒng)轉(zhuǎn)換文檔蚁廓。");
}
// 關(guān)閉
fileInputStreamWord.close();
// 關(guān)閉
os.close();
}catch (Exception e){
e.printStackTrace();
}finally {
if (fileInputStreamWord != null) {
fileInputStreamWord.close();
}
if (os != null) {
os.close();
}
}
}
/**
* 下載文件
* @param response 相應(yīng)
* @param data 數(shù)據(jù)
* @param fileName 文件名
*/
public static 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();
}
}
}
/**
* 通過(guò)libreoffice 實(shí)現(xiàn)word轉(zhuǎn)pdf -- linux 環(huán)境 需要有 libreoffice 服務(wù)
*/
public static void linuxWordToPdf(InputStream stream, FileOutputStream sourceOutput) {
// 創(chuàng)建臨時(shí)文件
File tempFile = createTempFileFromInputStream(stream);
// 構(gòu)建LibreOffice的命令行工具命令
String command =
"libreoffice7.6 --headless --invisible --convert-to pdf " + tempFile.getAbsolutePath() + " " +
"--outdir " + tempFile.getParent();
// 執(zhí)行轉(zhuǎn)換命令
try {
if (!executeLinuxCmd(command)) {
throw new IOException("轉(zhuǎn)換失敗");
}
readPdfFileToByteArrayOutputStream(tempFile, sourceOutput);
} catch (Exception e) {
log.error("ConvertWordToPdf: Linux環(huán)境word轉(zhuǎn)換為pdf時(shí)出現(xiàn)異常:" + e + tempFile.getPath());
// 清理臨時(shí)文件
tempFile.delete();
} finally {
File pdfFile = new File(tempFile.getParent(), tempFile.getName().replace(".docx", ".pdf"));
//清理轉(zhuǎn)換后的pdf文件
pdfFile.delete();
// 清理臨時(shí)文件,無(wú)論是否成功轉(zhuǎn)換
tempFile.delete();
}
}
/**
* 執(zhí)行命令行
*
* @param cmd 命令行
* @return
* @throws IOException
*/
private static boolean executeLinuxCmd(String cmd) throws IOException {
Process process = Runtime.getRuntime().exec(cmd);
try {
process.waitFor();
} catch (InterruptedException e) {
log.error("executeLinuxCmd 執(zhí)行Linux命令異常:", e);
Thread.currentThread().interrupt();
return false;
}
return true;
}
/**
* 創(chuàng)建臨時(shí)文件
*/
private static File createTempFileFromInputStream(InputStream inputStream) {
try {
File tempFile = File.createTempFile("temp_word", ".docx");
Files.copy(inputStream, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
return tempFile;
} catch (IOException e) {
log.error("創(chuàng)建臨時(shí)文件失敵谩:", e);
throw new RuntimeException("創(chuàng)建臨時(shí)文件失敗", e);
}
}
/**
* 讀取pdf文件
*/
private static void readPdfFileToByteArrayOutputStream(File tempFile, FileOutputStream sourceOutput) {
try {
Path outputFile = Paths.get(tempFile.getParent(), tempFile.getName().replace(".docx", ".pdf"));
Files.copy(outputFile, sourceOutput);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 通過(guò)documents4j 實(shí)現(xiàn)word轉(zhuǎn)pdf -- Windows 環(huán)境 需要有 Microsoft Office 服務(wù)
*/
public static void windowsWordToPdf(InputStream stream, FileOutputStream sourceOutput) {
try {
IConverter converter = LocalConverter.builder().build();
converter.convert(stream).as(DocumentType.DOCX).to(sourceOutput).as(DocumentType.PDF).execute();
} catch (Exception e) {
log.error("winWordToPdf windows環(huán)境word轉(zhuǎn)換為pdf時(shí)出現(xiàn)異常:", e);
}
}
}
3.最后的效果
主體依賴為
<!-- word導(dǎo)出 -->
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.7.3</version>
</dependency>
<!-- itextpdf -->
<dependency>
<groupId>com.documents4j</groupId>
<artifactId>documents4j-local</artifactId>
<version>1.0.3</version>
</dependency>
<dependency>
<groupId>com.documents4j</groupId>
<artifactId>documents4j-transformer-msoffice-word</artifactId>
<version>1.0.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.13.0</version> <!-- 請(qǐng)根據(jù)需要使用合適的版本 -->
</dependency>