Springboot如何根据docx填充生成word文件并导出pdf
作者:专注写bug
这篇文章主要介绍了Springboot如何根据docx填充生成word文件并导出pdf问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
在项目中碰见一个需求,需要将.doc
的合同,转换为pdf
实现打印与预览功能。
将docx模板填充数据生成doc文件
1、依赖引入
填充docx模板,只需要引入一个pom依赖即可实现。
<dependency> <groupId>com.deepoove</groupId> <artifactId>poi-tl</artifactId> <version>1.5.0</version> </dependency>
2、doc文件转换docx,并标注别名
用office或者wps,创建一个001.doc
文件,绘制表格,保存。
更改后缀为.docx
,确定后,在指定的位置,表示数据接受变量名称。
如下图所示:
3、编写java代码实现数据填充
import com.deepoove.poi.XWPFTemplate; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.HashMap; import java.util.Map; /** * word 填充测试 */ public class TestWord { public static void main(String[] args) throws IOException { Map<String, Object> params = new HashMap<>(); params.put("username","xiangjiao1"); params.put("password","******"); params.put("age",22); params.put("email","专注写bug测试中文"); Resource resource = new ClassPathResource("templates_report/001.docx"); File file = resource.getFile(); // 数据填充 XWPFTemplate template = XWPFTemplate.compile(file).render(params); String docOutPath = System.getProperty("user.dir")+File.separator+"springboot-poi"+File.separator+"pdf"+File.separator+ "1.doc"; OutputStream outputStream = new FileOutputStream(docOutPath); template.write(outputStream); } }
运行程序,查看结果。
测试项目结构如下:
docx文件填充数据导出pdf(web)
1、依赖引入
向docx
模板中填充数据,并导出pdf
类型的文件,除了上面的pom依赖之外,还需要引入其他的依赖信息,完整依赖如下所示:
<!-- docx 数据填充生成 doc文件 这个是主要 --> <dependency> <groupId>com.deepoove</groupId> <artifactId>poi-tl</artifactId> <version>1.5.0</version> </dependency> <!-- doc 转 pdf --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itextpdf</artifactId> <version>5.5.13</version> </dependency> <!-- docx4j docx2pdf --> <dependency> <groupId>org.docx4j</groupId> <artifactId>docx4j</artifactId> <version>6.1.2</version> </dependency> <dependency> <groupId>org.docx4j</groupId> <artifactId>docx4j-export-fo</artifactId> <version>6.0.0</version> </dependency>
2、字体文件
在src\main\resources
下创建一个font
文件夹,其中放入simsun.ttc
字体文件。
3、编写工具类
思想很简单
- 1、先使用上面的docx模板填充数据生成
临时doc
文件, - 2、再将doc文件转换为pdf文件
- 3、删除临时文件
【注意:】
为了避免出现多人同时操作,导致文件误删的问题,需要尽可能地保证临时文件名称的唯一性。
import com.deepoove.poi.XWPFTemplate; import com.itextpdf.text.*; import com.itextpdf.text.Image; import com.itextpdf.text.pdf.*; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.text.WordUtils; import org.docx4j.Docx4J; import org.docx4j.convert.out.FOSettings; import org.docx4j.fonts.IdentityPlusMapper; import org.docx4j.fonts.Mapper; import org.docx4j.fonts.PhysicalFonts; import org.docx4j.openpackaging.packages.WordprocessingMLPackage; import org.springframework.stereotype.Component; import java.io.*; import java.util.Map; import java.util.UUID; import java.util.zip.ZipOutputStream; /** * pdf 导出工具类 */ @Component @Slf4j public final class FreeMarkUtils { /** * 根据docx模板填充数据 并生成pdf文件 * * @param dataMap 数据源 * @param docxFile docx模板的文件名 * @return 生成的文件路径 */ public static byte[] createDocx2Pdf(Map<String, Object> dataMap, String docxFile) { //输出word文件路径和名称 (临时文件名,本次为测试,最好使用雪花算法生成,或者用uuid) String fileName = UUID.randomUUID().toString() + ".docx"; // word 数据填充 // 生成docx临时文件 final File tempPath = new File(fileName); final File docxTempFile = getTempFile(docxFile); XWPFTemplate template = XWPFTemplate.compile(docxTempFile).render(dataMap); try { template.write(new FileOutputStream(tempPath)); } catch (IOException e) { e.printStackTrace(); } // word转pdf final String pdfFile = convertDocx2Pdf(fileName); return getFileOutputStream(new File(pdfFile)).toByteArray(); } /** * word(doc)转pdf * * @param wordPath doc 生成的临时文件路径 * @return 生成的带水印的pdf路径 */ public static String convertDocx2Pdf(String wordPath) { OutputStream os = null; InputStream is = null; //输出pdf文件路径和名称 (临时文件 尽可能保证文件名称的唯一性) final String fileName = UUID.randomUUID().toString() + ".pdf"; try { is = new FileInputStream(wordPath); WordprocessingMLPackage mlPackage = WordprocessingMLPackage.load(is); Mapper fontMapper = new IdentityPlusMapper(); fontMapper.put("隶书", PhysicalFonts.get("LiSu")); fontMapper.put("宋体", PhysicalFonts.get("SimSun")); fontMapper.put("微软雅黑", PhysicalFonts.get("Microsoft Yahei")); fontMapper.put("黑体", PhysicalFonts.get("SimHei")); fontMapper.put("楷体", PhysicalFonts.get("KaiTi")); fontMapper.put("新宋体", PhysicalFonts.get("NSimSun")); fontMapper.put("华文行楷", PhysicalFonts.get("STXingkai")); fontMapper.put("华文仿宋", PhysicalFonts.get("STFangsong")); fontMapper.put("宋体扩展", PhysicalFonts.get("simsun-extB")); fontMapper.put("仿宋", PhysicalFonts.get("FangSong")); fontMapper.put("仿宋_GB2312", PhysicalFonts.get("FangSong_GB2312")); fontMapper.put("幼圆", PhysicalFonts.get("YouYuan")); fontMapper.put("华文宋体", PhysicalFonts.get("STSong")); fontMapper.put("华文中宋", PhysicalFonts.get("STZhongsong")); //解决宋体(正文)和宋体(标题)的乱码问题 PhysicalFonts.put("PMingLiU", PhysicalFonts.get("SimSun")); PhysicalFonts.put("新細明體", PhysicalFonts.get("SimSun")); // 字体文件 PhysicalFonts.addPhysicalFonts("SimSun", WordUtils.class.getResource("/font/simsun.ttc")); mlPackage.setFontMapper(fontMapper); os = new FileOutputStream(fileName); //docx4j docx转pdf FOSettings foSettings = Docx4J.createFOSettings(); foSettings.setWmlPackage(mlPackage); Docx4J.toFO(foSettings, os, Docx4J.FLAG_EXPORT_PREFER_XSL); is.close();//关闭输入流 os.close();//关闭输出流 } catch (Exception e) { e.printStackTrace(); } finally { // 删除docx 临时文件 File file = new File(wordPath); if (file != null && file.isFile() && file.exists()) { file.delete(); } try { if (is != null) { is.close(); } if (os != null) { os.close(); } } catch (Exception ex) { ex.printStackTrace(); } } return fileName; } /** * 文件转字节输出流 * * @param outFile 文件 * @return */ public static ByteArrayOutputStream getFileOutputStream(File outFile) { // 获取生成临时文件的输出流 InputStream input = null; ByteArrayOutputStream bytestream = null; try { input = new FileInputStream(outFile); bytestream = new ByteArrayOutputStream(); int ch; while ((ch = input.read()) != -1) { bytestream.write(ch); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { bytestream.close(); input.close(); log.info("删除临时文件"); if (outFile.exists()) { outFile.delete(); } } catch (IOException e) { e.printStackTrace(); } } return bytestream; } /** * 获取资源文件的临时文件 * 资源文件打jar包后,不能直接获取,需要通过流获取生成临时文件 * * @param fileName 文件路径 templates/xxx.docx * @return */ public static File getTempFile(String fileName) { final File tempFile = new File(fileName); InputStream fontTempStream = null; try { fontTempStream = FreeMarkUtils.class.getClassLoader().getResourceAsStream(fileName); FileUtils.copyInputStreamToFile(fontTempStream, tempFile); } catch (Exception e) { e.printStackTrace(); } finally { try { if (fontTempStream != null) { fontTempStream.close(); } } catch (IOException e) { e.printStackTrace(); } } return tempFile; } /** * 插入图片水印 * @param srcByte 已生成PDF的字节数组(流转字节) * @param destFile 生成有水印的临时文件 temp.pdf * @return */ public static FileOutputStream addWaterMark(byte[] srcByte, String destFile) { // 待加水印的文件 PdfReader reader = null; // 加完水印的文件 PdfStamper stamper = null; FileOutputStream fileOutputStream = null; try { reader = new PdfReader(srcByte); fileOutputStream = new FileOutputStream(destFile); stamper = new PdfStamper(reader, fileOutputStream); int total = reader.getNumberOfPages() + 1; PdfContentByte content; // 设置字体 //BaseFont font = BaseFont.createFont(); // 循环对每页插入水印 for (int i = 1; i < total; i++) { final PdfGState gs = new PdfGState(); // 水印的起始 content = stamper.getUnderContent(i); // 开始 content.beginText(); // 设置颜色 默认为蓝色 //content.setColorFill(BaseColor.BLUE); // content.setColorFill(Color.GRAY); // 设置字体及字号 //content.setFontAndSize(font, 38); // 设置起始位置 // content.setTextMatrix(400, 880); //content.setTextMatrix(textWidth, textHeight); // 开始写入水印 //content.showTextAligned(Element.ALIGN_LEFT, text, textWidth, textHeight, 45); // 设置水印透明度 // 设置笔触字体不透明度为0.4f gs.setStrokeOpacity(0f); Image image = null; image = Image.getInstance("url"); // 设置坐标 绝对位置 X Y 这个位置大约在 A4纸 右上角展示LOGO image.setAbsolutePosition(472, 785); // 设置旋转弧度 image.setRotation(0);// 旋转 弧度 // 设置旋转角度 image.setRotationDegrees(0);// 旋转 角度 // 设置等比缩放 图片大小 image.scalePercent(4);// 依照比例缩放 // image.scaleAbsolute(200,100);//自定义大小 // 设置透明度 content.setGState(gs); // 添加水印图片 content.addImage(image); // 设置透明度 content.setGState(gs); //结束设置 content.endText(); content.stroke(); } } catch (IOException e) { e.printStackTrace(); } catch (DocumentException e) { e.printStackTrace(); } finally { try { stamper.close(); fileOutputStream.close(); reader.close(); } catch (DocumentException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } return fileOutputStream; } }
4、编写测试接口
import cn.xj.util.FreeMarkUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.UUID; @RestController @RequestMapping("/report") public class ReportController { @GetMapping("/doc2pdf") public void doc2pdf(HttpServletResponse response) { Map<String, Object> params = new HashMap<>(); params.put("username","xiangjiao1"); params.put("password","******"); params.put("age",22); params.put("email","专注写bug测试中文"); final byte[] data = FreeMarkUtils.createDocx2Pdf(params, "templates_report/001.docx"); String fileName = UUID.randomUUID().toString() + "_001_test.pdf"; generateFile(response, data, fileName); } /** * 下载文件 * @param response 相应 * @param data 数据 * @param fileName 文件名 */ private void generateFile(HttpServletResponse response, byte[] data, String fileName) { response.setHeader("content-Type", "application/octet-stream"); response.setCharacterEncoding("utf-8"); response.setHeader("Content-disposition", "attachment;filename=" + fileName); try { response.getOutputStream().write(data); } catch (IOException e) { e.printStackTrace(); } finally { try { response.getOutputStream().close(); } catch (IOException e) { e.printStackTrace(); } } } }
请求测试
http://localhost/report/doc2pdf
docx4j 复杂docx文件转pdf碰见的坑总结
转pdf出现空格压缩、中文缩减等问题,可以考虑将半角替换成全角,将模板中的空格使用全角空格替换。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。