导出word文档生成docx格式,包含freemarker遍历多张图片

为了导出docx格式看了等多文档,最后做个总结依赖包用到dom4j和freemarker,最为方便。

<!-- https://mvnrepository.com/artifact/freemarker/freemarker -->
		<dependency>
			<groupId>freemarker</groupId>
			<artifactId>freemarker</artifactId>
			<version>2.3.9</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
		<dependency>
			<groupId>dom4j</groupId>
			<artifactId>dom4j</artifactId>
			<version>1.6.1</version>
		</dependency>

0.主要目的:将这样一个页面导出为word文档为doc格式,包含一些文本和循环遍历出来的echarts图表。

 1.新建一个word文档(docx格式或doc),生成模板内容,例如下面这种。

整体思路

-保存后,复制出来一份,

-修改后缀名为zip。

-解压到一个文件夹中。

-打开文件夹看到如下目录


-获取word里的document.xml文档以及_rels文件夹下的document.xml.rels文档
-把内容填充到document.xml里,以及图片配置信息填充至document.xml.rels文档里
-在输入docx文档的时候把填充过内容的的 document.xml、document.xml.rels用流的方式写入zip(详见下面代码)。
-把图片写入zip文件下word/media文件夹中
-输出docx文档(因为word文档本身就是ZIP格式实现的)

2. 目录结构如下:主要文件由上一步拷贝过来的

  • document.xml里存放主要数据
  • media存放图片信息
  • _rels里存放配置信息

document.xml中存放图片的模板主要内容

 

3.document.xml修改模板内容加上freemarker遍历map集合,填入数据

 

 4.document.xml.rels修改模板引用内容

注意:这里图片配置信息是根据 rId来获取的。docx模板总的${mdl.rId}就是rId的具体值。
为了避免重复,我的图片rId从12开始(在我没有修改之前,里面最大的rId是rId12)。

5.header1.xml页眉 (可不要)

6.[Content_Types].xml文件模板

7.前端页面关键就是请求,根据自己需求构建json字符串格式,map数据传到后台

//导出word docx
    function download_reportNew() {
//此处遍历页面得数据,放到json中,可根据自己需要省略-----------------start
        console.log("new wordx");
        var title= '${reportData.title}';
        var reportUnit= '${reportData.reportUnit!}';
        var reportTypeDate= '${reportData.reportTypeDate!}';
        var json;
        var jsonHead = {"title": title, "reportUnit": reportUnit,"reportTypeDate":reportTypeDate};
//此处用到了freemarker的模板遍历数据
        <#list reportData.reportModels as model>
        var modelTitle_${model ? index}= '${model.title!}';
        var modelDataSource_${model ? index}= '${model.dataSource!}';
        var modelShowContent_${model ? index}= '${model.showContent!}';
        var model_pic_${model ? index} = null ;
        var pic_${model ? index} = null ;
        if (model_${model ? index}.option != null) {
            pic_${model ? index}=model_${model ? index}.chart.getConnectedDataURL();
            model_pic_${model ? index}=pic_${model ? index}.substr(22,pic_${model ? index}.length);
        }
        var jsonBody = {"model_${model ? index}":{
                "modelTitle": modelTitle_${model ? index},
                "modelDataSource": modelDataSource_${model ? index},
                "modelShowContent":modelShowContent_${model ? index},
                "model_pic":model_pic_${model ? index}
            }};
//最后的json对象
        json=$.extend(true,jsonHead,jsonBody);
        </#list>
//此处遍历页面得数据,放到json中,可根据自己需要省略-----------------end
        $.ajax({
//第一次请求生成doc临时文件
            url: '${base}/report/reportView/reportExportNew.do',
            method: 'POST',
            contentType: 'application/json;charset=utf-8',
            data: JSON.stringify(json),
            success: function (data) {
                if (data.status == 0) {
//第二次请求读取文件写入response输出流,实现下载。
                    window.location.href =  '${base}/report/reportView/reportExportLast.do'+ "?filepath=" + data.retinfo ;
                } else {
                    alert("下载word失败!");
                }
            },
            error: function (data) {
                alert('文件下载失败' + data);
            }
        })
    }

8.第一次请求生成doc临时文件:word导出为doc格式的后台controller类

/**
     * @param
     * @Description 报表导出
     * @Date 2019/11/20 11:53
     * @Param map 填入模板的数据
     * @Author 
     */
    @PostMapping("/reportExportNew")
    public void reportExportNew(HttpServletRequest request, HttpServletResponse response, @RequestBody Map<String,
            Object> map) throws IOException {
        WebResult res = new WebResult();
        try {
            String lastFilePath = reportViewService.createWordDocx(map);
            String lastPath = lastFilePath.replace(SEPARATOR,"~");
            String text=filastPath;
//生成的文件名放到response中返回(此处根据自己需要可直接返回text)  ---------start
        PrintWriter out = null;
        try {
            response.setContentType("application/json;charset=UTF-8");
            out = response.getWriter();
            out.write(text);
        } catch (IOException var9) {
            LOGGER.error(var9.getMessage(), var9);
        } finally {
            if (out != null) {
                out.print("");
                out.close();
            }
        }
//生成的文件名放到response中返回(此处根据自己需要可直接返回text)  ---------end
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

9.后台构建map填入模板需要的数据createWordDocx(map)方法

/**
     * 创建docx 返回临时路径
     * @param map
     * @return
     * @throws IOException
     */
    public String createWordDocx(Map<String, Object> map) throws IOException{
        /**
         * @param dataMap               参数数据
         * @param docxTemplateFile      docx模主板名称
         * @param xmlDocument           docx中document.xml模板文件  用来存在word文档的主要数据信息
         * @param xmlDocumentXmlRels    docx中document.xml.rels 模板文件  用来存在word文档的主要数据配置 包括图片的指向
         * @param xmlContentTypes       docx中 [Content_Types].xml 模板文件 用来配置 docx文档中所插入图片的类型 如 png、jpeg、jpg等
         * @param xmlHeader             docx中 header1.xml 模板文件 用来配置docx文档的页眉文件
         * @param templatePath          模板存放路径 如 /templates/
         * @param outputFileTempPath    所生成的docx文件的临时路径文件夹 如果 temp/20180914051811/
         * @param outputFileName        所生成的docx文件名称  如  xxx.docx 或  xxx.doc
         * */
        String timeStr = LocalDateUtils.getCurrentTime_yyyyMMddHHmmssSSS();
        String docxTemplateFile = "docxTemplates.docx";
        String xmlDocument = "document.xml";
        String xmlDocumentXmlRels = "document.xml.rels";
        String xmlContentTypes = "[Content_Types].xml";
        //可以用来修改页眉的一些信息
        String xmlHeader = "header1.xml";
        String templatePath = SEPARATOR + "template" + SEPARATOR;
        String outputFileTempPath = templatePath+"temp" + SEPARATOR + timeStr + SEPARATOR;
        String outputFileName = timeStr + "."+SUFFIX_DOCX;
        String classPath=ReportViewServiceImpl.class.getResource("/").getPath().toString();
        LOGGER.info("classPath:{}",classPath);
        LOGGER.info("templatePath:{}",templatePath);
        LOGGER.info("outputFileTempPath:{}",outputFileTempPath);
        LOGGER.info("outputFileName:{}",outputFileName);
        //填充整体数据
        Map<String, Object> dataMap = new HashMap<>(16);
        //模块内容列表
        List<Map<String, Object>> modelList = new ArrayList<>(16);
        //单个模块
        Map<String, Object> model;
        //        页眉
        dataMap.put("ymdhis", LocalDateUtils.getCurrentTime_yyyyMMddHHmmss());
        //      图片类型
        List<String> modelTypes = new ArrayList<>();
        modelTypes.add("png");
        dataMap.put("mdlTypes", modelTypes);
        //取空白图片Base64码
        String url = classPath+"template/blank.png";
        String blankEncode = getImageStr(url);
        //        文档标题
        dataMap.put("title", map.get("title"));
        dataMap.put("reportUnit", map.get("reportUnit"));
        dataMap.put("reportTypeDate", map.get("reportTypeDate"));
        //模块数量
        int modelNum = map.size() - dataMap.size()+2;
        for (int i = 0; i < modelNum; i++) {
            model = (Map<String, Object>) map.get("model_" + i);
            if (model.get("model_pic") == null) {
                //64位编码格式改成path和name
                model.put("model_pic", blankEncode);
            }
            //每个文件路径
            String fileName = "pic"+i+".png";
            String filePath = classPath+outputFileTempPath+fileName;
            model.put("path",filePath);
            model.put("name",fileName);
            modelList.add(model);
        }
        //批量生成文件
        for (Map<String, Object> mod:modelList) {
            baseToFile(mod);
        }
        dataMap.put("modelList", modelList);
        try {
            String lastFilePath = WordUtils.createDocx(dataMap, docxTemplateFile, xmlDocument, xmlDocumentXmlRels, xmlContentTypes,
                    xmlHeader, templatePath, outputFileTempPath, outputFileName);
            return lastFilePath;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

10.wordUtiles类的createDocx

import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

import java.io.*;
import java.net.URL;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;


/**
 * @Description docx、doc文档生成工具类  (改变后缀名即可)
 * 在使用制作模板的过程中如果模板中有图片那就保留图片,注意[Content_Types].xml和document.xml.rels文档
 * 如果模板中没有图片 则不需要设置[Content_Types].xml和document.xml.rels
 * 由于word模板的个性化 所以 每次做模板都要重新覆盖原来的模板
 *  @Author 
 */
public class WordUtils {

    private final static String SEPARATOR = File.separator;

    /**
     * @param dataMap            参数数据
     * @param docxTemplateFile   docx模主板名称
     * @param xmlDocument        docx中document.xml模板文件  用来存在word文档的主要数据信息
     * @param xmlDocumentXmlRels docx中document.xml.rels 模板文件  用来存在word文档的主要数据配置 包括图片的指向
     * @param xmlContentTypes    docx中 [Content_Types].xml 模板文件 用来配置 docx文档中所插入图片的类型 如 png、jpeg、jpg等
     * @param xmlHeader          docx中 header1.xml 模板文件 用来配置docx文档的页眉文件
     * @param templatePath       模板存放路径 如 /templates/
     * @param outputFileTempPath 所生成的docx文件的临时路径文件夹 如果 temp/20180914051811/
     * @param outputFileName     所生成的docx文件名称  如  xxx.docx  或  xxx.doc
     */
    public static String createDocx(Map dataMap, String docxTemplateFile, String xmlDocument, String xmlDocumentXmlRels,
                                  String xmlContentTypes, String xmlHeader, String templatePath,
                                  String outputFileTempPath, String outputFileName) throws Exception {

        URL basePath = WordUtils.class.getClassLoader().getResource("");
        String realTemplatePath = basePath.getPath() + templatePath;
        //临时文件产出的路径
        String outputPath = basePath.getPath() + outputFileTempPath;
        String lastFilePath = outputFileTempPath+outputFileName;
        List<String> delFileList = new ArrayList<>();
        try {


            //获取 document.xml.rels 输入流
            String xmlDocumentXmlRelsComment = FreeMarkUtils.getFreemarkerContent(dataMap, xmlDocumentXmlRels, templatePath);
            ByteArrayInputStream documentXmlRelsInput = new ByteArrayInputStream(xmlDocumentXmlRelsComment.getBytes());

            //获取 header1.xml 输入流
            ByteArrayInputStream headerInput = FreeMarkUtils.getFreemarkerContentInputStream(dataMap, xmlHeader, templatePath);

            //获取 [Content_Types].xml 输入流
            ByteArrayInputStream contentTypesInput = FreeMarkUtils.getFreemarkerContentInputStream(dataMap, xmlContentTypes, templatePath);

            //读取 document.xml.rels  文件 并获取rId 与 图片的关系 (如果没有图片 此文件不用编辑直接读取就行了)
            Document document = DocumentHelper.parseText(xmlDocumentXmlRelsComment);
            // 获取根节点
            Element rootElt = document.getRootElement();
            // 获取根节点下的子节点head
            Iterator iter = rootElt.elementIterator();
            List<Map<String, String>> picList = (List<Map<String, String>>) dataMap.get("modelList");

            // 遍历Relationships节点
            while (iter.hasNext()) {
                Element recordEle = (Element) iter.next();
                String id = recordEle.attribute("Id").getData().toString();
                String target = recordEle.attribute("Target").getData().toString();
                if (target.indexOf("media") == 0) {
                    for (Map<String, String> picMap : picList) {
                        if (target.endsWith(picMap.get("name"))) {
                            picMap.put("rId", id);
                        }
                    }
                }
            }
            //覆盖原来的picList;
            dataMap.put("modelList", picList);

            //获取 document.xml 输入流
            ByteArrayInputStream documentInput = FreeMarkUtils.getFreemarkerContentInputStream(dataMap, xmlDocument, templatePath);

            File docxFile = new File(realTemplatePath + SEPARATOR + docxTemplateFile);
            if (!docxFile.exists()) {
                docxFile.createNewFile();
            }

            ZipFile zipFile = new ZipFile(docxFile);
            Enumeration<? extends ZipEntry> zipEntrys = zipFile.entries();
            File tempPath = new File(outputPath);
            //如果输出目标文件夹不存在,则创建
            if (!tempPath.exists()) {
                tempPath.mkdirs();
            }
            ZipOutputStream zipout = new ZipOutputStream(new FileOutputStream(outputPath + outputFileName));

            //覆盖文档
            int len = -1;
            byte[] buffer = new byte[1024];
            while (zipEntrys.hasMoreElements()) {
                ZipEntry next = zipEntrys.nextElement();
                InputStream is = zipFile.getInputStream(next);
                if (next.toString().indexOf("media") < 0) {
                    // 把输入流的文件传到输出流中 如果是word/document.xml由我们输入
                    zipout.putNextEntry(new ZipEntry(next.getName()));
                    //写入图片配置类型
                    if ("[Content_Types].xml".equals(next.getName())) {
                        if (contentTypesInput != null) {
                            while ((len = contentTypesInput.read(buffer)) != -1) {
                                zipout.write(buffer, 0, len);
                            }
                            contentTypesInput.close();
                        }
                    } else if (next.getName().indexOf("document.xml.rels") > 0) {
                        //写入填充数据后的主数据配置信息
                        if (documentXmlRelsInput != null) {
                            while ((len = documentXmlRelsInput.read(buffer)) != -1) {
                                zipout.write(buffer, 0, len);
                            }
                            documentXmlRelsInput.close();
                        }
                    } else if ("word/document.xml".equals(next.getName())) {
                        //写入填充数据后的主数据信息
                        if (documentInput != null) {
                            while ((len = documentInput.read(buffer)) != -1) {
                                zipout.write(buffer, 0, len);
                            }
                            documentInput.close();
                        }
                    } else if ("word/header1.xml".equals(next.getName())) {
                        //写入填充数据后的页眉信息
                        if (headerInput != null) {
                            while ((len = headerInput.read(buffer)) != -1) {
                                zipout.write(buffer, 0, len);
                            }
                            headerInput.close();
                        }
                    } else {
                        while ((len = is.read(buffer)) != -1) {
                            zipout.write(buffer, 0, len);
                        }
                        is.close();
                    }
                }
            }
            //覆盖文档
            //写入新图片
            len = -1;
            if (picList != null && !picList.isEmpty()) {
                for (Map<String, String> pic : picList) {
                    ZipEntry next = new ZipEntry("word" + SEPARATOR + "media" + SEPARATOR + pic.get("name"));
                    zipout.putNextEntry(new ZipEntry(next.toString()));
                    InputStream in = new FileInputStream(pic.get("path"));
                    while ((len = in.read(buffer)) != -1) {
                        zipout.write(buffer, 0, len);
                    }
                    in.close();
                }
            }
            zipout.close();
            return lastFilePath;
        } catch (Exception e) {
            e.printStackTrace();
            throw new Exception("生成docx文件失败!");
        }
    }

    /**
     * 递归删除文件夹
     *
     * @param dir
     */
    public static void delFiles(String dir) {
        try {
            File file = new File(dir);
            if(!file.exists()){
                return;
            }
            if(file.isFile() || file.list()==null) {
                file.delete();
                System.out.println("删除了"+file.getName());
            }else {
                File[] files = file.listFiles();
                for(File a:files) {
                    a.delete();
                }
                file.delete();
                System.out.println("删除了"+file.getName());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

11.第二次请求读取文件写入response输出流,实现下载。:将文件输出到response中,浏览器实现下载

/**
     * @param
     * @Description 报表导出
     * @Date 2019/11/13 11:53
     * @Author 
     */
    @RequestMapping("/reportExportLast")
    public void reportExportLast(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String templateName = request.getParameter("filepath");
        if (templateName.isEmpty()) {
            templateName = "report" + System.currentTimeMillis();
        }
        String classPath=ReportViewAction.class.getResource("/").getPath().toString();
        String fileName = templateName.replace("~",SEPARATOR);
        String filePath = classPath+fileName;
        String name = "";
        //文件后缀名
        String fileExt = fileName.substring(fileName.lastIndexOf(".")+1);
        File file = new File(filePath);
        try (InputStream inputStream = new FileInputStream(file);
             ServletOutputStream out = response.getOutputStream()) {
            if (SUFFIX_DOCX.equals(fileExt)){
                name = new String("大数据报告.docx".getBytes("UTF-8"),"UTF-8");
                response.setContentType("application/msword;charset=UTF-8");
            }else if (SUFFIX_PDF.equals(fileExt)){
                name = new String("大数据报告.pdf".getBytes("UTF-8"),"UTF-8");
                response.setContentType("application/pdf;charset=UTF-8");
            }
            name = URLEncoder.encode(name,"UTF-8");
            response.setHeader("Content-Disposition", "attachment;filename=" + name);
            byte[] buffer = new byte[1024];
            int bytesToRead;
            while ((bytesToRead = inputStream.read(buffer)) != -1) {
                out.write(buffer, 0, bytesToRead);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //删除临时文件
            String outputPath = filePath.substring(0,filePath.lastIndexOf(SEPARATOR)+1);
            WordUtils.delFiles(outputPath);
        }
    }

具体代码

https://gitee.com/zc0709/JavaUtilsProject

主要工具类

https://gitee.com/zc0709/JavaUtilsProject/blob/master/src/main/java/com/sl/utils/office/word/WordUtils.java

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java导出Word文档可以利用Apache POI和Freemarker模板引擎来实现。 Apache POI是一个用于操作Microsoft Office文档的Java API。它提供了对Word文档的读取、创建和修改的功能。通过使用Apache POI,可以轻松地在Java代码中创建一个空的Word文档,并添加文本、表格、图片等内容。同时,还可以设置文档的样式、格式和布局等,以满足不同的需求。 而Freemarker模板引擎则是用于生成动态文本输出的工具。它基于模板和数据模型,可以将数据动态填充到指定的Word模板中,生成最终的Word文档。使用Freemarker模板引擎可以使文档的生成更加灵活和可扩展,根据不同的数据模型生成不同的文档内容,提高了代码的可维护性和重用性。 要实现导出Word文档的功能,首先需要引入Apache POI和Freemarker的相关依赖库。接下来,创建一个空的Word文档,使用Apache POI的API对文档进行操作,例如添加标题、段落、表格等内容,以及设置样式和格式。然后,结合Freemarker模板引擎,根据自定义的数据模型填充数据到Word模板中,生成最终的Word文档。 通过以上的方法,利用Apache POI和Freemarker模板引擎可以轻松地实现Java导出Word文档的功能。这种方式方便、灵活,适用于需要动态生成Word文档的各种场景,例如报告生成、合同生成等。同时,这两个工具库都有良好的文档和丰富的示例代码,方便开发者进行学习和使用。 ### 回答2: Java 导出 Word 文档利用 Apache POI 和 FreeMarker 模板引擎的基本步骤如下: 1. 首先,确保你的项目已经导入了 Apache POI 和 FreeMarker 的相关依赖。 2. 创建一个 Word 文档模板,可以使用 Microsoft Office 软件创建一个空白文档,并将需要的样式和占位符添加到模板中。占位符可以使用自定义的标记,例如 `${name}`。 3. 在 Java 代码中,使用 Apache POI 创建一个 Word 文档对象 `XWPFDocument`。例如:`XWPFDocument document = new XWPFDocument();`。 4. 使用 FreeMarker 模板引擎加载并解析 Word 文档模板。创建一个 `Configuration` 对象,并设置模板文件的路径或类路径。例如:`Configuration configuration = new Configuration(Configuration.VERSION_2_3_23);`。 5. 根据模板文件路径或类路径加载模板文件。例如:`Template template = configuration.getTemplate("template.docx");`。 6. 创建一个数据模型,将数据填充到模板中。可以使用 `Map` 或自定义的 Java 对象作为数据模型。例如:`Map<String, String> data = new HashMap<>();`,然后将需要填充的数据放入 `data` 中。 7. 使用 FreeMarker 模板引擎的 `process` 方法将数据模型与模板进行合并,生成最终的 Word 文档内容。例如:`StringWriter writer = new StringWriter();`,`template.process(data, writer);`。 8. 将生成的 Word 文档内容写入到 Apache POI 的 `XWPFDocument` 对象中。可以使用 `OutputStream` 将内容写入到文档对象中。例如:`OutputStream outputStream = new FileOutputStream("output.docx");`,`writer.flush();`,`document.write(outputStream);`,`outputStream.close();`。 9. 最后,记得在使用完毕后关闭相关资源,例如关闭输出流、释放内存等。 以上就是利用 Apache POI 和 FreeMarker 模板引擎导出 Word 文档的基本步骤,通过动态填充数据,可以生成灵活且具有个性化内容的 Word 文档。 ### 回答3: Java导出Word文档可以使用Apache POI和Freemarker模板引擎相结合的方法来实现。Apache POI是一个用于处理Microsoft Office文档的Java库,而Freemarker是一个基于模板的文本生成引擎。 首先,我们需要在项目中引入Apache POI和Freemarker的相关依赖。 然后,我们需要创建一个Word文档模板文件,模板文件中可以包含一些占位符,用于动态添加数据。比如,我们可以在模板文件中添加一个{{name}}的占位符。 接下来,在Java中,我们可以使用Freemarker来解析模板文件。首先,我们需要创建一个Configuration对象,并指定模板文件的路径。然后,我们可以使用Template类的getTemplate方法来获取模板对象。 接着,我们可以创建一个Map对象,将需要动态添加的数据放入其中。比如,我们可以将姓名(name)放入Map中。 然后,我们可以调用Template类的process方法来解析模板并将数据填充到占位符中。我们可以将解析后的结果保存在一个字符串中。 最后,我们可以使用Apache POI来创建一个新的Word文档。我们可以创建一个XWPFDocument对象,并使用其createParagraph方法来创建段落。然后,我们可以使用XWPFRun对象的setText方法将之前解析后的结果添加到段落中。 最后,我们可以将生成Word文档保存到指定路径。我们可以使用XWPFDocument对象的write方法将文档保存为文件。 综上所述,通过使用Apache POI和Freemarker模板引擎,我们可以方便地导出Word文档。我们只需要创建一个模板文件,使用Freemarker来解析模板并将数据填充到占位符中,然后使用Apache POI来创建新的文档并保存即可。这种方法可以加快开发速度,同时也使得代码结构更加清晰易读。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值