01 背景
在后端开发中,通常会有文件下载的需求,常用的解决方案有两种:
- 不通过后端应用,直接使用
nginx直接转发文件地址下载(适用于一些公开的文件,因为这里不需要授权) - 通过后端进行下载,同时进行一些业务处理
本篇主要以方法2进行介绍,方法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 总结
一定要搞清楚InputStream和OutputStream的区别,如果搞不清楚的,可以和字符流进行映射,InputStream -> Reader,OutPutStream -> Writer,换成这样你就知道读取内容需要使用Reader,写入需要使用Writer了。
返回给前端的是输出流,不需要你显示的去返回(return response;),这样会报错
《SpringBoot返回文件让前端下载的几种方式》 是转载文章,点击查看原文。
