springboot html 動態數據轉pdf

需要的jar

            <!-- freemarker,生成 html -->
            <dependency>
                <groupId>org.freemarker</groupId>
                <artifactId>freemarker</artifactId>
                <version>2.3.28</version>
            </dependency>
            <!-- 渲染 css 樣式 -->
            <dependency>
                <groupId>org.xhtmlrenderer</groupId>
                <artifactId>flying-saucer-pdf</artifactId>
                <version>9.1.16</version>
            </dependency>
            <!-- iText,export pdf -->
            <dependency>
                <groupId>com.itextpdf</groupId>
                <artifactId>itextpdf</artifactId>
                <version>5.5.13</version>
            </dependency>

 

資源文件處理工具類



import org.springframework.util.ResourceUtils;

import java.io.File;
import java.io.FileNotFoundException;

/**
 * @Description: 項目靜態資源文件工具類
 *     僅可用於包含在web項目中的資源文件路徑,資源文件必須放置於 web 模塊下
 * @Author: junqiang.lu
 * @Date: 2019/1/4
 */
public class ResourceFileUtil {


    /**
     * 獲取資源文件
     *
     * @param relativePath 資源文件相對路徑(相對於 resources路徑,路徑 + 文件名)
     *                     eg: "templates/pdf_export_demo.ftl"
     * @return
     * @throws FileNotFoundException
     */
    public static File getFile(String relativePath) throws FileNotFoundException {
        if (relativePath == null || relativePath.length() == 0) {
            return null;
        }
        if (relativePath.startsWith("/")) {
            relativePath = relativePath.substring(1);
        }
        File file = ResourceUtils.getFile(ResourceUtils.CLASSPATH_URL_PREFIX
                + relativePath);

        return file;
    }

    /**
     * 獲取資源絕對路徑
     *
     * @param relativePath 資源文件相對路徑(相對於 resources路徑,路徑 + 文件名)
     *                     eg: "templates/pdf_export_demo.ftl"
     * @return
     * @throws FileNotFoundException
     */
    public static String getAbsolutePath(String relativePath) throws FileNotFoundException {
        return getFile(relativePath).getAbsolutePath();
    }

    /**
     * 獲取資源父級目錄
     *
     * @param relativePath 資源文件相對路徑(相對於 resources路徑,路徑 + 文件名)
     *                     eg: "templates/pdf_export_demo.ftl"
     * @return
     * @throws FileNotFoundException
     */
    public static String getParent(String relativePath) throws FileNotFoundException {
        return getFile(relativePath).getParent();
    }

    /**
     * 獲取資源文件名
     *
     * @param relativePath 資源文件相對路徑(相對於 resources路徑,路徑 + 文件名)
     *                     eg: "templates/pdf_export_demo.ftl"
     * @return
     * @throws FileNotFoundException
     */
    public static String getFileName(String relativePath) throws FileNotFoundException {
        return getFile(relativePath).getName();
    }

}

 

 PDF 生成工具類



import com.lowagie.text.DocumentException;
import com.lowagie.text.pdf.BaseFont;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;

import java.io.*;
import java.util.Map;

/**
 * @Description: pdf 導出工具類
 * @Author: junqiang.lu
 * @Date: 2018/12/25
 */
public class PDFUtil {

    private PDFUtil(){}

    private volatile static Configuration configuration;

    static {
        if (configuration == null) {
            synchronized (PDFUtil.class) {
                if (configuration == null) {
                    configuration = new Configuration(Configuration.VERSION_2_3_28);
                }
            }
        }
    }

    /**
     * freemarker 引擎渲染 html
     *
     * @param dataMap 傳入 html 模板的 Map 數據
     * @param ftlFilePath html 模板文件相對路徑(相對於 resources路徑,路徑 + 文件名)
     *                    eg: "templates/pdf_export_demo.ftl"
     * @return
     */
    public static String freemarkerRender(Map<String, Object> dataMap, String ftlFilePath) {
        Writer out = new StringWriter();
        configuration.setDefaultEncoding("UTF-8");
        configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
        try {
            configuration.setDirectoryForTemplateLoading(new File(ResourceFileUtil.getParent(ftlFilePath)));
            configuration.setLogTemplateExceptions(false);
            configuration.setWrapUncheckedExceptions(true);
            Template template = configuration.getTemplate(ResourceFileUtil.getFileName(ftlFilePath));
            template.process(dataMap, out);
            out.flush();
            return out.toString();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TemplateException e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 使用 iText 生成 PDF 文檔
     *
     * @param htmlTmpStr html 模板文件字符串
     * @param fontFile 所需字體文件(相對路徑+文件名)
     * */
    public static byte[] createPDF(String htmlTmpStr, String fontFile) {
        ByteArrayOutputStream outputStream = null;
        byte[] result = null;
        try {
            outputStream = new ByteArrayOutputStream();
            ITextRenderer renderer = new ITextRenderer();
            renderer.setDocumentFromString(htmlTmpStr);
            ITextFontResolver fontResolver = renderer.getFontResolver();
            // 解決中文支持問題,需要所需字體(ttc)文件
            fontResolver.addFont(ResourceFileUtil.getAbsolutePath(fontFile),BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
            renderer.layout();
            renderer.createPDF(outputStream);
            result=outputStream.toByteArray();
            if(outputStream != null) {
                outputStream.flush();
                outputStream.close();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

}

 PDF 導出所需資源文件
PDF 導出需要 freemarker 的渲染模板 ftl 文件

關於 ftl 模板的一些信息: ftl 模板使用和 html 一致的標籤,和 html 不同的是 ftl 模板文件可以添加獨有的邏輯元算標籤, 如 if-else, for 等,邏輯運算標籤具體可參考 freemarker 官方文檔:

https://freemarker.apache.org/docs/ref_directives.html

這是我的模板:pz.ftl

<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>Title</title>
    <style>
        @page {
            size: 279mm 216mm;
        }

        table.pzjbt {
            border-collapse:collapse;
            border-color:#0094b5;
            border-style:solid;
            border-width:1px;
            margin-bottom:3px;
            margin-top:3px;
            text-align: center;
        }
        td.pzjbt {
            border-left-width:1px;
            border-left-style:solid;
            border-left-color:#000000;
            border-top-width:1px;
            border-top-style:solid;
            border-top-color:#000000;
            border-right-width:1px;
            border-right-style:solid;
            border-right-color:#000000;
            border-bottom-width:1px;
            border-bottom-style:solid;
            border-bottom-color:#000000;
            padding-top:1px;
            padding-bottom:1px;
            padding-left:1px;
            padding-right:1px;
            vertical-align:top;
            text-align: center;
        }
        p.pzdl {
            color:#000000;
            font-family:NSimSun;
            font-size:12px;
            font-style:normal;
            font-variant:normal;
            font-weight:300;
            line-height:1.2;
            margin-bottom:0;
            margin-left:0;
            margin-right:0;
            margin-top:0;
            orphans:1;
            page-break-after:auto;
            page-break-before:auto;
            text-align:center;
            text-align-last:center;
            text-decoration:none;
            text-indent:0;
            text-transform:none;
            widows:1;
        }
        td.CellOverride-1 {
            border-bottom-color:#199fbc;
            border-bottom-style:solid;
            border-bottom-width:1px;
            border-left-color:#0094b5;
            border-left-style:solid;
            border-left-width:1px;
            border-right-color:#0094b5;
            border-right-style:solid;
            border-right-width:1px;
            border-top-color:#0094b5;
            border-top-style:solid;
            border-top-width:2px;
            vertical-align:middle;
        }
        td.CellOverride-2 {
            border-bottom-color:#0094b5;
            border-bottom-style:solid;
            border-bottom-width:1px;
            border-left-color:#199fbc;
            border-left-style:solid;
            border-left-width:1px;
            border-right-color:#191919;
            border-right-style:solid;
            border-right-width:0px;
            border-top-color:#199fbc;
            border-top-style:solid;
            border-top-width:1px;
            vertical-align:middle;
        }
        td.CellOverride-3 {
            border-bottom-color:#0094b5;
            border-bottom-style:solid;
            border-bottom-width:1px;
            border-left-color:#191919;
            border-left-style:solid;
            border-left-width:0px;
            border-right-color:#0094b5;
            border-right-style:solid;
            border-right-width:1px;
            border-top-color:#0094b5;
            border-top-style:solid;
            border-top-width:1px;
            vertical-align:middle;
        }
        td.CellOverride-4 {
            border-bottom-color:#0094b5;
            border-bottom-style:solid;
            border-bottom-width:1px;
            border-left-color:#199fbc;
            border-left-style:solid;
            border-left-width:1px;
            border-right-color:#191919;
            border-right-style:solid;
            border-right-width:0px;
            border-top-color:#0094b5;
            border-top-style:solid;
            border-top-width:1px;
            vertical-align:middle;
        }
        td.CellOverride-5 {
            border-bottom-color:#199fbc;
            border-bottom-style:solid;
            border-bottom-width:1px;
            border-left-color:#199fbc;
            border-left-style:solid;
            border-left-width:1px;
            border-right-color:#191919;
            border-right-style:solid;
            border-right-width:0px;
            border-top-color:#0094b5;
            border-top-style:solid;
            border-top-width:1px;
            vertical-align:middle;
        }
        td.CellOverride-6 {
            background-color:#0094b5;
            border-bottom-style:solid;
            border-bottom-width:0px;
            border-left-style:solid;
            border-left-width:0px;
            border-right-color:#0094b5;
            border-right-style:solid;
            border-right-width:1px;
            border-top-color:#199fbc;
            border-top-style:solid;
            border-top-width:1px;
            vertical-align:middle;
        }
        td.CellOverride-7 {
            background-color:#0094b5;
            border-bottom-style:solid;
            border-bottom-width:0px;
            border-left-color:#0094b5;
            border-left-style:solid;
            border-left-width:1px;
            border-right-color:#0094b5;
            border-right-style:solid;
            border-right-width:1px;
            border-top-color:#199fbc;
            border-top-style:solid;
            border-top-width:1px;
            vertical-align:middle;
        }
        td.CellOverride-8 {
            background-color:#0094b5;
            border-bottom-style:solid;
            border-bottom-width:0px;
            border-left-style:solid;
            border-left-width:0px;
            border-right-style:solid;
            border-right-width:0px;
            border-top-color:#0094b5;
            border-top-style:solid;
            border-top-width:1px;
            vertical-align:middle;
        }
        p.ParaOverride-1 {
            text-align:center;
            text-align-last:center;
        }
        p.ParaOverride-2 {
            text-align:left;
        }
        span.CharOverride-1 {
            color:#191919;
            font-family:NSimSun;
            font-style:normal;
            font-weight:normal;
        }
        span.CharOverride-2 {
            font-family:NSimSun;
            font-size:9px;
            font-style:normal;
            font-weight:normal;
        }
        span.CharOverride-3 {
            color:#f5760a;
            font-family:NSimSun;
            font-size:9px;
            font-style:normal;
            font-weight:300;
        }
        span.CharOverride-4 {
            font-family:NSimSun;
            font-style:normal;
            font-weight:300;
        }
        td._idGenCellOverride-1 {
            border-right-style:solid;
            border-right-width:0px;
        }
        col._idGenTableRowColumn-1 {
            width:81px;
        }
        col._idGenTableRowColumn-2 {
            width:35px;
        }
        col._idGenTableRowColumn-3 {
            width:45px;
        }
        tr._idGenTableRowColumn-4 {
            min-height:26px;
        }
        tr._idGenTableRowColumn-5 {
            min-height:23px;
        }
        tr._idGenTableRowColumn-6 {
            min-height:31px;
        }
        img._idGenObjectAttribute-1 {
            height:25px;
            width:57px;
        }
        img._idGenObjectAttribute-2 {
            height:20px;
            width:46px;
        }
    </style>

</head>
<body>

<div id="_idContainer002" style="width: 1000px;">
    <table id="table001" class="pzjbt" style="width: 100%;margin-left: 20%;">
        <colgroup>
            <col class="_idGenTableRowColumn-1" />
            <col class="_idGenTableRowColumn-2" />
            <col class="_idGenTableRowColumn-3" />
            <col class="_idGenTableRowColumn-1" />
            <col class="_idGenTableRowColumn-1" />
        </colgroup>

        <tr class="pzjbt _idGenTableRowColumn-4">
            <td class="pzjbt CellOverride-1" colspan="5">
                <p class="pzdl ParaOverride-1"><span class="CharOverride-1">收  據</span></p>
            </td>
        </tr>
        <tr class="pzjbt _idGenTableRowColumn-5">
            <td class="pzjbt CellOverride-2" colspan="2">
                <p class="pzdl ParaOverride-1"><span class="CharOverride-2">日期</span></p>
            </td>
            <td class="pzjbt CellOverride-3" colspan="3">
                <p class="pzdl ParaOverride-1"><span class="CharOverride-3">${(createTime?string("yyyy-MM-dd HH:mm:ss"))!}</span></p>
            </td>
        </tr>
        <tr class="pzjbt _idGenTableRowColumn-5">
            <td class="pzjbt CellOverride-4" colspan="2">
                <p class="pzdl ParaOverride-1"><span class="CharOverride-2">交易編號</span></p>
            </td>
            <td class="pzjbt CellOverride-3" colspan="3">
                <p class="pzdl ParaOverride-1"><span class="CharOverride-3">${orderCode!''}</span></p>
            </td>
        </tr>
        <tr class="pzjbt _idGenTableRowColumn-5">
            <td class="pzjbt CellOverride-4" colspan="2">
                <p class="pzdl ParaOverride-1"><span class="CharOverride-2">交易類型</span></p>
            </td>
            <td class="pzjbt CellOverride-3" colspan="3">
                <p class="pzdl ParaOverride-1"><span class="CharOverride-3">捐贈</span></p>
            </td>
        </tr>
        <tr class="pzjbt _idGenTableRowColumn-5">
            <td class="pzjbt CellOverride-4" colspan="2">
                <p class="pzdl ParaOverride-1"><span class="CharOverride-2">交易金額</span></p>
            </td>
            <td class="pzjbt CellOverride-3" colspan="3">
                <p class="pzdl ParaOverride-1"><span class="CharOverride-3">${productPrice!0}</span></p>
            </td>
        </tr>
        <tr class="pzjbt _idGenTableRowColumn-5">
            <td class="pzjbt CellOverride-5" colspan="2">
                <p class="pzdl ParaOverride-1"><span class="CharOverride-2">付款人</span></p>
            </td>
            <td class="pzjbt CellOverride-3" colspan="3">
                <p class="pzdl ParaOverride-1"><span class="CharOverride-3">${userName!''}</span></p>
            </td>
        </tr>
       
    </table>
</div>

</body>
</html>

 

html前端入口

 <a  href="${basePath}/exportPdf?orderId=${saleOrder.id}">下載</a>

Controller 層


    @RequestMapping(value = "/exportPdf", method = {RequestMethod.POST, RequestMethod.GET},
            produces = MediaType.MULTIPART_FORM_DATA_VALUE)
    public ResponseEntity<?> exportPdf(Long orderId){
        try {
            SaleOrderVo saleOrder = saleOrderExtService.getSaleOrderById(orderId);
            ResponseEntity<?> responseEntity = export(saleOrder);
            return responseEntity;
        } catch (Exception e) {
            e.printStackTrace();
        }

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
        return new ResponseEntity<String>("{ \"code\" : \"404\", \"message\" : \"not found\" }",
                headers, HttpStatus.NOT_FOUND);
    }

    /**
     * PDF 文件導出
     *
     * @return
     */
    public ResponseEntity<?> export(SaleOrderVo saleOrder) {
        HttpHeaders headers = new HttpHeaders();

        /**
         * 數據導出(PDF 格式)
         */
        Map<String, Object> dataMap = new HashMap<>(16);
        dataMap.put("createTime",saleOrder.getCreateTime());
        dataMap.put("orderCode",saleOrder.getOrderCode());
        dataMap.put("productPrice",saleOrder.getProductPrice());
        dataMap.put("userName",saleOrder.getUserName());

        String htmlStr = PDFUtil.freemarkerRender(dataMap, "templates/website/pz/pz.ftl");

        InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("templates/website/pz/simsun.ttc");
       // htmlStr = htmlStr.replaceAll("<br>", "");
       //htmlStr = htmlStr.replaceAll("</br>", "");
        byte[] pdfBytes = PDFUtil.createPDF(htmlStr, "templates/website/pz/simsun.ttc");
        if (pdfBytes != null && pdfBytes.length > 0) {
            String fileName = UUID.randomUUID() + ".pdf";
            headers.setContentDispositionFormData("attachment", fileName);
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            return new ResponseEntity<byte[]>(pdfBytes, headers, HttpStatus.OK);
        }
        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
        return new ResponseEntity<String>("{ \"code\" : \"404\", \"message\" : \"not found\" }",
                headers, HttpStatus.NOT_FOUND);
    }

 

simsun.ttc字體文件  :鏈接:https://pan.baidu.com/s/1UO6uSKaL-DERaFYaqZqs8g  密碼:yhdo

點擊下載效果

遇到的錯誤

simsun.ttc  is not a valid TTF file

原因:問題是Maven正在過濾字體文件並破壞它們。

pom.xml加入

          <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <configuration>
                    <nonFilteredFileExtensions>
                        <nonFilteredFileExtension>ttf</nonFilteredFileExtension>
                        <nonFilteredFileExtension>woff</nonFilteredFileExtension>
                        <nonFilteredFileExtension>woff2</nonFilteredFileExtension>
                    </nonFilteredFileExtensions>
                </configuration>
            </plugin>

參考文章: http://www.mamicode.com/info-detail-2411295.html

參考文章:https://blog.csdn.net/Mrqiang9001/article/details/86241836

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章