利用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"> 年  月  日 </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>