Use Freemarker para generar archivos pdf

2022-09-02

        El dia de hoy me llego una tarea para generar pdf, y el final de la web debe poder descargarse, tambien encontre muchas herramientas en internet como: itext, etc., siento que es bastante complicado y no tan facil de usar , y luego recordé que usé Freemarker para generar documentos del mundo antes. Es fácil de usar, y luego la investigación descubrió que también se puede generar pdf, pero hay una pequeña diferencia. Si Freemarker genera el mundo, usa el mundo documento como plantilla, mientras que pdf es relativamente simple, usando directamente archivos html para hacer plantillas, pero al final el sufijo del archivo debe ser .Cambie a archivo ftl.

Este blogger escribe muy bien, puede ir directamente al artículo de este blogger, solo lo registro como una nota y me refiero al enlace del artículo.

Enlace a este artículo: Java usa Freemarker para exportar archivos PDF a través de archivos de plantilla y mostrarlos horizontalmente 1. Confíe en el paquete jar<!-- freemarker lee el archivo de plantilla html--><dependency> <groupId>org.freemarker</groupId&gt ; & https://blog.csdn.net/weixin_39806100/article/details/86616041

el código se muestra a continuación:

  • dependencias expertas:

<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.29</version>
</dependency>

<dependency>
    <groupId>org.xhtmlrenderer</groupId>
    <artifactId>flying-saucer-pdf</artifactId>
    <version>9.1.18</version>
</dependency>
  • capa de servicio:

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<String,Object> 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();
            }
        }
    }

 PD:

1. Al usar la clase de herramienta, pase el parámetro del nombre del archivo

 2. Si no necesita descargar pdf en el lado web, puede usar la secuencia de salida del archivo para descargar directamente al local

 

  • Herramientas:

se puede utilizar directamente

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.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路径需要相对路径加图片名 如<img src="images/xh.jpg"/>
//            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();
        	}
        }
    }
}

PD:

El pdf que exporté contiene imágenes, pero mis imágenes son códigos de bytes base64 (se recomienda este método), y los datos no se completan de manera local. Si solo puede usar imágenes locales, cambie esta sección en la clase de herramientas Descomprima el código y mejorarlo tu mismo

 

  • Archivo de plantilla:

<!DOCTYPE html>
<html>

<head>
	<meta charset="utf-8" />
	<title></title>
	<style>
		* {
			margin: 0;
			padding: 0;
			box-sizing: border-box;
		}

		body {
			font-family: SimSun;
			padding: 30px 20px 0;
		}

		section {
			display: block;
			/* margin: 20px 10px; */
		}

		.title {
			text-align: center;
			margin-bottom: 20px;
		}

		.preface p {
			line-height: 30px;
			display: inline-block;
		}

		.preface p.content {
			text-indent: 2em;
		}

		section>table {
			border-collapse: collapse;
			table-layout: fixed;
			width: 100%;
			font-size: 13px;
			/* margin: 20px 0px; */
			text-align: center;
			word-wrap: break-word;
		}

		section table td {
			padding: 5px 0px;
		}
		.topTitle section{
			width: 30%;
			font-size: 13px;
			display: inline-block;
			margin-top: 20px;
		}
		.topTitle{
		}
		.outTitle{
		}
		.outTitle section{
			font-size: 13px;
			display: inline-block;
		}
		.detail{
			margin-top: 20px;
		}
		.outTable{
			margin-bottom: 20px;
		}
		.box1{
		}
		.box2{
			width: 80%;
			display: inline-block;
		}
		.box3{
			display: inline-block;
			width: 18%;
			/* min-width: 180px; */
		}
		.box3 img{
			width: 100%;
		}
		.box3 p{
			font-size: 12px;
		}
	</style>
</head>

<body>
<h3>${(fileName)!''}</h3>
<div class="box1">
	<section class="title">
		<h2>XXXXXXXXXXXXXX有限公司</h2>
		<h2>提货单</h2>
	</section>
	<div class="box2">
		<!-- 标题 start -->
		<!-- 标题 end -->

		<!-- 前言 start -->
		<div class="topTitle">
			<section class="preface">
				<p>提货单号:</p>
				<p>${(takeOrder.takeOrderNo)!''}</p>
			</section>
			<section class="preface">
				<p>提货日期:</p>
				<p>${(takeOrder.takeDate)!''}</p>
			</section>
			<section class="preface">
				<p>提货状态:</p>
				<p>${(takeOrder.orderStateName)!''}</p>
			</section>
			<section class="preface">
				<p>状态:</p>
				<p>${(takeOrder.stateName)!''}</p>
			</section>
<#--			<section class="preface">-->
<#--				<p>承运商:</p>-->
<#--				<p>${(takeOrder.takeOrderNo)!''}</p>-->
<#--			</section>-->
<#--			<section class="preface">-->
<#--				<p>车辆:</p>-->
<#--				<p>${(takeOrder.takeOrderNo)!''}</p>-->
<#--			</section>-->
			<section class="preface">
				<p>司机:</p>
				<p>${(takeOrder.driver)!''}</p>
			</section>
			<section class="preface">
				<p>发运方式:</p>
				<p>${(takeOrder.shippingMethod)!''}</p>
			</section>
		</div>
	</div>
	<div class="box3">
		<img src="${(takeOrder.qrCode)!''}"></img>
		<p>凭此二维码进出厂区</p>

	</div>
</div>
<!-- 前言 end -->


<!-- 产品列表 start -->
<#if takeOrder.outOrderProducts ??>
<section class="detail">
	<table border="1" cellspacing="0" cellpadding="0">
		<tr>
			<td width="15%">品名编号</td>
			<td width="12%">品名</td>
			<td width="12%">规格型号</td>
			<td width="12%">销售型号</td>
			<td width="12%">包装规格</td>
			<td width="12%">批号</td>
			<td width="12%">数量</td>
			<td width="12%">单位</td>
			<td width="12%">仓库编号</td>
			<td width="12%">仓库名称</td>
		</tr>
		<#list takeOrder.outOrderProducts as ad>
			<tr>
				<td>${(ad.productCode)!''}</td>
				<td>${(ad.productName)!''}</td>
				<td>${(ad.typeNum)!''}</td>
				<td>${(ad.saleType)!''}</td>
				<td>${(ad.packSize)!''}</td>
				<td>${(ad.batchNumber)!''}</td>
				<td>${(ad.num)!''}</td>
				<td>${(ad.uint)!''}</td>
				<td>${(ad.stockNo)!''}</td>
				<td>${(ad.stockName)!''}</td>
			</tr>
		</#list>
	</table>
</section>
</#if>
<!-- 产品列表 end -->

<!-- 出库单 start -->
<#if takeOrder.outOrders ??>
<section class="detail">
	<h3>出库单信息:</h3>
	<#list takeOrder.outOrders as add>
	<div class="outTitle" >
		<section class="preface">
			<p>出库单号:</p>
			<p>${(add.outOrderNo)!''}</p>
		</section>
		<section class="preface">
			<p>发货单号:</p>
			<p>${(add.sendOrderNo)!''}</p>
		</section>
		<section class="preface">
			<p>出库日期:</p>
			<p>${(add.outDate)!''}</p>
		</section>
		<section class="preface">
			<p>装车号:</p>
			<p>${(add.loadingNumber)!''}</p>
		</section>
		<section class="preface">
			<p>客户名称:</p>
			<p>${(add.customerName)!''}</p>
		</section>
	</div>
	<!--出库的单产品列表-->
	<#if add.outOrderProducts ??>
	<table class="outTable" border="1" cellspacing="0" cellpadding="0">
		<tr>
			<td width="15%">品名编号</td>
			<td width="12%">品名</td>
			<td width="12%">规格型号</td>
			<td width="12%">客户销售型号</td>
			<td width="12%">包装规格</td>
			<td width="12%">批号</td>
			<td width="12%">数量</td>
			<td width="12%">内部备注</td>
			<td width="12%">备注</td>
		</tr>
		<#list add.outOrderProducts as ad>
			<tr>
				<td>${(ad.productCode)!''}</td>
				<td>${(ad.productName)!''}</td>
				<td>${(ad.typeNum)!''}</td>
				<td>${(ad.saleType)!''}</td>
				<td>${(ad.packSize)!''}</td>
				<td>${(ad.batchNumber)!''}</td>
				<td>${(ad.num)!''}</td>
				<td>${(ad.innerRemark)!''}</td>
				<td>${(ad.remark)!''}</td>
			</tr>
		</#list>
	</table>
	</#if>
	</#list>
</section>
</#if>
<!-- 出库单 end -->
</body>

</html>

PD:

1. El estilo aquí se basa en el estilo html. Si lo diseña usted mismo, debe ajustar el estilo usted mismo. Solo admite el posicionamiento y la flotación, y no admite la autoadaptación. Se recomienda escribir el estilo en el clase en lugar del estilo.

2. Al igual que <#if takeOrder.outOrderProducts ??>, <#list takeOrder.outOrderProducts as ad> y ${(fileName)!''} son la gramática de los archivos ftl, si no los entiende, puede buscar

El marcador de posición se puede ver en la siguiente figura.

Enlace de este artículo: Método simple de Freemarker para juzgar si un objeto está vacío_OxYGC's blog-CSDN blog_freemarker si se considera que está vacío, freemarker informará un error. Si necesita juzgar si el objeto está vacío: 2. Por supuesto, también puede evitar el error de que el objeto está vacío configurando el valor predeterminado ${name!''}. Si el nombre está vacío, se mostrará con el valor predeterminado (el carácter después de "!"). 3. Si el usuario y el nombre del objeto son atributos del usuario, tanto el usuario como el nombre pueden estar vacíos, por lo que... https://blog.csdn.net/YangCheney/article/details/105832444 3. Colocación como se muestra en la imagen

 

 

  • archivo de fuente:

En la ruta de C:\Windows\Fonts  en el sistema windows10 , busque Arial después de ingresar ( ps: asegúrese de buscar Arial, no busque por el nombre simsun, de todos modos, preste atención al sufijo del archivo es . ttc, no .ttf de )

  • Interfaz:

1. Método de solicitud js: ( pd: preste atención al parámetro responseType: 'arraybuffer' dentro, porque el flujo de matriz de bytes utilizado en segundo plano está escrito, por lo que si usa responseType: 'blob' directamente, dará como resultado un encapsulado objeto blob Hay un problema, el archivo pdf descargado está dañado, el enlace de este artículo:

Cuando se usa FreeMarker para generar pdf, el código no es anormal, pero el archivo descargado desde el sitio web está dañado_A-Superman's Blog-CSDN Blog Cuando se usa FreeMarker para generar pdf, el código no es anormal, pero el archivo descargado desde el sitio web está dañado /blog.csdn.net/Jackbillzsc/article/details/126662319

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

2. Encapsule el objeto blob y descargue el método js de pdf:

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)
        }
      })
    },

Supongo que te gusta

Origin blog.csdn.net/Jackbillzsc/article/details/126783990
Recomendado
Clasificación