Java FreeMarker生成word

2,001 阅读2分钟

前言:

本文主要记录FreeMarker生成wore文档并导出的操作。

FreeMarker简介

FreeMarker 是一款模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。

详情可以通过官网进行一个初步认识:freemarker.apache.org/

准备工作

1. 制作word样板

新建一个word,构建需要导出的内容,如下:

image.png

2.生成xml文件

word布局构建好后,将word另存为xml文件

image.png

3.格式化xml文件

模板xml文件生成后,可以通过一些xml工具进行格式化,我这里使用的时idea,将xml文件复制到idea中并打开 ctrl + alt + l快捷键进行格式化。

4.编辑xml文件,添加占位符

doc转xml的格式不需要过于深入了解,我们只需要在<w:body></w:body>标签中找到需要动态加载数据添加占位符就行了。占位符写法为${key},该key对应后端的key值。效果如下:

image.png

5.编辑xml文件格式ftl

直接修改文件后缀名即可。

后端代码实现

导出工具类

public class WordUtils {

    private static Configuration configuration = null;

    private final static String templateFolder = "";

    static {
        configuration = new Configuration(Configuration.VERSION_2_3_0);
        configuration.setDefaultEncoding("utf-8");
        //            configuration.setDirectoryForTemplateLoading(new File(templateFolder));
        configuration.setClassForTemplateLoading(WordUtils.class, "/templates");
    }

    /**
     * 导出word
     *
     * @param request
     * @param response
     * @param title    导出的文件名称
     */
    public static void export(HttpServletRequest request, HttpServletResponse response, Map<?, ?> map, String title, String templateName) {
        OutputStream out = null;
        File file = null;
        InputStream fin = null;
        try {
            Template freemarkerTemplate = configuration.getTemplate(templateName);
            // 调用工具类的createDoc方法生成Word文档
            file = createDoc(map, freemarkerTemplate);
            fin = new FileInputStream(file);

            // 设置浏览器以下载的方式处理该文件名
            String userAgent = request.getHeader("USER-AGENT");
            if (!StringUtils.contains(userAgent, "Mozilla")) {// 火狐浏览器
                title = URLUtil.encode(title, "UTF-8");
            }
            response.setContentType("application/msword");
            response.addHeader("Accept-Ranges", "bytes");
            response.addHeader("Content-Disposition", "attachment;filename=" + title + ".doc");

            out = response.getOutputStream();
            byte[] buffer = new byte[512];  // 缓冲区
            int bytesToRead = -1;
            // 通过循环将读入的Word文件的内容输出到浏览器中
            while ((bytesToRead = fin.read(buffer)) != -1) {
                out.write(buffer, 0, bytesToRead);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
                fin.close();
                file.delete();
            } catch (NullPointerException e) {
                throw new RuntimeException(e);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 创建word文档
     *
     * @param dataMap
     * @param template
     * @return
     */
    public static File createDoc(Map<?, ?> dataMap, Template template) {
        String name = System.currentTimeMillis() + ".doc";
        File f = new File(name);
        Template t = template;
        try {
            Writer w = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(f), "utf-8"));
            t.process(dataMap, w);
            w.close();
        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
            throw new RuntimeException(ex);
        }
        return f;
    }

}

action

Map<String,Object> data = new HashMap<>();
data.put("text1","张三");
data.put("text2","男");
data.put("text3","18");
data.put("text4","广东");
data.put("text5","6级");
data.put("text6","3年");
data.put("text7","15215215201");
data.put("text8","hellow@word.com");
// 图片集合
List<String> list = new ArrayList<>();
list.add(ImageUtils.getImageBase("E:\lsy_data\img\1.png"));
list.add(ImageUtils.getImageBase("E:\lsy_data\img\2.png"));
list.add(ImageUtils.getImageBase("E:\lsy_data\img\3.png"));
list.add(ImageUtils.getImageBase("E:\lsy_data\img\4.png"));
data.put("images",list);
WordUtils.export(request,response,data,"测试","test1.ftl");

踩坑记录

带图片导出

word导出的过程中,图片是比较常见的元素,这里记录下自己通过freeMarker导出word带图片的踩坑的记录。

  1. 在模板中添加图片占位符,如上面的模板所示。此时的模板上会有一个base64的字符串,这个就是刚刚添加在模板中的图片占位。

image.png

  1. 上面的图片base64字符串替换成${key},${key}后端获取的图片(base64格式),如果是多张图片的使用循环来遍历,如:<#list images as item>,注意<pkg:part>标签中的pkg:name图片名称必须不一样,要不然会一直加载到第一张的图片。

image.png

  1. 动态添加公共资源,在xml模板中找到你<pkg:xmlData>标签。注意,这里的id不能重复,且Tatget的属性的图片名称需保持跟第二步设置的一致

image.png

  1. 完成上面步骤后,我们只要找到图片占位符的区域,进行数据遍历即可。

![1SZZ@N2{8ONG7D4_UEVJ56.png

细节:

  1. 图片导出的时候如果是多张的,最后先排好版,避免导出word的时候图片位置错乱
  2. 遍历图片的时候如果只显示一张,应该是部分参数是必须唯一的,这种情况下可以拿之前的word导出来的xml对面下,我就是这么操作的。