在开发中,我们不免会遇到需要把数据生成pdf 文件,其中使用的方法也有多种,这里我们主要介绍documents4j,documents4j 是一个跨平台的文档转换库,并且可以在 Linux 上进行 Word 转 PDF 的操作。这个比较推荐,开源而且转换后也不会有格式错误(推荐)。
那么接下来这里将带你实现documents4j 根据模板生成pdf文件。
注意
linux操作系统要安装libreoffice6,原因是documents4j调用的是office的API,建议安装6.4版本以上,否则表格中的样式无法被解析到。
如果依赖无法加载,参照最后导入的依赖
1、模板准备
image.png
该文件是一个.docx的模板文件,我们做的是先填充word 再转换为pdf 文件导出。模板数据我们一般使用 {{}},如果是对象包含列表的数据格式,那么我们就是先用{{list}}放在需要循环的表单上,里面的各个字段用[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<>();
//基础信息
params.put("name", "张三");
params.put("age", 18);
params.put("idCard", "5023657200126584785");
List<SysDept> deptsList = new ArrayList<>();
SysDept sysDept = new SysDept();
sysDept.setDeptName("研发中心");
sysDept.setDeptId(1l);
deptsList.add(sysDept);
sysDept = new SysDept();
sysDept.setDeptName("人事部");
sysDept.setDeptId(2l);
deptsList.add(sysDept);
params.put("list", deptsList);
//对象里如果存在多个列表属性,那么接着绑定
//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");
// 数据填充
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 转pdf
FreeMarkUtils.wordToPdf(docOutPath);
//修改输出的文件格式为pdf
String docOutPathPdf = docOutPath.replace(".docx", ".pdf");
// 读取文件内容到字节数组
File file1 = new File(docOutPathPdf);
byte[] fileBytes = Files.readAllBytes(file1.toPath());
response.getOutputStream().write(fileBytes);
FreeMarkUtils.generateFile(response, fileBytes, file1.getName());
}
pdf 导出工具类
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 导出工具类
*/
@Component
@Slf4j
public final class FreeMarkUtils {
/**
* word转pdf
* @param filePath
* @throws IOException
*/
public static void wordToPdf(String filePath) throws IOException {
//如果为文档类型 则生成同样的文件名的PDF
//创建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 当前操作系统:{}", systemOS);
if (systemOS.contains("win")) {
// Windows操作系统
windowsWordToPdf(fileInputStreamWord, os);
} else if (systemOS.contains("nix") || systemOS.contains("nux") || systemOS.contains("mac")) {
// Unix/Linux/Mac操作系统
linuxWordToPdf(fileInputStreamWord, os);
} else {
// 未知操作系统
throw new RuntimeException("不支持当前操作系统转换文档。");
}
// 关闭
fileInputStreamWord.close();
// 关闭
os.close();
}catch (Exception e){
e.printStackTrace();
}finally {
if (fileInputStreamWord != null) {
fileInputStreamWord.close();
}
if (os != null) {
os.close();
}
}
}
/**
* 下载文件
* @param response 相应
* @param data 数据
* @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();
}
}
}
/**
* 通过libreoffice 实现word转pdf -- linux 环境 需要有 libreoffice 服务
*/
public static void linuxWordToPdf(InputStream stream, FileOutputStream sourceOutput) {
// 创建临时文件
File tempFile = createTempFileFromInputStream(stream);
// 构建LibreOffice的命令行工具命令
String command =
"libreoffice7.6 --headless --invisible --convert-to pdf " + tempFile.getAbsolutePath() + " " +
"--outdir " + tempFile.getParent();
// 执行转换命令
try {
if (!executeLinuxCmd(command)) {
throw new IOException("转换失败");
}
readPdfFileToByteArrayOutputStream(tempFile, sourceOutput);
} catch (Exception e) {
log.error("ConvertWordToPdf: Linux环境word转换为pdf时出现异常:" + e + tempFile.getPath());
// 清理临时文件
tempFile.delete();
} finally {
File pdfFile = new File(tempFile.getParent(), tempFile.getName().replace(".docx", ".pdf"));
//清理转换后的pdf文件
pdfFile.delete();
// 清理临时文件,无论是否成功转换
tempFile.delete();
}
}
/**
* 执行命令行
*
* @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 执行Linux命令异常:", e);
Thread.currentThread().interrupt();
return false;
}
return true;
}
/**
* 创建临时文件
*/
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("创建临时文件失败:", e);
throw new RuntimeException("创建临时文件失败", 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);
}
}
/**
* 通过documents4j 实现word转pdf -- Windows 环境 需要有 Microsoft Office 服务
*/
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环境word转换为pdf时出现异常:", e);
}
}
}
3.最后的效果
image.png
主体依赖为
<!-- word导出 -->
<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> <!-- 请根据需要使用合适的版本 -->
</dependency>