springboot 根据模板生成pdf文件

在开发中,我们不免会遇到需要把数据生成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>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容