java根據模板動態生成PDF

1、需求說明:根據業務須要,須要在服務器端生成可動態配置的PDF文檔,方便數據可視化查看。css

2、解決方案:
iText+FreeMarker+JFreeChart生成可動態配置的PDF文檔
iText有很強大的PDF處理能力,可是樣式和排版很差控制,直接寫PDF文檔,數據的動態渲染很麻煩。
FreeMarker能配置動態的html模板,正好解決了樣式、動態渲染和排版問題。
JFreeChart有這方便的畫圖API,能畫出簡單的折線、柱狀和餅圖,基本能知足須要。html

3、實現功能:java

一、能動態配置PDF文檔內容
   二、支持中文字體顯示的動態配置
   三、設置自定義的頁眉頁腳信息
   四、能動態生成業務圖片
   五、完成PDF的分頁和圖片的嵌入

4、主要代碼結構說明:git

一、component包:PDF生成的組件 對外提供的是PDFKit工具類和HeaderFooterBuilder接口,其中PDFKit負責PDF的生成,HeaderFooterBuilder負責自定義頁眉頁腳信息。
   二、builder包:負責PDF模板以外的額外信息填寫,這裏主要是頁眉頁腳的定製。
   三、chart包:JFreeChart的畫圖工具包,目前只有一個線形圖。
   四、test包:測試工具類
   五、util包:FreeMarker等工具類。

5、關鍵代碼說明:github

一、模板配置apache

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <meta http-equiv="Content-Style-Type" content="text/css"/>
    <title></title>
    <style type="text/css">
        body {
            font-family: pingfang sc light;
        }
        .center{
            text-align: center;
            width: 100%;
        }
    </style>
</head>
<body>
<!--第一頁開始-->
<div class="page" >
    <div class="center"><p>${templateName}</p></div>
    <div><p>iText官網:${ITEXTUrl}</p></div>
    <div><p>FreeMarker官網:${freeMarkerUrl}</p></div>
    <div><p>JFreeChart教程:${JFreeChartUrl}</p></div>
    <div>列表值:</div>
    <div>
        <#list scores as item>
            <div><p>${item}</p></div>
        </#list>
    </div>
</div>
<!--第一頁結束-->
<!---分頁標記-->
<span style="page-break-after:always;"></span>
<!--第二頁開始-->
<div class="page">
    <div>第二頁開始了</div>
    <!--外部連接-->
    <p>百度圖標</p>
    <div>
        <img src="${imageUrl}" alt="百度圖標" width="270" height="129"/>
    </div>
    <!--動態生成的圖片-->
    <p>氣溫變化對比圖</p>
    <div>
        <img src="${picUrl}" alt="個人圖片" width="500" height="270"/>
    </div>
</div>


<!--第二頁結束-->
</body>
</html>

二、獲取模板內容並填充數據api

/**
 * @description 獲取模板
 */
public static String getContent(String fileName,Object data){

    String templatePath=getPDFTemplatePath(fileName);//根據PDF名稱查找對應的模板名稱
    String templateFileName=getTemplateName(templatePath);
    String templateFilePath=getTemplatePath(templatePath);
    if(StringUtils.isEmpty(templatePath)){
        throw new FreeMarkerException("templatePath can not be empty!");
    }
    try{
        Configuration config = new Configuration(Configuration.VERSION_2_3_25);//FreeMarker配置
        config.setDefaultEncoding("UTF-8");
        config.setDirectoryForTemplateLoading(new File(templateFilePath));//注意這裏是模板所在文件夾,不是文件
        config.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
        config.setLogTemplateExceptions(false);
        Template template = config.getTemplate(templateFileName);//根據模板名稱 獲取對應模板
        StringWriter writer = new StringWriter();
        template.process(data, writer);//模板和數據的匹配
        writer.flush();
        String html = writer.toString();
        return html;
    }catch (Exception ex){
        throw new FreeMarkerException("FreeMarkerUtil process fail",ex);
    }
}

三、導出模板到PDF文件服務器

/**
     * @description     導出pdf到文件
     * @param fileName  輸出PDF文件名
     * @param data      模板所須要的數據
     *
     */
public String exportToFile(String fileName,Object data){
     String htmlData= FreeMarkerUtil.getContent(fileName, data);//獲取FreeMarker的模板數據
    if(StringUtils.isEmpty(saveFilePath)){
        saveFilePath=getDefaultSavePath(fileName);//設置PDF文件輸出路徑
    }
    File file=new File(saveFilePath);
    if(!file.getParentFile().exists()){
        file.getParentFile().mkdirs();
    }
    FileOutputStream outputStream=null;
    try{
        //設置輸出路徑
        outputStream=new FileOutputStream(saveFilePath);
        //設置文檔大小
        Document document = new Document(PageSize.A4);//IText新建PDF文檔
        PdfWriter writer = PdfWriter.getInstance(document, outputStream);//設置文檔和輸出流的關係

        //設置頁眉頁腳
        PDFBuilder builder = new PDFBuilder(headerFooterBuilder,data);
        builder.setPresentFontSize(10);
        writer.setPageEvent(builder);

        //輸出爲PDF文件
        convertToPDF(writer,document,htmlData);
    }catch(Exception ex){
        throw new PDFException("PDF export to File fail",ex);
    }finally{
        IOUtils.closeQuietly(outputStream);
    }
    return saveFilePath;

}

四、測試工具類yii

public  String createPDF(Object data, String fileName){
            //pdf保存路徑
            try {
                //設置自定義PDF頁眉頁腳工具類
                PDFHeaderFooter headerFooter=new PDFHeaderFooter();
                PDFKit kit=new PDFKit();
                kit.setHeaderFooterBuilder(headerFooter);
                //設置輸出路徑
                kit.setSaveFilePath("/Users/fgm/Desktop/pdf/hello.pdf」);//設置出書路徑
                String saveFilePath=kit.exportToFile(fileName,data);
                return  saveFilePath;
            } catch (Exception e) {
                log.error("PDF生成失敗{}", ExceptionUtils.getFullStackTrace(e));
                return null;
            }
        
        }
public static void main(String[] args) {
         ReportKit360 kit=new ReportKit360();
                TemplateBO templateBO=new TemplateBO();//配置模板數據
                templateBO.setTemplateName("Hello iText! Hello freemarker! Hello jFreeChart!");
                templateBO.setFreeMarkerUrl("http://www.zheng-hang.com/chm/freemarker2_3_24/ref_directive_if.html");
                templateBO.setITEXTUrl("http://developers.itextpdf.com/examples-itext5");
    
    templateBO.setJFreeChartUrl("http://www.yiibai.com/jfreechart/jfreechart_referenced_apis.html");
        templateBO.setImageUrl("https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo/bd_logo1_31bdc765.png");
        
        
             List<String> scores=new ArrayList<String>();
                scores.add("90");
                scores.add("95");
                scores.add("98");
                templateBO.setScores(scores);
                List<Line> lineList=getTemperatureLineList();
                TemperatureLineChart lineChart=new TemperatureLineChart();
                String picUrl=lineChart.draw(lineList,0);//自定義的數據畫圖
                templateBO.setPicUrl(picUrl);
                String path= kit.createPDF(templateBO,"hello.pdf");
            System.out.println(path);
        
        }

6、生成效果圖:maven

clipboard.png

7、項目完整代碼

一、github地址:https://github.com/superad/pdf-kit
二、項目git地址:git@github.com:superad/pdf-kit.git

8、遇到的坑:

一、FreeMarker配置模板文件樣式,在實際PDF生成過程當中,可能會出現一些不一致的情形,目前解決方法,就是換種方式調整樣式。

二、字體文件放在resource下,在打包時會報錯,運行mvn -X compile 會看到詳細錯誤:
這是字體文件是二進制的,而maven項目中配置了資源文件的過濾,不能識別二進制文件致使的,
plugins中增長下面這個配置就行了:

<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
        </resource>
    </resources>
    <!--增長的配置,過濾ttf文件的匹配-->
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-resources-plugin</artifactId>
            <version>2.7</version>
            <configuration>
                <encoding>UTF-8</encoding>
                <nonFilteredFileExtensions>
                    <nonFilteredFileExtension>ttf</nonFilteredFileExtension>
                </nonFilteredFileExtensions>
            </configuration>
        </plugin>
    </plugins>
</build>

三、PDF分頁配置:

在ftl文件中,增長分頁標籤: <span style="page-break-after:always;"></span>

9、 完整maven配置:

<!--pdf生成 itext-->
  
 <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>itextpdf</artifactId>
        <version>5.4.2</version>
     </dependency>
 <dependency>
    <groupId>com.itextpdf.tool</groupId>
    <artifactId>xmlworker</artifactId>
    <version>5.4.1</version>
</dependency>
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itext-asian</artifactId>
    <version>5.2.0</version>
</dependency>
<dependency>
    <groupId>org.xhtmlrenderer</groupId>
    <artifactId>flying-saucer-pdf</artifactId>
    <version>9.0.3</version>
</dependency>
<!--freemarker-->
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.26-incubating</version>
</dependency>
<!--jfreechart-->
<dependency>
    <groupId>jfreechart</groupId>
    <artifactId>jfreechart</artifactId>
    <version>1.0.0</version>
</dependency>
<!--log-->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.0.13</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.0.13</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-access</artifactId>
    <version>1.0.13</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.5</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>1.7.21</version>
</dependency>
<!--util-->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>20.0</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.14.8</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-io</artifactId>
    <version>1.3.2</version>
</dependency>
<dependency>
    <groupId>commons-lang</groupId>
    <artifactId>commons-lang</artifactId>
    <version>2.6</version>
</dependency>
<!--servlet-->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    <version>2.5</version>
</dependency>
相關文章
相關標籤/搜索