freemarker+springboot实现word生成下载(导出Word)

编译器:idea
项目构建工具:maven
数据库:mysql、navicat

一、导入pom依赖

<dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.31</version>
</dependency>

二、创建Word模板

 创建你想要生成的word的模板,如上图,绿框内是需要填充数据的地方(先用中文占位一下)。然后在保存文件时选择保存成xml,我选择的保存位置是项目的classpath路径下的templates目录下。如下图:

 保存好后在项目中打开:

 找一个格式化xml的在线工具(比如:https://c.runoob.com/front-end/710/),将格式化后的代码粘到report.xml里:

 下一步制造填充模板需要的数据,我使用的是之前写好的一个接口,获取到的数据如下:

{
    "title": "主题",
    "status": "已完成",
    "time": "2023-04-11 15:40:53",
    "timeLen": "6分钟",
    "committee": "1",
    "remark": "某段会议描述",
    "record": [
        {
            "name": "王某",
            "updateTime": "2023-04-11 10:43:06",
            "recognition": "而是"
        },
        {
            "name": "张三",
            "updateTime": "2023-04-11 10:43:17",
            "recognition": "啥"
        },
        {
            "name": "李四",
            "updateTime": "2023-04-11 10:43:17",
            "recognition": "喂。"
        },
        {
            "name": "王五",
            "updateTime": "2023-04-11 10:44:47",
            "recognition": "我喂。"
        }
    ],
    "partyNames": [
        "张三",
        "李四"
    ],
    "rate": "50"
}

 将需要填充给模板的数据放到一个dataMap中:

 将占位符放到Word模板的正确位置:

 检索前面绿框圈出来的文字,修改为图下样式:

 其他位置也做如此替换:

添加<#if>判断

 如果${report.commitee}在模板中出现了,但是dataMap中没有这个数据,或者为空就会报错,所以可以添加一个if判断来避免这个异常。

list循环

 当传入的数据是一个数组对象(例如本示例中的List),可以使用<#list>遍历。

二、创建生成Word文件的工具类

package com.webro.utils;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.Version;

import java.io.*;
import java.util.Map;

/**
 * Word文档工具类
 */
public class WordUtil {
    /**
     * 使用FreeMarker自动生成Word文档
     * @param dataMap   生成Word文档所需要的数据
     * @param
     */
    public static void generateWord(Map<String, Object> dataMap, OutputStream outputStream) throws Exception {
        // 获取当前项目路径
        String projectPath = System.getProperty("user.dir");
        // 设置FreeMarker的版本和编码格式
        Configuration configuration = new Configuration(new Version("2.3.31"));
        configuration.setDefaultEncoding("UTF-8");

        // 设置FreeMarker生成Word文档所需要的模板的路径
        configuration.setDirectoryForTemplateLoading(new File(projectPath+"\\src\\main\\resources\\templates"));
        // 设置FreeMarker生成Word文档所需要的模板
        Template t = configuration.getTemplate("MinutesMeeting.ftl", "UTF-8");
        //FreeMarker使用Word模板和数据生成Word文档
        Writer out = new BufferedWriter(new OutputStreamWriter(outputStream, "UTF-8"));
        t.process(dataMap, out);
        out.flush();
    }
}

 其中OutputStream、BufferedWriter方法的讲解:https://www.cnblogs.com/qianyx/articles/17348979.html

三、编写controller层方法

/**
     * 导出会议纪要freemarker版本
     *
     * @param
     * @return
     */
    @GetMapping("exportMinutesMeeting/{id}")
    public ResponseEntity<byte[]> exportMinutesMeeting(@PathVariable("id") String id) throws Exception {
        // 生成文件
        Map<String,Object> dataMap = new HashMap<>();
        MinutesMeeting minutesMeeting = meetingService.getBasicMinutes(id);
        dataMap.put("report",minutesMeeting);

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        WordUtil.generateWord(dataMap,outputStream);

        HttpHeaders headers = new HttpHeaders();
        //Content-Disposition的作用:告知浏览器以何种方式显示响应返回的文件,用浏览器打开还是以附件的形式下载到本地保存
        //attachment表示以附件方式下载   inline表示在线打开   "Content-Disposition: inline; filename=文件名.mp3"
        // filename表示文件的默认名称,因为网络传输只支持URL编码,因此需要将文件名URL编码后进行传输,前端收到后需要反编码才能获取到真正的名称
        String fileName = minutesMeeting.getOrgName()+"-"+minutesMeeting.getTitle()+".doc";
        headers.add("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
        headers.add("Content-Length", "" + outputStream.toByteArray().length);
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        // 将文件内容和头部信息封装到响应实体中并返回
        return new ResponseEntity<>(outputStream.toByteArray(), headers, HttpStatus.OK);
    }

 这里本来使用的是HttpServletResponse来编辑响应信息,但是无论怎么改,都报错:getOutputStream() has already been called,经过百度,发现可能是和@Controller注解里的getOutputStream()方法冲突,所以改使用HttpServletResponse。

参考文章:https://bbs.huaweicloud.com/blogs/364658

posted @ 2023-04-24 13:12  钱有学  阅读(1385)  评论(0编辑  收藏  举报