天天看点

Spring MVC 文件下载以及中文文件名的浏览器兼容

下载文件时,如果文件名有中文等非ascii字符时,在不同的浏览器下有不同的表现,部分方案采用判断user-agent来对不同的浏览器做出不同的响应参数处理。

通过搜索,可使用如下的兼容办法:

方式一:

完全兼容现代化的浏览器和旧版IE

Content-Disposition: attachment; filename=%E6%B5%8B%E8%AF%95.txt; filename*=UTF-8''%E6%B5%8B%E8%AF%95.txt
           

方式二:

仅兼容现代化的浏览器

Content-Disposition: attachment; filename*=UTF-8''%E6%B5%8B%E8%AF%95.txt
           

在Spring mvc中的参考代码

import org.apache.commons.io.FileUtils;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.*;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * 文件下载 demo
 *
 * @author liyong 2019-10-05 21:40
 */
@RestController
public class DownloadFileController {

    /**
     * 兼容旧版的IE 和现代浏览器
     *
     * @return
     * @throws IOException
     */
    @RequestMapping(value = "/download/compatible")
    public ResponseEntity<byte[]> download() throws IOException {
        File download = new ClassPathResource("application.properties").getFile();
        HttpHeaders headers = new HttpHeaders();//http头信息
        String fileName = "测试.txt";
        String encodeFileName = URLEncoder.encode(fileName, "UTF-8");
        StringBuilder contentDispositionHeaderValue = new StringBuilder();
        contentDispositionHeaderValue.append("attachment; ").append("filename=").append(encodeFileName).append("; ")
                .append("filename*=").append(encodeHeaderFieldParam(fileName, UTF_8));
        headers.add(HttpHeaders.CONTENT_DISPOSITION, contentDispositionHeaderValue.toString());

        return new ResponseEntity<>(FileUtils.readFileToByteArray(download), headers, HttpStatus.OK);
    }

    /**
     * 仅对现代浏览器兼容,如 IE11 Edge Chrome Firefox
     * IE10 没测试过
     *
     * @return
     * @throws IOException
     */
    @RequestMapping(value = "/download/new/standard")
    public ResponseEntity<byte[]> downloadNewStandard() throws IOException {
        File download = new ClassPathResource("application.properties").getFile();
        HttpHeaders headers = new HttpHeaders();//http头信息
        String fileName = "测试.txt";
        ContentDisposition contentDisposition = ContentDisposition.builder("attachment").filename(fileName, UTF_8).build();
        headers.setContentDisposition(contentDisposition);
        return new ResponseEntity<>(FileUtils.readFileToByteArray(download), headers, HttpStatus.OK);
    }


    
    private StringBuilder encodeHeaderFieldParam(String input, Charset charset) {
        Assert.notNull(input, "Input String should not be null");
        Assert.notNull(charset, "Charset should not be null");
        if (StandardCharsets.US_ASCII.equals(charset)) {
            return new StringBuilder(input);
        }
        Assert.isTrue(UTF_8.equals(charset) || ISO_8859_1.equals(charset),
                "Charset should be UTF-8 or ISO-8859-1");
        byte[] source = input.getBytes(charset);
        int len = source.length;
        StringBuilder sb = new StringBuilder(len << 1);
        sb.append(charset.name());
        sb.append("''");
        for (byte b : source) {
            if (isRFC5987AttrChar(b)) {
                sb.append((char) b);
            } else {
                sb.append('%');
                char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16));
                char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16));
                sb.append(hex1);
                sb.append(hex2);
            }
        }
        return sb;
    }

    private static boolean isRFC5987AttrChar(byte c) {
        return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
                c == '!' || c == '#' || c == '$' || c == '&' || c == '+' || c == '-' ||
                c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~';
    }
}
           

标准

https://tools.ietf.org/html/rfc2183   描述的Content-Disposition header 规范

https://tools.ietf.org/html/rfc5987 描述的是HTTP header的编码规范,因为header的value默认只能是ISO-8859-1

参考资料

https://blog.csdn.net/liuyaqi1993/article/details/78275396

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Disposition

继续阅读