动态生成pdf

动态生成pdf

需求

最近有个需求,就是办公系统,往往有生成doc,pdf的需求。以往,我接触到的系统,生成doc,有这么几种方式,一种是直接利用ipoi来生成doc,这种最大的问题是,当模板的样式比较复杂的话,生成极为困难,我也没进过有人这么生成,目前这家公司倒是有这么一个思路,利用itext直接读取模板的documnet,然后因为模板里插有书签,那么我们可以在读到书签后,直接替换为数据,那么就可以达到生成doc的目的,但是需求中如果有表格,循环数据,这种需求的话,应该是要循环插入样式和数据,这种我觉得是比较复杂的,耗时耗力。然后我前一家的公司,主要做的是银行业务,这种doc报表还是比较多的,都是基本利用将doc另存为xml,然后利用freemarker来生成带有数据的doc,或者是直接转为html,然后渲染html来给前端预览,这种也是比较讨巧的。样式也很支持。

但是在遇到转为pdf的时候,问题暴露出来了,利用xml生成doc或者docx的方法,其本质还是xml,然后利用openoffice来生成pdf的话,就可能直接报错或者转换的是xml的pdf,这样肯定是不行的,所以需要找到一个方法能动态生成pdf。

第一个思路,xml转doc,在转pdf

因为doc转pdf,直接调用本地的openoffice,还是好转换pdf的,但是现在目前生成的是doc的皮,本质是xml,所以我们就需要将xml转换为doc,我本来以为是会比较多的方法,但是结果百度了一圈,发现,大部分都是不支持,或者支持的是在线转换,这样肯定是不行的,靠谱点的也就是利用调用本地的office,这又涉及到,不支持linux,不支持商用。毙掉

第二个思路,利用html转pdf

我们xml转pdf失败,那直接用html转pdf好吗?我又百度了一圈,发现比较好的思路也就是

https://www.cnblogs.com/yunfeiyang-88/p/10984740.html

但是主要问题也是两个,一个是图片资源问题,这种转换出来的html,他的图片资源需要自己管理,想想也知道,转换的html,他引用的是外部的图片,所以项目用的话,需要管理图片资源,然后样式转换,我记得说复杂样式是支持不好的,我手动转,doc转html,然后转回doc也是丢了些样式。小宇同学说他转换的工具样式支持的比较好,没尝试,待检验。

https://www.jianshu.com/p/4d65857ffe5e/

https://blog.csdn.net/qq_38423105/article/details/81389445

第三个思路,利用docx转pdf

我们动态生成的doc本质还是xml,那我们能不能动态生成docx呢?在我的理解下,doc本质是流,而docx是本质已经是xml了(其基于Office Open XML标准的压缩文件格式取代了其以前专有的默认文件格式,在传统的文件名扩展名后面添加了字母“x”),我百度了下,动态生成docx是可以做到的。

动态生成docx

具体思路就是:

1、将docx文档模板改为ZIP格式(修改.docx后缀名为.zip),然后把zip解压到当前目录

2.将解压后word目录下document.xml文档放入项目中,用于后边内容填充。

这里有个坑,document.xml的读取,因为我们部署是打包成jar来部署,那么就涉及到一个问题,项目开发能读到的资源,在打包成jar包后读取不到,报file not found 的错误,这个原因是在本地开发和war部署的时候,利用类加载器获取到当前类,然后加载,这个本质还是拼接路径。

String basePath = FreeMarkerWordUtil.class.getResource("/").getPath() + "templates/";

这样的话,导致打包成jar,jar里可不是让你随便读取的,所以会出错,java提供了以流的方式读取资源,所以freemarker的配置记得配置的模板路径为流。

3.修改document.xml,利用freemarker标签填充document.xml

这里又有坑,如果你格式化document.xml,然后写freemarker的标签,你会发现,生成的docx会格式乱了,样式乱了。

我猜想的是,docx读取的时候,其实里面的样式有读取换行和空格,所以导致docx全乱了。

然后我测试了下,xml的格式化网站的格式化后压缩,是不会丢样式的。

但是有个问题,我们的freemarker的<#if> <#list>这些标签,对xml来说是不合法的标签,如果xml格式化后压缩,还是丢失样式。

但是如果不格式化,然后填充数据,太折磨人了。

所以我们的解决方案是,先不管循环或者判断的填充数据,先格式化后把一个个填充的先填充好的,然后格式化,在手动把需要循环或者判断的标签加上去,比如之前用特殊字符占位,然后替换。

4.在输入docx文档的时候把填充过内容的的 document.xml用流的方式写入zip

package com.xy.microservice.orderserver.utils;


import com.xy.microservice.common.utils.idUtil.IdGen;
import freemarker.cache.ClassTemplateLoader;
import freemarker.cache.FileTemplateLoader;
import freemarker.cache.StringTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;

import java.io.*;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;


//注意有个坑
//freemarker读取文件的时候,默认是以文件形式的读取模板
//所以,打包成war或者本地运行都是可以的
//但是,打包成jar包,jar包里的文件不是文件,jar中的文件不能以File的形式读取,只能以流的形式读取。如果用File读取就会报文件不存在的异常。看下TemplateLoader的实现类,可以换成SpringTemplateLoader,这个模版加载器是用流的形式读取模版
//所以我改变了模板加载方式FreeMarkerWordUtil

@Slf4j
public class FreeMarkerWordUtil {

	/**
	 * @Desc:生成文件通过map,通过本地项目的resource的templates文件夹下模板文件渲染到对应文件夹下
	 * @Author:wzy
	 * @Date:
	 * @param dataMap
	 *            word中需要展示的动态数据,用map集合来保存
	 * @param templateName
	 *            word模板名称,例如:test.ftl
	 * @param fileName
	 *            生成的文件名称,例如:test.doc
	 */
	public static String createWord(Map dataMap, String templateName,String destPath, String fileName) {

		String filePath = "";
		try {
			String basePath = FreeMarkerWordUtil.class.getResource("/").getPath() + "templates/";
			//System.out.println(destPath);
			// 创建配置实例
			Configuration configuration = new Configuration();
			// 设置编码
			configuration.setDefaultEncoding("UTF-8");
			// ftl模板文件统一放至 resouce.template 包下面
			/*configuration.setClassForTemplateLoading(FreeMarkerWordUtil.class,
					"/config/model/");*/


			//读取jar包下的模板
			configuration.setClassForTemplateLoading(FreeMarkerWordUtil.class, "/templates/");
			configuration.setTemplateLoader(new ClassTemplateLoader(FreeMarkerWordUtil.class, "/templates/"));

			//war包方式读取文件
//			FileTemplateLoader ftl1 = new FileTemplateLoader(new File(basePath));
//			configuration.setTemplateLoader(ftl1);
			// 获取模板
			Template template = configuration.getTemplate(templateName, "UTF-8");
			filePath = destPath + File.separator + fileName;
			// 输出文件
			File outFile = new File(filePath);
			// 如果输出目标文件夹不存在,则创建
			if (!outFile.getParentFile().exists()) {
				outFile.getParentFile().mkdirs();
			}
			// 将模板和数据模型合并生成文件
			Writer out = new BufferedWriter(new OutputStreamWriter(
					new FileOutputStream(outFile), "UTF-8"));
			// 生成文件
			template.process(dataMap, out);
			try {
				Runtime.getRuntime().exec("chmod 775 " + destPath + File.separator + fileName);
			}catch (Exception e){
			}
			// 关闭流
			out.flush();
			out.close();
		} catch (Exception e) {
			e.printStackTrace();
		}

		return filePath;
	}



	/**
	 * @Desc:生成word文件通过对象,通过本地项目的resource的templates文件夹下模板文件渲染到对应文件夹下
	 * @Author:comlc
	 * @Date:
	 * @param data
	 *            word中需要展示的动态数据,用对象来保存
	 * @param templateName
	 *            word模板名称,例如:test.ftl
	 * @param fileName
	 *            生成的文件名称,例如:test.doc
	 */
	public static String createWordByObject(Object data, String templateName,String destPath, String fileName) {
		String filePath = "";
		try {
			String basePath = FreeMarkerWordUtil.class.getResource("/").getPath() + "templates/";
			//System.out.println(destPath);
			// 创建配置实例
			Configuration configuration = new Configuration();
			// 设置编码
			configuration.setDefaultEncoding("utf-8");
			// ftl模板文件统一放至 com.lun.template 包下面
			/*configuration.setClassForTemplateLoading(FreeMarkerWordUtil.class,
					"/config/model/");*/

			configuration.setClassForTemplateLoading(FreeMarkerWordUtil.class, "/templates/");
			configuration.setTemplateLoader(new ClassTemplateLoader(FreeMarkerWordUtil.class, "/templates/"));
			//war包方式读取文件
//			FileTemplateLoader ftl1 = new FileTemplateLoader(new File(basePath));
//			configuration.setTemplateLoader(ftl1);
			// 获取模板
			Template template = configuration.getTemplate(templateName, "utf-8");
			// 输出文件
			File outFile = new File(destPath + File.separator + fileName);
			// 如果输出目标文件夹不存在,则创建
			if (!outFile.getParentFile().exists()) {
				outFile.getParentFile().mkdirs();
			}
			// 将模板和数据模型合并生成文件
			Writer out = new BufferedWriter(new OutputStreamWriter(
					new FileOutputStream(outFile), "UTF-8"));
			template.process(data, out);
			filePath = destPath + File.separator + fileName;
			try {
				Runtime.getRuntime().exec("chmod 775 " + destPath + File.separator + fileName);
			}catch (Exception e){
			}

			// 关闭流
			out.flush();
			out.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return  filePath;
	}

	/**
	 * 获取模板文件的string
	 * @param o 数据对象
	 * @param templateName 模板名称
	 * @return
	 */
	public static String createStringByFile(Object o, String templateName){
		String returnString="";
		try {
			String basePath = FreeMarkerWordUtil.class.getResource("/").getPath() + "generatorConfig/";

			Configuration configuration = new Configuration(Configuration.VERSION_2_3_23);

			configuration.setDefaultEncoding("UTF-8");
			FileTemplateLoader ftl1 = new FileTemplateLoader(new File(basePath));
			configuration.setTemplateLoader(ftl1);
			// 获取模板
			Template template = configuration.getTemplate(templateName);
			StringWriter writer = new StringWriter();
			template.process(o,writer);
			returnString=writer.toString();
			System.out.println(writer.toString());
		} catch (Exception e) {
			e.printStackTrace();
		}
		return returnString;
	}


	public static String createString(Map dataMap, String templateString) {
		String returnString="";

		Configuration cfg = new Configuration(Configuration.VERSION_2_3_23);
		StringTemplateLoader stringLoader = new StringTemplateLoader();
		stringLoader.putTemplate("myTemplate",templateString);
		cfg.setTemplateLoader(stringLoader);

		try {
			Template template = cfg.getTemplate("myTemplate","utf-8");
			StringWriter writer = new StringWriter();
			try {
				template.process(dataMap, writer);
				returnString=writer.toString();
				System.out.println(writer.toString());
			} catch (TemplateException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return returnString;

	}


	private static String getJarResouce2TempFile(String filepath) throws IOException {
		if(filepath==null || "".equals(filepath)) return null;
		//返回读取指定资源的输入流
//		ClassPathResource classPathResource = new ClassPathResource(filepath);
		InputStream is=FreeMarkerWordUtil.class.getResourceAsStream(filepath);
		String fileSuffix = filepath.substring(filepath.lastIndexOf("."));
		BufferedReader br=new BufferedReader(new InputStreamReader(is));
		String tempfilePath="/tmp/"+IdGen.uuid()+fileSuffix;



		PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(tempfilePath)));
		String ss;
		while((ss=br.readLine()) != null){
			System.out.println(ss);
			pw.println(ss);

		}
		pw.close();
//		BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream(tempfilePath));
//		int len;
//		while((len=br.read())!=-1) {
//			bos.write(len);
//		}
		br.close();
//		bos.close();
		log.info("读取资源文件{}成功",filepath);

		return tempfilePath;

	}




	public static String getFileString(String file) throws IOException {
		FileInputStream fis = new FileInputStream(file);
		InputStreamReader isr = new InputStreamReader(fis, "utf-8");
		BufferedReader br = new BufferedReader(isr);
		StringBuilder stringBuilder = new StringBuilder();
		String temp="";
		while (temp != null) {
			temp = br.readLine();
			if (temp != null ) {
				stringBuilder.append(temp);
			}
		}
		return stringBuilder.toString();

	}


	public static boolean repalceZip(String tempFileName,String docxName,String targetPath) throws Exception {

		File file = new File(tempFileName);
		ZipFile zipFile = new ZipFile(docxName);
		Enumeration<? extends ZipEntry> zipEntrys = zipFile.entries();

		ZipOutputStream zipout = new ZipOutputStream(new FileOutputStream(targetPath));
		int len = -1;
		byte[] buffer = new byte[8888888];
		while (zipEntrys.hasMoreElements()) {
			ZipEntry next = zipEntrys.nextElement();
			InputStream is = zipFile.getInputStream(next);
			// 把输入流的文件传到输出流中 如果是word/document.xml由我们输入
			zipout.putNextEntry(new ZipEntry(next.toString()));
			if ("word/document.xml".equals(next.toString())) {
				InputStream in = new FileInputStream(file);
				while ((len = in.read(buffer)) != -1) {
					zipout.write(buffer, 0, len);
				}
				in.close();
			} else {
				while ((len = is.read(buffer)) != -1) {
					zipout.write(buffer, 0, len);
				}
				is.close();
			}
		}

		zipout.close();
		return true;
	}

	/**
	 * @Description 根据参数生成docx合同文档
	 * @throws Exception
	 */
	public static boolean outputWordByObject(String templateName,String docxName,String targetPath, Object object) throws Exception {
		String tempFileName= IdGen.uuid()+".xml";
		createWordByObject(object,templateName,"/tmp",tempFileName);
		File file = new File("/tmp/"+tempFileName);
		repalceZip(file.getPath(),docxName,targetPath);
		if(file.exists()){
			file.delete();
		}
		return true;
	}

	/**
	 * @Description 根据参数生成docx合同文档
	 * @throws Exception
	 */
	public static boolean outputWord(String templateName,String docxName,String targetPath, Map<String, Object> param) throws Exception {
		String tempFileName= IdGen.uuid()+".xml";
		createWord(param,templateName,"/tmp",tempFileName);
		File file = new File("/tmp/"+tempFileName);
		repalceZip(file.getPath(),docxName,targetPath);
		if(file.exists()){
			file.delete();
		}
		return true;
	}

	public static void main(String[] args) throws Exception {
//        Map<String,Object> hashMap = new HashMap<>();
//        hashMap.put("name","浙江鑫烨");
		createString(hashMap,"${name!}");
         createWord(hashMap,"test.xml","/home","document.xml");
		// ZipUtils 是一个工具类,主要用来替换
		ZipInputStream zipInputStream = ZipUtils.wrapZipInputStream(new FileInputStream(new File("F:\\home\\3221.zip")));
		ZipOutputStream zipOutputStream = ZipUtils.wrapZipOutputStream(new FileOutputStream(new File("F:\\home\\3221.docx")));
		String itemname = "word/document.xml";
		ZipUtils.replaceItem(zipInputStream, zipOutputStream, itemname, new FileInputStream(new File("/home/document.xml")));
		WordUtil.word7Pdf("F:\\home\\3221.docx","F:\\home\\3221.pdf");
//		outputWord("9757 OA-test.xml","2.docx","/home/3.docx",hashMap);
//
//
//		System.out.println("success");


		String file="/tmp/3.docx";
		int i = file.lastIndexOf(".");
		System.out.println(file.substring(i+1,file.length()));
	}









}

本质就是利用zipentry读取docx,然后替换掉document.xml内容,然后在输出为docx文件,这样就达到了动态生成的目的。

但是有问题,docx与document.xml绑定严重,想想也知道,他们是配套的。

然后怎么读取docx,如果是放项目的资源文件夹里,又有那个问题,读取jar包只能是流,

我想过读取流然后输出文件来再一步生成资源,但是乱码了,生成也失败了。

想过读取流来替换zipentry。没试验。

最后就要么本地建立个专门的文件夹,放模板。

要么传aws,我最后选择项目里的模板管理,将docx传到aws,用到再下载到本地。

5.输出docx文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值