请选择 进入手机版 | 继续访问电脑版
本站特色:极好的技术研究氛围!所有技术发帖,必有回复! 做最受欢迎的Java论坛

疯狂Java联盟

 找回密码
 加入联盟
查看: 194|回复: 7

SpringMVC不使用Servlet API实现文件下载

[复制链接]
发表于 2017-11-30 22:09:04 | 显示全部楼层 |阅读模式
本帖最后由 luo_wenqiang 于 2017-11-30 22:21 编辑

是否通过SpringMVC下载文件,其实原理是一样的,无非就是要设置3个响应头:
  • Content-Length : 下载的内容长度,以字节计算
  • Content-Type    : 内容类型,使用MIME-Type(多用途互联网邮件扩展类型)
  • Content-Disposition : 处理方式


其中Content-Disposition响应头在不同的浏览器上面处理还是不同的,所以需要特别处理!有下面两种主要方式:
  • attachment; filename=FILE_NAME   其中FILE_NAME是用URLEncoder编码后的文件名,用UTF-8的编码。支持IE的文件名处理。
  • attachment; filename*=UTF-8''FILE_NAME 这种方式可以指定FILE_NAME的字符编码,其中FILE_NAME也是要用URLEncoder进行编码!支持非
    IE浏览器。


特别两个问题:
  • 使用非IE浏览器,并且通过*=''方式指定字符编码的时候,必须要确保filename后面有个*,并且在字符编码后面必须跟着两个单引号!格式非常重要哦,否则浏览器不认识的。
  • 文件下载的时候,URL最好不要有中文名。通常来讲,文件上传到服务器以后,不会直接把文件名给用户的,而是把文件的ID给用户;下载时通过文件的ID来找到实际的文件信息。


ResponseEntity对象,是SpringMVC里面完全的自定义响应体,包括响应码、响应头、响应体都可以自定义。这里就是利用ResponseEntity实现文件的下载,这种方式脱离了Servlet的API,但是也对程序做出了限制:文件不能超过2G、下载大文件时需要较大内存。

  • @RequestMapping(value = "/{xx}", method = RequestMethod.GET)
  • public ResponseEntity<byte[]> download(//
  •                 @PathVariable("xx") String id, // 文件id
  •                 @RequestHeader("User-Agent") String userAgent// 浏览器版本
  •     ) throws UnsupportedEncodingException {
  •         // 根据文件的id获取文件信息,里面包括文件大小、中文文件名、文件的内容类型
  •         FileInfo info = this.fileService.getById(id);
  •         if (info == null) {
  •                 // 404
  •                 log.error("无法根据路径找到对应的文件信息");
  •                 return ResponseEntity.notFound().build();
  •         } else {
  •                 // 构建响应消息
  •                 // ok() 其实就是 HTTP 200 响应
  •                 BodyBuilder builder = ResponseEntity.ok();
  •                 builder.contentLength(info.getFileLength());// 内容长度
  •                 builder.contentType(//
  •                                 MediaType.parseMediaType(info.getContentType())// 内容类型
  •                 );
  •                 // 获得实际的文件名,实际的开发中需要根据不同的浏览器来进行判断做不同的编码
  •                 // 实际的浏览器类型可以通过请求头来获取
  •                 String name = info.getName();
  •                 name = URLEncoder.encode(name, "UTF-8");
  •                 // 设置实际的响应文件名,告诉浏览器文件要用于【下载】、【保存】
  •                 // 不同的浏览器,处理方式不同,要根据浏览器版本进行区别判断
  •                 if (userAgent.indexOf("MSIE") > 0) {
  •                         // 如果是IE,只需要用UTF-8字符集进行URL编码即可
  •                         builder.header("Content-Disposition", "attachment; filename=" + name);
  •                 } else {
  •                         // 而Google、FireFox、Chrome等浏览器,则需要说明编码的字符集
  •                         // 注意filename后面有个*号,在UTF-8后面有两个单引号!
  •                         builder.header("Content-Disposition", "attachment; filename*=UTF-8''" + name);
  •                 }
  •                 // 根据实际的文件路径得到文件,并且转换为byte[]
  •                 ByteArrayOutputStream out = new ByteArrayOutputStream();
  •                 try (FileInputStream in = new FileInputStream(info.getPath())) {
  •                         // 把输入流里面的信息,读取出来转换为byte[]
  •                         byte[] buf = new byte[1024];
  •                         for (int count = in.read(buf); count != -1; count = in.read(buf)) {
  •                                 out.write(buf, 0, count);
  •                         }
  •                         byte[] data = out.toByteArray();
  •                         // 构建响应体
  •                         ResponseEntity<byte[]> entity = builder.body(data);
  •                         return entity;
  •                 } catch (IOException e) {
  •                         log.error("找到了对应的文件信息,但是读取文件内容失败:" + e.getLocalizedMessage(), e);
  •                         // 文件没有找到,或者读取失败
  •                         return ResponseEntity.notFound().build();
  •                 }
  •         }
  • }

有问题,请接着看后面的回帖!



 楼主| 发表于 2017-11-30 22:13:41 | 显示全部楼层
本帖最后由 luo_wenqiang 于 2017-11-30 22:19 编辑

自己回复一下自己:如果下载的文件大于2G怎么办?如果不想要占用那么大的内存怎么办?SpringMVC已经提出了非常完美的解决方案,那就是:StreamingResponseBody!请继续看下面两个回帖!
 楼主| 发表于 2017-11-30 22:14:01 | 显示全部楼层
  1. @RequestMapping("/download")
  2. public StreamingResponseBody handle() {
  3.     return new StreamingResponseBody() {
  4.         @Override
  5.         public void writeTo(OutputStream outputStream) throws IOException {
  6.             // write...
  7.         }
  8.     };
  9. }
复制代码
 楼主| 发表于 2017-11-30 22:18:44 | 显示全部楼层
还能够这样干:
  1. @RequestMapping(value = "/{xx}", method = RequestMethod.GET)
  2. public ResponseEntity<StreamingResponseBody> download(//
  3.                 @PathVariable("xx") String id, // 文件id
  4.                 @RequestHeader("User-Agent") String userAgent// 浏览器版本
  5.     ) throws UnsupportedEncodingException {

  6.     BodyBuilder builder = ResponseEntity.ok();
  7.     builder.contentLength(info.getFileLength());// 内容长度
  8.     builder.contentType(//
  9.         MediaType.parseMediaType(info.getContentType())// 内容类型
  10.     );

  11.     //
  12.     StreamingResponseBody body = new StreamingResponseBody(){
  13.         @Override
  14.         public void writeTo(OutputStream outputStream) throws IOException {
  15.             // write...
  16.         }
  17.     };

  18.     ResponseEntity<byte[]> entity = builder.body(body);
  19.     return entity;
  20. }
复制代码
发表于 2017-12-1 21:53:11 | 显示全部楼层
嗯,这个比较实用。
发表于 2017-12-1 23:55:16 | 显示全部楼层
强哥就是强啊,可以转载吗?
 楼主| 发表于 2017-12-2 20:02:18 | 显示全部楼层
eddie 发表于 2017-12-1 23:55
强哥就是强啊,可以转载吗?

转载请说明来源哦
您需要登录后才可以回帖 登录 | 加入联盟

本版积分规则

视频、代码、电子书下载
请关注"疯狂图书"公众号
QQ交流1群: 545923995  未满

小黑屋|手机版|Archiver|疯狂Java联盟 ( 粤ICP备11063141号 )

GMT+8, 2017-12-18 20:40 , Processed in 0.325730 second(s), 6 queries , File On.

快速回复 返回顶部 返回列表