java實現word生成並轉pdf

前言

本篇博客主要解決java後臺動態生成word(docx格式),並將word轉換爲pdf並添加水印。html

思考

項目需求是要導出帶水印的pdf,表格樣式仍是有點複雜的,以前考慮過用itextpdf根據html來生成pdf,但框架用的是先後臺分java

離的,前臺用的是react,而且是在沒有展現出表格的狀況下,因此無法經過前臺獲取html代碼塊生成,後來又本身手動拼接react

html,但代碼量太大,難維護,且樣式不怎麼好看。因此決定用freemarker模板生成word,再轉成pdf。翻閱網上不少資料給spring

出了不少方案將word轉pdf,有用poi的、有用第三方工具的等等。用poi的寫的都太複雜,jar引用不少,用第三方工具的有局app

限性,不適合誇平臺,須要安裝服務。因此決定用docx4j,但docx4j只支持docx格式的word轉pdf,因此須要freemarker框架

生成docx的word。ide

動手

一、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>    

 

二、freemarker生成word(docx)編碼

  以前用freemarker生成word的時候都是生成doc格式的,將doc模板文件保存爲xml格式再生成word,生成出來的實際上是XML文件,idea

只不過文件後綴名是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;
    }
}

 

 

 

問題

生成文件下載的時候感受稍微有點慢,其實文件不大,有個八、9秒的樣子纔會有下載框出現,不知道是否是先後臺分離的緣由。

相關文章
相關標籤/搜索