zoukankan      html  css  js  c++  java
  • java根据模板HTML动态生成PDF

    原文:https://segmentfault.com/a/1190000009160184

    一、需求说明:根据业务需要,需要在服务器端生成可动态配置的PDF文档,方便数据可视化查看。

    二、解决方案:
    iText+FreeMarker+JFreeChart生成可动态配置的PDF文档
    iText有很强大的PDF处理能力,但是样式和排版不好控制,直接写PDF文档,数据的动态渲染很麻烦。
    FreeMarker能配置动态的html模板,正好解决了样式、动态渲染和排版问题。
    JFreeChart有这方便的画图API,能画出简单的折线、柱状和饼图,基本能满足需要。

    三、实现功能:

       1、能动态配置PDF文档内容
       2、支持中文字体显示的动态配置
       3、设置自定义的页眉页脚信息
       4、能动态生成业务图片
       5、完成PDF的分页和图片的嵌入
    

    四、主要代码结构说明:

       1、component包:PDF生成的组件 对外提供的是PDFKit工具类和HeaderFooterBuilder接口,其中PDFKit负责PDF的生成,HeaderFooterBuilder负责自定义页眉页脚信息。
       2、builder包:负责PDF模板之外的额外信息填写,这里主要是页眉页脚的定制。
       3、chart包:JFreeChart的画图工具包,目前只有一个线形图。
       4、test包:测试工具类
       5、util包:FreeMarker等工具类。
    

    五、关键代码说明:

    1、模板配置

      <!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;
                 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>
    

    2、获取模板内容并填充数据

    /**
     * @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);
        }
    }
        

    3、导出模板到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;
    
    }
        
    

    4、测试工具类

     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);
            
            }
    

    六、生成效果图:

    clipboard.png

    七、项目完整代码

    1、github地址:https://github.com/superad/pdf-kit
    2、项目git地址:git@github.com:superad/pdf-kit.git
    

    八、遇到的坑:

    1、FreeMarker配置模板文件样式,在实际PDF生成过程中,可能会出现一些不一致的情形,目前解决方法,就是换种方式调整样式。

    2、字体文件放在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>
    

    3、PDF分页配置:

      在ftl文件中,增加分页标签: <span style="page-break-after:always;"></span>
    

    九、 完整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>
  • 相关阅读:
    《大道至简》读书笔记 第3篇
    《大道至简》读书笔记 第2篇
    返回一个二维整数数组中最大联通子数组的和(思路)
    《大道至简》读书笔记 第1篇
    给尊敬的王老师
    团队开发——冲刺2.g
    第一阶段冲刺的总结报告
    团队开发——冲刺2.f
    《人月神话》读书笔记 第3篇
    团队开发——冲刺2.e
  • 原文地址:https://www.cnblogs.com/shihaiming/p/9591679.html
Copyright © 2011-2022 走看看