乐趣区

关于word:如何使用java代码导出word

前言:

导出 word 的需要其实在日常工作中用到的中央还不少,于是想写一篇文章好好记录一下,在导出之前,须要理解一下对于浏览器如何解决 servlet 的后盾数据。具体能够理解一下 http 通信下载行为在 servlet 的实现。

== 导出的工具类代码来源于网络,如有侵权能够分割我删除文章 ==

集体应用 ==ftl== 作为 word 导出模板引擎,有很多模板引擎能够选,集体通过查阅材料发现 ftl 用的比拟多,所以抉择这一种

<!– more –>

码云地址:

文章牵扯代码比拟多,如果要看具操作能够查看我本人瞎弄的一个码云地址:

https://gitee.com/lazyTimes/i…

成果演示:

给了一个测试页面,长期写了一些脚本,能够作为参考(后续会贴 Html 代码进去)

点击提交, 导出内容, 导出 word 报告

导出之后,关上 word 内容为:

实现步骤 – 制作 word 模板

第一步 新建 word,制作成绩样板

将须要导出 word 的内容,先粘贴到一个新建的 word 文件外面

第二步 转存格局 -> xml

抉择文件“另存为”,将格局设置为 xml 格局

第三步 格式化文件

将文件放到 idea 或者反对格式化的软件外面,进行格式化,保留:

留神占位符要匹配

第四步:模板数据替换占位符

在 word 页面将须要导入数据的中央,替换占位符

须要留神内容解决的时候: ${filename} 有可能被切割为多个局部,咱们须要把多个切割局部,改为上面的款式

肯定记得所有的改变之后,马上关上 xml 格局的 word,确认是不是改崩了

下面的步骤实现,阐明有一个 word 模板做好了

第五步:制作 ftl 文件,word 模板成型

在我的项目外面新建一个 ftl 文件,同时须要在工具类中配置,同时把做好站位符操作的 xml 内容贴进去

代码实现 – 导出代码

  1. 工具类的配置如下:

WordGeneratorUtil.java

/**
     * 模板常量类配置
     */
public static final class FreemarkerTemplate {
    public static final String REPORT = "report";
    public static final String REC_RECOMMEND = "recRecommend";
    // 减少你的模板文件名称:

}

在动态的代码块外面,须要注入对应的模板配置

// 留神初始化要载入对应模板
allTemplates.put(FreemarkerTemplate.REPORT, configuration.getTemplate(FreemarkerTemplate.REPORT + ".ftl"));
allTemplates.put(FreemarkerTemplate.REC_RECOMMEND,configuration.getTemplate(FreemarkerTemplate.REC_RECOMMEND + ".ftl"));
  1. 在配置实现之后,导出的时候就能够找到对应的文件了
  2. 建设一个通用的导出办法:
/**
     * 创立 doc 文档
     * dataMap 数据,须要对应模板的占位符,否则会出错
     * @param dataMap 数据
     * @param wordName  word 报表的名称
     * @param freemarkerTemplateName  指定须要应用哪个 freemarker 模板
     * @return
     */
public static File createDoc(String freemarkerTemplateName, String wordName, Map<String, String> dataMap) {
    try {File f = new File(wordName);
        Template t = allTemplates.get(freemarkerTemplateName);
        // 这个中央不能应用 FileWriter 因为须要指定编码类型否则生成的 Word 文档会因为有无奈辨认的编码而无奈关上
        Writer w = new OutputStreamWriter(new FileOutputStream(f), StandardCharsets.UTF_8);
        t.process(dataMap, w);
        w.close();
        return f;
    } catch (Exception ex) {ex.printStackTrace();
        throw new RuntimeException("生成 word 文档失败");
    }
}

工具类残缺代码:

package com.zxd.interview.export;


import freemarker.template.Configuration;
import freemarker.template.Template;
import org.springframework.util.CollectionUtils;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>
 * 从网络上依据材料找到的一个工具类
 * 次要以 freemarker 为外围的模板生成 word 文档的工具类
 * 这里默认配置了固定门路
 * 须要依据门路取到对应模板
 * 申请参数须要设置对应的模板名称
 * @author 
 * @className: WordGeneratorUtils
 * @description: 文档生成工具类
 * </p>
 * version: V1.0.0
 */
public final class WordGeneratorUtil {
    private static Configuration configuration = null;
    private static Map<String, Template> allTemplates = null;
    private static final String TEMPLATE_URL = "/templates";

    /**
     * 模板常量类配置
     */
    public static final class FreemarkerTemplate {
        public static final String Test = "test";
        public static final String REPORT = "report";
        public static final String REC_RECOMMEND = "recRecommend";

    }

    static {configuration = new Configuration(Configuration.VERSION_2_3_28);
        configuration.setDefaultEncoding("utf-8");
        configuration.setClassForTemplateLoading(WordGeneratorUtil.class, TEMPLATE_URL);
        allTemplates = new HashMap(4);
        try {
            // 留神初始化要载入对应模板
            allTemplates.put(FreemarkerTemplate.Test, configuration.getTemplate(FreemarkerTemplate.Test + ".ftl"));
            allTemplates.put(FreemarkerTemplate.REPORT, configuration.getTemplate(FreemarkerTemplate.REPORT + ".ftl"));
            allTemplates.put(FreemarkerTemplate.REC_RECOMMEND, configuration.getTemplate(FreemarkerTemplate.REC_RECOMMEND + ".ftl"));
        } catch (IOException e) {e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    private WordGeneratorUtil() {throw new AssertionError();
    }

    /**
     * 创立 doc 文档
     * dataMap 数据,须要对应模板的占位符,否则会出错
     * @param dataMap 数据
     * @param wordName  word 报表的名称
     * @param freemarkerTemplateName  指定须要应用哪个 freemarker 模板
     * @return
     */
    public static File createDoc(String freemarkerTemplateName, String wordName, Map<String, String> dataMap) {
        try {File f = new File(wordName);
            Template t = allTemplates.get(freemarkerTemplateName);
            // 这个中央不能应用 FileWriter 因为须要指定编码类型否则生成的 Word 文档会因为有无奈辨认的编码而无奈关上
            Writer w = new OutputStreamWriter(new FileOutputStream(f), StandardCharsets.UTF_8);
            t.process(dataMap, w);
            w.close();
            return f;
        } catch (Exception ex) {ex.printStackTrace();
            throw new RuntimeException("生成 word 文档失败");
        }
    }


}

调用层:

  1. 在业务层,将须要导出的数据,依据占位符的 i 信息进行赋值,留神不能漏,否则导出之后的文件会打不开
@Override
public File exportQualityStep4Word(WordReportDTO exportWordRequest) {Map<String, String> datas = new HashMap(QualityConstants.HASH_MAP_INIT_VALUE);
    // 主题目
    datas.put("schoolName", exportWordRequest.getSchoolName());
    datas.put("title1", exportWordRequest.getBaseSituation());
    datas.put("title2", exportWordRequest.getLearningEnvRec());
    datas.put("title3", exportWordRequest.getLearningEnvPro());
    datas.put("title4", exportWordRequest.getDayLifeRec());
    datas.put("title5", exportWordRequest.getDayLifePro());
    datas.put("title6", exportWordRequest.getLearningActivityRec());
    datas.put("title7", exportWordRequest.getLearningActivityPro());
    datas.put("title8", exportWordRequest.getDevRecommend());

    datas.put("base64_1", exportWordRequest.getBase64_1());
    datas.put("base64_2", exportWordRequest.getBase64_2());
    datas.put("base64_3", exportWordRequest.getBase64_3());
    datas.put("base64_4", exportWordRequest.getBase64_4());
    datas.put("base64_5", exportWordRequest.getBase64_5());
    datas.put("base64_6", exportWordRequest.getBase64_6());


    // 导出
    return WordGeneratorUtil.createDoc(WordGeneratorUtil.FreemarkerTemplate.REPORT,
                                       exportWordRequest.getWordName(),
                                       datas);
}
  1. 上面是生成报表导出的基本操作,能够在用到的中央复制过来改变即可
/**
 * 生成报告的导出报表操作
 *
 * @param request           request
 * @param response          响应数据
 * @param exportWordRequest 导出 dto
 */
@PostMapping("/quality/exportword")
@ResponseBody
public void povertyExportWord(HttpServletRequest request, HttpServletResponse response,
                              WordReportDTO exportWordRequest) {File file = qualityReportService.exportQualityStep4Word(exportWordRequest);

    InputStream fin = null;
    OutputStream out = null;
    try {
        // 调用工具类 WordGeneratorUtils 的 createDoc 办法生成 Word 文档
        fin = new FileInputStream(file);

        response.setCharacterEncoding(QualityConstants.UTF_8);
        response.setContentType(QualityConstants.CONTENT_TYPE_WORD);
        // 设置浏览器以下载的形式解决该文件
        // 设置文件名编码解决文件名乱码问题
        // 取得申请头中的 User-Agent
        String filename = exportWordRequest.getWordName();
        String agent = request.getHeader(QualityConstants.USER_AGENT);
        String filenameEncoder = "";
        // 依据不同的浏览器进行不同的判断
        if (agent.contains(QualityConstants.MSIE)) {
            // IE 浏览器
            filenameEncoder = URLEncoder.encode(filename, QualityConstants.UTF_8);
            filenameEncoder = filenameEncoder.replace("+", " ");
        } else if (agent.contains(QualityConstants.FIREFOX)) {
            // 火狐浏览器
            BASE64Encoder base64Encoder = new BASE64Encoder();
            filenameEncoder = "=?utf-8?B?" + base64Encoder.encode(filename.getBytes(QualityConstants.UTF_8)) + "?=";
        } else {
            // 其它浏览器
            filenameEncoder = URLEncoder.encode(filename, QualityConstants.UTF_8);
        }
        response.setHeader(QualityConstants.ACCESS_CONTROL_ALLOW_ORIGIN, "*");// 所有域都能够跨
        response.setHeader(QualityConstants.CONTENT_TYPE, QualityConstants.CONTENT_TYPE_STEAM);// 二进制  流文件
        response.setHeader(QualityConstants.CONTENT_DISPOSITION, "attachment;filename=" + filenameEncoder + ".doc");// 下载及其文件名
        response.setHeader(QualityConstants.CONNECTION, QualityConstants.CLOSE);// 敞开申请头连贯
        // 设置文件在浏览器关上还是下载
        response.setContentType(QualityConstants.CONTENT_TYPE_DOWNLOAD);
        out = response.getOutputStream();
        byte[] buffer = new byte[QualityConstants.BYTE_512];
        int bytesToRead = QualityConstants.NUM_MINUS_1;
        // 通过循环将读入的 Word 文件的内容输入到浏览器中
        while ((bytesToRead = fin.read(buffer)) != QualityConstants.NUM_MINUS_1) {out.write(buffer, QualityConstants.NUM_ZERO, bytesToRead);
        }

    } catch (Exception e) {throw new RuntimeException(QualityConstants.FARIURE_EXPORT, e);
    } finally {
        try {if (fin != null) {fin.close();
            }
            if (out != null) {out.close();
            }
            if (file != null) {file.delete();
            }
        } catch (IOException e) {throw new RuntimeException(QualityConstants.FARIURE_EXPORT, e);
        }
    }

}

导出实体 dto

上面写了一个导出的实体 dto,实体对象能够本人定制:

package com.zxd.interview.dto;

/**
 * 测试应用的 dto,用于封装导出 word 的对象
 *
 * @author zhaoxudong
 * @version 1.0
 * @date 2020/11/7 23:37
 */
public class TestReportDTO {

    /**
     * 测试
     */
    private String test0;

    /**
     * 测试
     */
    private String test1;

    /**
     * 测试
     */
    private String test2;

    /**
     * 测试
     */
    private String test4;

    /**
     * 测试
     */
    private String test5;

    /**
     * 测试
     */
    private String test6;

    /**
     * 报告名称
     */
    private String wordName;

    public String getTest0() {return test0;}

    public void setTest0(String test0) {this.test0 = test0;}

    public String getTest1() {return test1;}

    public void setTest1(String test1) {this.test1 = test1;}

    public String getTest2() {return test2;}

    public void setTest2(String test2) {this.test2 = test2;}

    public String getTest4() {return test4;}

    public void setTest4(String test4) {this.test4 = test4;}

    public String getTest5() {return test5;}

    public void setTest5(String test5) {this.test5 = test5;}

    public String getTest6() {return test6;}

    public void setTest6(String test6) {this.test6 = test6;}

    public String getWordName() {return wordName;}

    public void setWordName(String wordName) {this.wordName = wordName;}


    @Override
    public String toString() {
        return "TestReportDTO{" +
                "test0='" + test0 + '\'' +
                ", test1='" + test1 + '\'' +
                ", test2='" + test2 + '\'' +
                ", test4='" + test4 + '\'' +
                ", test5='" + test5 + '\'' +
                ", test6='" + test6 + '\'' +
                '}';
    }
}

常量配置模块:

集体很不喜爱硬编码这货色,又丑又难看,所以很多货色会用不可变对象代替.

package com.zxd.interview.constant;

/**
 * 常量配置类
 *
 * @author zhouhui
 */
public class QualityConstants {

    /**
     * 品质检测 的督导事项 id
     */
    public static final int EVENTID = 12;

    /**
     * 数字 0
     */
    public static final int NUM_ZERO = 0;
    /**
     * 数字 1
     */
    public static final int NUM_ONE = 1;

    /**
     * 数字 2
     */
    public static final int NUM_TWO = 2;
    /**
     * 数字 -1
     */
    public static final int NUM_MINUS_1 = -1;
    /**
     * 字节大小 512
     */
    public static final int BYTE_512 = 512;
    /**
     * 500 谬误编码
     */
    public static final int CODE_500 = 500;
    /**
     * 500 谬误提示信息 - 状态非法
     */
    public static final String CODE_500_MSG_1 = "状态非法!";
    /**
     * 500 谬误提示信息 - 非督导用户不容许查看品质检测记录
     */
    public static final String CODE_500_MSG_2 = "非督导用户不容许查看品质检测记录!";
    /**
     * 500 谬误提示信息 - 这条品质监测曾经实现!无奈批改
     */
    public static final String CODE_500_MSG_3 = "这条品质监测曾经实现!无奈批改!";
    /**
     * 500 谬误提示信息 - 提交失败,资料上传不能为空
     */
    public static final String CODE_500_MSG_4 = "提交失败,资料上传不能为空";
    /**
     * 500 谬误提示信息 - 提交失败,请稍后重试或分割管理员
     */
    public static final String CODE_500_MSG_5 = "提交失败,请稍后重试或分割管理员!";
    /**
     * 500 谬误提示信息 - 提交失败,意见反馈不能为空
     */
    public static final String CODE_500_MSG_6 = "提交失败,意见反馈不能为空!";
    /**
     * 405 谬误编码
     */
    public static final int CODE_405 = 405;
    /**
     * 405 谬误提示信息 - 该信息只容许督导查看
     */
    public static final String CODE_405_MSG_1 = "该信息只容许督导查看!";
    /**
     * 200 胜利编码
     */
    public static final int CODE_200 = 200;
    /**
     * 200 胜利提示信息 - 该信息只容许督导查看
     */
    public static final String CODE_200_MSG_1 = "提交胜利!";

    /**
     * 谬误提示信息 - 尚未抉择记录
     */
    public static final String DELETE_FAIRURE_MSG = "删除失败,尚未抉择记录!";
    /**
     * 谬误提示信息 - 尚未抉择记录
     */
    public static final String NO_RECORD_SELECTED = "尚未抉择记录!";
    /**
     * 字符编码 utf-8
     */
    public static final String UTF_8 = "utf-8";
    /**
     * 默认 pid
     */
    public static final int PID = 0;
    /**
     * 默认层级
     */
    public static final int DEFUALT_LAYER = 1;

    /**
     * 不适当最低得分
     */
    public static final Integer MIN_SCORE = 1;
    /**
     * 优良最高得分
     */
    public static final Integer MAX_SCORE = 7;

    /**
     * map 的 hash 初始值
     */
    public static final int HASH_MAP_INIT_VALUE = 32;

    /**
     * 全园平均分
     */
    public final static String WHOLE_AVERAGE = "全园平均分";
    /**
     * 查问失败
     */
    public final static String QUERY_FAIRURE = "查问失败";
    /**
     * 操作胜利
     */
    public final static String SUCCESS_MSG = "操作胜利!";
    /**
     * 操作失败
     */
    public final static String FARIURE_MSG = "操作失败!";
    /**
     * 导出失败
     */
    public final static String FARIURE_EXPORT = "导出失败!";

    /**
     * 申请头 - 文档
     */
    public final static String CONTENT_TYPE_WORD = "application/msword";
    /**
     * 申请头 - 下载
     */
    public final static String CONTENT_TYPE_DOWNLOAD = "application/x-download";
    /**
     * 申请头 - 二进制文件
     */
    public final static String CONTENT_TYPE_STEAM = "application/octet-stream;charset=UTF-8";
    /**
     * 申请头
     */
    public final static String USER_AGENT = "User-Agent";
    /**
     * 申请头
     */
    public final static String CONTENT_TYPE = "Content-Type";
    /**
     * 连贯
     */
    public final static String CONNECTION = "Connection";
    /**
     * 敞开连贯
     */
    public final static String CLOSE = "close";
    /**
     * 连贯
     */
    public final static String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
    /**
     * 连贯
     */
    public final static String CONTENT_DISPOSITION = "Content-Disposition";

    /**
     * 浏览器 - ie
     */
    public final static String MSIE = "MSIE";
    /**
     * 浏览器 - Firefox
     */
    public final static String FIREFOX = "Firefox";

    /**
     * 填写报告的 step
     */
    public final static String MODULE_STEP3_REPORT = "qualityreport";

    /**
     * 督导下园核实的资料
     */
    public final static String MODULE_STEP1_MATERIAL = "qualitymetrail";

    /**
     * 数字 3
     */
    public final static int NUM_3 = 3;
    /**
     * 数字 4
     */
    public final static int NUM_4 = 4;

    /**
     * 数字 5
     */
    public final static int NUM_5 = 5;
    /**
     * 数字 6
     */
    public final static int NUM_6 = 6;
    /**
     * 数字 7
     */
    public final static int NUM_7 = 7;
    /**
     * 数字 8
     */
    public final static int NUM_8 = 8;
    /**
     * 数字 9
     */
    public final static int NUM_9 = 9;
    /**
     * 数字 10
     */
    public final static int NUM_10 = 10;
    /**
     * 数字 11
     */
    public final static int NUM_11 = 11;

    /**
     * 数字 12
     */
    public final static int NUM_12 = 12;
    /**
     * 数字 13
     */
    public final static int NUM_13 = 13;
    /**
     * 数字 14
     */
    public final static int NUM_14 = 14;
    /**
     * 数字 15
     */
    public final static int NUM_15 = 15;
    /**
     * 数字 16
     */
    public final static int NUM_16 = 16;
    /**
     * 数字 17
     */
    public final static int NUM_17 = 17;
    /**
     * 数字 18
     */
    public final static int NUM_18 = 18;
    /**
     * 数字 19
     */
    public final static int NUM_19 = 19;
    /**
     * 数字 20
     */
    public final static int NUM_20 = 20;

    /**
     * 格式化数字
     */
    public final static String DECIMAL_Format = "######.00";

}

页面层解决:

前端减少一个 form 提交,应用 form 提交表单数据,实现 word 导出性能:

(留神应用的模板引擎是 thymeleaf)

html 代码:
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<!-- 最新版本的 Bootstrap 外围 CSS 文件 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

<head>
    <meta charset="UTF-8">
    <title>Title</title>

</head>
<body>
<div class="container">
    <div id="vue1" class="row">
<!--        <form @submit.prevent="submit">-->
<!--            <div>-->
<!--                导出 word 名称:<input class="form-control" type="text" v-model="student.wordName">-->
<!--            </div>-->

<!--            <div>-->
<!--                test0:<input type="text" class="form-control" v-model="student.test0">-->
<!--                test1:<input type="text" class="form-control" v-model="student.test1">-->
<!--                test2:<input type="text" class="form-control" v-model="student.test2">-->
<!--                test4:<input type="text" class="form-control" v-model="student.test4">-->
<!--                test5:<input type="text" class="form-control" v-model="student.test5">-->
<!--                test6:<input type="text" class="form-control" v-model="student.test6">-->
<!--            </div>-->

<!--            <input type="submit" class="btn btn-danger" value="提交">-->
<!--        </form>-->


        <form method="post" action="/quality/exportword">
            <div>
                导出 word 名称:<input class="form-control" type="text" name="wordName">
            </div>

            <div>
                test0:<input type="text" class="form-control" name="test0">
                test1:<input type="text" class="form-control" name="test1">
                test2:<input type="text" class="form-control" name="test2">
                test4:<input type="text" class="form-control" name="test4">
                test5:<input type="text" class="form-control" name="test5">
                test6:<input type="text" class="form-control" name="test6">
            </div>

            <input type="submit" class="btn btn-danger" value="提交">
        </form>

    </div>
</div>

</body>
<!-- 开发环境版本,蕴含了有帮忙的命令行正告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.staticfile.org/vue-resource/1.5.1/vue-resource.min.js"></script>
<script th:src="@{/js/exportword.js}"></script>
</html>
js 代码

应用 js 代码解决 form 表单提交,应用了 jquery 进行导出,其实始终不太懂前端怎么导出后盾产生的二进制流,做法挺多,下次写一篇文章好好汇总一下几种用法。

var v1 = new Vue({
    el: '#vue1',
    data: {
        counter: 0,
        student: {
            test0:'',
            test1:'',
            test2:'',
            test3:'',
            test4:'',
            test5:'',
            test6:'',
            wordName: '',

        }
    },
    methods: {test: function () {console.log(this.counter);
        },
        submit() {console.log(this.student);
            var url = '/quality/exportword';
            var formData = JSON.stringify(this.student); // this 指向这个 VUE 实例 data 默认绑定在实例下的。所以间接 this.student 就是要提交的数据
            this.$http.post(url, formData).then(function (data) {console.log(data);
                let blob = new Blob([data.data],{type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document;charset=utf-8'});
                let objectUrl = URL.createObjectURL(blob);
                window.location.href = objectUrl;
            }).catch(function () {console.log('test');
            });
        }
    }
})

结尾:

集体程度个别,心愿通过这篇文章能够帮到读者,有谬误的中央欢送指导,看到会及时改成,谢谢!

前段时间忙于面试找到新的中央工作了,等工作安宁之后,会持续深耕博客和技术栈。

退出移动版