SpringBoot返回文件让前端下载的几种方式

作者:N***p365日期:2025/12/4

01 背景

在后端开发中,通常会有文件下载的需求,常用的解决方案有两种:

  1. 不通过后端应用,直接使用nginx直接转发文件地址下载(适用于一些公开的文件,因为这里不需要授权)
  2. 通过后端进行下载,同时进行一些业务处理

本篇主要以方法2进行介绍,方法2的原理步骤如下:

  1. 读取文件,得到文件的字节流
  2. 将字节流写入到响应输出流中
02 一次性读取到内存,通过响应输出流输出到前端
1    @GetMapping("/file/download")
2    public void fileDownload(HttpServletResponse response, @RequestParam("filePath") String filePath) {
3        File file = new File(filePath);
4        if (!file.exists()) {
5            throw new BusinessException("当前下载的文件不存在,请检查路径是否正确");
6        }
7
8        // 将文件写入输入流
9        try (InputStream is = new BufferedInputStream(Files.newInputStream(file.toPath()))) {
10            
11            // 一次性读取到内存中
12            byte[] buffer = new byte[is.available()];
13            int read = is.read(buffer);
14
15            // 清空 response
16            response.reset();
17            response.setCharacterEncoding("UTF-8");
18
19            // Content-Disposition的作用:告知浏览器以何种方式显示响应返回的文件,用浏览器打开还是以附件的形式下载到本地保存
20            // attachment表示以附件方式下载   inline表示在线打开   "Content-Disposition: inline; filename=文件名.mp3"
21            // filename表示文件的默认名称,因为网络传输只支持URL编码的相关支付,因此需要将文件名URL编码后进行传输,前端收到后需要反编码才能获取到真正的名称
22            response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(file.getName(), "UTF-8"));
23
24            // 告知浏览器文件的大小
25            response.addHeader("Content-Length", "" + file.length());
26
27            OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());
28            response.setContentType("application/octet-stream");
29            outputStream.write(buffer);
30            outputStream.flush();
31            outputStream.close();
32
33        } catch (IOException e) {
34            throw new RuntimeException(e);
35        }
36
37    }
38

适用于小文件,如果文件过大,一次性读取到内存中可能会出现oom的问题

02 将文件流通过循环写入到响应输出流中(推荐)
1    @GetMapping("/file/download")
2    public void fileDownload(HttpServletResponse response, @RequestParam("filePath") String filePath) {
3        File file = new File(filePath);
4        if (!file.exists()) {
5            throw new BusinessException("当前下载的文件不存在,请检查路径是否正确");
6        }
7
8        // 清空 response
9        response.reset();
10        response.setCharacterEncoding("UTF-8");
11
12        response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(file.getName(), "UTF-8"));
13        response.setContentType("application/octet-stream");
14
15        // 将文件读到输入流中
16        try (InputStream is = new BufferedInputStream(Files.newInputStream(file.toPath()))) {
17            
18            OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());
19            
20            byte[] buffer = new byte[1024];
21            int len;
22
23            //从输入流中读取一定数量的字节,并将其存储在缓冲区字节数组中,读到末尾返回-1
24            while((len = is.read(buffer)) > 0){
25                outputStream.write(buffer, 0, len);
26            }
27
28            outputStream.close();
29
30        } catch (IOException e) {
31            throw new RuntimeException(e);
32        }
33
34    }
35
03 从网络上获取文件并返回给前端
1    @GetMapping("/net/download")
2    public void netDownload(HttpServletResponse response, @RequestParam("fileAddress") String fileAddress, @RequestParam("filename") String filename) {
3
4        try {
5            URL url = new URL(fileAddress);
6            URLConnection conn = url.openConnection();
7            InputStream inputStream = conn.getInputStream();
8
9            response.reset();
10            response.setContentType(conn.getContentType());
11
12            response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(filename, "UTF-8"));
13
14            byte[] buffer = new byte[1024];
15            int len;
16            
17            OutputStream outputStream = response.getOutputStream();
18            
19            while ((len = inputStream.read(buffer)) > 0) {
20                outputStream.write(buffer, 0, len);
21            }
22            
23            inputStream.close();
24            
25        } catch (IOException e) {
26            throw new RuntimeException(e);
27        }
28        
29    }
30
04 从网络上获取文本并下载到本地
1    @GetMapping("/netDownloadLocal")
2    public void downloadNet(@RequestParam("netAddress") String netAddress, @RequestParam("filepath") String filepath) {
3
4        try {
5            URL url = new URL(netAddress);
6            URLConnection conn = url.openConnection();
7            InputStream inputStream = conn.getInputStream();
8
9            FileOutputStream fileOutputStream = new FileOutputStream(filepath);
10            int byteread;
11            byte[] buffer = new byte[1024];
12
13            while ((byteread = inputStream.read(buffer)) != -1) {
14                fileOutputStream.write(buffer, 0, byteread);
15            }
16
17            fileOutputStream.close();
18        } catch (IOException e) {
19            throw new RuntimeException(e);
20        }
21
22    }
23
05 总结

一定要搞清楚InputStreamOutputStream的区别,如果搞不清楚的,可以和字符流进行映射,InputStream -> Reader,OutPutStream -> Writer,换成这样你就知道读取内容需要使用Reader,写入需要使用Writer了。

返回给前端的是输出流,不需要你显示的去返回(return response;),这样会报错


SpringBoot返回文件让前端下载的几种方式》 是转载文章,点击查看原文


相关推荐


【鸿蒙开发案例篇】快速掌握使用NAPI调用C标准库的功能
威哥爱编程2025/12/1

大家好,我是 V 哥。今天我们来深入探讨在鸿蒙 6.0(API 21)开发中,如何通过 NAPI(Native API)框架调用 C 标准库的功能。NAPI 是连接 ArkTS 应用层与 C/C++ 原生代码的关键桥梁,能够有效提升计算密集型任务的执行效率。 联系V哥获取 鸿蒙学习资料 一、NAPI 基础与项目结构 技术架构: ArkTS 业务层 → NAPI 接口桥接 → C++ 原生逻辑 → C 标准库函数 NAPI 将 ECMAScript 标准中的数据类型(如 Number、String


一文搞懂 Webpack 分包:async、initial 与 all 的区别【附源码】
程序员大卫2025/11/29

大家好,我是前端架构师,关注微信公众号【程序员大卫】免费领取精品资料。 1. 背景 最近在优化一个项目的加载性能时,对 optimization.splitChunks.chunks 的三个可选值 async、initial 和 all 的具体效果产生了疑惑。为了彻底搞清楚它们的区别,我专门搭建了一个 Demo 进行对比研究。 2. 核心区别:async vs initial chunks 属性决定了 Webpack 对哪些类型的代码块进行分割。其中 async 是默认配置。 经过测试发现:在单


用LLM+CadQuery自动生成CAD模型:CAD-Coder让文本秒变3D零件
安意诚Matrix2025/11/26

你有没有过这样的经历?想画个法兰盘的CAD模型,得打开专业软件、调坐标系、画草图、做拉伸……步骤繁琐还得熟稔操作逻辑。现在,基于大语言模型(LLM)和CadQuery的CAD-Coder方案,能让你输入一段零件描述,直接生成可执行的CAD代码——今天我们就来拆解这个让CAD建模“动口不动手”的技术! 一、从文本到3D模型:不同表示方式的对比 先看一个法兰盘的例子: 文本描述:只需要说明“120mm直径的金属法兰盘,15mm厚,中心50mm孔,6个12mm安装孔(分布在90mm圆周),还有70m


SpringMVC的工作流程
q***48252025/11/24

1.工作流程 (1)用户通过浏览器向服务器发送请求,请求会被Spring MVC的前端控制器DispatcherServlet拦截。 (2)DispatcherServlet拦截到请求后,会调用HandlerMapping(处理器映射器)。 (3)处理器映射器根据请求URL找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。 (4)DispatcherServlet会通过返回信息选择合适的HandlerAdapter(处理器适配器)。 (


Spring Boot接收参数的19种方式
a***56062025/11/22

Spring Boot是一个强大的框架,允许开发人员通过多种方式接收和处理参数。无论是HTTP请求参数、路径变量,还是请求体中的数据,Spring Boot都能提供灵活的处理方式。本文将介绍19种不同的方式来接收参数。 1. 查询参数(Query Parameters) 使用@RequestParam注解接收查询参数。适用于GET请求。 @GetMapping("/greet") public String greet(@RequestParam String name) { retur


为什么 VARCHAR(1000) 存不了 1000 个汉字? —— 详解主流数据库“字段长度”的底层差异
G探险者2025/11/20

大家好,我是G探险者! 在开发过程中,我们经常遇到这样的灵异现象:明明数据库字段设置为 VARCHAR(1000),前端校验也限制了 1000 个字,但用户提交时后端却报错:“Data too long”。 这并非因为数据库坏了,而是因为不同的数据库对**“长度(Length)”**的定义完全不同。有的按“人眼看到的字”算,有的按“计算机存储的字节”算。 本文将横向对比主流数据库(MySQL, Oracle, SQL Server, PostgreSQL, 达梦)的字段长度表现,并给出避坑建议。


django测试缓存命令的解读
一路生花工作室2025/11/19

命令如下: python manage.py test_cache_performance --clear-cache --iterations 5 是 Django 自定义管理命令(custom management command) 的一种调用方式,用于测试缓存性能。下面逐部分解释其含义: 🔹 1. python manage.py Django 项目的标准命令行入口。所有 Django 内置或自定义的管理命令都通过它执行。 🔹 2. test_cache_performa


深入浅出蓝桥杯:算法基础概念与实战应用(三)搜索
铭哥的编程日记2025/11/18

算法基础概念与实战应用(三)搜索 文章目录 算法基础概念与实战应用(三)搜索1.1 深度优先搜索 - DFS1.1.1 枚举⼦集1.1.2 组合型枚举1.1.3 枚举排列1.1.4 全排列问题 1.2 DFS1.2.1 选数1.2.2 ⻜机降落 整体源代码总结 1.1 深度优先搜索 - DFS 1.1.1 枚举⼦集 代码如下(示例): #include <iostream> using namespace std; int n;


Snapchat 开源全新跨平台框架 Valdi ,一起来搞懂它究竟有什么特别之处
恋猫de小郭2025/11/17

最近看到好几篇在推 Valdi 的文章,大致意思就是 「RN/Flutter 的地位将受到威胁」,「Valdi 将成为全新的跨平台流行架构」云云,这不仅就让我好奇这个新框架有什么魔力,还能在 2025 的跨平台领域玩出新花样? 首先,Valdi 是由 Snapchat 开源的跨平台框架,其核心技术已在 Snap 的生产应用中验证长达 8 年,号称在不牺牲开发速度的前提下提供原生性能 ,那它是怎么做到的? 简单来说,Valdi 是一个 “用 TypeScript(TSX)写 UI,然后编


Java游戏高级编程 | 深入探索游戏引擎与优化技巧
mfnart_2822025/11/15

在线编译C语言|探索在线编译器的优势与应用在线编译C语言是一项非常实用的技术,它使得程序员能够在没有本地开发环境的情况下直接在浏览器中编写、调试和执行C语言代码。在线编译器通过提供一个即时反馈的开发环境,大大提高了学习和工作的效率,尤其对于初学者而言,更是降低了编程的门槛。相比传统的桌面编译器,在线编译器的优势在于其便捷性和灵活性。用户不需要安装任何开发工具,只需在浏览器中输入代码,点击“编译”按钮,就可以立即看到运行结果。这种即时性大大减少了开发中的等待时间,尤其是在调试阶段,可以快速查看修改

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2025 聚合阅读