《SpringBoot2.0 實戰》系列-整合FlyingSaucer + thymeleaf 實現模板文件轉pdf打印

前言

最近,接到一個模板打印pdf的任務,後來網上找了很多案例,本文做下記錄。

如何開始

添加依賴包

<!-- thymeleaf -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--FlyingSaucer-->
<dependency>
    <groupId>org.xhtmlrenderer</groupId>
    <artifactId>flying-saucer-pdf</artifactId>
    <version>9.1.9</version>
</dependency>

yml配置(可選,不配置默認也可)

spring:
  # thymeleaf
  thymeleaf:
    prefix: classpath:/templates/
    check-template-location: true
    suffix: .html
    encoding: UTF-8
    mode: HTML
    cache: false
    servlet:
      content-type: text/html

模板文件

如下文件放置位置,文件都在我的開源項目中,文末有地址。

pdfPage模板文件:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.w3.org/1999/xhtml" layout:decorator="layout">
<head lang="en">
    <title>Spring Boot Demo - PDF</title>
    <style>
        @page {
            size: 210mm 297mm; /*設置紙張大小:A4(210mm 297mm)、A3(297mm 420mm) 橫向則反過來*/
            margin: 0.25in;
            padding: 1em;
            @bottom-center{
                content:"葫蘆科技 © 版權所有";
                font-family: SimSun;
                font-size: 12px;
                color:red;
            };
            @top-center { content: element(header) };
            @bottom-right{
                content:"第" counter(page) "頁  共 " counter(pages) "頁";
                font-family: SimSun;
                font-size: 12px;
                color:#000;
            };
        }
        body{font-family: 'SimSun'}
        h2{color: crimson}
        #myheader{
            width: 500px;
            height: 22px;
            border: 1px solid #000000;
        }
        table, th , td  {
            border: 1px solid grey;
            border-collapse: collapse;
            padding: 5px;
        }
        table tr:nth-child(odd) {
            background-color: #f1f1f1;
        }
        table tr:nth-child(even) {
            background-color: #ffffff;
        }
        #input1{
            border-bottom: 1px solid #000000;
        }
    </style>
</head>
<!--這樣配置不中文不會顯示-->
<!--<body style="font-family: 宋體">-->
<body style="font-family: 'SimSun'">
<div>1.標題-中文</div>
<h2 th:text="${title}"></h2>

<div>2.按鈕:按鈕的邊框需要寫css渲染</div>
<button class="a" style="border: 1px solid #000000"> click me t-p</button>
<div id="divsub"></div>

<div>3.普通div</div>
<div id="myheader">Alice's Adventures in Wonderland</div>

<div>4.圖片 絕對定位到左上角(注意:圖片必須用全路徑或者http://開頭的路徑,否則無法顯示)</div>
<img th:src="${imageUrl}"/>

<div>5.普通table表格</div>
<div>
    <table style="width: 700px">
        <tr>
            <th>姓名</th>
            <th>暱稱</th>
            <th>年齡</th>
        </tr>
        <tr th:each="info : ${demoList}">
            <td th:text="${info.name}"></td>
            <td th:text="${info.nick}"></td>
            <td th:text="${info.age}"></td>
        </tr>
    </table>

</div>

<div>6.input控件,邊框需要寫css渲染 (在模板中一般不用input,因爲不存在輸入操作)</div>
<div>
    <label>姓名:</label>
    <input id="input1" aria-label="葫蘆胡" type="text" value="葫蘆胡"/>
</div>
</body>
</html>

避坑:

測試的時候,模板文件中一定至少有一個:th:text="${xxx}",否則會報模板不存在

圖片路徑需絕對路徑,如:http://localhost:8088/hu/imgs/sg.jpg

核心處理類

工具類

/**
 * pdf處理工具類
 *
 * @author gourd.hu
 * @version 1.0
 */
@Slf4j
public class PdfUtil {

    /**
     * 按模板和參數生成html字符串,再轉換爲flying-saucer識別的Document
     *
     * @param templateName 模板名稱
     * @param variables   模板參數
     * @return Document
     */
    private static Document generateDoc(TemplateEngine templateEngine, String templateName, Map<String, Object> variables)  {
        // 聲明一個上下文對象,裏面放入要存到模板裏面的數據
        final Context context = new Context();
        context.setVariables(variables);
        StringWriter stringWriter = new StringWriter();
        try(BufferedWriter writer = new BufferedWriter(stringWriter)) {
            templateEngine.process(templateName,context, writer);
            writer.flush();
            DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            return builder.parse(new ByteArrayInputStream(stringWriter.toString().getBytes()));
        }catch (Exception e){
            ResponseEnum.TEMPLATE_PARSE_ERROR.assertFail(e);
        }
        return null;
    }

    /**
     * 核心: 根據freemarker模板生成pdf文檔
     *
     * @param templateEngine 配置
     * @param templateName 模板名稱
     * @param out          輸出流
     * @param listVars     模板參數
     * @throws Exception 模板無法找到、模板語法錯誤、IO異常
     */
    private static void generateAll(TemplateEngine templateEngine, String templateName, OutputStream out, List<Map<String, Object>> listVars) throws Exception {
        // 斷言參數不爲空
        ResponseEnum.TEMPLATE_DATA_NULL.assertNotEmpty(listVars);
        ITextRenderer renderer = new ITextRenderer();
        //設置字符集(宋體),此處必須與模板中的<body style="font-family: SimSun">一致,區分大小寫,不能寫成漢字"宋體"
        ITextFontResolver fontResolver = renderer.getFontResolver();
        //避免中文爲空設置系統字體
        fontResolver.addFont("static/fonts/simsun.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);

        //根據參數集個數循環調用模板,追加到同一個pdf文檔中
        //(注意:此處從1開始,因爲第0是創建pdf,從1往後則向pdf中追加內容)
        for (int i = 0; i < listVars.size(); i++) {
            Document docAppend = generateDoc(templateEngine, templateName, listVars.get(i));
            renderer.setDocument(docAppend, null);
            //展現和輸出pdf
            renderer.layout();
            if(i==0){
                renderer.createPDF(out, false);
            }else {
                //寫下一個pdf頁面
                renderer.writeNextDocument();
            }

        }
        renderer.finishPDF(); //完成pdf寫入
    }

    /**
     * pdf下載
     *
     * @param templateEngine   配置
     * @param templateName 模板名稱
     * @param listVars     模板參數集
     * @param response     HttpServletResponse
     * @param fileName     下載文件名稱
     */
    public static void download(TemplateEngine templateEngine, String templateName, List<Map<String, Object>> listVars, HttpServletResponse response, String fileName) {
        // 設置編碼、文件ContentType類型、文件頭、下載文件名
        response.setCharacterEncoding("utf-8");
        response.setContentType("multipart/form-data");
        try {
            response.setHeader("Content-Disposition", "attachment;fileName=" +
                    new String(fileName.getBytes("gb2312"), "ISO8859-1"));
        } catch (UnsupportedEncodingException e) {
            log.error(e.getMessage(), e);
        }
        try (ServletOutputStream out = response.getOutputStream()) {
            generateAll(templateEngine, templateName, out, listVars);
            out.flush();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * pdf下載到特定位置
     *
     * @param templateEngine   配置
     * @param templateName 模板名稱
     * @param listVars     模板參數集
     * @param filePath     下載文件路徑
     */
    public static void save(TemplateEngine templateEngine, String templateName, List<Map<String, Object>> listVars, String filePath) {
        try (OutputStream out = new FileOutputStream(filePath);) {
            generateAll(templateEngine, templateName, out, listVars);
            out.flush();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * pdf預覽
     *
     * @param templateEngine   配置
     * @param templateName 模板名稱
     * @param listVars     模板參數集
     * @param response     HttpServletResponse
     */
    public static void preview(TemplateEngine templateEngine, String templateName, List<Map<String, Object>> listVars, HttpServletResponse response) {
        try (ServletOutputStream out = response.getOutputStream()) {
            generateAll(templateEngine, templateName, out, listVars);
            out.flush();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }
}

Controller

/**
 * pdf預覽、下載
 *
 * @author gourd.hu
 * @version 1.0
 */
@RestController
@RequestMapping(value = "/pdf")
@Api(tags = "pdf測試API", description = "pdf功能")
public class PdfController {

    @Autowired
    private TemplateEngine templateEngine;

    @Value("${pdf.windowsFileTempLoc}")
    private String pdfWindowsPath;

    @Value("${pdf.linuxFileTempLoc}")
    private String pdfLinuxPath;
    
/**
 * 文檔預覽、下載
 *
 * @author gourd.hu
 * @version 1.0
 */
@RestController
@RequestMapping(value = "/document")
@Api(tags = "文檔預覽、下載API")
public class DocumentController {

    @Autowired
    private TemplateEngine templateEngine;

    @Value("${pdf.windowsFileTempLoc}")
    private String pdfWindowsPath;

    @Value("${pdf.linuxFileTempLoc}")
    private String pdfLinuxPath;

    /**
     * pdf預覽
     *
     * @param response HttpServletResponse
     */
    @GetMapping(value = "/pdf/preview")
    @ApiOperation(value="pdf預覽")
    public void preview(HttpServletResponse response) {
        // 構造freemarker模板引擎參數,listVars.size()個數對應pdf頁數
        List<Map<String,Object>> listVars = new ArrayList<>();
        Map<String,Object> variables = new HashMap<>(4);
        variables.put("title","測試預覽PDF!");
        variables.put("imageUrl",sslEnabled?"https://localhost:10001/imgs/sg.jpg":"http://localhost:10001/imgs/sg.jpg");
        List<Map<String,String>> demoList = new ArrayList<>();
        Map<String,String> demoMap = new HashMap<>(8);
        demoMap.put("name","哈哈");
        demoMap.put("nick","娃娃");
        demoMap.put("age","19");
        Map<String,String> demoMap2 = new HashMap<>(8);
        demoMap2.put("name","天天");
        demoMap2.put("nick","飯飯");
        demoMap2.put("age","14");
        demoList.add(demoMap);
        demoList.add(demoMap2);
        variables.put("demoList",demoList);
        Map<String,Object> variables2 = new HashMap<>(4);
        variables2.put("title","測試預覽PDF2!");
        listVars.add(variables);
        listVars.add(variables2);

        PdfUtil.preview(templateEngine,"pdfPage",listVars,response);
    }

    /**
     * pdf下載
     *
     * @param response HttpServletResponse
     */
    @GetMapping(value = "/pdf/download")
    @ApiOperation(value="pdf下載")
    public void download(HttpServletResponse response) {
        List<Map<String,Object>> listVars = new ArrayList<>(1);
        Map<String,Object> variables = new HashMap<>(4);
        variables.put("title","測試下載PDF!");
        variables.put("imageUrl",sslEnabled?"https://localhost:10001/imgs/sg.jpg":"http://localhost:10001/imgs/sg.jpg");
        List<Map<String,String>> demoList = new ArrayList<>();
        Map<String,String> demoMap = new HashMap<>(8);
        demoMap.put("name","哈哈");
        demoMap.put("nick","娃娃");
        demoMap.put("age","19");
        Map<String,String> demoMap2 = new HashMap<>(8);
        demoMap2.put("name","天天");
        demoMap2.put("nick","飯飯");
        demoMap2.put("age","14");
        demoList.add(demoMap);
        demoList.add(demoMap2);
        variables.put("demoList",demoList);
        listVars.add(variables);
        PdfUtil.download(templateEngine,"pdfPage",listVars,response,"測試打印.pdf");
    }

    /**
     * pdf下載到特定位置
     *
     */
    @GetMapping(value = "/pdf/save")
    @ApiOperation(value="pdf下載到特定位置")
    public void save() {
        List<Map<String,Object>> listVars = new ArrayList<>(1);
        Map<String,Object> variables = new HashMap<>(4);
        variables.put("title","測試下載PDF!");
        variables.put("imageUrl",sslEnabled?"https://localhost:10001/imgs/sg.jpg":"http://localhost:10001/imgs/sg.jpg");
        List<Map<String,String>> demoList = new ArrayList<>();
        Map<String,String> demoMap = new HashMap<>(8);
        demoMap.put("name","哈哈");
        demoMap.put("nick","娃娃");
        demoMap.put("age","19");
        Map<String,String> demoMap2 = new HashMap<>(8);
        demoMap2.put("name","天天");
        demoMap2.put("nick","飯飯");
        demoMap2.put("age","14");
        demoList.add(demoMap);
        demoList.add(demoMap2);
        variables.put("demoList",demoList);
        listVars.add(variables);
        // pdf文件下載位置
        String pdfPath = CommonUtil.isLinux() ? pdfLinuxPath : pdfWindowsPath;
        PdfUtil.save(templateEngine,"pdfPage",listVars,pdfPath);
    }
}
}

演示效果

結語

至此,pdf打印功能就完成了,如果本文有錯誤的地方,歡迎評論指正。

===============================================

代碼均已上傳至本人的開源項目

cloud-plus:https://blog.csdn.net/HXNLYW/article/details/104635673

 

 

 

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