freemarker 模板生成pdf文件并下载

利用freemarker 模板生成pdf文件,通过浏览器直接下载或生成文件到指定目录
1.pom.xml文件
<!--引入Freemarker的依赖-->
<dependency>
   <groupId>org.freemarker</groupId>
   <artifactId>freemarker</artifactId>
   <version>2.3.30</version>
</dependency>
<!--pdf输出用-->
<dependency>
   <groupId>org.xhtmlrenderer</groupId>
   <artifactId>flying-saucer-pdf</artifactId>
   <version>9.0.9</version>
</dependency>
2.pdf模板配置工具类
package com.jeesite.modules.jss.Utils;

import com.lowagie.text.pdf.BaseFont;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.xhtmlrenderer.pdf.ITextRenderer;

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.util.Locale;
import java.util.Map;

public class PDFTemplateUtil {
    /**
     * 通过模板导出pdf文件
     * @param data 数据
     * @param templateFileName 模板文件名
     * @throws Exception
     */
    public static ByteArrayOutputStream createPDF(Map<String,Object> data, String templateFileName) throws Exception {
        // 创建一个FreeMarker实例, 负责管理FreeMarker模板的Configuration实例
        Configuration cfg = new Configuration(Configuration.VERSION_2_3_30  );
        // 空值时不显示
        cfg.setClassicCompatible(true);
        // 指定FreeMarker模板文件的位置
        cfg.setClassForTemplateLoading(PDFTemplateUtil.class,"/template");
        ITextRenderer renderer = new ITextRenderer();
        OutputStream out = new ByteArrayOutputStream();
        try {
            // 设置 css中 的字体样式 必须,不然中文不显示
            renderer.getFontResolver().addFont("/template/font/simsun.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
            // 设置模板的编码格式
            cfg.setEncoding(Locale.CHINA, "UTF-8");
            // 获取模板文件
            Template template = cfg.getTemplate(templateFileName, "UTF-8");
            StringWriter writer = new StringWriter();

            // 将数据输出到html中
            template.process(data, writer);
            writer.flush();

            String html = writer.toString();
            // 把html代码传入渲染器中
            renderer.setDocumentFromString(html);

            // 设置模板中的图片路径 (这里的images在resources目录下) 模板中img标签src路径需要相对路径加图片名 如<img src="images/xh.jpg"/>
            String url = PDFTemplateUtil.class.getClassLoader().getResource("images").toURI().toString();
            renderer.getSharedContext().setBaseURL(url);
            renderer.layout();

            renderer.createPDF(out, false);
            renderer.finishPDF();
            out.flush();
            return (ByteArrayOutputStream)out;
        } finally {
            if(out != null){
                out.close();
            }
        }
    }
}

添加字体:在C盘搜索到需要的字体,然后复制到/template/font/文件夹下
在这里插入图片描述

3.freemarker模板

直接使用html+css绘制模板,然后改为.ftl后缀

ordersPdfTemplate.ftl

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <title></title>
    <style>
        *{
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body{
            font-family: SimSun;
            font-size:16px;
            font-weight: bold;
        }
        section{
            display:block;
            margin: 20px 10px;
        }
        .title{
            text-align: center;
            font-size: 32px;
        }
        .main-content{
            width: 100%;
            margin: 0 auto;
        }
        section > table{
            table-layout: fixed;
            width: 100%;
            margin: 10px 0px;
            word-wrap:break-word;
        }
        section table td{
            padding:5px 5px;
        }
        .header{
            text-align:left;
        }
        .count-footer div{
            font-size: 21px;
            text-align: right;
            line-height: 2em;
        }
        section p{
            padding-left: 30px;
            text-indent: -30px;
            line-height: 1.8em;
        }
        #letter{
            margin-top: 25px;
            position: relative;
            width: 100%;
            height: 130px;
            text-align: right;
        }
        #company-text{
            position: relative;
            top: 5px;
            right: 5%;
        }
        #date-text{
            position: relative;
            top: 15px;
            right: 2%;
        }
        #stamp{
            position: absolute;
            top: 0px;
            right: 2%;
            width: 100%;
            height: 100%;
            z-index: -1;
        }
        #stamp img{
            width: 130px;
            height: 130px;
        }
    </style>
</head>
<body>
<!-- 标题 -->
<section class="title">
    合同签订通知单
</section>

<div class="main-content">
    <!-- 头部 start -->
    <section>
        <table cellspacing="0" cellpadding="0" class="header">
            <tr>
                <td width="65%"></td>
                <td>合同编号:${jssOrders.contractNo}</td>
            </tr>
            <tr>
                <td width="65%"></td>
                <td>签订时间:${jssOrders.signDate?string('yyyy-MM-dd')}</td>
            </tr>
        </table>
    </section>
    <!-- 头部 end -->

    <!-- 产品明细 start -->
    <section>
        <p>一、产品名称、规格、数量</p>
        <table border="1" style="border-collapse: collapse" class="detail">
            <tr style="text-align:center">
                <td width="18%">产品名称</td>
                <td width="20%">规格型号</td>
                <td width="8%">数量</td>
                <td width="22%">生产编号</td>
                <td>备注</td>
            </tr>
            <#list jssOrders.jssProductList as product>
            <#list product.jssDeviceList as device>
                 <!--  项目名_index 可取得循环的下标值 -->
                <#if (device_index = 0)>
                <tr>
                    <!-- 向下合并单元格 -->
                    <td rowspan="${product.jssDeviceList?size}"> ${product.itemDesc} </td>
                    <td rowspan="${product.jssDeviceList?size}"> ${product.model} </td>
                    <td rowspan="${product.jssDeviceList?size}"> ${product.qty} </td>
                    <td>${device.deviceNo}</td>
                    <td>${device.remark2}</td>
                </tr>
                </#if>
            <#if (device_index > 0)>
                <tr>
                    <td>${device.deviceNo}</td>
                    <td>${device.remark2}</td>
                </tr>
            </#if>
            </#list>
            </#list>
        </table>
    </section>
    <!--产品明细 end -->

    <!-- 底部内容 start -->
    <section class="count-footer">
        <p>条例一:</p>
        <p>条例二:</p>
        <div id="letter">
            <div id="company-text"> 销    售    处 </div>
            <div id="date-text">&#160;&#160;</div>
            <div id="stamp"><img src="images/stamp.png"/></div>
        </div>
    </section>
    <!-- 底部内容 end -->
</div>
</body>
</html>
4.controller

此方法不在服务器端生成pdf临时文件,直接以流的方式响应到浏览器生成文件。

/**
 * 导出数据到Pdf
 *
 * @param request
 * @param response
 * @return
 * @throws IOException
 */
@RequestMapping(value = "exportData")
public void exportData(String ordersId, HttpServletRequest request, HttpServletResponse response) throws Exception, TemplateException, DocumentException {
   //获取数据
   JssOrders jssOrders = jssOrdersService.getExportData(ordersId);
   Map<String,Object> root = new HashMap<String,Object>();
   root.put("jssOrders", jssOrders);

   ByteArrayOutputStream baos = null;
   OutputStream out = null;
   try {
      baos = PDFTemplateUtil.createPDF(root, "ordersPdfTemplate.ftl");
      response.setContentType("application/x-msdownload");
      String fileName = URLEncoder.encode("通知单.pdf", "UTF-8");
      response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
      out = response.getOutputStream();
      baos.writeTo(out);
      baos.close();
   } catch (Exception e) {
      e.printStackTrace();
      throw new Exception("导出失败:" + e.getMessage());
   } finally {
      if (baos != null) {
         baos.close();
      }
      if (out != null) {
         out.close();
      }
   }
}

若想生成文件到指定目录:

// 取得流
ByteArrayOutputStream baos = null
 baos = PDFTemplateUtil.createPDF(root, "ordersPdfTemplate.ftl");
// tempPdfPath:生成文件路径,如 D:/files  IdGen.uuid():这里使用了UUID获得一个随机文件名,可换成自己想要生成的文件名
String priceFileName = tempPdfPath + IdGen.uuid() + ".pdf";
FileUtils.createFile(priceFileName);
// 将流写入文件
FileOutputStream fileOutputStream = null;
fileOutputStream = new FileOutputStream(priceFileName);
fileOutputStream.write(baos.toByteArray());
fileOutputStream.close();

将指定目录的文件通过浏览器下载:

InputStream inStream = new FileInputStream(priceFileName);
// 设置输出的格式
response.reset();
response.setContentType( "application/x-msdownload");
String fileName = URLEncoder.encode("报价单.pdf", "UTF-8");
response.setHeader( "Content-Disposition", "attachment;filename=" + fileName);
// 循环取出流中的数据
byte[] b = new byte[100];
int len;
try {
    while ((len = inStream.read(b)) > 0) {
        response.getOutputStream().write(b, 0, len);
    }
    inStream.close();
} catch (IOException e) {
    e.printStackTrace();
}
5.页面请求
// 打印按钮按下事件
$("#printPrice").click(function(){
   // 下载通知单
   $("#dataGrid input:checkbox:checked").each(function(){
      var id = $(this).attr("id").substr(13);
      var iframe = document.createElement("iframe");
      iframe.style.display = "none";
      iframe.style.height = 0;
      iframe.src = "${ctx}/jss/jssOrders/exportData?ordersId=" + id;
      document.body.appendChild(iframe);
      // 5分钟之后删除
      setTimeout(()=>{
         iframe.remove();
      }, 5 * 60 * 1000);
   });
});
若要在模板里添加页眉页脚,.ftl文件里写法如下

摘自:flying-saucer-pdf预览及下载(https://blog.csdn.net/u010138825/article/details/80732786)


```html
<!DOCTYPE html>
<html>
<head lang="en">
    <title>${title} - PDF</title>
    <link href="http://localhost:8080/bootstrap/css/bootstrap.min.css" rel="stylesheet" type="text/css"/>
    <link href="http://localhost:8080/css/pdfPage.css" rel="stylesheet" type="text/css"/>
    <link href="http://localhost:8080/editormd/css/editormd.css" rel="stylesheet" type="text/css"/>
    <style>
        @page {
            size: 210mm 297mm; /*设置纸张大小:A4(210mm 297mm)、A3(297mm 420mm) 横向则反过来*/
            margin: 0.25in;
            padding: 1em;
            @bottom-center {
                content: "Bamboo © 版权所有";
                font-family: SimSun;
                font-size: 12px;
                color: #000;
            };
            @top-center {
                content: element(header)
            };
            @bottom-right {
                content: "第" counter(page) "页  共 " counter(pages) "页";
                font-family: SimSun;
                font-size: 12px;
                color: #000;
            };
        }
    </style>
</head>
<body style="font-family: SimSun;">
<div class="container">
        <div class="projects-header page-header">
            <h2 id="pdftitle" class="text-center" style=" width: 70%;">${title!获取标题失败}</h2>
            <p id="pdflen" class="text-center" style=" width: 70%;">字数:${len!'0'}</p>
        </div>
        <div class="row" style="white-space:normal;word-wrap:break-word;word-break:break-all;width: 70%;">${content!'获取内容失败'}
        </div>
    </div>
</body>
</html>
  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值