添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

有一个word文档,内容如下图所示,现在需要定义一个模板,通过后台查询获取我们需要的数据,然后自动填充到我们的word之中,生成需要的文档供我们下载。

使用freemaker生成word文档并下载

一:导入所需要的maven依赖

<!--word模板工具包-->
<dependency>
	<groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.28</version>
</dependency>
<!--打包压缩工具包-->
<dependency>
	<groupId>org.apache.ant</groupId>
    <artifactId>ant</artifactId>
    <version>1.10.5</version>
</dependency>

二:根据word文档生成我们需要的ftl模板文件

打开word文档-->另存为--->'将word文档保存类型选择xml类型'-->保存就得到一个名为***.xml的模板文件 将生成的**.xml文件重命名,将后缀的“xml”改为“ftl”的格式: 然后打开这个后缀为ftl的word模板文件,将我们需要通过后台生成的内容替换成${***}的形式

解释一下:${***}是ftl模板的占位符,我们可以通过这个占位符来进行对模板内容进行赋值,生成我们想要的word文档。

这里我打开ftl模板用的是工具是Edit with Notepad++(可以用其他的,文件编辑器也可以)。 搜索我们需要替换的内容信息:这里我将“我的姓名”改为占位符的形式 下面按照这种方式挨个将我们需要修改的内容全部替换掉。 注意: 占位符中${studentname}的studentname是自己命名的名字,自己按需要起名。 将修改完成的模板保存起来备用。

三:将word需要的数据存入一个map中

		Map<String, Object> dataMap = new HashMap<String, Object>();
        String studentName = "张三";
        dataMap.put("studentname", studentName);          // 姓名
        dataMap.put("gender", "男");                    // 性别
        dataMap.put("hometown", "江苏省-苏州市");         // 籍贯: **省-**市
        dataMap.put("birthday", "1996-04-05");          // 出生年月
        dataMap.put("classname", "三年级一班");          // 班级
        dataMap.put("subjectname", "英语");          // 学科
        dataMap.put("fraction", "98");          // 分数
        dataMap.put("rank", "3");          // 排名
        String phoneImage = downPicture("C:/Users/wangxk/Pictures/question.jpg");
        dataMap.put("imagephone", phoneImage);          // 照片

这里我们用到了一个方法downPicture(""),该方法时将指令路径下的图片处理成base64的格式,然后返回处理后的字符串:

* 图片转码处理 public String downPicture(String picture) { InputStream in = null; byte[] data = null; //读取图片字节数组 try { String fileName = picture; // 图片的存储路径 in = new FileInputStream(fileName); data = new byte[in.available()]; in.read(data); in.close(); } catch (Exception e) { e.printStackTrace(); BASE64Encoder encoder = new BASE64Encoder(); // String address = "data:image/jpeg;base64," + encoder.encode(data); // 返回Base64编码过的字节数组字符串 String address = encoder.encode(data); // 返回Base64编码过的字节数组字符串 return address;

注意:一般图片转换成base64格式时,有一个特定的开头:data:image/jpeg;base64,,如果我们需要用base64还原图片时,需要将格式头加上

四:编写文档生成工具类:FreeMarkerFileUtils

package com.blog.web.controller.tool;
import com.blog.framework.config.properties.BlogConfig;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.apache.commons.io.FileUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import java.io.*;
import java.util.Map;
 * word文档模板支持工具
 * @author Aaron Wang
 * @data 2020/7/14
public class FreeMarkerFileUtils {
    // 文档数据填充
    public static String documentDataFilling(Map<String,Object> dataMap, String fileNmae){
        try{
            // 模板
            //Configuration 用于读取ftl文件
            Configuration configuration = new Configuration();
            //设置编码
            configuration.setDefaultEncoding("UTF-8");
             * 以下是两种指定ftl文件所在目录路径的方式,注意这两种方式都是
             * 指定ftl文件所在目录的路径,而不是ftl文件的路径
            //指定路径的第一种方式(根据某个类的相对路径指定)
            // configuration.setClassForTemplateLoading(PrjWorkerController.class,"/");
            //指定路径的第二种方式,我的路径是C:/a.ftl
            // 设置模板文件的文件夹路径(注意:这里是模板文件所在文件夹的路径,不是模板文件的路径)
            File templeteFile = new File(BlogConfig.getProfile()  + "laborTemplete/");
            if (!templeteFile.exists()) {
                // 创建模板文件目录,如果目录不存在则创建
                templeteFile.mkdirs();
            configuration.setDirectoryForTemplateLoading(templeteFile);
            //获取inu模板文件
            Resource resource = new ClassPathResource("wordTemplete/word模板.ftl");
            File inuModel = new File(BlogConfig.getProfile() + "/laborTemplete/laborFile.ftl");
            FileUtils.copyToFile(resource.getInputStream(), inuModel);
            //输出文档路径及名称
            File fileLeave = new File(BlogConfig.getProfile()  + "laborFIle/");
            if (!fileLeave.exists()) {
                // 创建档案存放目录,如果目录不存在则创建
                fileLeave.mkdirs();
            String wordName = fileNmae + ".doc";
            File outFile = new File(BlogConfig.getProfile() + "/laborFIle/" + new String( wordName.getBytes("utf-8") , "utf-8"));
            //以utf-8的编码读取ftl文件
            Template template = configuration.getTemplate("laborFile.ftl");
            Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"), 10240);
            template.process(dataMap, out);
            // 关闭文件流
            out.close();
            // 文档存放的目录
            return BlogConfig.getProfile() + "/laborFIle/"; // 这里的getProfile()是我自己定义的一个方法:可以直接指定路径,如:D:/profile
        }catch (Exception e){
            e.printStackTrace();
        return BlogConfig.getProfile() + "/laborFIle/";

注意:代码中引用了一个BlogConfig.getProfile()的方法,这是我自己定义的一个方法,用于获取生成的word文档的存储目录,可以直接指定我们自己电脑下的目录,如:D:/profile

五:编写文件压缩工具类:CompressUtil

package com.blog.web.controller.tool;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipOutputStream;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
 * 文件压缩工具
 * @author Aaron Wang
 * @data 2020/7/25
public class CompressUtil {
     * @param sourcePath   要压缩的文件路径
     * @param suffix 生成的格式后最(zip、rar)
    public static void generateFile(String sourcePath, String suffix) throws Exception {
        File file = new File(sourcePath);
        // 压缩文件的路径不存在
        if (!file.exists()) {
            throw new Exception("路径 " + sourcePath + " 不存在文件,无法进行压缩...");
        // 用于存放压缩文件的文件夹
        String generateFile = file.getParent() + File.separator +"CompressFile";
        File compress = new File(generateFile);
        // 如果文件夹不存在,进行创建
        if( !compress.exists() ){
            compress.mkdirs();
        // 目的压缩文件
        String generateFileName = compress.getAbsolutePath() + File.separator + "AAA" + file.getName() + "." + suffix;
        // 输入流 表示从一个源读取数据
        // 输出流 表示向一个目标写入数据
        // 输出流
        FileOutputStream outputStream = new FileOutputStream(generateFileName);
        // 压缩输出流
        ZipOutputStream zipOutputStream = new ZipOutputStream(new BufferedOutputStream(outputStream));
        generateFile(zipOutputStream,file,"");
        System.out.println("源文件位置:" + file.getAbsolutePath() + ",目的压缩文件生成位置:" + generateFileName);
        // 关闭 输出流
        zipOutputStream.close();
     * @param out  输出流
     * @param file 目标文件
     * @param dir  文件夹
     * @throws Exception
    private static void generateFile(ZipOutputStream out, File file, String dir) throws Exception {
        // 当前的是文件夹,则进行一步处理
        if (file.isDirectory()) {
            //得到文件列表信息
            File[] files = file.listFiles();
            //将文件夹添加到下一级打包目录
            out.putNextEntry(new ZipEntry(dir + "/"));
            dir = dir.length() == 0 ? "" : dir + "/";
            //循环将文件夹中的文件打包
            for (int i = 0; i < files.length; i++) {
                generateFile(out, files[i], dir + files[i].getName());
        } else { // 当前是文件
            // 输入流
            FileInputStream inputStream = new FileInputStream(file);
            // 标记要打包的条目
            out.putNextEntry(new ZipEntry(dir));
            // 进行写操作
            int len = 0;
            byte[] bytes = new byte[1024];
            while ((len = inputStream.read(bytes)) > 0) {
                out.write(bytes, 0, len);
            // 关闭输入流
            inputStream.close();
     * 递归压缩文件
     * @param output ZipOutputStream 对象流
     * @param file 压缩的目标文件流
     * @param childPath 条目目录
    private static void zip(ZipOutputStream output,File file,String childPath){
        FileInputStream input = null;
        try {
            // 文件为目录
            if (file.isDirectory()) {
                // 得到当前目录里面的文件列表
                File list[] = file.listFiles();
                childPath = childPath + (childPath.length() == 0 ? "" : "/")
                        + file.getName();
                // 循环递归压缩每个文件
                for (File f : list) {
                    zip(output, f, childPath);
            } else {
                // 压缩文件
                childPath = (childPath.length() == 0 ? "" : childPath + "/")
                        + file.getName();
                output.putNextEntry(new ZipEntry(childPath));
                input = new FileInputStream(file);
                int readLen = 0;
                byte[] buffer = new byte[1024 * 8];
                while ((readLen = input.read(buffer, 0, 1024 * 8)) != -1) {
                    output.write(buffer, 0, readLen);
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            // 关闭流
            if (input != null) {
                try {
                    input.close();
                } catch (IOException ex) {
                    ex.printStackTrace();
     * 压缩文件(文件夹)
     * @param path 目标文件流
     * @param format zip 格式 | rar 格式
     * @throws Exception
    public static String zipFile(File path,String format) throws Exception {
        String generatePath = "";
        if( path.isDirectory() ){
            generatePath = path.getParent().endsWith("/") == false ? path.getParent() + File.separator + path.getName() + "." + format: path.getParent() + path.getName() + "." + format;
        }else {
            generatePath = path.getParent().endsWith("/") == false ? path.getParent() + File.separator : path.getParent();
            generatePath += path.getName().substring(0,path.getName().lastIndexOf(".")) + "." + format;
        // 输出流
        FileOutputStream outputStream = new FileOutputStream( generatePath );
        // 压缩输出流
        ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(outputStream));
        zip(out, path,"");
        out.flush();
        out.close();
        return generatePath;
     * 递归删除目录下的所有文件及子目录下所有文件
     * @param dir 将要删除的文件目录
    public static boolean deleteDir(File dir) {
        if (dir.isDirectory()) {
            String[] children = dir.list();
            //递归删除目录中的子目录下
            for (int i=0; i<children.length; i++) {
                boolean success = deleteDir(new File(dir, children[i]));
                if (!success) {
                    return false;
        // 目录此时为空,可以删除
        return dir.delete();
    public static void main(String[] args) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        String sd = sdf.format(new Date());      // 时间戳转换成时间
        System.out.println(sd);
        String path = "D:\\profile\\laborFIle";
//        String path = "D:\\profile\\laborFIle";
//        String format = "zip";
        try {
            System.out.println(deleteDir(new File(path)));
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println(e.getMessage());

五:编写访问的controller方法:

* 下载生成的word文档 * @param zjhms 勾选的民工证件号码集合 * @param prjnumber 项目合同备案号 * @return * @throws IOException @GetMapping("/downnloadFiles") @ResponseBody public AjaxResult downnloadOtherFiles(String zjhms, String prjnumber) { try { return downFile(zjhms, prjnumber); }catch (Exception e){ e.printStackTrace(); return AjaxResult.error("下载失败,请联系管理员!"); * 下载档案文件 * @param zjhms 员工证件号码集合 * @return public AjaxResult downFile(String zjhms, String prjnumber){ Map<String, Object> dataMap = new HashMap<String, Object>(); String studentName = "张三"; dataMap.put("studentname", studentName); // 姓名 dataMap.put("gender", "男"); // 性别 dataMap.put("hometown", "江苏省-苏州市"); // 籍贯: **省-**市 dataMap.put("birthday", "1996-04-05"); // 出生年月 dataMap.put("classname", "三年级一班"); // 班级 dataMap.put("subjectname", "英语"); // 学科 dataMap.put("fraction", "98"); // 分数 dataMap.put("rank", "3"); // 排名 String phoneImage = downPicture("C:/Users/wangxk/Pictures/question.jpg"); dataMap.put("imagephone", phoneImage); // 照片 // 根据模板生成档案文档 String wordName = studentName + "_" + "成绩单"; // 文档名称 String wordsPath = FreeMarkerFileUtils.documentDataFilling(dataMap, wordName); // 接受文档存放目录 // 将存放文件的文件夹打包压缩 try { // 压缩文件 // zipFile()参数说明:被压缩的文件所在目录路径,压缩格式 String path = CompressUtil.zipFile(new File(wordsPath), "zip"); // System.out.println("压缩后存放目录:" + path); // 压缩完成后,删除目录wordsPath中生成的word文件 boolean delete = CompressUtil.deleteDir(new File(wordsPath)); // System.out.println("word目录删除状态:" + delete); return AjaxResult.success("laborFIle.zip"); } catch (Exception e) { e.printStackTrace(); return AjaxResult.error("下载失败。。。");

我们指定了前端访问的路径为:***/downnloadFiles的形式,当我们通过网页请求到该路径时,我们就可以下载生成的word文档文件压缩包

六:前端代码编写

<a class="btn btn-outline btn-info btn-rounded" onclick="downnloadOtherFiles()">
	<i class="fa fa-download"></i> word文档下载
<script th:inline="javascript">
    var prefix = ctx + "blogback";
    // 下载选中的民工档案
    function downnloadOtherFiles() {
        $.modal.loading("正在下载,请稍后。。。"); // 打开遮罩层
        var url = prefix + "/downnloadFiles";
        var param = "?zjhms=" + "411521185477412587" + "&prjnumber=" + "3256987422114";
        console.log("建筑民工登记表的url = " + url + param);
        $.get(url + param, function (result) {
            if (result.code == web_status.SUCCESS) {
                window.location.href = ctx + "laborerFileMess/downloadZip?fileName=" + encodeURI(result.msg) + "&delete=" + true;
                // window.location.href = ctx + "laborerFileMess/download?fileName=" + encodeURI("laborFIle.zip") + "&delete=" + true;
                $.modal.closeLoading(); // 关闭遮罩层
            } else if (result.code == web_status.WARNING) {
                $.modal.closeLoading(); // 关闭遮罩层
                $.modal.alertWarning(result.msg)
            } else {
                $.modal.alertError(result.msg);
</script>

点击按钮直接请求到后台我们指定的方法进行word文档的下载。laborerFileMess/downloadZip这是一个下载的方法,比较简单,下面试方法的代码。

七:编写下载word文档压缩包的方法:

* 下载压缩包 * @param fileName 文件名称 * @param delete 是否删除 @GetMapping("laborerFileMess/downloadZip") public void downloadZip(String fileName, Boolean delete, HttpServletResponse response, HttpServletRequest request) { try { String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1); String filePath = BlogConfig.getProfile() + fileName; response.setCharacterEncoding("utf-8"); response.setContentType("multipart/form-data"); response.setHeader("Content-Disposition", "attachment;fileName=" + setFileDownloadHeader(request, realFileName)); FileUtils.writeBytes(filePath, response.getOutputStream()); if (delete) { FileUtils.deleteFile(filePath); } catch (Exception e) { System.out.println("压缩包下载失败。。"); e.printStackTrace(); public String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException { final String agent = request.getHeader("USER-AGENT"); String filename = fileName; if (agent.contains("MSIE")) { // IE浏览器 filename = URLEncoder.encode(filename, "utf-8"); filename = filename.replace("+", " "); } else if (agent.contains("Firefox")) { // 火狐浏览器 filename = new String(fileName.getBytes(), "ISO8859-1"); } else if (agent.contains("Chrome")) { // google浏览器 filename = URLEncoder.encode(filename, "utf-8"); } else { // 其它浏览器 filename = URLEncoder.encode(filename, "utf-8"); return filename;

这里还用到了一个文件处理类:FileUtils

package com.blog.common.utils.file;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import javax.servlet.http.HttpServletRequest;
 * 文件处理工具类
public class FileUtils
    public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+";
     * 输出指定文件的byte数组
     * @param filePath 文件路径
     * @param os 输出流
     * @return
    public static void writeBytes(String filePath, OutputStream os) throws IOException
        FileInputStream fis = null;
            File file = new File(filePath);
            if (!file.exists())
                throw new FileNotFoundException(filePath);
            fis = new FileInputStream(file);
            byte[] b = new byte[1024];
            int length;
            while ((length = fis.read(b)) > 0)
                os.write(b, 0, length);
        catch (IOException e)
            throw e;
        finally
            if (os != null)
                    os.close();
                catch (IOException e1)
                    e1.printStackTrace();
            if (fis != null)
                    fis.close();
                catch (IOException e1)
                    e1.printStackTrace();
     * 删除文件
     * @param filePath 文件
     * @return
    public static boolean deleteFile(String filePath)
        boolean flag = false;
        File file = new File(filePath);
        // 路径为文件且不为空则进行删除
        if (file.isFile() && file.exists())
            file.delete();
            flag = true;
        return flag;
     * 文件名称验证
     * @param filename 文件名称
     * @return true 正常 false 非法
    public static boolean isValidFilename(String filename)
        return filename.matches(FILENAME_PATTERN);
     * 下载文件名重新编码
     * @param request 请求对象
     * @param fileName 文件名
     * @return 编码后的文件名
    public static String setFileDownloadHeader(HttpServletRequest request, String fileName)
            throws UnsupportedEncodingException
        final String agent = request.getHeader("USER-AGENT");
        String filename = fileName;
        if (agent.contains("MSIE"))
            // IE浏览器
            filename = URLEncoder.encode(filename, "utf-8");
            filename = filename.replace("+", " ");
        else if (agent.contains("Firefox"))
            // 火狐浏览器
            filename = new String(fileName.getBytes(), "ISO8859-1");
        else if (agent.contains("Chrome"))
            // google浏览器
            filename = URLEncoder.encode(filename, "utf-8");
            // 其它浏览器
            filename = URLEncoder.encode(filename, "utf-8");
        return filename;

八:下面是下载的结果

将下载的压缩包解压后就得到我们想生成的成绩单的word文档文件。

九:批量生成多个word文档,统一打包到一个压缩包中

其实这个想实现很简单,在文档的第五步,就是根据信息生成一个word文档,然后将该文档所在的目录打包成压缩文件供我们下载,想要实现生成多个word文档然后在同一打包压缩,我们只需要先循环定义我们生成word文档的Map集合信息,生成多个文档存放在我们指定的目录下,最后再打包压缩该目录即可。

文章到这里就告一段落了,如果有什么错误的地方欢迎大家指正。我们一起学习。 欢迎大家评论留言。我都会一一回复。

项目下载源码