• java实现word生成并转pdf


    前言

    本篇博客主要解决java后台动态生成word(docx格式),并将word转换为pdf并添加水印。

    思考

    项目需求是要导出带水印的pdf,表格样式还是有点复杂的,之前考虑过用itextpdf根据html来生成pdf,但框架用的是前后台分

    离的,前台用的是react,并且是在没有展示出表格的情况下,所以没法通过前台获取html代码块生成,后来又自己手动拼接

    html,但代码量太大,难维护,且样式不怎么好看。所以决定用freemarker模板生成word,再转成pdf。翻阅网上很多资料给

    出了很多方案将word转pdf,有用poi的、有用第三方工具的等等。用poi的写的都太复杂,jar引用很多,用第三方工具的有局

    限性,不适合夸平台,需要安装服务。所以决定用docx4j,但docx4j只支持docx格式的word转pdf,所以需要freemarker

    生成docx的word。

    动手

    1、pom引入依赖

     
            <dependency>
                <groupId>com.itextpdf</groupId>
                <artifactId>itextpdf</artifactId>
                <version>5.4.3</version>
            </dependency>
    
            <dependency>
                <groupId>freemarker</groupId>
                <artifactId>freemarker</artifactId>
                <version>2.3.23</version>
            </dependency>
    
            <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、freemarker生成word(docx)

      之前用freemarker生成word的时候都是生成doc格式的,将doc模板文件保存为xml格式再生成word,生成出来的其实是XML文件,

    只不过文件后缀名是doc的,即使在生成的时候将文件后缀名改为docx,生成出来的文件是打不开的。其实docx本质上是个压缩包,

    里面有包含word主要内容的document.xml文件、资源定义文件document.xml.rels、还有页眉页脚文件、图片资源等等,解决思路是替

    换内容区的document.xml文件。

    1)、准备docx模板文件,编辑插入占位符。

    2)、将docx文件复制一份,将复制的文件后缀名改为zip

     

    3)、拷贝出zip包里word路径下的document.xml文件,这个就是word的主内容文件。之前的那个tmp.docx和拷贝出的document.xml

    这两个文件是我们所需要用到的两个模板文件,拷贝到项目工程里面去。

     

    4)、工具类代码

     

      1 package com.eazytec.zqtong.common.utils;
      2 
      3 import com.itextpdf.text.*;
      4 import com.itextpdf.text.pdf.*;
      5 import org.docx4j.Docx4J;
      6 import org.docx4j.convert.out.FOSettings;
      7 import org.docx4j.fonts.IdentityPlusMapper;
      8 import org.docx4j.fonts.Mapper;
      9 import org.docx4j.fonts.PhysicalFonts;
     10 import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
     11 import org.springframework.core.io.ClassPathResource;
     12 
     13 import java.io.*;
     14 import java.util.*;
     15 import java.util.zip.ZipEntry;
     16 import java.util.zip.ZipFile;
     17 import java.util.zip.ZipOutputStream;
     18 
     19 public class PdfUtil {
     20     private static String separator = File.separator;//文件夹路径分格符
     21 
     22     //=========================================生成申请表pdf===================================
     23 
     24     /**
     25      * freemark生成word----docx格式
     26      * @param dataMap 数据源
     27      * @param documentXmlName  document.xml模板的文件名
     28      * @param docxTempName   docx模板的文件名
     29      * @return 生成的文件路径
     30      */
     31     public static String createApplyPdf(Map<String,Object> dataMap,String documentXmlName,String docxTempName) {
     32         ZipOutputStream zipout = null;//word输出流
     33         File tempPath = null;//docx格式的word文件路径
     34         try {
     35             //freemark根据模板生成内容xml
     36             //================================获取 document.xml 输入流================================
     37             ByteArrayInputStream documentInput = FreeMarkUtils.getFreemarkerContentInputStream(dataMap, documentXmlName, separator + "template" + separator + "downLoad" + separator);
     38             //================================获取 document.xml 输入流================================
     39             //获取主模板docx
     40             ClassPathResource resource = new ClassPathResource("template" + File.separator + "downLoad" + File.separator + docxTempName);
     41             File docxFile = resource.getFile();
     42 
     43             ZipFile zipFile = new ZipFile(docxFile);
     44             Enumeration<? extends ZipEntry> zipEntrys = zipFile.entries();
     45 
     46             //输出word文件路径和名称
     47             String fileName = "applyWord_" + System.currentTimeMillis() + ".docx";
     48             String outPutWordPath = System.getProperty("java.io.tmpdir").replaceAll(separator + "$", "") + separator + fileName;
     49 
     50             tempPath = new File(outPutWordPath);
     51             //如果输出目标文件夹不存在,则创建
     52             if (!tempPath.getParentFile().exists()) {
     53                 tempPath.mkdirs();
     54             }
     55             //docx文件输出流
     56             zipout = new ZipOutputStream(new FileOutputStream(tempPath));
     57 
     58             //循环遍历主模板docx文件,替换掉主内容区,也就是上面获取的document.xml的内容
     59             //------------------覆盖文档------------------
     60             int len = -1;
     61             byte[] buffer = new byte[1024];
     62             while (zipEntrys.hasMoreElements()) {
     63                 ZipEntry next = zipEntrys.nextElement();
     64                 InputStream is = zipFile.getInputStream(next);
     65                 if (next.toString().indexOf("media") < 0) {
     66                     zipout.putNextEntry(new ZipEntry(next.getName()));
     67                     if ("word/document.xml".equals(next.getName())) {
     68                         //写入填充数据后的主数据信息
     69                         if (documentInput != null) {
     70                             while ((len = documentInput.read(buffer)) != -1) {
     71                                 zipout.write(buffer, 0, len);
     72                             }
     73                             documentInput.close();
     74                         }
     75                     }else {//不是主数据区的都用主模板的
     76                         while ((len = is.read(buffer)) != -1) {
     77                             zipout.write(buffer, 0, len);
     78                         }
     79                         is.close();
     80                     }
     81                 }
     82             }
     83             //------------------覆盖文档------------------
     84             zipout.close();//关闭
     85 
     86             //----------------word转pdf--------------
     87             return convertDocx2Pdf(outPutWordPath);
     88 
     89         } catch (Exception e) {
     90             e.printStackTrace();
     91             try {
     92                 if(zipout!=null){
     93                     zipout.close();
     94                 }
     95             }catch (Exception ex){
     96                 ex.printStackTrace();
     97             }
     98         }
     99         return "";
    100     }
    101 
    102     /**
    103      * word(docx)转pdf
    104      * @param wordPath  docx文件路径
    105      * @return  生成的带水印的pdf路径
    106      */
    107     public static String convertDocx2Pdf(String wordPath) {
    108         OutputStream os = null;
    109         InputStream is = null;
    110         try {
    111             is = new FileInputStream(new File(wordPath));
    112             WordprocessingMLPackage mlPackage = WordprocessingMLPackage.load(is);
    113             Mapper fontMapper = new IdentityPlusMapper();
    114             fontMapper.put("隶书", PhysicalFonts.get("LiSu"));
    115             fontMapper.put("宋体", PhysicalFonts.get("SimSun"));
    116             fontMapper.put("微软雅黑", PhysicalFonts.get("Microsoft Yahei"));
    117             fontMapper.put("黑体", PhysicalFonts.get("SimHei"));
    118             fontMapper.put("楷体", PhysicalFonts.get("KaiTi"));
    119             fontMapper.put("新宋体", PhysicalFonts.get("NSimSun"));
    120             fontMapper.put("华文行楷", PhysicalFonts.get("STXingkai"));
    121             fontMapper.put("华文仿宋", PhysicalFonts.get("STFangsong"));
    122             fontMapper.put("宋体扩展", PhysicalFonts.get("simsun-extB"));
    123             fontMapper.put("仿宋", PhysicalFonts.get("FangSong"));
    124             fontMapper.put("仿宋_GB2312", PhysicalFonts.get("FangSong_GB2312"));
    125             fontMapper.put("幼圆", PhysicalFonts.get("YouYuan"));
    126             fontMapper.put("华文宋体", PhysicalFonts.get("STSong"));
    127             fontMapper.put("华文中宋", PhysicalFonts.get("STZhongsong"));
    128 
    129             mlPackage.setFontMapper(fontMapper);
    130 
    131             //输出pdf文件路径和名称
    132             String fileName = "pdfNoMark_" + System.currentTimeMillis() + ".pdf";
    133             String pdfNoMarkPath = System.getProperty("java.io.tmpdir").replaceAll(separator + "$", "") + separator + fileName;
    134 
    135             os = new java.io.FileOutputStream(pdfNoMarkPath);
    136 
    137             //docx4j  docx转pdf
    138             FOSettings foSettings = Docx4J.createFOSettings();
    139             foSettings.setWmlPackage(mlPackage);
    140             Docx4J.toFO(foSettings, os, Docx4J.FLAG_EXPORT_PREFER_XSL);
    141 
    142             is.close();//关闭输入流
    143             os.close();//关闭输出流
    144 
    145             //添加水印
    146             return addTextMark(pdfNoMarkPath);
    147         } catch (Exception e) {
    148             e.printStackTrace();
    149             try {
    150                 if(is != null){
    151                     is.close();
    152                 }
    153                 if(os != null){
    154                     os.close();
    155                 }
    156             }catch (Exception ex){
    157                 ex.printStackTrace();
    158             }
    159         }finally {
    160             File file = new File(wordPath);
    161             if(file!=null&&file.isFile()&&file.exists()){
    162                 file.delete();
    163             }
    164         }
    165        return "";
    166     }
    167 
    168     /**
    169      * 添加水印图片
    170      * @param inPdfPath 无水印pdf路径
    171      * @return 生成的带水印的pdf路径
    172      */
    173     private static String addTextMark(String inPdfPath) {
    174         PdfStamper stamp = null;
    175         PdfReader reader = null;
    176         try {
    177             //输出pdf带水印文件路径和名称
    178             String fileName = "pdfMark_" + System.currentTimeMillis() + ".pdf";
    179             String outPdfMarkPath = System.getProperty("java.io.tmpdir").replaceAll(separator + "$", "") + separator + fileName;
    180 
    181             //添加水印
    182             reader = new PdfReader(inPdfPath, "PDF".getBytes());
    183             stamp = new PdfStamper(reader, new FileOutputStream(new File(outPdfMarkPath)));
    184             PdfContentByte under;
    185             int pageSize = reader.getNumberOfPages();// 原pdf文件的总页数
    186             //水印图片
    187             ClassPathResource resource = new ClassPathResource("template" + File.separator + "downLoad" + File.separator + "mark.png");
    188             File file = resource.getFile();
    189             Image image = Image.getInstance(file.getPath());
    190             for (int i = 1; i <= pageSize; i++) {
    191                 under = stamp.getUnderContent(i);// 水印在之前文本下
    192                 image.setAbsolutePosition(100, 210);//水印位置
    193                 under.addImage(image);
    194             }
    195             stamp.close();// 关闭
    196             reader.close();//关闭
    197 
    198             return outPdfMarkPath;
    199         } catch (Exception e) {
    200             e.printStackTrace();
    201             try {
    202                 if (stamp != null) {
    203                     stamp.close();
    204                 }
    205                 if (reader != null) {
    206                     reader.close();//关闭
    207                 }
    208             } catch (Exception ex) {
    209                 ex.printStackTrace();
    210             }
    211         } finally {
    212             //删除生成的无水印pdf
    213             File file = new File(inPdfPath);
    214             if (file != null && file.exists() && file.isFile()) {
    215                 file.delete();
    216             }
    217         }
    218         return "";
    219     }
    220 
    221     public static void main(String[] args) {
    222 //        createApplyPdf();
    223 //        convert();
    224 //        addTextMark("D:\test\test.pdf", "D:\test\test2.pdf");
    225     }
    226 
    227 }

     

    package com.eazytec.zqtong.common.utils;
    
    
    import freemarker.template.Configuration;
    import freemarker.template.Template;
    
    import java.io.*;
    import java.util.Map;
    
    /**
     * 获取freemarker模板字符串
     */
    public class FreeMarkUtils {
    
        /**
         * 获取模板字符串
         * @param dataMap   参数
         * @param templateName  模板名称
         * @param temp_path  模板路径 classes下的路径 如果  classes/templates  传入 /templates即可
         * @return
         */
        public static String getFreemarkerContent(Map dataMap, String templateName, String temp_path) {
            String result = "";
            try {
                //创建配置实例
                Configuration configuration = new Configuration();
    
                //设置编码
                configuration.setDefaultEncoding("UTF-8");
    
                configuration.setClassForTemplateLoading(FreeMarkUtils.class, temp_path);
                //获取模板
                Template template = configuration.getTemplate(templateName);
    
                StringWriter swriter = new StringWriter();
                //生成文件
                template.process(dataMap, swriter);
                result = swriter.toString();
    
    
            } catch (Exception e) {
                e.printStackTrace();
            }
            return result;
        }
    
        /**
         * 生成主数据模板xml
         *
         * @param dataMap      数据参数
         * @param templateName 模板名称 eg: xxx.xml
         * @param pathPrefix   模板路径 eg: /templates/
         * @param filePath     生成路径 eg: d:/ex/ee/xxx.xml
         */
        public static void createTemplateXml(Map dataMap, String templateName, String pathPrefix, String filePath) {
            try {
                //创建配置实例
                Configuration configuration = new Configuration();
    
                //设置编码
                configuration.setDefaultEncoding("UTF-8");
    
                //ftl模板文件统一放至 com.lun.template 包下面
    //            configuration.setDirectoryForTemplateLoading(new File("D:/idea_workspace/alarm/alarm/src/main/resources/template/"));
    //            configuration.setClassForTemplateLoading(FreemarkerWordUtils.class,"/template/doc");
                configuration.setClassForTemplateLoading(FreeMarkUtils.class, pathPrefix);
                //获取模板
                Template template = configuration.getTemplate(templateName);
    //            System.out.println("filePath ==> " + filePath);
                //输出文件
                File outFile = new File(filePath);
    //            System.out.println("outFile.getParentFile() ==> " + outFile.getParentFile());
                //如果输出目标文件夹不存在,则创建
                if (!outFile.getParentFile().exists()) {
                    outFile.getParentFile().mkdirs();
                }
    
                //将模板和数据模型合并生成文件
                Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "UTF-8"));
    
    
                //生成文件
                template.process(dataMap, out);
    
                //关闭流
                out.flush();
                out.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 获取模板字符串输入流
         * @param dataMap   参数
         * @param templateName  模板名称
         * @param tempPath  模板路径 classes下的路径 如果  classes/templates  传入 /templates即可
         * @return
         */
        public static ByteArrayInputStream getFreemarkerContentInputStream(Map dataMap, String templateName, String tempPath) {
            ByteArrayInputStream in = null;
    
            try {
                //创建配置实例
                Configuration configuration = new Configuration();
    
                //设置编码
                configuration.setDefaultEncoding("UTF-8");
    
                //ftl模板文件统一放至 com.lun.template 包下面
    //            configuration.setDirectoryForTemplateLoading(new File("D:/idea_workspace/alarm/alarm/src/main/resources/template/"));
                configuration.setClassForTemplateLoading(FreeMarkUtils.class, tempPath);
                //获取模板
                Template template = configuration.getTemplate(templateName);
    
                StringWriter swriter = new StringWriter();
                //生成文件
                template.process(dataMap, swriter);
                String result = swriter.toString();
                in = new ByteArrayInputStream(swriter.toString().getBytes());
    
            } catch (Exception e) {
                e.printStackTrace();
            }
            return in;
        }
    }

     

     

    问题

    生成文件下载的时候感觉稍微有点慢,其实文件不大,有个8、9秒的样子才会有下载框出现,不知道是不是前后台分离的原因。

     

     

     

  • 相关阅读:
    c# 框架学习(nop )总结-------删除功能
    c# 框架学习(nop )总结-------编辑功能
    约束
    索引
    受限操作的变通解决方案
    删除数据表
    修改已有数据表
    定义外键
    定义主键
    定义默认值
  • 原文地址:https://www.cnblogs.com/zhouyun-yx/p/11211354.html
Copyright © 2020-2023  润新知