java中freemarker使用ftl模版生成PDF文件

您所在的位置:网站首页 Java导出pdf文件 java中freemarker使用ftl模版生成PDF文件

java中freemarker使用ftl模版生成PDF文件

2024-07-17 10:06| 来源: 网络整理| 查看: 265

说明 调用方法生成PDF时,使用的ftl模版,以及字体都是从jar中读取的,无需担心多节点部署 引用jar org.freemarker freemarker-gae 2.3.23 commons-io commons-io 2.6 com.alibaba fastjson 1.2.76 文件存放路径 // 字体文件 resources/fonts // 模版文件 resources/templates 代码 import com.lowagie.text.pdf.BaseFont; import freemarker.cache.StringTemplateLoader; import freemarker.template.Configuration; import freemarker.template.Template; import org.apache.commons.io.FileUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.xhtmlrenderer.pdf.ITextRenderer; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.StringWriter; import java.util.Map; @Component public class PdfHelper { /** * classpath路径 */ private String classpath = getClass().getResource("/").getPath(); /** * 指定FreeMarker模板文件的位置 */ private String templatePath = "/templates"; /** * freeMarker模板文件名称 */ private String templateFileName = "pdf.ftl"; /** * 图片路径 —— 默认是classpath下面的images文件夹 */ private String imagePath = "/images/"; /** * 字体资源文件 存放路径 */ private String fontPath = "fonts/"; /** * 字体 [宋体][simsun.ttc] [黑体][simhei.ttf] */ private String font = "simsun.ttc"; /** * 指定编码 */ private String encoding = "UTF-8"; @Value("{$file.path}") private String filePath; /** * 生成pdf * * @param data 传入到freemarker模板里的数据 * @param out 生成的pdf文件流 */ public void createPDF(Map data, OutputStream out, String templateName) throws Exception { // 创建一个FreeMarker实例, 负责管理FreeMarker模板的Configuration实例 Configuration cfg = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS); // 指定FreeMarker模板文件的位置 ITextRenderer renderer = new ITextRenderer(); String simsun = System.getProperty("user.dir") + "/simsun.ttc"; String arialuni = System.getProperty("user.dir") + "/arialuni.ttf"; File fondFile = new File(simsun); if (!fondFile.exists()) { getFondPath(); } // 设置 css中 的字体样式(暂时仅支持宋体和黑体) renderer.getFontResolver().addFont(simsun, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); renderer.getFontResolver().addFont(arialuni, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); StringTemplateLoader stringLoader = new StringTemplateLoader(); String templateString = getFileData("templates/" + templateName); stringLoader.putTemplate("myTemplate", templateString); cfg.setTemplateLoader(stringLoader); Template tpl = cfg.getTemplate("myTemplate", "utf-8"); StringWriter writer = new StringWriter(); // 把null转换成"" Map dataMap = CommunalTool.objectToMap(data); // 将数据输出到html中 tpl.process(data, writer); writer.flush(); String html = writer.toString(); // 把html代码传入渲染器中 renderer.setDocumentFromString(html); renderer.layout(); renderer.createPDF(out, false); renderer.finishPDF(); out.flush(); out.close(); } // 读取jar中的ftl模版文件 private String getFileData(String path) { InputStream stream = getClass().getClassLoader().getResourceAsStream(path); // log.info("infile:"+infile); StringBuffer sb = new StringBuffer(); BufferedReader br = null; try { br = new BufferedReader(new InputStreamReader(stream, "UTF-8")); String s = null; while ((s = br.readLine()) != null) { sb.append(s); } br.close(); return sb.toString(); } catch (Exception e) { e.printStackTrace(); return null; } finally { if (br != null) { try { br.close(); } catch (Exception e) { e.printStackTrace(); } } } } /** * 获取字体路径 * * @return * @throws IOException */ private String getFondPath() throws IOException { String fontPath = System.getProperty("user.dir"); InputStream inputStream = getClass().getClassLoader().getResourceAsStream("fonts/simsun.ttc"); //在根目录生成一个文件 File targetFile = new File(fontPath + "/simsun.ttc"); // //将流转成File格式 FileUtils.copyInputStreamToFile(inputStream, targetFile); InputStream arialuniStream = getClass().getClassLoader().getResourceAsStream("fonts/arialuni.ttf"); File arialuni = new File(fontPath + "/arialuni.ttf"); FileUtils.copyInputStreamToFile(arialuniStream, arialuni); return fontPath; } public void setClasspath(String classpath) { this.classpath = classpath; } public void setTemplatePath(String templatePath) { this.templatePath = templatePath; } public void setTemplateFileName(String templateFileName) { this.templateFileName = templateFileName; } public void setImagePath(String imagePath) { this.imagePath = imagePath; } public void setFontPath(String fontPath) { this.fontPath = fontPath; } public void setFont(String font) { this.font = font; } public void setEncoding(String encoding) { this.encoding = encoding; } } 工具类 import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson.serializer.ValueFilter; import org.apache.commons.lang3.StringUtils; import sun.misc.BASE64Encoder; import java.io.File; import java.io.FileInputStream; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.Map; public class CommunalTool { /** * 转换数据中的null为""(空串) * * @param obj 转换之前的数据 * @param beanClass 转换之后的数据类型 * @return 返回Object * @throws Exception */ public static Object objectToData(Object obj, Class beanClass) throws Exception { return JSONObject.parseObject(objectToString(obj), beanClass); } /** * Map中的null转换成""(空串) * * @param map 转换之前的数据 * @return 返回 Map * @throws Exception */ public static Map objectToMap(Map map) throws Exception { return JSONObject.parseObject(objectToString(map), Map.class); } /** * 把list中的null转换成""(空串) * * @param list 转换之前的数据 * @return 返回list * @throws Exception */ public static List objectToList(List list) throws Exception { return JSONObject.parseObject(objectToString(list), List.class); } // fistJson 的一个过滤器 static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); static DateFormat format = new SimpleDateFormat("yyyy-MM-dd"); private static ValueFilter filter = new ValueFilter() { @Override public Object process(Object obj, String s, Object v) { if (v instanceof Date) { return simpleDateFormat.format(v); } else { if (v == null) { return ""; } else { return v; } } } }; /** * 将对象转换为json格式的字符串 * * @param obj * @return String */ public static String objectToString(Object obj) { // JSON.DEFFAULT_DATE_FORMAT = "yyyy-MM-dd"; return JSON.toJSONString(obj, filter, SerializerFeature.WriteNonStringKeyAsString, SerializerFeature.WriteNullStringAsEmpty); } /** * 将图片转成base64 字符串 * * @param path 文件路径 * @return * @throws Exception */ public static String encodeBase64Picture(String path) throws Exception { File file = new File(path); FileInputStream inputFile = new FileInputStream(file); byte[] buffer = new byte[(int) file.length()]; inputFile.read(buffer); inputFile.close(); String result = new BASE64Encoder().encode(buffer); if (StringUtils.isNotBlank(result)) { result = "data:image/png;base64," + result; } else { result = null; } return result; } /** * 时间转换 yyyy-MM-dd * * @param date * @return */ public static String getTransformationDate(Date date) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); return sdf.format(date); } /** * 时间转换 yyyy-MM-dd * * @param date * @return */ public static String getTimeDate(Date date) { int minte = getMinute(date); minte = minte % 5; SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); Date afterDate = new Date(date.getTime() + 300000); System.out.println(sdf.format(afterDate)); return "1"; } /** * 功能描述:返回分 * * @param date * 日期 * @return 返回分钟 */ public static int getMinute(Date date) { Calendar calendar = Calendar.getInstance(); calendar.setTime(date); return calendar.get(Calendar.MINUTE); } /** * 获取日期年份 * @param date 日期 * @return */ public static String FORMAT_FULL = "yyyy-MM-dd HH:mm:ss"; public static String getTime(Date date) { SimpleDateFormat df = new SimpleDateFormat(FORMAT_FULL); return df.format(date).substring(12, 16); } } ftl模版 电子化模版单个表格 模版 body{width:100%;} @media screen and (max-width: 768px) { body{font-size: 14px;} table{table-layout: fixed;} } p{line-height: 26px;margin-right:15px;} table td p{line-height:6px;margin-right:0;padding-right:0;padding-left:0;} span{margin:0;padding:0;} @page { size: a4 landscape;/*210*297*/ /*size:195mm 271mm;*/ margin: 8mm 8mm; padding: 0; /*border:solid #F00 1px;*/ background-image:url(images/water.gif); background-repeat:no-repeat; background-position:center; @top-left { content: element(header); } @bottom-left { content: element(footer); } @bottom-center { content: "--- " counter(page) " ---" counter(main-heading); } @bottom-right { content: ""; } } @page blank { size: a4 portrait; /*size:195mm 271mm;*/ margin: 10mm 10mm; padding: 0; @top-left { content: normal } @bottom-left { content: normal } @top-right { content: normal } @bottom-right { content: normal } @bottom-center { content: normal } } #pagenumber:before { content: counter(page); } #pagecount:before { content: counter(pages); } /* Used for generating Table of content */ #toc a::after { content: leader('.') target-counter(attr(href), page); } /* Use this class for first level titles */ .page_break_before { page-break-before: always; } /* Use this class for forcing page break inside pdf */ .page_breaker { page-break-after: always; } .blank { page : blank; } .version { page: version; } @page :[counter(page)=3] { margin-top:0; margin-right:0; @bottom-center { content: "第N页"; font-family:SimSun,'宋体'; font-size:10px; } } @page version { @bottom-left { content: "version"; margin: 0; padding: 0; font-size: 8pt; font-style:italic; font-family:SimSun,'宋体'; } } /* footnote */ .notebox { page: notebox; } @page notebox { page-break-after: auto; } @media print { .notebox { position: relative; } .notebox .content { width: 100%; margin-bottom: 0px; } .notebox .footnote { position: absolute; top: 265mm; left: 0; z-index: 100; border-top: 1pt solid #000; width: 100%; font-size: 8.76pt; } } body { line-height: 100%; font-family:SimSun,'宋体'; /*width:178mm;*/ margin: 0; } .page { /*size:195mm 271mm;*/ height: 100mm; /*height:246mm; 16K*/ border: 0px; } .font { font-size: 10px; line-height: 20px; } .xhx { border-bottom:solid 1px #000000; } td{ border-top:solid 1px #000000; border-bottom:solid 0px #000000; border-left:solid 1px #000000; border-right:solid 0px #000000; display:inline-black; padding:6px 0px 6px 0px; margin:0px; } tr td:last-child { border-right:solid 1px #000000; } table { page-break-inside:auto; -fs-table-paginate:paginate;border-spacing: 0;border-bottom:solid 1px #000000;} ${name} 货物交接单号: 生成时间:2021-12-16 工程名称 合同名称 合同编号 违约事实 约谈情况 供应商 物资公司 项目管理部门 物资部 财务部 注:1.涉及技术的违约事实,由项目管理部门确认;涉及商务的违约事实,由物资公司/供应中心确认;技术、商务均涉及的,由项目管理部门,由物资公司/供应中心确认。各个单位可根据实际情况增加其他部门(单位)确认。 调用主函数生成 import com.sgcc.cn.utils.PdfHelper; import java.io.File; import java.io.FileOutputStream; import java.util.HashMap; import java.util.Map; public class test { public static void main(String[] args) { try { PdfHelper pdfHelper = new PdfHelper(); Map map = new HashMap(); map.put("name","唯有碎银解千愁"); FileOutputStream out = new FileOutputStream(new File( "D:\\1.pdf")); pdfHelper.createPDF(map,out,"gererate.ftl"); } catch (Exception e) { e.printStackTrace(); } } } 电子化模版嵌套表格 调配申请单 body{width:100%;} @media screen and (max-width: 768px) { body{font-size: 14px;} table{table-layout: fixed;} } p{line-height: 26px;margin-right:15px;} table td p{line-height:6px;margin-right:0;padding-right:0;padding-left:0;} span{margin:0;padding:0;} @page { size: a4 landscape;/*210*297*/ /*size:195mm 271mm;*/ margin: 8mm 8mm; padding: 0; /*border:solid #F00 1px;*/ background-image:url(images/water.gif); background-repeat:no-repeat; background-position:center; @top-left { content: element(header); } @bottom-left { content: element(footer); } @bottom-center { content: "--- " counter(page) " ---" counter(main-heading); } @bottom-right { content: ""; } } @page blank { size: a4 portrait; /*size:195mm 271mm;*/ margin: 10mm 10mm; padding: 0; @top-left { content: normal } @bottom-left { content: normal } @top-right { content: normal } @bottom-right { content: normal } @bottom-center { content: normal } } #pagenumber:before { content: counter(page); } #pagecount:before { content: counter(pages); } /* Used for generating Table of content */ #toc a::after { content: leader('.') target-counter(attr(href), page); } /* Use this class for first level titles */ .page_break_before { page-break-before: always; } /* Use this class for forcing page break inside pdf */ .page_breaker { page-break-after: always; } .blank { page : blank; } .version { page: version; } @page :[counter(page)=3] { margin-top:0; margin-right:0; @bottom-center { content: "第N页"; font-family:SimSun,'宋体'; font-size:10px; } } @page version { @bottom-left { content: "version"; margin: 0; padding: 0; font-size: 8pt; font-style:italic; font-family:SimSun,'宋体'; } } /* footnote */ .notebox { page: notebox; } @page notebox { page-break-after: auto; } @media print { .notebox { position: relative; } .notebox .content { width: 100%; margin-bottom: 0px; } .notebox .footnote { position: absolute; top: 265mm; left: 0; z-index: 100; border-top: 1pt solid #000; width: 100%; font-size: 8.76pt; } } body { line-height: 100%; font-family:SimSun,'宋体'; /*width:178mm;*/ margin: 0; } .page { /*size:195mm 271mm;*/ height: 100mm; /*height:246mm; 16K*/ border: 0px; } .font { font-size: 10px; line-height: 20px; } .xhx { border-bottom:solid 1px #000000; } td{ border-top:solid 1px #000000; border-bottom:solid 0px #000000; border-left:solid 1px #000000; border-right:solid 0px #000000; display:inline-black; padding:6px 0px 6px 0px; margin:0px; } tr td:last-child { border-right:solid 1px #000000; } table { page-break-inside:auto; -fs-table-paginate:paginate;border-spacing: 0;border-bottom:solid 1px #000000;} 调配申请单 编号:${receive.zdpsq} 申请单位 ${receive.zbutxt} 申请日期 ${receive.createDate} 调配类型 市内调配/省内跨市调配 需求描述 调配方式 划转 拟调配地址 ${receive.zdpdz} 联系人 ${receive.zxqflxr} 联系电话 ${receive.zxqflxfs} 序号 物料编码 物料名称 规格型号 单位 需求数量 备注 ${item_index+1} ${item.matnr} ${item.maktx} ${item.zggxh} ${item.meins} ${item.menge} ${item.remark} 市公司分管领导(签字) 市公司物资部/物资供应中心(签字、盖章) 市公司专业部门(签字、盖章) 本调配单一式四份,省/地市物资部、省物资公司/县公司供应分中心、调入方和调出方各一份 如果不想把文件存在本地,想在生成之后返回流文件,代码如下 public byte[] createPDF(Map data, String templateName) throws Exception { // 创建一个FreeMarker实例, 负责管理FreeMarker模板的Configuration实例 Configuration cfg = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS); // 指定FreeMarker模板文件的位置 ITextRenderer renderer = new ITextRenderer(); String simsun = System.getProperty("user.dir") + "/simsun.ttc"; String arialuni = System.getProperty("user.dir") + "/arialuni.ttf"; File fondFile = new File(simsun); if (!fondFile.exists()) { getFondPath(); } // 设置 css中 的字体样式(暂时仅支持宋体和黑体) renderer.getFontResolver().addFont(simsun, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); renderer.getFontResolver().addFont(arialuni, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); StringTemplateLoader stringLoader = new StringTemplateLoader(); String templateString = getFileData("templates/" + templateName); stringLoader.putTemplate("myTemplate", templateString); cfg.setTemplateLoader(stringLoader); Template tpl = cfg.getTemplate("myTemplate", "utf-8"); StringWriter writer = new StringWriter(); // 把null转换成"" Map dataMap = CommunalTool.objectToMap(data); // 将数据输出到html中 tpl.process(data, writer); writer.flush(); String html = writer.toString(); // 把html代码传入渲染器中 renderer.setDocumentFromString(html); ByteArrayOutputStream out = new ByteArrayOutputStream(); renderer.layout(); renderer.createPDF(out); renderer.finishPDF(); byte[] fileByte = out.toByteArray(); out.flush(); out.close(); return fileByte; } 在文件中添加图片,代码如下 在ftl文件中添加代码 货物交接单号: 用java代码把图片转换成base64 /** * 将图片转成base64 字符串 * * @param path 文件路径 * @return * @throws Exception */ public static String encodeBase64Picture(String path) throws Exception { File file = new File(path); FileInputStream inputFile = new FileInputStream(file); byte[] buffer = new byte[(int) file.length()]; inputFile.read(buffer); inputFile.close(); String result = new BASE64Encoder().encode(buffer); if (StringUtils.isNotBlank(result)) { result = "data:image/png;base64," + result; } else { result = null; } return result; } 如果想要动态读取模版也可以 templateString 这个就是模版字符串,然后你可以写一个扩展方法去读取,如你想从数据库中存储和读取 String templateString = getFileData("templates/" + templateName); stringLoader.putTemplate("myTemplate", templateString); 设置mysql数据库存储文件的字段为大字段类型blob // 这里只提供一个公共方法,读取文件后返回一个字符串。 public String uploadModelSql(MultipartFile file) { BufferedReader reader = null; StringBuffer sbf = new StringBuffer(); try { // Reader read = new InputStreamReader(file.getInputStream(), "UTF-8"); Reader read = new InputStreamReader(file.getInputStream(), "utf-8"); reader = new BufferedReader(read); String tmpString = null; //一行一行的读取文件里面的内容,这个是防止文件存储数据库之后,在读取模版布局问题 while ((tmpString = reader.readLine()) != null) { sbf.append(tmpString).append("\r\n"); } if (sbf == null) { throw new RuntimeException("文件内容为空!"); } return sbf.toString(); } catch (IOException e) { e.printStackTrace(); return sbf.toString(); } finally { if (reader != null) { try { reader.close(); } catch (IOException e1) { e1.printStackTrace(); } } } } 模版下载 /** * 下载模版文件 * * @param response * @param id * @return */ @RequestMapping("/downLoad") public void downLoadFile(HttpServletResponse response, @RequestParam String id) { try { //根据id查询数据库中的记录 SignatureTemplatesFile record = signatureTemplatesFileService.downLoadFile(id); // 清空response response.reset(); // 设置response的Header,record.getTemplateName() 文件名称 response.addHeader("Content-Disposition", "attachment;filename=" + new String(record.getTemplateName())); // response.addHeader("Content-Length", "" + file.length()); OutputStream toClient = new BufferedOutputStream(response.getOutputStream()); response.setContentType("application/octet-stream"); //大字段中的字符串,转换成byte数组 byte[] bytes = record.getTemplateFile().getBytes(); toClient.write(bytes); toClient.flush(); toClient.close(); } catch (Exception e) { e.printStackTrace(); } } 总结

代码写的有点乱,总体来说该有的功能都有了,如:从jar中读取字体和ftl模版,如果你不想从jar中读取,可以自己改造一下,就是不管你是从其他地方读文件或者从数据库中字符串,总之你要把他转换成字符串,最后给(templateString)它就行。还有PDF生成的时候也可以返回流文件,你可以把返回的流文件,存储fastDFS(文件管理插件)上面去统一管理。



【本文地址】


今日新闻


推荐新闻


    CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3