方法一:用 Flying-Saucer 生产pdf
项目依赖
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.28</version>
</dependency>
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf</artifactId>
<version>9.0.8</version>
</dependency>
项目中建立目录 template/font 字体文件(arialuni.ttf、simsun.ttc)和图片(logo.png)放在 font 目录下,模板文件
protocolTemplate.ftl 放在template目录下。
主要的操作类PdfUtils
package com.ssish.eoms.util;
import com.lowagie.text.DocumentException;
import freemarker.core.ParseException;
import freemarker.template.MalformedTemplateNameException;
import freemarker.template.TemplateException;
import freemarker.template.TemplateNotFoundException;
import org.xhtmlrenderer.pdf.ITextRenderer;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
/**
* <p>Title: PdfUtils.java</p>
* <p>Description: PDF生成工具类</p>
* <p>Company: </p>
* <p> Just go on !!!</p>
* @date 2018年3月1日 下午8:44:18
* @version v1.0
*/
@SuppressWarnings("all")
public class PDFUtil {
static BASE64Encoder encoder = new BASE64Encoder();
static BASE64Decoder decoder = new BASE64Decoder();
/**
* <p>Description: 生成PDF到文件</p>
* <p>Company: </p>
*
* @param ftlPath 模板文件路径(不含文件名)
* @param ftlName 模板文件(不含路径)
* @param imageDiskPath 图片的磁盘路径
* @param data 数据 (填到模板上的数据)
* @param outputFile 目标文件(全路径名称)
* @throws Exception
*/
public static void generateToFile(String ftlPath, String ftlName, String imageDiskPath, Object data,String outputFile) throws Exception {
OutputStream out = null;
ITextRenderer render = null;
try {
String html = PdfHelper.getPdfContent(ftlPath, ftlName, data); //组装模板和数据生成html串
out = new FileOutputStream(outputFile);
render = PdfHelper.getRender();
render.setDocumentFromString(html); //此处抛异常
if (imageDiskPath != null && !imageDiskPath.equals("")) {
// html中如果有图片,图片的路径则使用这里设置的路径的相对路径,这个是作为根路径
render.getSharedContext().setBaseURL("file:/" + imageDiskPath);
}
render.layout();
render.createPDF(out);
render.finishPDF();
render = null;
} catch (Exception e) {
System.out.println("Exception:"+e);
throw e;
} finally{
if (out != null) {
try {
out.close();
} catch (IOException e) {
System.out.println("Exception:"+e);
throw e;
}
}
}
}
/**
* 生成PDF到输出流中(ServletOutputStream用于下载PDF)
*
* @param ftlPath
* ftl模板文件的路径(不含文件名)
* @param ftlName
* ftl模板文件的名称(不含路径)
* @param imageDiskPath
* 如果PDF中要求图片,那么需要传入图片所在位置的磁盘路径
* @param data
* 输入到FTL中的数据
* @param response
* HttpServletResponse
* @return
* @throws TemplateNotFoundException
* @throws MalformedTemplateNameException
* @throws ParseException
* @throws IOException
* @throws TemplateException
* @throws DocumentException
*/
public static OutputStream generateToServletOutputStream(String ftlPath, String ftlName, String imageDiskPath,
Object data, HttpServletResponse response) throws Exception {
String html = PdfHelper.getPdfContent(ftlPath, ftlName, data);
OutputStream out = null;
ITextRenderer render = null;
out = response.getOutputStream();
render = PdfHelper.getRender();
render.setDocumentFromString(html);
if (imageDiskPath != null && !imageDiskPath.equals("")) {
// html中如果有图片,图片的路径则使用这里设置的路径的相对路径,这个是作为根路径
render.getSharedContext().setBaseURL("file:/" + imageDiskPath);
}
render.layout();
render.createPDF(out);
render.finishPDF();
render = null;
return out;
}
/**
* 将PDF转换成base64编码
* 1.使用BufferedInputStream和FileInputStream从File指定的文件中读取内容;
* 2.然后建立写入到ByteArrayOutputStream底层输出流对象的缓冲输出流BufferedOutputStream
* 3.底层输出流转换成字节数组,然后由BASE64Encoder的对象对流进行编码
* */
public static String getPDFBinary(File file) {
FileInputStream fin =null;
BufferedInputStream bin =null;
ByteArrayOutputStream baos = null;
BufferedOutputStream bout =null;
try {
//建立读取文件的文件输出流
fin = new FileInputStream(file);
//在文件输出流上安装节点流(更大效率读取)
bin = new BufferedInputStream(fin);
// 创建一个新的 byte 数组输出流,它具有指定大小的缓冲区容量
baos = new ByteArrayOutputStream();
//创建一个新的缓冲输出流,以将数据写入指定的底层输出流
bout = new BufferedOutputStream(baos);
byte[] buffer = new byte[1024];
int len = bin.read(buffer);
while(len != -1){
bout.write(buffer, 0, len);
len = bin.read(buffer);
}
//刷新此输出流并强制写出所有缓冲的输出字节,必须这行代码,否则有可能有问题
bout.flush();
byte[] bytes = baos.toByteArray();
//sun公司的API
// return encoder.encodeBuffer(bytes).trim();
//apache公司的API
//return Base64.encodeBase64String(bytes);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
fin.close();
bin.close();
//关闭 ByteArrayOutputStream 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException
//baos.close();
bout.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
/**
* 将base64编码转换成PDF
* @param base64sString
* 1.使用BASE64Decoder对编码的字符串解码成字节数组
* 2.使用底层输入流ByteArrayInputStream对象从字节数组中获取数据;
* 3.建立从底层输入流中读取数据的BufferedInputStream缓冲输出流对象;
* 4.使用BufferedOutputStream和FileOutputSteam输出数据到指定的文件中
*/
static void base64StringToPDF(String base64sString) {
BufferedInputStream bin = null;
FileOutputStream fout = null;
BufferedOutputStream bout = null;
try {
//将base64编码的字符串解码成字节数组
byte[] bytes = decoder.decodeBuffer(base64sString);
//apache公司的API
//byte[] bytes = Base64.decodeBase64(base64sString);
//创建一个将bytes作为其缓冲区的ByteArrayInputStream对象
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
//创建从底层输入流中读取数据的缓冲输入流对象
bin = new BufferedInputStream(bais);
//指定输出的文件
File file = new File("/home/amarsoft/test.pdf");
//创建到指定文件的输出流
fout = new FileOutputStream(file);
//为文件输出流对接缓冲输出流对象
bout = new BufferedOutputStream(fout);
byte[] buffers = new byte[1024];
int len = bin.read(buffers);
while(len != -1){
bout.write(buffers, 0, len);
len = bin.read(buffers);
}
//刷新此输出流并强制写出所有缓冲的输出字节,必须这行代码,否则有可能有问题
bout.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bin.close();
fout.close();
bout.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
try {
Map<Object, Object> o= new HashMap<Object, Object>();
o.put( "name" , "张三" );
String path=PdfHelper.getPath();
System.out.println("path="+path);
generateToFile(path, "protocolTemplate.ftl" , path+"font/" , o, "D:\\demo.pdf" );
/*如果需要PDF的下载,可以通过generateToServletOutputStream这个方法来获取PDF的输出流,然后通过response写到客户端去*/
} catch (Exception e) {
e.printStackTrace();
}
}
}
辅助类PdfHelper
package com.ssish.eoms.util;
import com.lowagie.text.DocumentException;
import com.lowagie.text.pdf.BaseFont;
import freemarker.core.ParseException;
import freemarker.template.*;
import org.xhtmlrenderer.pdf.ITextRenderer;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.net.URISyntaxException;
import java.util.Locale;
/**
* @author wyw
* @Description TODO
* @date 2019/7/16 18:33
*/
public class PdfHelper {
public static ITextRenderer getRender() throws DocumentException, IOException, URISyntaxException {
ITextRenderer render = new ITextRenderer();
String path = getResourcesFilePath("template/font/");
//添加字体,以支持中文
render.getFontResolver().addFont(path + "arialuni.ttf" , BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
render.getFontResolver().addFont(path + "simsun.ttc" , BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
return render;
}
//获取要写入PDF的内容
public static String getPdfContent(String ftlPath, String ftlName, Object o) throws TemplateNotFoundException, MalformedTemplateNameException, ParseException, IOException, TemplateException {
return useTemplate(ftlPath, ftlName, o);
}
//使用freemarker得到html内容
public static String useTemplate(String ftlPath, String ftlName, Object o) throws TemplateNotFoundException, MalformedTemplateNameException, ParseException, IOException, TemplateException {
String html = null ;
Template tpl = getFreemarkerConfig(ftlPath).getTemplate(ftlName);
tpl.setEncoding( "UTF-8" );
StringWriter writer = new StringWriter();
tpl.process(o, writer);
writer.flush();
html = writer.toString();
return html;
}
/**
* 获取Freemarker配置
* @param templatePath
* @return
* @throws IOException
*/
private static Configuration getFreemarkerConfig(String templatePath) throws IOException {
Configuration config = new Configuration();
config.setDirectoryForTemplateLoading( new File(templatePath));
config.setEncoding(Locale.CHINA, "utf-8" );
return config;
}
/**
* 获取类路径
* @return
*/
public static String getPath(){
//return PDFUtil.class.getResource("").getPath().substring(1); //返回类路径(当前类所在的路径)
return PDFUtil.class.getResource("/").getPath(); //返回项目根路径(编译之后的根路径)
}
/**
* 通过文件名称获取resources资源文件夹下文件绝对路径
* @param fileName
* @return 模块文件完整路径
* @throws URISyntaxException
*/
public static String getResourcesFilePath(String fileName) throws URISyntaxException {
StringBuilder builder = new StringBuilder();
//判断是否Linux系统
if (System.getProperty("os.name").toLowerCase().indexOf("linux") != -1) {
//linux系统在路径前面加/
builder.append("/");
}
//获取项目路径this.getClass()
String srcPath = PDFUtil.class.getResource("/").toURI().getPath();
System.out.println(srcPath);
if (srcPath.contains("WEB-INF")) {
builder.append(srcPath.substring(1, srcPath.lastIndexOf("WEB-INF")));
} else {
builder.append(srcPath.substring(1));
}
//builder.append("resources/");
builder.append(fileName);
return builder.toString();
}
}
模板文件protocolTemplate.ftl
<html>
<head>
<meta charset="UTF-8"></meta>
<title>
服务协议
</title>
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"></meta>
<style type="text/css">
@page {
size:A4;
}
html,
body {
background-color: #fff;
}
* {
padding: 0;
margin: 0;
}
img {
display: block;
}
.relativeAgreement {
padding: 10px 15px 30px;
}
.relativeAgreement p {
color: #333;
}
.perLineTit1 {
margin-bottom: 20px;
}
.perLineTit {
font-size: 15px;
font-weight: bold;
color: #444;
text-align: center;
}
.normal {
font-size: 15px;
color: #444;
}
.indent2em {
font-size: 15px;
text-indent: 2em;
}
.logoImg {
width: 200px;
margin: 10px auto 12px;
}
.marginTop30 {
margin-top: 20px;
}
.textAlignRight {
text-align: right;
font-size: 15px;
}
p.normalTit {
margin-bottom: 15px;
}
.marginbot {
margin-bottom: 140px;
}
</style>
</head>
<body style="font-family: SimSun;">
<div class="main">
<div class="relativeAgreement">
<img src="logo.png" alt="" class="logoImg" />
<p class="perLineTit">《保险经纪服务委托协议书》</p>
<p class="perLineTit perLineTit1">授权委托书</p>
<p class="normal normalTit">
致:有关保险公司
</p>
<p class="indent2em">
从${(data.year)!}年${(data.month)!}月${(data.day)!}日起,我司委托宇泰保险经纪(北京)有限公司为我司唯一的保险经纪人,代表我们处理所有保险相关事宜。
</p>
<p class="indent2em">
除非另行下达了取消该委托书的书面通知,本委托书将持续有效。同时,所有以前有关这方面的委托全部作废。
</p>
<p class="indent2em">
宇泰保险经纪(北京)有限公司被授权审核我司的保险计划及安排,并按照其营业执照所规定的营业范围提供风险管理及保险服务。
</p>
<p class="normal">
主要服务范围如下:
</p>
<p class="normal">①协助我司安排相关保险;</p>
<p class="normal">②帮助我司识别未投保风险;</p>
<p class="normal">③代我司签署保险协议;</p>
<p class="normal">
④帮助我司检查并核对各种保险文件及其他有关风险转移安排的文件,包括保险单和批单;
</p>
<p class="normal">⑤准备保险安排概要;</p>
<p class="normal">
就风险/保险及赔偿等问题向我司提供建议、帮助及指导;
</p>
<p class="normal">⑥与我司举行保险工作会议;</p>
<p class="normal">⑦检测保险公司的财务稳定性;</p>
<p class="normal">⑧协助我司进行索赔,帮助并指导我司准备索赔文件。</p>
<p class="normal marginTop30">
委托人:(盖章)
<img alt="盖章" style="vertical-align:middle;margin-left:50px;" src="${(data.imageUrl)!}" />
</p>
<p class="textAlignRight" >
${(data.year)!}年${(data.month)!}月${(data.day)!}日
</p>
</div>
<!-- 标记分页
<div style="page-break-after:always; border:1px solid blue;"></div> -->
</div>
</body>
</html>
主要调用代码
@RequestMapping(value = Urls.SSI_BACK_PROTOCOL_MAKE_SEAL, method = RequestMethod.POST)
public void makeSeal(HttpServletResponse response, String imgData,String procotolId) throws IOException {
JSONObject jb = new JSONObject();
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
String filePathAdd ="protocol/img/"+sdf.format(new Date())+".png";
System.out.println("filePathAdd="+filePathAdd);
ossService.upload(new ByteArrayInputStream(Base64.decode(imgData)), filePathAdd);
try {
String pdfurl = createPDF2(response,procotolId,filePathAdd);
System.out.println("pdfurl="+pdfurl);
jb.put("retFlag", "true");
jb.put("pdfurl", pdfurl);
} catch (TemplateException e) {
jb.put("retFlag", "flase");
jb.put("retMsg", e.getMessage());
}
writeDateToWeb(response, jb);
}
public String createPDF2(HttpServletResponse response, String procotolId,String filePath) throws IOException, TemplateException {
log.info("==========================进入生成服务协议pdf");
if(StringUtils.isNotBlank(procotolId)){
WxServiceAgreement wxServiceAgreement = protocolService.queryProtocolById(procotolId);
if(wxServiceAgreement!=null){
//拼接签名储存路径
StringBuilder builder = new StringBuilder();
ProtocolVo protocolVo = new ProtocolVo();
protocolVo.setName(wxServiceAgreement.getClientName());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String cerateStr = sdf.format(wxServiceAgreement.getCreateDate());
protocolVo.setYear(cerateStr.substring(0,4));
protocolVo.setMonth(cerateStr.substring(5,7));
protocolVo.setDay(cerateStr.substring(8,10));
String imageUrl = newbeeFileWeb+"/"+newbeeObjectName+filePath;
System.out.println("imageUrl="+imageUrl);
protocolVo.setImageUrl(imageUrl);
try {
Map<Object, Object> o= new HashMap<Object, Object>();
o.put( "data" , protocolVo );
//String path=PdfHelper.getPath();
//System.out.println("path="+path);
String fontPath = PdfHelper.getResourcesFilePath("template/");
System.out.println("FontPath===================" + fontPath);
String exportFilePath = "/data/eoms/temp/";
//判断文件储存目录是否存在
File saveDir = new File(exportFilePath);
if (!saveDir.exists()) {
saveDir.mkdirs();
log.info("============create directory:" + exportFilePath);
}
exportFilePath = exportFilePath+ wxServiceAgreement.getServiceNo()+".pdf";
PDFUtil.generateToFile(fontPath, "protocolTemplate.ftl" , fontPath+"font/" , o, exportFilePath );
/*如果需要PDF的下载,可以通过generateToServletOutputStream这个方法来获取PDF的输出流,然后通过response写到客户端去*/
//上传到oss
File pdfFile = new File(exportFilePath);
InputStream isZipFile = new FileInputStream(pdfFile);
String osspath="protocol/pdf/"+pdfFile.getName();
boolean flag = ossService.upload(isZipFile,osspath);
log.info("===============flag="+flag);
log.info("===============osspath="+osspath);
WxServiceAgreement serviceAgreement = new WxServiceAgreement();
serviceAgreement.setId(wxServiceAgreement.getId());
serviceAgreement.setFileUrl(newbeeFileWeb+"/"+newbeeObjectName+osspath);
protocolService.updatebyKey(serviceAgreement);
pdfFile.delete();
return newbeeFileWeb+"/"+newbeeObjectName+osspath;
} catch (Exception e) {
e.printStackTrace();
}
}
}
return null;
}
生成后的pdf
注意事项:
1.中文支持问题
因为这个框架是外国人写的,所有的flying-saucer jar包中均不支持中文,但是外国人提供了一个字体类:BaseFont类。只要在这个类中,加入你想要支持的字体文件即可(.afm,.pfm,.ttf ,.otf ,.ttc 均支持),具体代码如下
String path = getResourcesFilePath("template/font/"); //此处得到的路径是项目中 template/font/ 编译后所在的路径 //添加字体,以支持中文 render.getFontResolver().addFont(path + "arialuni.ttf" , BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); render.getFontResolver().addFont(path + "simsun.ttc" , BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
2.在你的模板文件上加上字体!html上加上字体!html上加上字体!重要的事情说三遍!!! 否则,pdf中中文不显示(只能加项目中已经引入了的字体)
<body style="font-family: SimSun;">
3.换行问题 flying-saucer-pdf这个jar包在9.0.1版本之前都是不支持中文换行的 ,强烈不推荐使用9.0.1及其之前的版本!!!
4.路径问题
要导出的html中有图片,图片的路径
图片的路径必须是绝对路径才可以识别哦~代码如下:
render.getSharedContext().setBaseURL("file://"+Thread.currentThread().getContextClassLoader().getResource(imagePath).getPath());
//imagePath 为:图片在html中使用的路径
//这里路径eg : file:///E:...的路径,在windows和linux中均可以识别。