Java用Freemarker导出docx、pdf文件

使用freemarker生成docx文件

一、依赖引入

		<!-- freemarker文档处理器 开始 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-freemarker</artifactId>
			<version>2.4.3</version>
		</dependency>
        <!-- freemarker文档处理器 结束 -->
            
        <!-- 配置文件处理器 开始 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-configuration-processor</artifactId>
			<optional>true</optional>
			<version>2.4.3</version>
		</dependency>
		<!-- 配置文件处理器 结束 -->
            
        <!-- pdf依赖 开始 -->
		<dependency>
			<groupId>fr.opensagres.xdocreport</groupId>
			<artifactId>xdocreport</artifactId>
			<version>2.0.2</version>
		</dependency>
		<dependency>
			<groupId>org.apache.poi</groupId>
			<artifactId>ooxml-schemas</artifactId>
			<version>1.4</version>
		</dependency>
		<dependency>
			<groupId>com.lowagie</groupId>
			<artifactId>itext</artifactId>
			<version>2.1.7</version>
		</dependency>
		<dependency>
			<groupId>com.itextpdf</groupId>
			<artifactId>itext7-core</artifactId>
			<version>7.1.12</version>
			<type>pom</type>
		</dependency>
        <!-- pdf依赖 结束 -->

二、pom修改

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>

			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-resources-plugin</artifactId>
				<configuration>
					<!--防止对resources下文件编码-->
					<nonFilteredFileExtensions>
						<nonFilteredFileExtension>zip</nonFilteredFileExtension>
					</nonFilteredFileExtensions>
				</configuration>
			</plugin>
		</plugins>
	</build>

三、封装方法


import fr.opensagres.poi.xwpf.converter.pdf.PdfConverter;
import fr.opensagres.poi.xwpf.converter.pdf.PdfOptions;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.springframework.util.Assert;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;

import static freemarker.template.Configuration.VERSION_2_3_28;

/**
 * @author author
 * @date 2022/1/20 14:49
 */
@Slf4j
public class WordUtils {
	
	/**
	 * 导出docx格式Word文档
	 *
	 * @param templateXmlPath 模板XML地址
	 * @param docxZipPath     模板ZIP地址
	 * @param dataMap         填充数据MAP
	 * @param outputStream    输出流
	 */
	public static void exportDocx(String templateXmlPath, String docxZipPath, Map<String, Object> dataMap, OutputStream outputStream) {
		String templateFileName = templateXmlPath.substring(templateXmlPath.lastIndexOf("/") + 1);
		
		InputStream templateXmlInputStream = null;
		InputStreamReader inputStreamReader = null;
		ByteArrayOutputStream byteArrayOutputStream = null;
		OutputStreamWriter outputStreamWriter = null;
		ByteArrayInputStream byteArrayInputStream = null;
		InputStream docxZipInputStream = null;
		
		try {
			templateXmlInputStream = NbpWordUtils.class.getClassLoader().getResourceAsStream(templateXmlPath);
			Assert.notNull(templateXmlInputStream, String.format("模板XML文件不存在, 路径:%s", templateXmlPath));
			Configuration configuration = new Configuration(Configuration.VERSION_2_3_28);
			configuration.setDefaultEncoding(StandardCharsets.UTF_8.name());
			inputStreamReader = new InputStreamReader(templateXmlInputStream);
			Template template = new Template(templateFileName, inputStreamReader, configuration);
			byteArrayOutputStream = new ByteArrayOutputStream();
			outputStreamWriter = new OutputStreamWriter(byteArrayOutputStream);
			template.process(dataMap, outputStreamWriter);
			byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
			docxZipInputStream = NbpWordUtils.class.getClassLoader().getResourceAsStream(docxZipPath);
			Assert.notNull(docxZipInputStream, String.format("模板ZIP文件不存在, 路径:%s", docxZipPath));
			ZipUtils.replaceItem(docxZipInputStream, outputStream, "word/document.xml", byteArrayInputStream);
			
		} catch (IOException | TemplateException e) {
			log.error(e.getMessage(), e.getCause());
			throw new RuntimeException("获取DOCX文件异常");
		} finally {
			NbpIoUtils.close(docxZipInputStream);
			NbpIoUtils.close(byteArrayInputStream);
			NbpIoUtils.close(outputStreamWriter);
			NbpIoUtils.close(byteArrayOutputStream);
			NbpIoUtils.close(inputStreamReader);
			NbpIoUtils.close(templateXmlInputStream);
		}
	}
	
	/**
	 * 导出PDF格式文档(由docx格式Word文档转换而成)
	 *
	 * @param templateXmlPath 模板XML地址
	 * @param docxZipPath     模板ZIP地址
	 * @param dataMap         填充数据MAP
	 * @param outputStream         输出流
	 */
	public static void exportPdf(String templateXmlPath, String docxZipPath, Map<String, Object> dataMap, OutputStream outputStream) {
		
		ByteArrayOutputStream docxByteArrayOutputStream = null;
		ByteArrayInputStream swapStream = null;
		try {
			docxByteArrayOutputStream = new ByteArrayOutputStream();
			NbpWordUtils.exportDocx(templateXmlPath, docxZipPath, dataMap, docxByteArrayOutputStream);
			swapStream = new ByteArrayInputStream(docxByteArrayOutputStream.toByteArray());
			XWPFDocument xwpfDocument = new XWPFDocument(swapStream);
			PdfOptions pdfOptions = PdfOptions.create();
			PdfConverter.getInstance().convert(xwpfDocument, outputStream, pdfOptions);
		} catch (IOException e) {
			log.error(e.getMessage(), e.getCause());
			throw new RuntimeException("获取PDF文件异常");
		} finally {
			NbpIoUtils.close(swapStream);
			NbpIoUtils.close(docxByteArrayOutputStream);
		}
	}
	
}

四、准备模板

1、准备docx格式文件模板

2、修改后缀名称为zip

3、使用压缩工具打开zip文件,复制document.xml (路径word/document.xml )粘贴至桌面

4、使用文本编辑工具(sublime、idea等)打开桌面的xml文件,使用变量占位符替换后续展示值,参考如下修改为 n a m e ! " " 及 {name!""}及 name!""{bz!“”}

			<w:tr w:rsidR="00272276" w:rsidRPr="00272276" w14:paraId="6808549E" w14:textId="0DC638F3"
			      w:rsidTr="00272276">
				<w:trPr>
					<w:trHeight w:val="454"/>
					<w:jc w:val="center"/>
				</w:trPr>
				<w:tc>
					<w:tcPr>
						<w:tcW w:w="2270" w:type="dxa"/>
						<w:vAlign w:val="center"/>
					</w:tcPr>
					<w:p w14:paraId="7C1263BB" w14:textId="77777777" w:rsidR="00272276" w:rsidRPr="00272276"
					     w:rsidRDefault="00272276">
						<w:pPr>
							<w:widowControl/>
							<w:jc w:val="center"/>
							<w:rPr>
								<w:bCs/>
								<w:kern w:val="0"/>
								<w:szCs w:val="21"/>
							</w:rPr>
						</w:pPr>
						<w:r w:rsidRPr="00272276">
							<w:rPr>
								<w:rFonts w:ascii="宋体" w:hAnsi="宋体" w:hint="eastAsia"/>
								<w:bCs/>
								<w:kern w:val="0"/>
								<w:szCs w:val="21"/>
							</w:rPr>
							<w:t>姓名</w:t>
						</w:r>
					</w:p>
				</w:tc>
				<w:tc>
					<w:tcPr>
						<w:tcW w:w="7081" w:type="dxa"/>
						<w:gridSpan w:val="2"/>
						<w:vAlign w:val="center"/>
					</w:tcPr>
					<w:p w14:paraId="6888ED66" w14:textId="686E13B9" w:rsidR="00272276" w:rsidRPr="00272276"
					     w:rsidRDefault="00272276" w:rsidP="000C68A5">
						<w:pPr>
							<w:widowControl/>
							<w:rPr>
								<w:bCs/>
								<w:kern w:val="0"/>
								<w:szCs w:val="21"/>
							</w:rPr>
						</w:pPr>
						<w:r w:rsidRPr="00272276">
							<w:rPr>
								<w:rFonts w:ascii="宋体" w:hAnsi="宋体" w:hint="eastAsia"/>
								<w:bCs/>
								<w:kern w:val="0"/>
								<w:szCs w:val="21"/>
							</w:rPr>
							<!-- 修改内容 -->
							<w:t>${name!""}</w:t>
						</w:r>
					</w:p>
				</w:tc>
			</w:tr>			
			<w:tr w:rsidR="00272276" w:rsidRPr="00272276" w14:paraId="6808549E" w14:textId="0DC638F3"
			      w:rsidTr="00272276">
				<w:trPr>
					<w:trHeight w:val="454"/>
					<w:jc w:val="center"/>
				</w:trPr>
				<w:tc>
					<w:tcPr>
						<w:tcW w:w="2270" w:type="dxa"/>
						<w:vAlign w:val="center"/>
					</w:tcPr>
					<w:p w14:paraId="7C1263BB" w14:textId="77777777" w:rsidR="00272276" w:rsidRPr="00272276"
					     w:rsidRDefault="00272276">
						<w:pPr>
							<w:widowControl/>
							<w:jc w:val="center"/>
							<w:rPr>
								<w:bCs/>
								<w:kern w:val="0"/>
								<w:szCs w:val="21"/>
							</w:rPr>
						</w:pPr>
						<w:r w:rsidRPr="00272276">
							<w:rPr>
								<w:rFonts w:ascii="宋体" w:hAnsi="宋体" w:hint="eastAsia"/>
								<w:bCs/>
								<w:kern w:val="0"/>
								<w:szCs w:val="21"/>
							</w:rPr>
							<w:t>备注</w:t>
						</w:r>
					</w:p>
				</w:tc>
				<w:tc>
					<w:tcPr>
						<w:tcW w:w="7081" w:type="dxa"/>
						<w:gridSpan w:val="2"/>
						<w:vAlign w:val="center"/>
					</w:tcPr>
					<w:p w14:paraId="6888ED66" w14:textId="686E13B9" w:rsidR="00272276" w:rsidRPr="00272276"
					     w:rsidRDefault="00272276" w:rsidP="000C68A5">
						<w:pPr>
							<w:widowControl/>
							<w:rPr>
								<w:bCs/>
								<w:kern w:val="0"/>
								<w:szCs w:val="21"/>
							</w:rPr>
						</w:pPr>
						<w:r w:rsidRPr="00272276">
							<w:rPr>
								<w:rFonts w:ascii="宋体" w:hAnsi="宋体" w:hint="eastAsia"/>
								<w:bCs/>
								<w:kern w:val="0"/>
								<w:szCs w:val="21"/>
							</w:rPr>
							<!-- 修改内容 -->
							<w:t>${bz!""}</w:t>
						</w:r>
					</w:p>
				</w:tc>
			</w:tr>

6、有的xml文件打开后document中缺少引入可能影响后续导出结果,建议添加如下信息

<w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:o="urn:schemas-microsoft-com:office:office"
            xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
            xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
            xmlns:v="urn:schemas-microsoft-com:vml"
            xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing"
            xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
            xmlns:w10="urn:schemas-microsoft-com:office:word"
            xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
            xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml"
            xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml"
            xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup"
            xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk"
            xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml"
            xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape"
            mc:Ignorable="w14 w15 wp14">

7、将xml文件及zip文件放置在项目的resources下

五、生成文件并导出(示例中是模拟浏览器下载,如需下载到指定路径可以替换outputStream)

/**
* 导出DOCX文件
*
*/
public void exportElementWord() {
    	// 路径是resources下路径开始
		String templateXmlPath = "template/word/模板.xml";
		String templateZipPath = "template/word/模板.zip";
    	// 模拟测试数据
    	HashMap<String, Object> dataMap = Maps.newHashMap();
		dataMap.put("name", "李四");
		dataMap.put("remark", "备注1\n备注2");
    	// 注意事项 因为xml中的换行标签是<w:br/>,所以如果“\n”的换行符需要转换,实现如下
    	dataMap.put("remark", String.join("</w:t><w:br/><w:t>", dataMap.get("remark").toString().split("\n")));
    
		String templateFileName = templateXmlPath.substring(templateXmlPath.lastIndexOf("/") + 1);
		String currentDate = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
    	// 拼接导出文件名称
		String fileName = String.format("%s_%s", dataMap.get("name").toString(), templateFileName).replace(".xml", String.format("_%s.docx", currentDate));
		HttpServletResponse response = WebUtils.getResponse();
		response.setCharacterEncoding(StandardCharsets.UTF_8.name());
		response.setContentType("application/pdf");
		response.setHeader("Content-Disposition", "attachment;filename=" + EncoderUtils.encodeToUtf8(fileName));
		
		try (ServletOutputStream outputStream = response.getOutputStream()) {
			WordUtils.exportDocx(templateXmlPath, templateZipPath, dataMap, outputStream);
		} catch (IOException e) {
			log.error(e.getMessage(), e);
			throw new RuntimeException("文件导出异常");
		}
	}

/**
* 导出PDF文件
*
*/
public void exportElementPdf() {
		// 路径是resources下路径开始
		String templateXmlPath = "template/word/模板.xml";
		String templateZipPath = "template/word/模板.zip";
    	// 模拟测试数据
    	HashMap<String, Object> dataMap = Maps.newHashMap();
		dataMap.put("name", "李四");
		dataMap.put("remark", "备注1\n备注2");
        // 注意事项 因为xml中的换行标签是<w:br/>,所以如果“\n”的换行符需要转换,实现如下
    	dataMap.put("remark", String.join("</w:t><w:br/><w:t>", dataMap.get("remark").toString().split("\n")));
		
		String templateFileName = templateXmlPath.substring(templateXmlPath.lastIndexOf("/") + 1);
		String currentDate = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
    	// 拼接导出文件名称
		String fileName = String.format("%s_%s", dataMap.get("cpmc").toString(), templateFileName).replace(".xml", String.format("_%s.pdf", currentDate));
		HttpServletResponse response = WebUtils.getResponse();
		response.setCharacterEncoding(StandardCharsets.UTF_8.name());
		response.setContentType("application/pdf");
		response.setHeader("Content-Disposition", "attachment;filename=" + EncoderUtils.encodeToUtf8(fileName));
		
		try (ServletOutputStream outputStream = response.getOutputStream()) {
			NbpWordUtils.exportPdf(templateXmlPath, templateZipPath, dataMap, outputStream);
		} catch (IOException e) {
			log.error(e.getMessage(), e);
			throw new RuntimeException("文件导出异常");
		}
		
	}

六、涉及代码引用

package com.bs.utils;

import lombok.extern.slf4j.Slf4j;

import java.io.*;

/**
 * @author admin
 * @description IO工具类
 * @createDate 2022/6/22
 */
@Slf4j
public class IOUtils {
	
	/**
	 * 关闭InputStream
	 *
	 * @param stream InputStream
	 */
	public static void close(InputStream stream) {
		if (stream != null) {
			try {
				stream.close();
			} catch (IOException e) {
				log.error("close stream error", e);
			}
		}
	}
	
	/**
	 * 关闭OutputStream
	 *
	 * @param stream OutputStream
	 */
	public static void close(OutputStream stream) {
		if (stream != null) {
			try {
				stream.close();
			} catch (IOException e) {
				log.error("close stream error", e);
			}
		}
	}
	
	/**
	 * 关闭Reader
	 *
	 * @param stream Reader
	 */
	public static void close(Reader stream) {
		if (stream != null) {
			try {
				stream.close();
			} catch (IOException e) {
				log.error("close stream error", e);
			}
		}
	}
	
	/**
	 * 关闭Writer
	 *
	 * @param stream Writer
	 */
	public static void close(Writer stream) {
		if (stream != null) {
			try {
				stream.close();
			} catch (IOException e) {
				log.error("close stream error", e);
			}
		}
	}
}
package com.bs.utils;

import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletResponse;

/**
 * @author admin
 * @description Web工具类
 * @createDate 2022/6/22
 */
public class WebUtils {

	/**
	 * 获取Response
	 *
	 */
	public static HttpServletResponse getResponse() {
		ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		Assert.notNull(requestAttributes, "requestAttributes is null");
		return requestAttributes.getResponse();
	}
}
package com.bs.utils;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
 * ZIP工具类
 *
 * @author author
 * @date 2022/8/3
 */
@Slf4j
public class ZipUtils {
	
	/**
	 * 替换某个 item
	 *
	 * @param inputStream     输入流
	 * @param outputStream    输出流
	 * @param itemName        要替换的 item 名称
	 * @param itemInputStream 要替换的 item 的内容输入流
	 */
	public static void replaceItem(InputStream inputStream, OutputStream outputStream, String itemName, InputStream itemInputStream) {
		
		ZipEntry entryIn;
		try (ZipInputStream zipInputStream = new ZipInputStream(inputStream);
		     ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream);
		) {
			while ((entryIn = zipInputStream.getNextEntry()) != null) {
				String entryName = entryIn.getName();
				ZipEntry entryOut = new ZipEntry(entryName);
				// 只使用 name
				zipOutputStream.putNextEntry(entryOut);
				// 缓冲区
				byte[] buf = new byte[8 * 1024];
				int len;
				
				if (entryName.equals(itemName)) {
					// 使用替换流
					while ((len = (itemInputStream.read(buf))) > 0) {
						zipOutputStream.write(buf, 0, len);
					}
				} else {
					// 输出普通Zip流
					while ((len = (zipInputStream.read(buf))) > 0) {
						zipOutputStream.write(buf, 0, len);
					}
				}
				// 关闭此 entry
				zipOutputStream.closeEntry();
			}
		} catch (IOException e) {
			log.error(e.getMessage(), e.getCause());
			throw new RuntimeException("替换item失败");
		}
	}
	
}

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值