springboot 根據(jù)模板生成pdf文件

在開(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)備

image.png

該文件是一個(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.最后的效果

image.png

主體依賴為

    <!-- 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>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末相嵌,一起剝皮案震驚了整個(gè)濱河市腿时,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌饭宾,老刑警劉巖批糟,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異捏雌,居然都是意外死亡跃赚,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門性湿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)纬傲,“玉大人,你說(shuō)我怎么就攤上這事肤频√纠ǎ” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵宵荒,是天一觀的道長(zhǎng)汁雷。 經(jīng)常有香客問(wèn)我,道長(zhǎng)报咳,這世上最難降的妖魔是什么侠讯? 我笑而不...
    開(kāi)封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮暑刃,結(jié)果婚禮上厢漩,老公的妹妹穿的比我還像新娘。我一直安慰自己岩臣,他們只是感情好溜嗜,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著架谎,像睡著了一般炸宵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上谷扣,一...
    開(kāi)封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天土全,我揣著相機(jī)與錄音,去河邊找鬼会涎。 笑死涯曲,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的在塔。 我是一名探鬼主播,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼拨黔,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蛔溃!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤贺待,失蹤者是張志新(化名)和其女友劉穎徽曲,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體麸塞,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡秃臣,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了哪工。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奥此。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖雁比,靈堂內(nèi)的尸體忽然破棺而出稚虎,到底是詐尸還是另有隱情,我是刑警寧澤偎捎,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布蠢终,位于F島的核電站,受9級(jí)特大地震影響茴她,放射性物質(zhì)發(fā)生泄漏寻拂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一丈牢、第九天 我趴在偏房一處隱蔽的房頂上張望祭钉。 院中可真熱鬧,春花似錦赡麦、人聲如沸朴皆。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)遂铡。三九已至,卻和暖如春晶姊,著一層夾襖步出監(jiān)牢的瞬間扒接,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工们衙, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留钾怔,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓蒙挑,卻偏偏與公主長(zhǎng)得像宗侦,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子忆蚀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容