最近项目要求做一个freemarker转PDF和Word的功能,因为以前没用过,遇到了很多坑所以在这里记录一下,方便自己以后用到,不说了直接上代码:
1.先说PDF的
(1)先做一个模板(用HTML页面把你需要的模板文件画出来,记得用行内样式,所有标签必须闭合,画完之后复制一份改名为.ftl格式的文件)
正常的HTML文件就不贴了(画完之后里面什么都不用更改,再ftl文件里记得加上下面这两个,不然生成不了)
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta name="generator" content="Aspose.Words for .NET 15.1.0.0" />
body {
font-family: SimSun;
backgrou nd: none;
margin-left: 25px;
margin-right: auto;
}
然后把需要替换值得地方用freemarker标签去替换;这是集合替换值得方法(key是别名)
<#list tYxyUserMeal as key>
<td>
<div>${key.mealName}</div>
</td>
</#list>
下面是对象替换方法(直接对象点属性就好);
<li>版本日期:${dataTime.date}</li>
还有一种if判断的方法(我是判断值等于一或者二显示这一行 )
<#if (tyxyKeywordConf.encryption) == "1" || (tyxyKeywordConf.encryption) == "2">
</#if>
(2)Freemarker转化(tmppdfPath 是在你的电脑上找一个位置放置生成的HTML文件,这个直接可以使用只需要改模板名称就好)
这个再不同的操作模式下路径//的转换方法
package com.ums.dmp.base.common;
import java.io.File;
import javax.servlet.http.HttpServletRequest;
public class GetWebProjectRealPathTool {
/**
* 获取项目在服务其中的真实路径的工具类
* 这是在web项目中,获取项目实际路径的最佳方式,在windows和linux系统下均可正常使用
*/
//获取项目的根路径
public static String classPath = GetWebProjectRealPathTool.class.getClassLoader().getResource("/").getPath();
//对项目的根路径进行解析,拿到项目路径
public static String getRootPath() {
String rootPath = "";
//windows下
if("\\".equals(File.separator)){
System.out.println("windows");
rootPath = classPath.substring(1,classPath.indexOf("/WEB-INF/classes"));
rootPath = rootPath.replace("/", "\\");
}
//linux下
if("/".equals(File.separator)){
System.out.println("linux");
rootPath = classPath.substring(0,classPath.indexOf("/WEB-INF/classes"));
rootPath = rootPath.replace("\\", "/");
}
return rootPath;
}
public static String getBasePath(HttpServletRequest request){
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()+request.getContextPath()+"/";
return basePath;
}
}
这个是转换方法
package com.ums.dmp.base.service.impl;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Image;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfGState;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;
import com.lowagie.text.pdf.BaseFont;
import com.ums.dmp.base.common.GetWebProjectRealPathTool;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
/**
* html to pdf
*
* @author yl
*
*/
public class H2p {
private Logger logger = LoggerFactory.getLogger(getClass());
private static H2p instance = new H2p();
private H2p() {
}
public static H2p getInstance() {
return instance;
}
private String tmppdfPath = GetWebProjectRealPathTool.getRootPath()
+ "/report/";
/**
* 接口文档 html to pdf
*
* @param archiveInfoMap
* @return
* @throws Exception
*/
public File preExportPdfForSh(Map<String, Object> archiveInfoMap)
throws Exception {
// UUID uuid = UUID.randomUUID();
String outputFile = tmppdfPath + "接口文档"+ dateFormat()+".pdf"; // 生成的PDF名
// a.创建一个合适的Configration对象
Configuration configuration = new Configuration();
// configuration.setDirectoryForTemplateLoading(new
// File(basePath));//方式一:使用绝对路径设置模版路径
configuration.setDirectoryForTemplateLoading(new File(tmppdfPath+File.separatorChar+"ftl"));
// configuration.setClassForTemplateLoading(tmppdfPath, "ftl");// 方式二:使用所在类的相对路径设置模版路径
configuration.setObjectWrapper(new DefaultObjectWrapper());
configuration.setDefaultEncoding("UTF-8"); // 这个一定要设置,不然在生成的页面中 会乱码
// b.获取或创建一个模版
Template template = configuration.getTemplate("interfaceDoc.ftl");
String outPutFileName = tmppdfPath + "interfaceDoc.html"; // 生成的html名
File outFile = new File(outPutFileName); // 生成文件的路径
Writer writer = null;
writer = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(outFile), "UTF-8"));
// c.使用template将数据注入模版页面生成要转pdf的html页面
template.process(archiveInfoMap, writer);
writer.flush();
// STEP2:使用itext将html页面转成PDF文件
OutputStream os = new FileOutputStream(outputFile);
String url = new File(outPutFileName).toURI().toURL().toString();
// 生成ITextRenderer实例
ITextRenderer renderer = new ITextRenderer();
// 关联html页面
renderer.setDocument(url);
ITextFontResolver fontResolver = renderer.getFontResolver();
//字体
fontResolver.addFont("" + tmppdfPath + "Fonts/simsun.ttc",
BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
renderer.layout();
renderer.createPDF(os);
writer.close();
os.close();
File emptyPDfFile = new File(tmppdfPath + "接口文档" + dateFormat()+".pdf");
if (!emptyPDfFile.exists()) {
emptyPDfFile.createNewFile();
}
// BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(emptyPDfFile));
// // 将pdf文件先加水印然后输出
// setWatermark(bos,outputFile);
// bos.close();
return emptyPDfFile;
}
/**
* pdf 添加水印
* @param bos 输出文件(空文件PDF)
* @param input (输入文件 freemark 生成的pdf文件)
* @throws DocumentException
* @throws IOException
* @throws com.lowagie.text.DocumentException
*/
public void setWatermark(BufferedOutputStream bos, String input) throws DocumentException,
IOException,DocumentException {
PdfReader reader = new PdfReader(input);
PdfStamper stamper = new PdfStamper(reader, bos);
int total = reader.getNumberOfPages() + 1;
PdfContentByte content;
PdfGState gs = new PdfGState();
for (int i = 1; i < total; i++) {
content = stamper.getOverContent(i);// 在内容上方加水印
//content = stamper.getUnderContent(i);//在内容下方加水印
gs.setFillOpacity(0.2f);
content.setGState(gs);
String imageAbsolutPath = GetWebProjectRealPathTool.getRootPath() + "/images/yxlogo1.jpg";
Image image = Image.getInstance(imageAbsolutPath);
image.setAbsolutePosition(-50, 50); // set the first background image of the absolute
content.addImage(image);
}
stamper.close();
}
/**+
* 日期方法
* @return
*/
public String dateFormat(){
return new SimpleDateFormat("yyyyMMdd").format(new Date());
}
}
(3)把所有数据以Map的形式封装好(日期封装只需要建一个日期的实体类DataTime)。
我把这个PDF需要的所有数据放到一个Map里面一起返回的
package com.ums.dmp.base.service.impl;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import sun.misc.BASE64Encoder;
import com.ums.dmp.base.common.GetWebProjectRealPathTool;
import com.ums.dmp.base.entity.DataTime;
import com.ums.dmp.base.entity.LabelInformationTable;
import com.ums.dmp.base.entity.TYxyKeywordConf;
import com.ums.dmp.base.entity.TYxyMeal;
import com.ums.dmp.base.entity.TYxyUserMeal;
import com.ums.dmp.base.entity.TagInformationTable;
import com.ums.dmp.base.entity.TyxySeviceMessage;
import com.ums.dmp.base.entity.TyxySeviceMessageFenZhi;
import com.ums.dmp.base.manager.LabelInformationTableManager;
import com.ums.dmp.base.manager.TYxyMealManager;
import com.ums.dmp.base.manager.TYxyUserMealManager;
import com.ums.dmp.base.manager.TagInformationTableManager;
import com.ums.dmp.base.manager.TyxyKeywordConfManager;
import com.ums.dmp.base.manager.TyxySeviceMessageFenZhiManager;
import com.ums.dmp.base.manager.TyxySeviceMessagerManager;
import com.ums.dmp.base.service.TYxyUserMealService;
@Service
public class TYxyUserMealServiceImpl implements TYxyUserMealService {
@Autowired
private TYxyUserMealManager tYxyUserMealManager;
@Autowired
private TyxyKeywordConfManager tyxyKeywordConfManager;
@Autowired
private TYxyMealManager tYxyMealManager;
@Autowired
private LabelInformationTableManager LabelInformationTableManager;
@Autowired
private TagInformationTableManager TagInformationTableManager;
@Autowired
private TyxySeviceMessageFenZhiManager tyxySeviceMessageFenZhiManager;
@Autowired
private TyxySeviceMessagerManager tyxySeviceMessagerManager;
public Map<String, Object> getReportData(String userId, String mealId) {
/**
* 根据用户号和套餐号查询套餐名称
*/
Map<String, Object> dbMap = new HashMap<>();
List<TYxyUserMeal> tYxyUserMeal = tYxyUserMealManager.getMealNameByMealId(userId, mealId);
dbMap.put("tYxyUserMeal", tYxyUserMeal);
dbMap.put("mealId", mealId);
/**
* 日期封装
*/
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");
String dayFormat=sdf.format(date);
DataTime dataTime = new DataTime();
dataTime.setDate(dayFormat);
dbMap.put("dataTime", dataTime);
/**
* 根据用户号查询
*/
TYxyKeywordConf tyxyKeywordConf = tyxyKeywordConfManager.queryByUserId(userId, mealId);
dbMap.put("tyxyKeywordConf", tyxyKeywordConf);
/**
* 到套餐表中根据服务编号(meal_id)读取index_id字段信息
*/
List<String> indexIds = tYxyMealManager.selectmealId(mealId);
/**
* 根据套餐表中index_id对应的flag字段信息: 0-到标签信息表(tag_information_table)读取标签名称
* 1-到指标信息表(label_information_table)读取指标名称
*/
List<Object> objs = new ArrayList<>();
for (String indexId : indexIds) {
TYxyMeal tYxyMealflag = tYxyMealManager.queryindexId(indexId,mealId);
if (tYxyMealflag.getFlag().equals("0")) {
// 到标签信息表(tag_information_table)读取标签名称
LabelInformationTable table = LabelInformationTableManager.queryByIndexNames(indexId);
objs.add(table);
} else if (tYxyMealflag.getFlag().equals("1")) {
// 到指标信息表(label_information_table)读取指标名称
TagInformationTable table = TagInformationTableManager.selectIndexNames(indexId);
objs.add(table);
}
}
dbMap.put("objs", objs);
/**
* 根据comb查询请求报文和响应报文
* @param comb
* @return
*/
TyxySeviceMessageFenZhi tyxySeviceMessager=tyxySeviceMessageFenZhiManager.selectbaowen(mealId);
dbMap.put("tyxySeviceMessager", tyxySeviceMessager);
return dbMap;
}
(4)因为转PDF是不能自动换行的所以我写了一个方法直接调用就好(这个工具类直接使用就好)
package com.ums.dmp.base.common;
import java.awt.FontMetrics;
import javax.swing.JLabel;
import java.io.UnsupportedEncodingException;
/**
* 按字节截取字符串util
* @author yl
*
*/
public class NewlinetoolUtil {
/**
* jkb编码按字节截取字符串拼接<br/>方法
* @param str
* @param bytes
* @param charSetName
* @return
* @throws UnsupportedEncodingException
*/
public static String subStringOnlyByGBK(String str, int bytes){
String result = ""; // 每次循环截取的字符串的内容
String parameter=""; // 最后返回的结果字符串
int i = 0; // 已经截取字符串的长度累计和
int j = 0; // 已经截取字符串的字节长度累计和
int y = 0; // 每次循环截取字符串的的长度
int t = 0; // 已经截取过的字符串的长度和
byte[] a; // 减掉截取过的剩下的字符串的字节数组
if(str != null){
try {
int length = str.getBytes("GBK").length;// 获取整个字符串的字节长度
while(true){
a = str.substring(i, str.length()).getBytes("GBK");
if(bytes<length-j){
result = new String(a, 0, bytes);
t = result.length();
y= y+t;
if(str.charAt(y-1) != result.charAt(t-1)){
//如果截取最后一个字,产生乱码情况,则替换为后一个字。
result = result.replace(result.charAt(t-1), str.charAt(y-1));
j +=1;
}
parameter+=result+"<br/>";
j += bytes;
i += result.length();
}else{
break;
}
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
// 加上截取之后后面剩余字符串
parameter+=str.substring(i,str.length());
}
return parameter;
}
/**
* 按字节截取字符串拼接<br/>通用方法
* @param str
* @param bytes
* @param charSetName
* @returnsubStringByBytes
*/
public static String subStringByPX(String str, int bytes,String encode){
String result = ""; // 每次循环截取的字符串的内容
String parameter=""; // 最后返回的结果字符串
int i = 0; // 截取掉的字符串的总长度
int j = 0; // 截取掉的字符串的字节长总度
String ss= ""; // 减掉截取过的剩下的字符串
if(str != null){
try {
int length = str.getBytes(encode).length;// 获取整个字符串的字节长度
while(true){
ss = str.substring(i, str.length());
if(bytes<length-j){
//循环每次截取的结果
result = idgui(ss,bytes,encode);
parameter+=result+"<br/>";
//截取掉的字符串字节数
j += result.getBytes(encode).length;
//截取掉的字符串长度
i += result.length();
}else{
break;
}
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
// 加上截取之后后面剩余字符串
parameter+=str.substring(i,str.length());
}
return parameter;
}
public static String idgui(String str,int bytes,String encode){
int changdu;
try {
changdu = str.getBytes(encode).length;
if(changdu > bytes){
str = str.substring(0, str.length() - 1);
str = idgui(str,bytes,encode);
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return str;
}
/**
* 按像素截取字符串加</br>
* @param str
* @return
*/
public static String subStringByBytes(String str){
char[] charArray = str.substring(0, str.length()).toCharArray();
String result = ""; // 每次循环截取的字符串的内容
String parameter=""; // 最后返回的结果字符串
String sheng =""; // 每次循环剩下的字符串
String p = "";
int j = 0;
JLabel label3 = new JLabel(str);
FontMetrics metrics3 = label3.getFontMetrics(label3.getFont());
int textW3 = metrics3.stringWidth(label3.getText()); //字符串的宽
if(str != null && textW3 > 600){
for(int i = 0; i<=charArray.length-1; i++){
result+=charArray[i];
JLabel label = new JLabel(result);
FontMetrics metrics = label.getFontMetrics(label.getFont());
int textW = metrics.stringWidth(label.getText()); //字符串的宽
if(textW <= 600 && textW >= 587){
parameter+=result+"<br/>";
p+=result;
result = "";
j = p.length();
sheng = str.substring(j, str.length());
JLabel label2 = new JLabel(sheng);
FontMetrics metrics2 = label2.getFontMetrics(label2.getFont());
int textW2 = metrics2.stringWidth(label2.getText()); //字符串的宽
if(textW2 <= 600){
parameter+=sheng;
break;
}
}
}
return parameter;
}
return str;
}
}
(5).使用的方法在实体类的get()方法调用就好
public String getRespMessage() {
if (this.respMessage != null
&& this.respMessage.indexOf("<br/>") == -1) {
this.respMessage = NewlinetoolUtil.subStringByPX(this.respMessage, 90, "UTF-8");
}
return respMessage;
}
4.最后是下载的调用方法(Falg是我有两个 我是判断要下载word还是PDF)
package com.ums.dmp.base.controller;
import com.ums.dmp.base.service.TYxyUserMealService;
import com.ums.dmp.base.service.impl.H2p;
import com.ums.dmp.base.service.impl.MDoc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.URLEncoder;
import java.util.Map;
/**
* 获取接口文档控制器
*
* @author yl
*
*/
@RestController
@RequestMapping("/downloadFile")
public class KeywordConfXZController {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private TYxyUserMealService tYxyUserMealService;
/**
* 生成文档
*
* @param
* @param req
* @return
* @throws IOException
*/
@RequestMapping("/download.do")
public ResponseEntity<byte[]> download(String userId,String mealId,String flag,HttpServletRequest req)
throws IOException {
Map<String, Object> baseInfoMap = null;
File file = null;
try {
if ("1".equals(flag)) {
logger.info("开始进行PDF文件的参数获取");
baseInfoMap = tYxyUserMealService.getReportData(userId, mealId);
logger.info("开始进行PDF文件的参数获取成功");
file = H2p.getInstance().preExportPdfForSh(baseInfoMap);
} else if ("0".equals(flag)) {
baseInfoMap = tYxyUserMealService.createWord(userId, mealId);
file=MDoc.getInstance().createDoc(baseInfoMap);
}
} catch (Exception e) {
// return 提示生成文档失败
logger.error("生成文档失败");
}
InputStream in;
ResponseEntity<byte[]> response=null ;
try {
in = new FileInputStream(file);
byte[] b=new byte[in.available()];
in.read(b);
HttpHeaders headers = new HttpHeaders();
String fileName = "";
String userAgent = req.getHeader("user-agent").toLowerCase();
if (userAgent.contains("msie") || userAgent.contains("like gecko")) {
// win10 ie edge 浏览器 和其他系统的ie
fileName = URLEncoder.encode(file.getName(), "UTF-8");
} else {
fileName = new String(file.getName().getBytes("UTF-8"), "iso-8859-1");
}
headers.add("Content-Disposition", "attachment;filename="+fileName);
headers.setContentType(MediaType.APPLICATION_PDF);
HttpStatus statusCode=HttpStatus.OK;
response = new ResponseEntity<byte[]>(b, headers, statusCode);
in.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return response;
}
}
5.word的方法和PDF得差不多(只是模板的制作方法不一样,word的模板制作切记用office低版本的做,不然会出错,下篇说具体的制作方法)