Spring MVC文件下载及中文文件名乱码解决

来源:互联网 发布:淘宝十大人气主播 编辑:程序博客网 时间:2024/04/28 20:36

原文地址:http://www.jianshu.com/p/58e605d881e3

内容参考 Paul Deck 著的《Spring MVC学习指南1 2版》

将文件这样的资源发送到浏览器,需要在controller中完成以下工作:

  1. 在请求的处理方法中使用void返回类型,并在方法中添加HttpServletResponse
  2. 设置Content-Type, Content-Type就是常说的MIME类型,将响应的内容类型设为文件的内容类型。Content-Type header 在某个实体的body中定义数据类型, 包含media type(媒体类型)和subtype identifiers(子类标识符)。访问http://www.iana.org/assignments/media-types 以了解更多的标准内容类型(standard content types)。如果不清楚如何设置Content-Type,要达到下载资源的效果,可以将Content-Type设置为application/octet-stream(不区分大小写)。
  3. 设置一个 HTTP response header: Content-Disposition, 值为attachment; filename=fileName, 这个fileName就是下载文件后的文件名,这个文件名有可能会和这里设置fileName不一样。

上码:
方式1:读取文件作为FileInputStream, 将内容加载到一个字节数组。随后获取HttpServletResponse的OutputStream,并调用write方法传入字节数组。
方式2:使用Java NIO's Files.copy()方法:

Path file = Paths.get(...);Files.copy(file, response.getOutputStream());

这样代码少、而且运行得更快。

看码吧:

@Controllerpublic class DownloadController {        @RequestMapping(value="/download-resource")    public void downloadResource(HttpServletResponse response) {       // 文件保存在/WEB_INF/data目录下         String dataDirectory = request.                getServletContext().getRealPath("/WEB-INF/data");        File file = new File(dataDirectory, "sunny.pdf");        if (file.exists()) {            response.setContentType("application/pdf");            response.addHeader("Content-Disposition",                     "attachment; filename=sunny.pdf");            byte[] buffer = new byte[1024];            FileInputStream fis = null;            BufferedInputStream bis = null;            // if using Java 7, use try-with-resources            try {                fis = new FileInputStream(file);                bis = new BufferedInputStream(fis);                OutputStream os = response.getOutputStream();                int i = bis.read(buffer);                while (i != -1) {                    os.write(buffer, 0, i);                    i = bis.read(buffer);                }            } catch (IOException ex) {                // do something,                 // probably forward to an Error page            } finally {                if (bis != null) {                    try {                        bis.close();                    } catch (IOException e) {                    }                }                if (fis != null) {                    try {                        fis.close();                    } catch (IOException e) {                    }                }            }        }    }    @RequestMapping(value="/download-resource1")    public void downloadResource(HttpServletResponse response) {        String dataDirectory = request.                getServletContext().getRealPath("/WEB-INF/data");        Path file = Paths.get(dataDirectory, "sunny.pdf");        if (Files.exists(file)) {            response.setContentType("application/pdf");            String fileName = "sunny.pdf";            response.addHeader("Content-Disposition",                     "attachment; filename=" + fileName);/*如果文件名有中文的话,进行URL编码,让中文正常显示response.addHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));*/            try {                Files.copy(file, response.getOutputStream());            } catch (IOException ex) {            }        }    }}

针对中文文件名下载后乱码分析:

上面有说

response.addHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));

这行代码是能解决中文文件名乱码问题的,但在Safari浏览器下下载的中文文件名还是乱码的,下面提供一个解决方案,看代码:

@RequestMapping(value = "/faq")    public void downloadFAQPdf(HttpServletResponse response) throws Exception {        String fileName = "帮助说明.pdf";// 获取classpath下的文件        URL url = UserController.class.getClassLoader().getResource("data/" + fileName);        Path file = Paths.get(url.toURI());        fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString());        response.setContentType(MediaType.APPLICATION_OCTET_STREAM.toString());// 解决中文文件名乱码关键行         response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"; filename*=utf-8''" + fileName);        Files.copy(file, response.getOutputStream());    }

具体原因我就不细说了,参看下面这个链接(有详细的描述):

在项目中发现,使用Safari下载中文名文件的时候,下载的文件名是乱码的问题。但是这个问题在IE,Firefox,Chrome中 是没有的。原先以为是Safari的bug,但是细细研究之下你会发现这个简单的文件下载问题在HTTP协议里经历了多少波折。

研究的结果也不是我自己写的,具体可以看这两篇文章

  • Serving file downloads with non-ascii
  • 正确处理下载文件时HTTP头的编码问题(Content-Disposition)

这里直接贴出java代码

/*** <pre>* 浏览器下载文件时需要在服务端给出下载的文件名,当文件名是ASCII字符时没有问题* 当文件名有非ASCII字符时就有可能出现乱码** 这里的实现方式参考这篇文章* http://blog.robotshell.org/2012/deal-with-http-header-encoding-for-file-download/** 最终设置的response header是这样:** Content-Disposition: attachment;*                      filename="encoded_text";*                      filename*=utf-8''encoded_text** 其中encoded_text是经过RFC 3986的“百分号URL编码”规则处理过的文件名* </pre>* @param response* @param filename* @return*/public static void setFileDownloadHeader(HttpServletResponse response, String filename) {  String headerValue = "attachment;";  headerValue += " filename=\"" + encodeURIComponent(filename) +"\";";  headerValue += " filename*=utf-8''" + encodeURIComponent(filename);  response.setHeader("Content-Disposition", headerValue);}/*** <pre>* 符合 RFC 3986 标准的“百分号URL编码”* 在这个方法里,空格会被编码成%20,而不是+* 和浏览器的encodeURIComponent行为一致* </pre>* @param value* @return*/public static String encodeURIComponent(String value) {  try {    return URLEncoder.encode(value, "UTF-8").replaceAll("\\+", "%20");  } catch (UnsupportedEncodingException e) {    e.printStackTrace();    return null;  }}

阅读全文
0 0
原创粉丝点击