freemarker实现word导出

WORD文档导出

1.实现原理

将模板文件放置项目中,通过freemarker处理模板文件,将相应的值填入对应的模板中,最后再导出下载

2.工具类

freemarker的Jar包导入项目

		<dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.30</version>
        </dependency>

freemarker调用工具类

package cn.gov.customs.hb.common.utility.util;

import freemarker.template.Configuration;
import freemarker.template.Template;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
 * @author: Baymax
 * @Date: 2022年06月17日 15:57
 * @Description:
 */
public class GenerateDocxUtil {
    private static Logger logger = LoggerFactory.getLogger(GenerateDocxUtil.class);

    public static void freemarkerDocxTest(Map<String, Object> lists, String fileName, HttpServletResponse response) throws Exception {
        String reportPath = GenerateDocxUtil.class.getResource("/static/opinionTemplate").getPath();
        String rootPath =reportPath + "\\";
        String docxPath =reportPath + "\\"+fileName+".docx";

        //配置freemarker模板
        Configuration configuration = new Configuration();
        String fileDirectory = rootPath;
        configuration.setDirectoryForTemplateLoading(new File(fileDirectory));
        String temName = fileName + ".xml";
        String docxZipPath = rootPath + fileName + ".zip";
        Template template = configuration.getTemplate(temName);

        //通过模板生成的xml临时文件 方法结束后删除该临时文件
        String outFilePath = rootPath + UUID.randomUUID().toString().replace("-", "") + ".xml";
        //指定输出word xml文件的路径
        File docXmlFile = new File(outFilePath);
        FileOutputStream fos = new FileOutputStream(docXmlFile);
        Writer out = new BufferedWriter(new OutputStreamWriter(fos), 10240);
        template.process(lists, out);
        if (out != null) {
            out.close();
        }

        //以下代码 主要用来加密已经生成的xml文件,把xml文件正式转换成加密的word文档
        //包装输入流
        ZipInputStream zipInputStream = wrapZipInputStream(new FileInputStream(new File(docxZipPath)));
        //包装输出流
        ZipOutputStream zipOutputStream = wrapZipOutputStream(new FileOutputStream(new File(docxPath)));
        //正式加密替换成docx格式文档
        List<String> itemNameList = new ArrayList<>();
        itemNameList.add("word/document.xml");

        List<InputStream> itemInputStreamList = new ArrayList<>();
        itemInputStreamList.add(new FileInputStream(new File(outFilePath)));

        replaceItemList(zipInputStream, zipOutputStream, itemNameList, itemInputStreamList);
        new File(outFilePath).delete();
        logger.info("Word-docx文档生成完成");
        XWPFDocument doc = new XWPFDocument(OPCPackage.open(docxPath));
        DownloadFileUtil.download(fileName, doc, response);
        logger.info("文档下载完成!");
    }

    public static ZipInputStream wrapZipInputStream(InputStream inputStream) {
        ZipInputStream zipInputStream = new ZipInputStream(inputStream);
        return zipInputStream;
    }

    public static ZipOutputStream wrapZipOutputStream(OutputStream outputStream) {
        ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream);
        return zipOutputStream;
    }

    public static void replaceItemList(ZipInputStream zipInputStream, ZipOutputStream zipOutputStream, List<String> itemNameList, List<InputStream> itemInputStreamList) {
        if (null == zipInputStream) {
            return;
        }
        if (null == zipOutputStream) {
            return;
        }
        ZipEntry entryIn;
        try {
            while ((entryIn = zipInputStream.getNextEntry()) != null) {
                String entryName = entryIn.getName();
                ZipEntry entryOut = new ZipEntry(entryName);
                zipOutputStream.putNextEntry(entryOut);
                byte[] buf = new byte[8 * 1024];
                int len;

                if (itemNameList.indexOf(entryName) != -1) {
                    // 使用替换流
                    while ((len = (itemInputStreamList.get(itemNameList.indexOf(entryName)).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) {
            e.printStackTrace();
        } finally {
            //e.printStackTrace();
            for (InputStream itemInputStream : itemInputStreamList) {
                close(itemInputStream);
            }
            close(zipInputStream);
            close(zipOutputStream);
        }
    }

    private static void close(InputStream inputStream) {
        if (null != inputStream) {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static void close(OutputStream outputStream) {
        if (null != outputStream) {
            try {
                outputStream.flush();
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

"/static/opinionTemplate"是项目中Resources下的地址,注意根据模板的具体位置修改,注意要将xml文件以及zip文件放置在同一个文件夹
zip文件是将docx模板文件后缀直接修改成zip,后来解压成文件夹,其中word下的document.xml文件即为需要修改的xml文件。
后续freemaker工具会将填充数据后的xml文件,替换掉zip文件中的document.xml文件。然后修改后缀名,最后下载

通过浏览器下载工具类

package cn.gov.customs.hb.common.utility.util;

import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.OutputStream;
import java.net.URLEncoder;

/**
 * @author: Baymax
 * @Date: 2022年06月16日 16:20
 * @Description:
 */
public class DownloadFileUtil {
    private final static String UTF8 = "UTF-8";
    private final static String CONTENTYPE = "application/msword";
    private static Logger logger = LoggerFactory.getLogger(DownloadFileUtil.class);
    /**
     * 下载
     * @param
     * @param response
     */
    public static void download(String fileName, XWPFDocument document, HttpServletResponse response) {
        try {
            if (!fileName.contains(".docx")) {
                fileName += ".docx";
            }
            response.setCharacterEncoding(UTF8);
            response.setContentType(CONTENTYPE);
            response.setHeader("Content-Disposition", "attachment;filename="
                    .concat(String.valueOf(URLEncoder.encode(fileName, UTF8))));
            // word写入到文件
            OutputStream output = response.getOutputStream();
            BufferedOutputStream bufferedOutPut = new BufferedOutputStream(output);
            //刷新此缓冲的输出流,保证数据全部都能写出
            bufferedOutPut.flush();
            document.write(bufferedOutPut);
            bufferedOutPut.close();
            logger.info("Word导出成功");
        } catch (Exception e) {
            logger.info("Word导出失败");
            throw new RuntimeException(e);
        }
    }

}

xml文件修改

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:oel="http://schemas.microsoft.com/office/2019/extlst" 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:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" 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 w16se w16cid w16 w16cex w16sdtdh wp14">
<w:body>
<w:p w14:paraId="2D496CC2" w14:textId="77777777" w:rsidR="00365812" w:rsidRPr="00F04D53" w:rsidRDefault="00F04D53" w:rsidP="00F04D53">
<w:pPr>
<w:spacing w:afterLines="100" w:after="312" w:line="360" w:lineRule="auto"/>
<w:jc w:val="center"/>
<w:rPr>
<w:b/>
<w:bCs/>
</w:rPr>
</w:pPr>
<w:r w:rsidRPr="00F04D53">
<w:rPr>
<w:rFonts w:ascii="黑体"/>
<w:b/>
<w:bCs/>
<w:position w:val="50"/>
<w:sz w:val="44"/>
</w:rPr>
<w:t>${title}</w:t>
</w:r>
</w:p>
<#list lists as map>
<w:p w14:paraId="74DE0DF0" w14:textId="523506EA" w:rsidR="00365812" w:rsidRPr="00F04D53" w:rsidRDefault="00F04D53" w:rsidP="00F04D53">
<w:pPr>
<w:spacing w:line="360" w:lineRule="auto"/>
<w:ind w:left="560" w:hangingChars="200" w:hanging="560"/>
<w:jc w:val="left"/>
<w:rPr>
<w:b/>
<w:bCs/>
</w:rPr>
</w:pPr>
<w:r w:rsidRPr="00F04D53">
<w:rPr>
<w:rFonts w:ascii="华文宋体"/>
<w:b/>
<w:bCs/>
<w:position w:val="28"/>
<w:sz w:val="28"/>
</w:rPr>
<w:t>${map.contentTitle}</w:t>
</w:r>
</w:p>
<#list map.list as maps>
<w:p w14:paraId="2B9ECFC0" w14:textId="545C3C8A" w:rsidR="009755F5" w:rsidRPr="00414FB1" w:rsidRDefault="00414FB1" w:rsidP="00414FB1">
<w:pPr>
<w:pStyle w:val="a3"/>
<w:numPr>
<w:ilvl w:val="0"/>
<w:numId w:val="1"/>
</w:numPr>
<w:spacing w:line="360" w:lineRule="auto"/>
<w:ind w:firstLineChars="0"/>
<w:rPr>
<w:rFonts w:ascii="华文宋体" w:hint="eastAsia"/>
<w:position w:val="28"/>
<w:sz w:val="28"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:ascii="华文宋体" w:hint="eastAsia"/>
<w:position w:val="28"/>
<w:sz w:val="28"/>
</w:rPr>
<w:t>${maps.content}</w:t>
</w:r>
<w:r w:rsidR="00F04D53" w:rsidRPr="00414FB1">
<w:rPr>
<w:rFonts w:ascii="华文宋体"/>
<w:position w:val="28"/>
<w:sz w:val="28"/>
</w:rPr>
<w:t>(</w:t>
</w:r>
<w:r w:rsidR="00F04D53" w:rsidRPr="00414FB1">
<w:rPr>
<w:rFonts w:ascii="华文宋体"/>
<w:position w:val="28"/>
<w:sz w:val="28"/>
</w:rPr>
<w:t xml:space="preserve"> </w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:ascii="华文宋体" w:hint="eastAsia"/>
<w:position w:val="28"/>
<w:sz w:val="28"/>
</w:rPr>
<w:t>${maps.username}</w:t>
</w:r>
<w:r w:rsidR="00F04D53" w:rsidRPr="00414FB1">
<w:rPr>
<w:rFonts w:ascii="华文宋体"/>
<w:position w:val="28"/>
<w:sz w:val="28"/>
</w:rPr>
<w:t xml:space="preserve"> </w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:ascii="华文宋体" w:hint="eastAsia"/>
<w:position w:val="28"/>
<w:sz w:val="28"/>
</w:rPr>
<w:t>${maps.date}</w:t>
</w:r>
<w:r w:rsidR="00F04D53" w:rsidRPr="00414FB1">
<w:rPr>
<w:rFonts w:ascii="华文宋体"/>
<w:position w:val="28"/>
<w:sz w:val="28"/>
</w:rPr>
<w:t xml:space="preserve"> </w:t>
</w:r>
<w:r w:rsidR="00F04D53" w:rsidRPr="00414FB1">
<w:rPr>
<w:rFonts w:ascii="华文宋体"/>
<w:position w:val="28"/>
<w:sz w:val="28"/>
</w:rPr>
<w:t>)</w:t>
</w:r>
</w:p>
</#list>
</#list>
<w:sectPr w:rsidR="009755F5" w:rsidRPr="00414FB1">
<w:pgSz w:w="11906" w:h="16838"/>
<w:pgMar w:top="1440" w:right="1800" w:bottom="1440" w:left="1800" w:header="851" w:footer="992" w:gutter="0"/>
<w:cols w:space="425"/>
<w:docGrid w:type="lines" w:linePitch="312"/>
</w:sectPr>
</w:body>
</w:document>

将xml模板文件中需要替换的内容,使用占位符替代,需要循环输出的位置可以通过加上<#list lists as map></#list>包裹住相应的片段,嵌套循环同理(目前只测试了二层嵌套)

3.代码实现

package cn.gov.customs.hb.common.server.processopinion.service;

import cn.gov.customs.hb.common.utility.util.GenerateDocxUtil;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @author: Baymax
 * @Date: 2022年06月16日 20:52
 * @Description: 办理意见打印
 */
public class DealWithDownLoad {
    public void downLoad(HttpServletResponse response, HttpServletRequest request) throws Exception {
            //根据模板导出docx,创建文档标题层map
            Map<String, Object> dataMap = new HashMap<>();
            dataMap.put("title", "办理意见");
        	List<Map<String, Object>> list = new ArrayList<>();
        	for (int i = 0; i < 3; i++) {
                Map<String, Object> data = new HashMap<>();
            	data.put("contentTitle", "办理意见" + i);
            	List<Map<String, Object>> list2 = new ArrayList<>();
            	for (int j = 0; j < 5; j++) {
                	Map<String, Object> data1 = new HashMap<>();
                	data1.put("content", "这是一个办理意见" + i);
                	data1.put("date", "2022-2-2 10:10" + i);
                	data1.put("username", "大白" + i);
                	list2.add(data1);
            	}
            data.put("list", list2);
            list.add(data);
        }
            dataMap.put("lists", timeList);
            String fileName = "OpinionByTime";
            GenerateDocxUtil.freemarkerDocxTest(dataMap, fileName, response);
        }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值