当前位置: 首页 > news >正文

Freemarker生成Word文档下载到浏览器(下载word)

73万字的Java面试题库【全网最详细-找工作/实习必备神器】

https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzE5MTY1NzczOA==&action=getalbum&album_id=4057608455186808839
Java面试题库ps:网上面试题多而杂,自己整理了一套面试题,我靠这套面试题 2年经验拿15k~

导入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
创建模板

新建一个word文档,打开后编辑成想要的格式,这个word文档是用作模版的,也就是后面就是把数据填充到这个word文档里,替换里面的占位符,一开始在Word里面就需要把模版的样式给设置好,例如字体大小、字体型号、颜色等等,如下所示

骚戴理解:动态数据替换成${xxx},如果是多条集合用${xx.xxx};这里特别注意如果没有对应数据会报错!!!所以最好替换成${(xx.xxx)?if_exists} (这里表示内容为空不显示)就不会报错了,可以看到我上面的写法都是用的字段+后缀?if_exists。【创建模版的时候要要么用wps创建docx后缀的文件,要么用office来创建,这样可以避免后面导出的文件出现不兼容问题,具体内容看问题三的描述】

根据word模版生成ftl文件

把上面用占位符写好的word模版另存为,一定记得文件类型选择xml文档!!!然后把这个xml文件后缀改成.ftl 后缀,如下所示

最后把这个文件复制到项目中的resources目录下,这里我创建了一个templates文件夹专门放word的模版,然后可以把模版文件给格式化一下,IDEA代码格式化快捷键是Ctrl+Alt+L,后续需要对ftl文件进行一定的调整,特别是有集合需要遍历的时候!

前端代码编写
// 下载议题目录async downTopicCatalog(){let params = {conferenceId: this.conId,dataSource: this.dataSource}console.log('params',params)let res = await rxAjax.postJson("/api/downTopicCatalog",params);const link=document.createElement('a');// let blob = new Blob([res.data],{type: 'applicationnd.ms-excel'});    //如果后台返回的不是blob对象类型,先定义成blob对象格式let blob =   new Blob([res], { type: 'application/octet-stream' })    //如果后台返回的直接是blob对象类型,直接获取数据link.style.display='none';// 兼容不同浏览器的URL对象const url = window.URL || window.webkitURL || window.moxURL;link.href=url.createObjectURL(blob);link.download =`会议议题目录.docx`;   //下载的文件名称link.click();window.URL.revokeObjectURL(url);},

骚戴理解:前端主要是封装好要下载的数据传给后端,各个公司可能都会对ajax包一层代码,所以代码略有差异,这里主要的核心代码是把后端返回的内容下载到浏览器,下面的代码是通用的,作用就是把后端返回的内容下载到浏览器,只需要改一下下载文件的名称即可,例如第8行的名称是会议议题目录,按需修改

const link=document.createElement('a');
// let blob = new Blob([res.data],{type: 'applicationnd.ms-excel'});    //如果后台返回的不是blob对象类型,先定义成blob对象格式
let blob =   new Blob([res], { type: 'application/octet-stream' })    //如果后台返回的直接是blob对象类型,直接获取数据
link.style.display='none';
// 兼容不同浏览器的URL对象
const url = window.URL || window.webkitURL || window.moxURL;
link.href=url.createObjectURL(blob);
link.download =`会议议题目录.docx`;   //下载的文件名称
link.click();
window.URL.revokeObjectURL(url);
后端代码编写
创建word工具栏

(1)通过流的方式获取模版

package com.util;import java.io.*;
import java.net.URLEncoder;
import java.util.Map;import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;import freemarker.template.Configuration;
import freemarker.template.Template;/*** Word文档生成工具类* 基于Freemarker模板引擎生成Word文档并提供下载功能*/
public class WordExportUtil {private static Configuration configuration;static {configuration = new Configuration(Configuration.VERSION_2_3_31);configuration.setDefaultEncoding("UTF-8");// 设置模板加载路径为类路径下的templates目录configuration.setClassForTemplateLoading(WordExportUtil.class, "/templates");}private WordExportUtil() {throw new AssertionError("工具类不允许实例化");}/*** 导出Word文档* * @param response HTTP响应对象* @param dataMap  填充数据* @param fileName 下载文件名* @param template 模板文件名* @throws IOException 输入输出异常*/public static void exportWord(HttpServletResponse response, Map<String, Object> dataMap, String fileName, String template) throws IOException {Template freemarkerTemplate = configuration.getTemplate(template);File tempFile = null;InputStream fileStream = null;ServletOutputStream outputStream = null;try {// 生成临时Word文件tempFile = generateTempDoc(dataMap, freemarkerTemplate);fileStream = new FileInputStream(tempFile);// 设置响应头和内容类型response.setCharacterEncoding("UTF-8");response.setContentType("application/msword");String encodedFileName = URLEncoder.encode(fileName + ".doc", "UTF-8");response.setHeader("Content-Disposition", "attachment;filename=" + encodedFileName);// 输出文件内容outputStream = response.getOutputStream();byte[] buffer = new byte[1024];int bytesRead;while ((bytesRead = fileStream.read(buffer)) != -1) {outputStream.write(buffer, 0, bytesRead);}} finally {// 关闭资源并删除临时文件closeQuietly(fileStream);closeQuietly(outputStream);deleteQuietly(tempFile);}}/*** 根据模板生成临时Word文件* * @param dataMap  填充数据* @param template Freemarker模板* @return 临时文件*/private static File generateTempDoc(Map<?, ?> dataMap, Template template) {try {// 创建临时文件File tempFile = File.createTempFile("word-template-", ".doc");try (Writer writer = new OutputStreamWriter(new FileOutputStream(tempFile), "UTF-8")) {// 处理模板并写入数据template.process(dataMap, writer);}return tempFile;} catch (Exception e) {throw new RuntimeException("生成Word文档失败", e);}}/*** 静默关闭Closeable资源*/private static void closeQuietly(Closeable closeable) {if (closeable != null) {try {closeable.close();} catch (IOException e) {// 静默处理}}}/*** 静默删除文件*/private static void deleteQuietly(File file) {if (file != null && file.exists()) {try {file.delete();} catch (Exception e) {// 静默处理}}}
}

骚戴理解:上面工具类21行的 configuration.setClassForTemplateLoading(WordUtil.class, "/templates");需要根据自己的情况进行修改,这个是读取ftl模版文件的流,/templates表示文件存储的目录,例如我这里是放到resources目录下的templates文件夹里面的,所以这里就是/templates

这里我一开始是用读取路径的方式去拿文件模版,但是由于生产环境会出现路径错误,无法拿到模版,所以要用流

好文参考:freemarker加载模板目录的方法_setdirectoryfortemplateloading-CSDN博客

把文档转成流存数据库好文参考【与本业务无关的扩展】:java使用freemarker模板导出word(docx格式;流形式输入输出)_freemarker doc docx-CSDN博客

(2)通过文件路径的方式获取模版

package com.zbcj.business.util;import java.io.*;
import java.net.URLEncoder;
import java.util.Map;import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;import freemarker.template.Configuration;
import freemarker.template.Template;
import org.springframework.util.ResourceUtils;public class WordUtil {private WordUtil() {throw new AssertionError();}/*** 导出word* @param response 响应* @param map 数据* @param title word标题【没用,需要在前端去拿这个响应头才行】* @param ftlFile ftl模版文件名* @throws IOException*/public static void exportMillCertificateWord(HttpServletResponse response, Map map, String title, String ftlFile, String path) throws IOException {Configuration configuration = new Configuration();configuration.setDefaultEncoding("utf-8");configuration.setDirectoryForTemplateLoading(new File(path));Template freemarkerTemplate = configuration.getTemplate(ftlFile);File file = null;InputStream fin = null;ServletOutputStream out = null;try {// 调用工具类的createDoc方法生成Word文档file = createDoc(map,freemarkerTemplate);fin = new FileInputStream(file);response.setCharacterEncoding("utf-8");response.setContentType("application/msword");// 设置浏览器以下载的方式,处理该文件名String fileName = title+ ".doc";response.setHeader("Content-Disposition", "attachment;filename=".concat(String.valueOf(URLEncoder.encode(fileName, "UTF-8"))));out = response.getOutputStream();byte[] buffer = new byte[512];int bytesToRead = -1;// 通过循环将读入的Word文件的内容输出到浏览器中while((bytesToRead = fin.read(buffer)) != -1) {out.write(buffer, 0, bytesToRead);}} finally {if(fin != null) fin.close();if(out != null) out.close();    //关闭对应流if(file != null) file.delete(); // 删除临时文件}}private static File createDoc(Map<?, ?> dataMap, Template template) {String name =  "sellPlan.doc";File f = new File(name);Template t = template;try {// 这个地方不能使用FileWriter因为需要指定编码类型否则生成的Word文档会因为有无法识别的编码而无法打开Writer w = new OutputStreamWriter(new FileOutputStream(f), "utf-8");t.process(dataMap, w);w.close();} catch (Exception ex) {ex.printStackTrace();throw new RuntimeException(ex);}return f;}
}

这种方式是模版放到了服务器的文件目录里面,和上面的点在于这里传入的参数多了一个,上面只需要传入模版名称即可

// 调用工具栏填充word文档
WordUtil.exportMillCertificateWord(response,map, String.valueOf(java.util.UUID.randomUUID()), fileName,url);

上面代码的fileName是模版名称,例如/opt/words/规上工业报告模版.ftl,模版名称为“规上工业报告模版.ftl”,而url是模版名称前面的完整路径,例如/opt/words/

使用工具类
@PostMapping("/downTopicCatalog")
public void downTopicCatalog(@RequestBody JSONObject params, HttpServletResponse response) throws IOException {conferencePersonService.downTopicCatalog(params, response);
}
public void downTopicCatalog(JSONObject params, HttpServletResponse response) {// 格式化日期【2024年4月25日(星期四) 下午04:34】SimpleDateFormat formatter = new SimpleDateFormat("yyyy年M月d日(EEEE) ahh:mm", Locale.CHINA);String formattedDate = formatter.format(conference.getTimeStart());// 填充会议信息Map<String, Object> map = MapUtils.newHashMap();map.put("title", conference.getTitle());map.put("timeStart", formattedDate);map.put("compere", conference.getCompere());map.put("address", getAddressValue(conference.getAddress()));//List集合数据map.put("topicList", list);try {// 调用工具栏填充word文档WordUtil.exportMillCertificateWord(response, map, "标题", "wordTempelets.ftl");} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}
}

骚戴理解:把数据封装好放到map里面去,不管是单条数据还是集合都放map里面去,然后调用WordUtil工具类的exportMillCertificateWord方法即可,其中wordTempelets.ftl是ftl模版文件名,也就是上面放到resources目录下templates文件夹的文件

调整Ftl文件代码(遍历集合)
<w:body><!-- 标题段落 --><w:p><w:pPr><w:jc w:val="center"/><w:rPr><w:rFonts w:eastAsia="黑体"/><w:sz w:val="44"/></w:rPr></w:pPr><w:r><w:rPr><w:rFonts w:eastAsia="黑体"/><w:sz w:val="44"/></w:rPr><w:t>${title?if_exists}</w:t></w:r></w:p><!-- 时间信息 --><w:p><w:pPr><w:ind w:firstLineChars="262"/><w:rPr><w:rFonts w:eastAsia="黑体"/><w:sz w:val="32"/></w:rPr></w:pPr><w:r><w:rPr><w:rFonts w:eastAsia="黑体"/></w:rPr><w:t>一、时 间</w:t></w:r></w:p><w:p><w:pPr><w:ind w:firstLineChars="262"/><w:rPr><w:rFonts w:eastAsia="仿宋"/><w:sz w:val="32"/></w:rPr></w:pPr><w:r><w:t>${timeStart?if_exists}</w:t></w:r></w:p><!-- 地点信息 --><w:p><w:pPr><w:ind w:firstLineChars="262"/><w:rPr><w:rFonts w:eastAsia="黑体"/></w:rPr></w:pPr><w:r><w:t>二、地 点</w:t></w:r></w:p><w:p><w:pPr><w:ind w:firstLineChars="262"/><w:rPr><w:rFonts w:eastAsia="仿宋"/></w:rPr></w:pPr><w:r><w:t>${address?if_exists}</w:t></w:r></w:p><!-- 主持人信息 --><w:p><w:pPr><w:ind w:firstLineChars="262"/><w:rPr><w:rFonts w:eastAsia="黑体"/></w:rPr></w:pPr><w:r><w:t>三、主持人</w:t></w:r></w:p><w:p><w:pPr><w:ind w:firstLineChars="262"/><w:rPr><w:rFonts w:eastAsia="仿宋"/></w:rPr></w:pPr><w:r><w:t>${compere?if_exists}</w:t></w:r></w:p><!-- 主要议程列表 --><w:p><w:pPr><w:ind w:firstLineChars="262"/><w:rPr><w:rFonts w:eastAsia="黑体"/></w:rPr></w:pPr><w:r><w:t>四、主要议程</w:t></w:r></w:p><#list topicList as tp><w:p><w:pPr><w:ind w:firstLineChars="262"/><w:rPr><w:rFonts w:eastAsia="仿宋"/></w:rPr></w:pPr><w:r><w:t>${tp.index?if_exists}</w:t></w:r><w:r><w:tab/></w:r><w:r><w:t>${tp.topic?if_exists}</w:t></w:r></w:p></#list><!-- 页面设置 --><w:sectPr><w:pgSz w:w="11906" w:h="16838"/><w:pgMar w:top="2098" w:right="1361" w:bottom="1984" w:left="1587"/></w:sectPr>
</w:body>

骚戴理解:观察这个ftl文件个核心内容的结构,可以发现<w:p>标签表示的是一行内容,里面包括了字体样式、大小、内容等,由于我这里需要对List集合进行遍历,所以我在需要遍历的<w:p>标签上面加了一个<#list topicList as tp>,并且在</w:p>后面加了</#list>来实现遍历,其中topicList就是后端放到map中的集合变量名,tp表示这个集合里的单个变量,如下所示,注意是在行标签的外面去遍历!!!如果是调整了ftl文件的代码,那一定要记得重启后端!!!

<#list topicList as tp><w:p><w:pPr><w:spacing w:beforeLines="0" w:afterLines="0" w:line="336" w:lineRule="auto"/><w:ind w:firstLine="796" w:firstLineChars="262"/><w:rPr><w:rFonts w:hint="eastAsia" w:ascii="仿宋" w:hAnsi="仿宋" w:eastAsia="仿宋" w:cs="仿宋"/><w:sz w:val="32"/><w:szCs w:val="32"/></w:rPr></w:pPr><w:r><w:rPr><w:rFonts w:hint="eastAsia" w:ascii="仿宋" w:hAnsi="仿宋" w:eastAsia="仿宋" w:cs="仿宋"/><w:w w:val="95"/><w:sz w:val="32"/><w:szCs w:val="32"/></w:rPr><w:t>${tp.index?if_exists}</w:t></w:r><w:r><w:rPr><w:rFonts w:hint="eastAsia" w:ascii="仿宋" w:hAnsi="仿宋" w:eastAsia="仿宋" w:cs="仿宋"/><w:w w:val="95"/><w:sz w:val="32"/><w:szCs w:val="32"/><w:lang w:val="en-US" w:eastAsia="zh-CN"/></w:rPr><w:tab/></w:r><w:r><w:rPr><w:rFonts w:hint="eastAsia" w:ascii="仿宋" w:hAnsi="仿宋" w:eastAsia="仿宋" w:cs="仿宋"/><w:w w:val="95"/><w:sz w:val="32"/><w:szCs w:val="32"/></w:rPr><w:t>${tp.topic?if_exists}</w:t></w:r></w:p></#list>

好文参考:JAVA实现Freemarker生成动态数据的Word文档下载到浏览器_java 动态文档-CSDN博客

好文参考:java+freemarker生成Word文档 循环java对象 Vue前端下载Word文档_freemarker生成word 循环-CSDN博客

http://www.dtcms.com/a/290176.html

相关文章:

  • 上海GEO优化公司找哪家怎么做
  • uniapp底部导航栏凸起
  • windows电脑给iOS手机安装ipa包的方法
  • Kubernetes Pod调度基础
  • Leetcode力扣解题记录--第238题(前/后缀积)
  • 【Git#6】多人协作 企业级开发模型
  • 3D可视化模型轻量化陷阱:STL转GLTF的精度损失与压缩比平衡策略
  • 【系统全面】Linux内核原理——基础知识介绍
  • H3C路由器模拟PPPOE拨号
  • MTSC2025参会感悟:Multi-Agent RAG 应用质量保障建设
  • Java IO流体系详解:字节流、字符流与NIO/BIO对比及文件拷贝实践
  • postgresql安装教程-个人笔记
  • 股票分红派息及其数据获取(使用Python)
  • selenium爬取图书信息
  • 关于JVM
  • 低速信号设计之 RGMII 篇
  • Rk3568驱动开发_非阻塞IO_16
  • 有关Mysql数据库的总结
  • Pytest 输出捕获详解:掌握如何查看和控制打印信息
  • Nacos 探活机制深度解析:临时 / 永久实例差异及与 Sentinel 的熔断协作
  • C++11之右值引用与移动语义(提高效率)重要
  • 「日拱一码」033 机器学习——严格划分
  • 【VASP】VASP 机器学习力场(MLFF)实战
  • 机器学习对词法分析、句法分析、浅层语义分析的积极影响
  • Taro 本地存储 API 详解与实用指南
  • 京东疯狂投资具身智能:众擎机器人+千寻智能+逐际动力 | AI早报
  • 从“被动照料”到“主动预防”:智慧养老定义的养老4.0时代
  • 迁移科技3D视觉系统:赋能机器人上下料,开启智能制造高效新纪元
  • Nacos中feign.FeignException$BadGateway: [502 Bad Gateway]
  • 第15次:商品搜索