Java整合FreeMarker導出Pdf文件

来源:https://www.cnblogs.com/menghl/p/18241663
-Advertisement-
Play Games

引入依賴 <!--Freemarker wls--> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.30</version> </dependency> ...


引入依賴

<!--Freemarker wls-->
		<dependency>
			<groupId>org.freemarker</groupId>
			<artifactId>freemarker</artifactId>
			<version>2.3.30</version>
		</dependency>
		<dependency>
			<groupId>com.itextpdf.tool</groupId>
			<artifactId>xmlworker</artifactId>
			<version>5.5.11</version>
		</dependency>

		<!-- 支持中文 -->
		<dependency>
			<groupId>com.itextpdf</groupId>
			<artifactId>itext-asian</artifactId>
			<version>5.2.0</version>
		</dependency>
		<!-- 支持css樣式渲染 -->
		<dependency>
			<groupId>org.xhtmlrenderer</groupId>
			<artifactId>flying-saucer-pdf-itext5</artifactId>
			<version>9.1.18</version>
		</dependency>
    <dependency>
			<groupId>gui.ava</groupId>
			<artifactId>html2image</artifactId>
			<version>2.0.1</version>
		</dependency>

代碼示例

後端代碼

/**
 * @author alin
 * @date 2024-06-11
 */
@Slf4j
public class TestCreatePdf {

    public static void main(String[] args) throws Exception {
        generatePdfUrl();
    }


    /**
     * 生成pdf
     *
     * @return
     */
    public static String generatePdfUrl() throws Exception {
        // 構造參數
        Model model = assembleData();
        return createPdfAndUpload(beanToMap(model), UUID.randomUUID() + ".pdf", "testCreatePdf.html", "testCreatePdf.css");
    }

    /**
     * 創建pdf並上傳/輸出
     * 
     * @param data
     * @param fileName 文件名
     * @param templateFileName 模板文件名, html模板文件
     * @param cssPath css文件路徑
     * @return
     * @throws Exception
     */
    private static String createPdfAndUpload(Map<String, Object> data, String fileName, String templateFileName, String cssPath) throws Exception {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        try {
            // 根據模板生成html字元串
            String pdf = createHtmlStr(data, templateFileName);
            // 通過html字元串生成pdf文件
            generatePdf(pdf, outputStream, cssPath);
        } catch (Exception e) {
            return null;
        }
        // 輸出/上傳至指定位置
        FileOutputStream out = new FileOutputStream("d:/testPdf/" + fileName);
        out.write(outputStream.toByteArray());
        return "d:/testPdf/" + fileName;
    }

    /**
     * bean轉map
     * 
     * @param bean
     * @return
     */
    public static Map<String, Object> beanToMap(Object bean) {
        Class<?> clazz = bean.getClass();
        return Arrays.stream(clazz.getDeclaredFields())
                .collect(Collectors.toMap(
                        Field::getName,
                        field -> {
                            try {
                                field.setAccessible(true);
                                return field.get(bean);
                            } catch (IllegalAccessException e) {
                                // 處理異常
                                return null;
                            }
                        }
                ));
    }

    /**
     * 模板生成html字元串
     *
     * @param data             數據
     * @param templateFileName 模板文件名
     * @throws Exception 捕獲異常
     */
    public static String createHtmlStr(Map<String, Object> data, String templateFileName) throws Exception {
        // 創建一個FreeMarker實例, 負責管理FreeMarker模板的Configuration實例
        Configuration cfg = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
        // 指定FreeMarker模板文件的位置
        cfg.setClassForTemplateLoading(TestCreatePdf.class, "/template");
        // 獲取模板文件
        Template template = cfg.getTemplate(templateFileName, "UTF-8");
        StringWriter stringWriter = new StringWriter();
        BufferedWriter writer = new BufferedWriter(stringWriter);
        template.process(data, writer);
        String htmlStr = stringWriter.toString();
        writer.flush();
        writer.close();
        return htmlStr;
    }

    /**
     * 通過html字元串生成pdf文件
     *
     * @param htmlStr
     * @param out
     * @param cssPath
     * @throws IOException
     * @throws DocumentException
     */
    public static void generatePdf(String htmlStr, OutputStream out, String cssPath) throws IOException, DocumentException {
        Document document = new Document(PageSize.A3);
        PdfWriter writer = PdfWriter.getInstance(document, out);
        document.open();
        // html內容解析
        HtmlPipelineContext htmlContext = new HtmlPipelineContext(
                new CssAppliersImpl(new XMLWorkerFontProvider() {
                    @Override
                    public Font getFont(String fontName, String encoding,
                                        float size, final int style) {
                        Font font = null;
                        if (fontName == null) {
                            //字體
                            BaseFont bf;
                            try {
                                bf = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
                                font = new Font(bf, size, style);
                            } catch (Exception e) {
                                log.error("getFont", e);
                            }
                        }
                        return font;
                    }
                })) {
            @Override
            public HtmlPipelineContext clone()
                    throws CloneNotSupportedException {
                HtmlPipelineContext context = super.clone();
                try {
                    ImageProvider imageProvider = this.getImageProvider();
                    context.setImageProvider(imageProvider);
                } catch (Exception e) {
                    log.error("clone", e);
                }
                return context;
            }
        };
        // 圖片解析
        htmlContext.setImageProvider(new AbstractImageProvider() {
            @Override
            public String getImageRootPath() {
                return StringUtils.EMPTY;
            }

            @Override
            public Image retrieve(String src) {
                if (StringUtils.isEmpty(src)) {
                    return null;
                }
                try {
                    int pos = src.indexOf("base64,");
                    try {
                        if (src.startsWith("data") && pos > 0) {
                            byte[] img = Base64.decode(src.substring(pos + 7));
                            return Image.getInstance(img);
                        } else if (src.startsWith("http")) {
                            return Image.getInstance(src);
                        }
                    } catch (Exception ex) {
                        log.error("retrieve", ex);
                        return null;
                    }
                    return null;
                } catch (Throwable e) {
                    log.error("retrieve", e);
                }
                return super.retrieve(src);
            }
        });
        htmlContext.setAcceptUnknown(true).autoBookmark(true).setTagFactory(Tags.getHtmlTagProcessorFactory());
        // css解析
        CSSResolver cssResolver = new StyleAttrCSSResolver();
        InputStream cssInputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(cssPath);
        CssFile cssfile = XMLWorkerHelper.getCSS(cssInputStream);
        cssResolver.addCss(cssfile);
        HtmlPipeline htmlPipeline = new HtmlPipeline(htmlContext, new PdfWriterPipeline(document, writer));
        Pipeline<?> pipeline = new CssResolverPipeline(cssResolver, htmlPipeline);
        XMLWorker worker = new XMLWorker(pipeline, true);
        XMLParser parser = new XMLParser(true, worker, StandardCharsets.UTF_8);
        try (InputStream inputStream = new ByteArrayInputStream(
                htmlStr.getBytes())) {
            parser.parse(inputStream, StandardCharsets.UTF_8);
        }
        document.close();
    }


    /**
     * 組裝數據
     *
     * @return
     * @throws Exception
     */
    private static Model assembleData() throws Exception {
        TestCreatePdf.Model model = new TestCreatePdf.Model();
        model.setCompanyName("公司名稱");
        model.setField1("欄位一");
        model.setField2("欄位二");
        model.setField3("欄位三");
        model.setField4("欄位四");
        model.setField5("欄位五");
        model.setField6("欄位六");
        model.setField7("欄位七");
        model.setRemark("備註~~~~~~~~~");
        model.setSignUrl1("D:/testPdf/test.png");
        model.setSignUrl2("D:/testPdf/test.png");
        model.setSignUrl3("D:/testPdf/test.png");
        model.setSignTime1("2024-04-28 17:08:52");
        model.setSignTime2("2024-04-28 17:08:52");
        model.setSignTime3("2024-04-28 17:08:52");
        List<Object> modeDetailFieldList = Lists.newArrayList();
        modeDetailFieldList.add("表頭一");
        modeDetailFieldList.add("表頭二");
        modeDetailFieldList.add("表頭三");
        modeDetailFieldList.add("表頭四");
        modeDetailFieldList.add("表頭五");
        modeDetailFieldList.add("表頭六");
        model.setModeDetailFieldList(modeDetailFieldList);
        List<List<Object>> modeDetailValueList = Lists.newArrayList();
        for (int i = 1; i < 6; i++) {
            List<Object> valueList = Lists.newArrayList();
            valueList.add("表頭一值--" + i);
            valueList.add("表頭二值--" + i);
            valueList.add("表頭三值--" + i);
            valueList.add("表頭四值--" + i);
            valueList.add("表頭五值--" + i);
            valueList.add("表頭六值--" + i);
            modeDetailValueList.add(valueList);
        }
        model.setModeDetailValueList(modeDetailValueList);
        return model;
    }

    @Data
    public static class Model {

        /**
         * companyName
         */
        private String companyName;

        /**
         * 欄位1
         */
        private String field1;

        /**
         * 欄位2
         */
        private String field2;

        /**
         * 欄位3
         */
        private String field3;

        /**
         * 欄位4
         */
        private String field4;

        /**
         * 欄位5
         */
        private String field5;

        /**
         * 欄位6
         */
        private String field6;

        /**
         * 欄位7
         */
        private String field7;

        /**
         * 備註
         */
        private String remark;

        /**
         * 圖片地址(base64結構)
         */
        private String imgBase64;

        /**
         * signUrl1
         */
        private String signUrl1;

        /**
         * signUrl2
         */
        private String signUrl2;

        /**
         * signUrl3
         */
        private String signUrl3;

        /**
         * signTime1
         */
        private String signTime1;

        /**
         * signTime2
         */
        private String signTime2;

        /**
         * signTime3
         */
        private String signTime3;

        /**
         * 表格欄位名稱
         */
        private List<Object> modeDetailFieldList;

        /**
         * 表格欄位值
         */
        private List<List<Object>> modeDetailValueList;

    }


}

模板及樣式

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <title>測試流程表格文件</title>
  <link type="text/css" rel="stylesheet" href="style/testCreatePdf.css"/>
  <style/>
</head>

<body>
<div class="app-container">
  <div class="app-container-top">
    <div class="title">測試流程表格文件</div>
  </div>
  <div class="app-container-body">
    <div class="park-label">${companyName}</div>
    <div class="nbsp-5"></div>
    <table class="baseInfo-table">
      <tr>
        <td style=" background-color: #F0F0F0; text-align: left; width:250px;">欄位1</td>
        <td style="width:420px;">${field1}</td>
        <td style=" background-color: #F0F0F0;text-align: left; width:250px;">欄位2</td>
        <td style="width:400px;">${field2}</td>
        <td style=" background-color: #F0F0F0;text-align: left; width:250px;">欄位3</td>
        <td style="width:420px;">${field4}</td>
      </tr>
      <tr>
        <td style=" background-color: #F0F0F0;text-align: left;">欄位4</td>
        <td>${field4}</td>
        <td style=" background-color: #F0F0F0;text-align: left;">欄位5</td>
        <td>${field5}</td>
        <td style=" background-color: #F0F0F0;text-align: left;">欄位6</td>
        <td>${field6}</td>
      </tr>
      <tr>
        <td style=" background-color: #F0F0F0;text-align: left;">欄位7</td>
        <td colspan="5">${field7}</td>
      </tr>
    </table>
    <table class="mode-table">
      <thead>
      <tr>
        <#list modeDetailFieldList as obj >
        <td>${obj}</td>
      </#list>
      </tr>
      </thead>
      <tbody>
      <#list modeDetailValueList as detailValue >
      <tr>
        <#list detailValue as obj >
        <td>${obj}</td>
      </#list>
      </tr>
      </#list>
      </tbody>
    </table>
    <div class="nbsp-20"></div>
    <div class="remark-div">
      <div class="remark">${remark}</div>
    </div>
    <div class="nbsp-20"></div>
    <div class="sign">
      <div class="sign-div-seal">
        <div class="sign-div-title">簽字1:</div>
        <div class="nbsp-40"></div>
        <div style="width:100%;height:300px">
          <div class="nbsp-width" style="width:20%"></div>
          <div class="nbsp-width" style="width:70%">
            <img width="100%" height="100%" src="${signUrl1}" />
          </div>
        </div>
        <div class="sign-time">時間:${signTime1}</div>
      </div>
      <div class="sign-div-seal">
        <div class="sign-div-title">簽字2:</div>
        <div class="nbsp-40"></div>
        <div style="width:100%;height:300px">
          <div class="nbsp-width" style="width:20%"></div>
          <div class="nbsp-width" style="width:70%">
            <img width="100%" height="100%" src="${signUrl2}" />
          </div>
        </div>
        <div class="sign-time">時間:${signTime3}</div>
      </div>
      <div class="sign-div-seal">
        <div class="sign-div-title">簽字3:</div>
        <div class="nbsp-40"></div>
        <div style="width:100%;height:300px">
          <div class="nbsp-width" style="width:20%"></div>
          <div class="nbsp-width" style="width:70%">
            <img width="100%" height="100%" src="${signUrl3}" />
          </div>
        </div>
        <div class="sign-time">時間:${signTime3}</div>
      </div>
    </div>
  </div>
</div>
</body>
</html>


body {
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
}
html {
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
}
.app-container {
    padding: 20px;
}
.app-container-top {
    width:100%;
    height:100px;
}
.nbsp-5 {
    width: 100%;
    height: 5px;
}
.nbsp-10 {
    width: 100%;
    height: 10px;
}
.nbsp-20 {
    width: 100%;
    height: 20px;
}
.nbsp-40 {
    width: 100%;
    height: 40px;
}
.nbsp-width {
    height:300px;
    float:left;
}
.nbsp-div {
    width: 35%;
    height: 100px;
    float: left;
}
.logo-div {
    width: 200px;
    float: left;
    height: 100px;
}
.logo-div img {
    width: 200px;
    height:40px;
    margin: 10px 0 20px 0;
}
.qrcode-div {
    width: 100px;
    display: flex;
    height: 100px;
    float: right;
}
.qrcode-div img {
    width: 100px;
    object-fit: cover;
}
.title {
    font-size: 30px;
    height: 100px;
    float: left;
    text-align: center;
}
.park-label {
    font-size: 20px;
    height: 50px;
    float: left;
}
.app-container-body {
    margin-top: 5px;
}
table tr td {
    height:40px;
}
.baseInfo-table {
    width: 100%;
    margin: 0 auto;
    border: 1px solid #9D9D9D;
    border-collapse: collapse;
}
.baseInfo-table tr td {
    border: 1px solid #9D9D9D;
    text-align: center;
}
.mode-table {
    width: 100%;
    margin: 10px 0 auto;
    border: 1px solid #9D9D9D;
    border-collapse: collapse;
}
.mode-table tr td {
    border: 1px solid #9D9D9D;
    text-align: center;
}
.mode-table tr td:nth-child(1) {
    width: 50px;
}
.mode-table tr td:nth-child(2) {
    width: 150px;
}
.mode-table tr td:nth-child(4) {
    width: 80px;
}
.mode-table tr td:nth-child(8) {
    width: 100px;
}
.mode-table tr td:nth-child(9) {
    width: 100px;
}
.test-table {
    margin-top: 10px;
    width: 100%;
    margin: 50px 0 auto;
    border: 1px solid #9D9D9D;
    border-collapse: collapse;
}
.test-table tr td {
    border: 1px solid #9D9D9D;
    text-align: center;
}
.test-table tr td:nth-child(1) {
    width: 30px;
    height: 60px;
}
.test-table tr td:nth-child(2) {
    width: 120px;
}
.sign {
    width: calc(100% - 80px);
    margin: 40px;
}
.sign-div {
    width: 50%;
    height: 450px;
    float: left;
    text-align: left;
}
.sign-div-seal {
    width: 325px;
    height: 450px;
    float: left;
    text-align: left;
}
.sign-img {
    height: 300px;
}
.sign-div-title {
    text-align: left;
}
.sign-time {
    margin-top: 10px;
    text-align: left
}

效果圖


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • NumPy 中的簡單算術運算可以通過 `add`, `subtract`, `multiply`, `divide`, `power`, `mod`, `remainder` 等函數實現,這些函數支持條件運算,並接受 `where` 參數。例如,`add()` 實現加法,`subtract()` 表... ...
  • JSON 簡介 JSON(JavaScript Object Notation,JavaScript對象表示法)是一種輕量級的數據交換格式。它基於ECMAScript(歐洲電腦製造商協會制定的js規範)的一個子集,採用完全獨立於編程語言的文本格式來存儲和表示數據。簡潔和清晰的層次結構使得JSON成 ...
  • 關註作者,復旦AI博士,分享AI領域與雲服務領域全維度開發技術。擁有10+年互聯網服務架構、AI產品研發經驗、團隊管理經驗,同濟本復旦碩博,復旦機器人智能實驗室成員,國家級大學生賽事評審專家,發表多篇SCI核心期刊學術論文,阿裡雲認證的資深架構師,項目管理專業人士,上億營收AI產品研發負責人。 精講 ...
  • 在MyBatis中,可以通過使用一些特定的標簽(if、choose...)以及其他動態SQL功能來實現條件判斷。 這使得SQL查詢可以根據不同的條件動態生成,從而提高查詢的靈活性和可維護性。 本文以訂單列表簡單查詢為例, 對mybatis條件判斷及動態sql進行簡單拓展。 建表語句 CREATE T ...
  • 在處理PDF文件時,我們可能會遇到這樣的情況:原始PDF文檔不符合我們的閱讀習慣,或者需要適配不同顯示設備等。這時,我們就需要及時調整PDF文檔中的頁面尺寸,以滿足不同應用場景的需求。 利用Python語言的高效性和靈活性,再結合Spire.PDF for Python 庫的強大功能,我們可以通過P ...
  • 最近以來,我在力扣上堅持完成每天一題,今天系統推的題目為《甲板上的戰艦》,在此記錄一下。 題目描述如下: 給你一個大小為 m x n 的矩陣 board 表示甲板,其中,每個單元格可以是一艘戰艦 'X' 或者是一個空位 '.' ,返回在甲板 board 上放置的 戰艦 的數量。 戰艦 只能水平或者垂 ...
  • 正文 所有鋼筆墨水都寫完了,今天先用簽字筆吧,懶得打墨水了。 這貨跟我搶被子,我沒搶贏…… 本來空調被就薄,一個人很容易就全捲上跑了。於是我半夜冷醒好多次,每次半夢半醒都要把自己的衣服下擺往下拉。這樣感覺才會好一些。 這弔人還嘲笑我搶不過,媽耶。於是早上非常困。跟他一起到了他的值班室,他開始玩植物大 ...
  • 你應如何運行程式 互動式命令模式 開始一個互動式會話 一般是在操作系統命令行下輸入python,且不帶任何參數 系統路徑 如果沒有設置系統的PATH環境變數來包括Python的安裝路徑,可能需要機器上Python可執行文件的完整路徑來代替python 運行的位置:代碼位置 不要輸入的內容:提示符和註 ...
一周排行
    -Advertisement-
    Play Games
  • PasteSpider是什麼? 一款使用.net編寫的開源的Linux容器部署助手,支持一鍵發佈,平滑升級,自動伸縮, Key-Value配置,項目網關,環境隔離,運行報表,差量升級,私有倉庫,集群部署,版本管理等! 30分鐘上手,讓開發也可以很容易的學會在linux上部署你得項目! [從需求角度介 ...
  • SQLSugar是什麼 **1. 輕量級ORM框架,專為.NET CORE開發人員設計,它提供了簡單、高效的方式來處理資料庫操作,使開發人員能夠更輕鬆地與資料庫進行交互 2. 簡化資料庫操作和數據訪問,允許開發人員在C#代碼中直接操作資料庫,而不需要編寫複雜的SQL語句 3. 支持多種資料庫,包括但 ...
  • 在C#中,經常會有一些耗時較長的CPU密集型運算,因為如果直接在UI線程執行這樣的運算就會出現UI不響應的問題。解決這類問題的主要途徑是使用多線程,啟動一個後臺線程,把運算操作放在這個後臺線程中完成。但是原生介面的線程操作有一些難度,如果要更進一步的去完成線程間的通訊就會難上加難。 因此,.NET類 ...
  • 一:背景 1. 講故事 前些天有位朋友在微信上丟了一個崩潰的dump給我,讓我幫忙看下為什麼出現了崩潰,在 Windows 的事件查看器上顯示的是經典的 訪問違例 ,即 c0000005 錯誤碼,不管怎麼說有dump就可以上windbg開幹了。 二:WinDbg 分析 1. 程式為誰崩潰了 在 Wi ...
  • CSharpe中的IO+NPOI+序列化 文件文件夾操作 學習一下常見的文件、文件夾的操作。 什麼是IO流? I:就是input O:就是output,故稱:輸入輸出流 將數據讀入記憶體或者記憶體輸出的過程。 常見的IO流操作,一般說的是[記憶體]與[磁碟]之間的輸入輸出。 作用 持久化數據,保證數據不再 ...
  • C#.NET與JAVA互通之MD5哈希V2024 配套視頻: 要點: 1.計算MD5時,SDK自帶的計算哈希(ComputeHash)方法,輸入輸出參數都是byte數組。就涉及到字元串轉byte數組轉換時,編碼選擇的問題。 2.輸入參數,字元串轉byte數組時,編碼雙方要統一,一般為:UTF-8。 ...
  • CodeWF.EventBus,一款靈活的事件匯流排庫,實現模塊間解耦通信。支持多種.NET項目類型,如WPF、WinForms、ASP.NET Core等。採用簡潔設計,輕鬆實現事件的發佈與訂閱。通過有序的消息處理,確保事件得到妥善處理。簡化您的代碼,提升系統可維護性。 ...
  • 一、基本的.NET框架概念 .NET框架是一個由微軟開發的軟體開發平臺,它提供了一個運行時環境(CLR - Common Language Runtime)和一套豐富的類庫(FCL - Framework Class Library)。CLR負責管理代碼的執行,而FCL則提供了大量預先編寫好的代碼, ...
  • 本章將和大家分享在ASP.NET Core中如何使用高級客戶端NEST來操作我們的Elasticsearch。 NEST是一個高級別的Elasticsearch .NET客戶端,它仍然非常接近原始Elasticsearch API的映射。所有的請求和響應都是通過類型來暴露的,這使得它非常適合快速上手 ...
  • 參考delphi的代碼更改為C# Delphi 檢測密碼強度 規則(仿 google) 仿 google 評分規則 一、密碼長度: 5 分: 小於等於 4 個字元 10 分: 5 到 7 字元 25 分: 大於等於 8 個字元 二、字母: 0 分: 沒有字母 10 分: 全都是小(大)寫字母 20 ...