Freemarker를 사용하여 PDF 파일 생성

2022-09-02

        오늘 저는 pdf를 생성하는 작업을 받았고 웹 엔드에서 다운로드할 수 있어야 합니다. 또한 인터넷에서 itext 등과 같은 많은 도구를 찾았습니다. 상당히 복잡하고 사용하기 쉽지 않은 것 같습니다. , 그리고 전에 Freemarker를 사용하여 세계 문서를 생성했던 것이 생각났습니다. 사용하기 쉽고 조사 결과 pdf도 생성 될 수 있지만 약간의 차이가 있습니다. Freemarker가 세계를 생성하면 문서를 템플릿으로, pdf는 비교적 간단하게 html 파일을 직접 사용하여 템플릿을 만들지만 마지막에 파일 접미사가 .ftl 파일로 변경됩니다.

이 블로거 글 잘쓰시네요 이 블로거 글로 바로가셔도 됩니다 저는 그냥 메모로 녹음해서 글 링크 참고합니다

이 기사 링크: Java는 Freemarker를 사용하여 템플릿 파일을 통해 PDF 파일을 내보내고 수평으로 표시하거나 1. 의존 jar 패키지<!-- freemarker는 html 템플릿 파일을 읽습니다-><dependency> <groupId>org.freemarker</groupId> & https://blog.csdn.net/weixin_39806100/article/details/86616041

아래와 같이 코드 쇼:

  • 메이븐 종속성:

<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>
  • 서비스 레이어:

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

 추신:

1. 도구 클래스를 사용할 때 파일 이름의 매개 변수를 전달하십시오.

 2. 웹 측에서 pdf를 다운로드할 필요가 없는 경우 파일 출력 스트림을 사용하여 로컬로 직접 다운로드할 수 있습니다.

 

  • 도구:

직접 사용할 수 있습니다

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

추신:

내보낸 pdf에 사진이 포함되어 있는데 내 사진은 base64 바이트코드(이 방법 권장)이고, 데이터는 로컬 방식으로 채워지지 않습니다. 직접 개선

 

  • 템플릿 파일:

<!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>

추신:

1. 여기의 스타일은 html 스타일을 기반으로 합니다. 직접 디자인하는 경우 스타일을 직접 조정해야 합니다. 위치 지정 및 플로팅만 지원하고 자체 적응을 지원하지 않습니다. 스타일을 작성하는 것이 좋습니다. 스타일 대신 클래스.

2. <#if takeOrder.outOrderProducts ??>, <#list takeOrder.outOrderProducts as ad> 및 ${(fileName)!''}은 ftl 파일의 문법과 같이, 이해가 되지 않으면 검색할 수 있습니다.

자리 표시자는 아래 그림에서 볼 수 있습니다.

이 문서의 링크: 개체가 비어 있는지 여부를 판단하는 Freemarker의 간단한 방법_OxYGC의 블로그-CSDN blog_freemarker 비어 있는 것으로 판단되면 freemarker는 오류를 보고합니다. 개체가 비어 있는지 여부를 판단해야 하는 경우: 2. 물론 기본값 ${name!''}을 설정하여 개체가 비어 있다는 오류를 방지할 수도 있습니다. 이름이 비어 있으면 기본값("!" 뒤의 문자)으로 표시됩니다. 3. 오브젝트 user와 name이 user의 속성이면 user와 name이 모두 비어있을 수 있으니... https://blog.csdn.net/YangCeney/article/details/105832444 3. 그림과 같이 배치

 

 

  • 글꼴 파일:

windows10 시스템 의 C:\Windows\Fonts  경로에 Arial을 입력 후 검색합니다 . .ttf가 아닌 ttc )

  • 프런트 엔드:

1. 요청 방법 js: ( ps: 백그라운드에서 사용되는 바이트 배열 스트림이 작성되기 때문에 responseType: 'arraybuffer' 내부 매개 변수에 주의하십시오. 따라서 responseType: 'blob'을 직접 사용하면 캡슐화됩니다. Blob 개체 문제가 있습니다. 다운로드한 PDF 파일이 손상되었습니다. 이 문서의 링크는 다음과 같습니다.

FreeMarker를 이용하여 PDF 생성시 코드 이상은 없으나 웹단에서 다운로드 받은 파일이 손상됨_A-Superman's Blog-CSDN Blog FreeMarker를 이용하여 PDF 생성시 코드 이상은 없으나 웹단에서 다운로드한 파일이 손상됨 손상됨 /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)
        }
      })
    },

추천

출처blog.csdn.net/Jackbillzsc/article/details/126783990