Java通过freemarker生成word文档

摘要

项目预期效果

有一个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文档下载
</a>

<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;
        try
        {
            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)
            {
                try
                {
                    os.close();
                }
                catch (IOException e1)
                {
                    e1.printStackTrace();
                }
            }
            if (fis != null)
            {
                try
                {
                    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");
        }
        else
        {
            // 其它浏览器
            filename = URLEncoder.encode(filename, "utf-8");
        }
        return filename;
    }
}

八:下面是下载的结果

将下载的压缩包解压后就得到我们想生成的成绩单的word文档文件。
在这里插入图片描述

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

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

完结

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

项目下载源码

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我是王小贱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值