spring文件下载的方式

    • 方式一:通过ResponseEntity<Resource> 方式来下载
    • 方式二:通过ResponseEntity<StreamingResponseBody> 方式来下载
    • 方式三:通过Servlet原生下载
    • 方式四:通过ResponseEntity<byte[]> 方式来下载
    • 四种下载方式的对比
      • 1、核心特性对比
      • 2、典型场景推荐
    • 完整的代码

方式一:通过ResponseEntity 方式来下载

@ApiOperation(value = "下载数据文档模板")
@GetMapping("/download-template/1")
public ResponseEntity<Resource> downloadFile01(HttpServletRequest request) {try {Resource resource = new ClassPathResource("templates/ubuntu-20.04.6-desktop-amd64.iso");// 检查资源是否存在if (resource.exists() || resource.isReadable()) {return ResponseEntity.ok().header(HttpHeaders.CONTENT_LENGTH, String.valueOf(resource.contentLength())).header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + encodeFileName(request, resource.getFilename())).contentType(MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM)).body(resource);} else {return ResponseEntity.notFound().build();}} catch (Exception e) {log.error("文件模板下载失败!", e);return ResponseEntity.internalServerError().build();}
}/*** 根据浏览器类型编码文件名* @param request 请求对象* @param fileName 原始文件名* @return 编码后的文件名*/
private static String encodeFileName(HttpServletRequest request, String fileName) {String userAgent = request.getHeader("User-Agent");// 处理IE及Edge浏览器if (userAgent.contains("MSIE") || userAgent.contains("Trident") || userAgent.contains("Edge")) {return cn.hutool.core.net.URLEncoder.createDefault().encode(fileName, StandardCharsets.UTF_8);}// 其他浏览器使用ISO-8859-1编码return new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
}

方式二:通过ResponseEntity 方式来下载

@ApiOperation(value = "下载数据文档模板")
@GetMapping("/download-template/2")
public ResponseEntity<StreamingResponseBody> downloadFile02(HttpServletRequest request) {try {Resource resource = new ClassPathResource("templates/ubuntu-20.04.6-desktop-amd64.iso");StreamingResponseBody body = outputStream -> {FileUtil.writeToStream(resource.getFile(), outputStream);};// 检查资源是否存在if (resource.exists() || resource.isReadable()) {return ResponseEntity.ok().header(HttpHeaders.CONTENT_LENGTH, String.valueOf(resource.contentLength()))//	省略 encodeFileName 方法.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + encodeFileName(request, resource.getFilename())).contentType(MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM)).body(body);} else {return ResponseEntity.notFound().build();}} catch (Exception e) {log.error("文件模板下载失败!", e);return ResponseEntity.internalServerError().build();}
}

方式三:通过Servlet原生下载

@ApiOperation(value = "下载数据文档模板")
@GetMapping("/download-template/3")
public void downloadFile03(HttpServletRequest request, HttpServletResponse response) throws IOException {Resource resource = new ClassPathResource("templates/ubuntu-20.04.6-desktop-amd64.iso");// 设置响应头response.setContentType(MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM).toString());response.setHeader("Content-Disposition", "attachment; filename=" + encodeFileName(request, resource.getFilename()));//	这里是关键,如果设置 response.setContentLength() 则下载的文件只能是 50 多兆response.setContentLengthLong(resource.contentLength());FileUtil.writeToStream(resource.getFile(), response.getOutputStream());
}

方式四:通过ResponseEntity<byte[]> 方式来下载

@ApiOperation(value = "下载数据文档模板")
@GetMapping("/download-template/4")
public ResponseEntity<byte[]> downloadFile04(HttpServletRequest request, HttpServletResponse response) throws IOException {try {Resource resource = new ClassPathResource("templates/ubuntu-20.04.6-desktop-amd64.iso");// 检查资源是否存在if (resource.exists() || resource.isReadable()) {return ResponseEntity.ok().header(HttpHeaders.CONTENT_LENGTH, String.valueOf(resource.contentLength())).header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + encodeFileName(request, resource.getFilename())).contentType(MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM)).body(FileUtil.readBytes(resource.getFile()));} else {return ResponseEntity.notFound().build();}} catch (Exception e) {log.error("文件模板下载失败!", e);return ResponseEntity.internalServerError().build();}
}

四种下载方式的对比

1、核心特性对比

方式内存占用适用场景流式支持资源管理代码复杂度
ResponseEntity<byte[]>高(全量加载)小文件(<10MB)如配置文件、图片自动释放内存低(简单封装)
ResponseEntity<Resource>中(按需加载)动态资源(如JAR内文件、数据库Blob)✅(需手动关闭流)需显式关闭InputStream中(需处理流)
ResponseEntity<StreamingResponseBody>极低(分块传输)超大文件(视频、日志等)✅(自动分块)自动处理流生命周期高(需分块逻辑)
Servlet原生下载低(手动控制)需要精细控制响应头/流的场景✅(手动实现)需手动关闭流高(冗余代码)

2、典型场景推荐

  • 快速小文件下载‌:优先使用ResponseEntity<byte[]>,直接返回字节数组,无需流控制。
  • 动态资源或加密文件‌:选择ResponseEntity<Resource>,灵活处理输入流。
  • ‌**超大文件(GB级)**‌:必须用StreamingResponseBody分块传输,避免OOM。
  • 兼容旧系统或特殊需求‌:Servlet原生下载(如需要自定义响应头逻辑)

完整的代码

import cn.hutool.core.io.FileUtil;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.MediaTypeFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;/*** description** @author lijin* @since 2025-08-12 14:05*/
@Slf4j
@Controller
@RequestMapping("/cycle-data/document01")
public class DownloadController {@ApiOperation(value = "下载数据文档模板")@GetMapping("/download-template/1")public ResponseEntity<Resource> downloadFile01(HttpServletRequest request) {try {Resource resource = new ClassPathResource("templates/ubuntu-20.04.6-desktop-amd64.iso");// 检查资源是否存在if (resource.exists() || resource.isReadable()) {return ResponseEntity.ok().header(HttpHeaders.CONTENT_LENGTH, String.valueOf(resource.contentLength())).header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + encodeFileName(request, resource.getFilename())).contentType(MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM)).body(resource);} else {return ResponseEntity.notFound().build();}} catch (Exception e) {log.error("文件模板下载失败!", e);return ResponseEntity.internalServerError().build();}}@ApiOperation(value = "下载数据文档模板")@GetMapping("/download-template/2")public ResponseEntity<StreamingResponseBody> downloadFile02(HttpServletRequest request) {try {Resource resource = new ClassPathResource("templates/ubuntu-20.04.6-desktop-amd64.iso");StreamingResponseBody body = outputStream -> {FileUtil.writeToStream(resource.getFile(), outputStream);};// 检查资源是否存在if (resource.exists() || resource.isReadable()) {return ResponseEntity.ok().header(HttpHeaders.CONTENT_LENGTH, String.valueOf(resource.contentLength())).header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + encodeFileName(request, resource.getFilename())).contentType(MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM)).body(body);} else {return ResponseEntity.notFound().build();}} catch (Exception e) {log.error("文件模板下载失败!", e);return ResponseEntity.internalServerError().build();}}@ApiOperation(value = "下载数据文档模板")@GetMapping("/download-template/3")public void downloadFile03(HttpServletRequest request, HttpServletResponse response) throws IOException {Resource resource = new ClassPathResource("templates/ubuntu-20.04.6-desktop-amd64.iso");// 设置响应头response.setContentType(MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM).toString());response.setHeader("Content-Disposition", "attachment; filename=" + encodeFileName(request, resource.getFilename()));response.setContentLengthLong(resource.contentLength());FileUtil.writeToStream(resource.getFile(), response.getOutputStream());}@ApiOperation(value = "下载数据文档模板")@GetMapping("/download-template/4")public ResponseEntity<byte[]> downloadFile04(HttpServletRequest request, HttpServletResponse response) throws IOException {try {Resource resource = new ClassPathResource("templates/ubuntu-20.04.6-desktop-amd64.iso");// 检查资源是否存在if (resource.exists() || resource.isReadable()) {return ResponseEntity.ok().header(HttpHeaders.CONTENT_LENGTH, String.valueOf(resource.contentLength())).header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + encodeFileName(request, resource.getFilename())).contentType(MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM)).body(FileUtil.readBytes(resource.getFile()));} else {return ResponseEntity.notFound().build();}} catch (Exception e) {log.error("文件模板下载失败!", e);return ResponseEntity.internalServerError().build();}}/*** 根据浏览器类型编码文件名* @param request 请求对象* @param fileName 原始文件名* @return 编码后的文件名*/private static String encodeFileName(HttpServletRequest request, String fileName) {String userAgent = request.getHeader("User-Agent");// 处理IE及Edge浏览器if (userAgent.contains("MSIE") || userAgent.contains("Trident") || userAgent.contains("Edge")) {return cn.hutool.core.net.URLEncoder.createDefault().encode(fileName, StandardCharsets.UTF_8);}// 其他浏览器使用ISO-8859-1编码return new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/95300.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/95300.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/95300.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

写一个redis客户端软件,参考 Another Redis Desktop Manager 的设计风格。

一个基于 Electron 开发的现代化 Redis 桌面客户端&#xff0c;参考 Another Redis Desktop Manager 的设计风格。 github仓库地址 https://github.com/henkuoai/redis-man-pc

Web3: DeFi借贷的安全基石, 了解喂价与清算机制的原理与重要性

今天我们要聊一个DeFi世界里至关重要&#xff0c;但又时常被误解的话题&#xff1a;为什么DeFi协议需要定期更新喂价和执行清算&#xff1f; 如果大家参与过DeFi借贷&#xff0c;大家可能看到过“清算”这个词&#xff0c;甚至会有点谈虎色变。但实际上&#xff0c;清算和为其提…

「iOS」————响应者链与事件传递链

iOS学习响应者链和事件传递链传递链&#xff1a;hitTest:withEvent**pointInside:withEvent**响应链第一响应者和最佳响应者触摸事件&#xff08;UITouch&#xff09;UIGestureRecognizer&#xff08;手势识别器&#xff09;响应者链和事件传递链 iOS事件的主要由&#xff1a;…

修复图像、视频和3D场景的AI工具–Inpaint Anything

TL; DR&#xff1a;用户可以通过单击来选择图像中的任何对象。借助强大的视觉模型&#xff0c;例如SAM、LaMa和稳定扩散 (SD)&#xff0c;Inpaint Anything能够顺利地移除对象&#xff08;即Remove Anything&#xff09;。此外&#xff0c;在用户输入文本的提示下&#xff0c;I…

java -jar xxx.jar 提示xxx.jar中没有主清单属性报错解决方案

xxx.jar 中没有主清单属性 &#xff08;no main manifest attribute&#xff09;解决方案 java -jar xxx.jar 提示xxx.jar中没有主清单属性报错解决方案 这个错通常出现在你用 java -jar xxx.jar 启动&#xff0c;但 JAR 的 META-INF/MANIFEST.MF 里没有 Main-Class 条目&#…

Myqsl建立库表练习

目录 一、windows中选择一种方式安装Mysql8.0 二、新建产品库mydb6_product 1. 新建3张表如下&#xff1a; 1&#xff09;employees表 2&#xff09;orders表 3&#xff09;invoices表 三、新建员工库mydb8_worker&#xff0c;添加自定义表内容并插入数据 1. 新建库表 2. 插…

STM32 输入捕获,串口打印,定时器,中断综合运用

实验目的 使用定时器 2 通道 2 来捕获按键 2 按下时间&#xff0c;并通过串口打印。 计一个数的时间&#xff1a;1us&#xff0c;PSC71&#xff0c;ARR65535 下降沿捕获、输入通道 2 映射在 TI2 上、不分频、不滤波输入捕获原理定时器输入捕获实验配置步骤测量按键按下时长思路…

Nacos-2--Nacos1.x版本的通信原理

在Nacos 1.x版本中&#xff0c;客户端长轮询&#xff08;Long Polling&#xff09;和服务端UDP主动推送是两种不同的机制&#xff0c;分别用于配置管理和服务发现场景。它们的核心目标都是实现动态更新的实时感知&#xff0c;但实现方式、数据内容和适用场景完全不同。 1、长轮…

机器学习——09 聚类算法

1 聚类算法聚类算法&#xff1a; 是一种无监督学习算法&#xff0c;它不需要预先知道数据的类别信息&#xff0c;而是根据样本之间的相似性&#xff0c;将样本划分到不同的类别中&#xff1b;不同的相似度计算方法&#xff0c;会得到不同的聚类结果&#xff0c;常用的相似度计算…

生成式AI应用生态的爆发与专业化演进:从零和博弈到正和共赢

2025年,生成式AI产业规模已突破7000亿元,全球生成式AI市场规模预计在2028年达到2842亿美元(IDC数据)。在这场技术革命中,AI基础模型的分化已证明:差异化竞争而非同质化替代,才是推动产业发展的核心逻辑。如今,这一规律正从基础模型层向应用生成平台层蔓延——Lovable、…

Mysql——Sql的执行过程

目录 一、Sql的执行过程流程图解 二、Sql的执行过程流程 1.2.1、建立连接 1.2.2、服务层(缓存、解析器、预处理器、优化器、执行器) 1.2.2.1、缓存 1.2.2.2、解析器 1.2.2.3、预处理器 1.2.2.4、优化器 1.2.2.5、执行器 1.2.3、引擎层 一、Sql的执行过程流程图解 Sql的执行过…

【Axure高保真原型】地图路线和定位

今天和大家分享地图路线和定位的原型模版&#xff0c;载入后&#xff0c;可以查看汽车行进路线和所在定位 提供了停靠和不停靠站点两个案例&#xff0c;具体效果可以打开下方原型地址体验或者点击下方视频观看 【Axure高保真原型】地图路线和定位【原型预览含下载地址】 https…

【96页PPT】华为IPD流程管理详细版(附下载方式)

篇幅所限&#xff0c;本文只提供部分资料内容&#xff0c;完整资料请看下面链接 https://download.csdn.net/download/2501_92808811/91633108 资料解读&#xff1a;华为IPD流程管理详细版 详细资料请看本解读文章的最后内容 华为的集成产品开发&#xff08;IPD&#xff09;…

深度解析Mysql的开窗函数(易懂版)

SQL 开窗函数&#xff08;Window Function&#xff09;是一种强大的分析工具&#xff0c;它能在保留原有数据行的基础上&#xff0c;对 "窗口"&#xff08;指定范围的行集合&#xff09;进行聚合、排名或分析计算&#xff0c;解决了传统GROUP BY聚合会合并行的局限性…

Java静态代理和动态代理

Java静态代理和动态代理 静态代理 现在有一个计算类&#xff0c;有四个方法&#xff0c;加减乘除&#xff0c;如果需要给这四个方法都加上同一个逻辑&#xff0c;可以创建一个类作为代理类&#xff0c;把计算类注入到这个类中&#xff0c;然后再代理类中定义方法&#xff0c;并…

MySQL——MySQL引擎层BufferPool工作过程原理

目录一、MySQL引擎层BufferPool工作过程图解二、MySQL引擎层BufferPool工作过程原理一、MySQL引擎层BufferPool工作过程图解 图解 二、MySQL引擎层BufferPool工作过程原理 首先关闭自动提交&#xff0c;执行一条修改语句。 SET AUTOCOMMIT 0; update employees set name张三…

Python初学者笔记第二十二期 -- (JSON数据解析)

第31节课 JSON数据解析 1.JSON基础概念 JSON 是一种轻量级的数据交换格式&#xff08;另一个叫XML&#xff09;&#xff0c;具有简洁、易读的特点&#xff0c;并且在不同编程语言之间能很好地实现数据传递。在 Python 中&#xff0c;json模块能够实现 Python 数据类型与 JSON 数…

基于多模态大模型的个性化学习路径生成系统研究

摘要 随着互联网技术的迅猛发展&#xff0c;个性化学习路径生成系统的研究在教育领域日益凸显其重要性。本研究聚焦于基于多模态大模型的个性化学习路径生成系统&#xff0c;旨在通过整合多模态数据&#xff0c;为学习者提供更加精准、个性化的学习路径。多模态大模型&#xf…

ESP32 烧录固件失败原因排除

ESP32 烧录固件时&#xff0c;有哪些特殊引脚需要注意电平状态的在 ESP32 烧录固件时&#xff0c;有几个关键引脚的电平状态会直接影响烧录过程&#xff0c;需要特别注意&#xff1a;GPIO0&#xff08;BOOT 引脚&#xff09;&#xff1a;烧录模式&#xff1a;需要拉低&#xff…

3D视觉系统在机器人行业中的应用

视觉引导机器人技术&#xff08;VGR&#xff09;具有成熟的2D成像技术&#xff0c;但是经济高效的3D技术的出现使机器人应用的可能性更大。工业自动化的第一次迭代使用“盲”机器人&#xff0c;该机器人取决于待处理材料的精确定位。这样的机器人相对不灵活&#xff0c;只能通过…