使用Freemarker来生成pdf文件

2022-09-02

        今天接到一个生成pdf的任务,并且web端要能下载;在网上也找了许多的工具如:itext等,感觉挺复杂的没那么好用,然后想起了之前使用Freemarker来生成world文档,挺好用的,然后调查发现也能生成pdf,就是有一点区别如果Freemarker来生成world是使用world文档来当模板,而pdf相对于简单,直接使用html文件来制作模板,只不过最后要将文件后缀改成ftl的文件。

这个博主写的挺好的,可以直接去看这个博主的文章,我只是当笔记记录一下,参考的文章链接

本文链接:Java使用Freemarker通过模板文件导出PDF文件、横向显示_虚心若愚求知若渴的博客-CSDN博客_freemarker ftl生成pdf前言:​尝试了不少方法通过模板文件导出pdf文件,要么实现起来负责,要么实现效果不理想,经过反复查找资料发现此方法是最理想的。一,依赖jar包<!-- freemarker 读取html模板文件 --><dependency> <groupId>org.freemarker</groupId> &https://blog.csdn.net/weixin_39806100/article/details/86616041

代码如下:

  • maven依赖:


    org.freemarker
    freemarker
    2.3.29



    org.xhtmlrenderer
    flying-saucer-pdf
    9.1.18
  • service层:

public void exportPdf(HttpServletResponse response, Integer id, Integer type) throws Exception {
        ByteArrayOutputStream baos = null;
        OutputStream out = null;
        FileOutputStream fileOutputStream = null;
        try {
            //获取提货单数据,根据提货单id
            TakeOrder takeOrder = this.getTakeById(id);
            //翻译提货单状态
            String[] stateName = {"待备货","备货中","已备货","已出库","装车中","已装车","已进厂","已出厂"};
            takeOrder.setStateName(takeOrder.getState() == null ? "" : stateName[takeOrder.getState() - 1]);
            //翻译提货单提货状态
            String[] orderStateName = {"待提货","已提货","作废"};
            takeOrder.setOrderStateName(orderStateName[takeOrder.getOrderState() - 1]);
            
            // 模板中的数据,实际运用从数据库中查询
            Map data = new HashMap<>();
            data.put("takeOrder", takeOrder);
            data.put("fileName", type == 1 ? "备货联" : "承运联");

            //因为我自己的需求有两套模板,所以我让模板名称动态化了,如果不用直接删除这个type参数,正常填文件名称就可以,记得带上后缀
            baos = PDFTemplateUtil.createPDF(data, "modezs"+type+".ftl");
            // 设置响应消息头,告诉浏览器当前响应是一个下载文件
            response.setContentType( "application/x-msdownload");
            // 告诉浏览器,当前响应数据要求用户干预保存到文件中,以及文件名是什么 如果文件名有中文,必须URL编码 
            String fileName = URLEncoder.encode("月度报告.pdf", "UTF-8");
            response.setHeader( "Content-Disposition", "attachment;filename=" + fileName);
            out = response.getOutputStream();
            baos.writeTo(out);
            baos.close();
            //下载到本地位置
//            fileOutputStream = new FileOutputStream("D:\\zscProject\\zsc.pdf");
            //生成pdf完成记录行为记录
            this.addActionLog(takeOrder.getTakeOrderNo(),1);
        } catch (Exception e) {
            e.printStackTrace();
            throw new Exception("导出失败:" + e.getMessage());
        } finally{
            if(baos != null){
                baos.close();
            }
            if(out != null){
                out.close();
            }
            if (fileOutputStream != null){
                fileOutputStream.close();
            }
        }
    }

 ps:

1. 在使用工具类时,传文件名称的参数

 2. 在如果不需要web端的方式下载pdf,可以使用文件输出流直接下载到本地

 

  • 工具类:

可以直接拿来使用

public class PDFTemplateUtil {

	/**
	 * 通过模板导出pdf文件
	 * @param data 数据
	 * @param templateFileName 模板文件名
	 * @throws Exception
	 */
    public static ByteArrayOutputStream createPDF(Map data, String templateFileName) throws Exception {
        // 创建一个FreeMarker实例, 负责管理FreeMarker模板的Configuration实例
        Configuration cfg = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
        // 指定FreeMarker模板文件的位置 
        cfg.setClassForTemplateLoading(PDFTemplateUtil.class,"/templates");
        ITextRenderer renderer = new ITextRenderer();
        OutputStream out = new ByteArrayOutputStream();
        try {
            // 设置 css中 的字体样式(暂时仅支持宋体和黑体) 必须,不然中文不显示
            renderer.getFontResolver().addFont("/templates/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路径需要相对路径加图片名 如
//            URI images = PDFTemplateUtil.class.getClassLoader().getResource("images").toURI();
//            if (images != null) {
//                String url = images.toString();
//                renderer.getSharedContext().setBaseURL(url);
//            }
            renderer.layout();
            
            renderer.createPDF(out, false);
            renderer.finishPDF();
            out.flush();
            return (ByteArrayOutputStream)out;
        } finally {
        	if(out != null){
        		 out.close();
        	}
        }
    }
}

ps:

我导出pdf里面是含有图片的,但是我的图片是base64的字节码(建议使用这种方式),不是本地的方式填充数据的,如果只能使用本地的图片,将工具类中的这段代码解开,然后自己改进一下

 

  • 模板文件:





	
	
	



${(fileName)!''}

XXXXXXXXXXXXXX有限公司

提货单

提货单号:

${(takeOrder.takeOrderNo)!''}

提货日期:

${(takeOrder.takeDate)!''}

提货状态:

${(takeOrder.orderStateName)!''}

状态:

${(takeOrder.stateName)!''}

<#--
--> <#--

承运商:

--> <#--

${(takeOrder.takeOrderNo)!''}

--> <#--
--> <#--
--> <#--

车辆:

--> <#--

${(takeOrder.takeOrderNo)!''}

--> <#--
-->

司机:

${(takeOrder.driver)!''}

发运方式:

${(takeOrder.shippingMethod)!''}

凭此二维码进出厂区

<#if takeOrder.outOrderProducts ??>
<#list takeOrder.outOrderProducts as ad>
品名编号 品名 规格型号 销售型号 包装规格 批号 数量 单位 仓库编号 仓库名称
${(ad.productCode)!''} ${(ad.productName)!''} ${(ad.typeNum)!''} ${(ad.saleType)!''} ${(ad.packSize)!''} ${(ad.batchNumber)!''} ${(ad.num)!''} ${(ad.uint)!''} ${(ad.stockNo)!''} ${(ad.stockName)!''}
<#if takeOrder.outOrders ??>

出库单信息:

<#list takeOrder.outOrders as add>

出库单号:

${(add.outOrderNo)!''}

发货单号:

${(add.sendOrderNo)!''}

出库日期:

${(add.outDate)!''}

装车号:

${(add.loadingNumber)!''}

客户名称:

${(add.customerName)!''}

<#if add.outOrderProducts ??> <#list add.outOrderProducts as ad>
品名编号 品名 规格型号 客户销售型号 包装规格 批号 数量 内部备注 备注
${(ad.productCode)!''} ${(ad.productName)!''} ${(ad.typeNum)!''} ${(ad.saleType)!''} ${(ad.packSize)!''} ${(ad.batchNumber)!''} ${(ad.num)!''} ${(ad.innerRemark)!''} ${(ad.remark)!''}

ps:

1.这里面的样式是按照html的样式来的,自己设计的要自己调整样式,只支持定位和浮动,不支持自适应,建议在class里写样式,而不是style里写样式

2.像<#if takeOrder.outOrderProducts ??>,<#list takeOrder.outOrderProducts as ad>和${(fileName)!''}这个都是ftl文件的语法,不懂的可以搜一下

占位符可以看下图

使用Freemarker来生成pdf文件_第1张图片

来自本文链接:简单的Freemarker判断对象是否为空方法_OxYGC的博客-CSDN博客_freemarker if判断为空Freemarker判断对象是否为空1. freemarker中显示某对象使用${name}.但如果name为null,freemarker就会报错。如果需要判断对象是否为空:2. 当然也可以通过设置默认值${name!’’}来避免对象为空的错误。如果name为空,就以默认值(“!”后的字符)显示。3. 对象user,name为user的属性的情况,user,name都有可能为空,那么......https://blog.csdn.net/YangCheney/article/details/1058324443.放置位置如图

使用Freemarker来生成pdf文件_第2张图片 

 

  • 字体文件:

在windows10系统中的 C:\Windows\Fonts 这个路径中,进入后搜索宋体(ps:一定要搜索宋体,不要按照simsun这个名字去搜索,反正就是注意文件的后缀是.ttc的,而不是.ttf的

使用Freemarker来生成pdf文件_第3张图片

使用Freemarker来生成pdf文件_第4张图片

  • 前端:

1.请求方法js:(ps:注意里面的responseType: 'arraybuffer'这个参数,因为后台使用的字节数组流的方式写入的,所以如果直接使用responseType: 'blob',会导致封装的blob对象是有问题的,下载出来的pdf文件损坏,本文链接:

使用FreeMarker生成pdf时,代码没异常产生,但是web端下载下来的文件损坏_A-Superman的博客-CSDN博客使用FreeMarker生成pdf时,代码没异常产生,但是web端下载下来的文件损坏https://blog.csdn.net/Jackbillzsc/article/details/126662319

export function exportPdf(parameter) {
  return request({
    url: 'XXXXXXXXXXXXXXX/export/pdf',
    method: 'get',
    params:parameter,
    responseType: 'arraybuffer',
    
  })
}

2.封装blob对象,下载pdf的js方法:

exportPdf(type) {
      this['loading'+type] = true
      exportPdf({ id: this.pageList.id, type: type }).then((res) => {
        if (!res) {
          alert('数据为空')
          return
        }
        const content = res
        const blob = new Blob([content], { type: 'application/pdf' })
        // const fileName = titName?titName: ''
        let fileName = this.pageList.takeOrderNo
        if ('download' in document.createElement('a')) {
          // 非IE下载
          const elink = document.createElement('a')
          elink.download = fileName
          elink.style.display = 'none'
          elink.href = URL.createObjectURL(blob)
          document.body.appendChild(elink)
          elink.click()
          URL.revokeObjectURL(elink.href) // 释放URL 对象
          document.body.removeChild(elink)
          this['loading'+type] = false
        } else {
          // IE10+下载
          navigator.msSaveBlob(blob, fileName)
        }
      })
    },

你可能感兴趣的:(笔记,java,前端,html)