现象

开发了一个下载文件的接口,使用 Content-Disposition 指定默认下载附件文件名,但是每次下载默认文件名与指定的不一致

基础环境

  • JDK 8
  • SpringBoot 2.2.5

接口代码

出于某些原因,这里只展示下载相关的代码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.CacheControl;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

import java.io.File;
import java.net.URLEncoder;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping({"/api/v1"})
public class DownloadController {
    private Logger logger = LoggerFactory.getLogger(getClass());

    @GetMapping("/download/{id}")
    @ApiOperation("Download file by id")
    public ResponseEntity<FileSystemResource> downloadFile(@PathVariable("id") Long id) throws Exception {
        String url = String.format("data/%s.pdf", id);
        File file = new File(url);
        if (!file.isFile()) {
            logger.error("ERROR download file, can not find the file, ID: {}, url: {}", id, url);
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity
                .ok()
                .header("Content-Disposition", "attachment; filename=" + URLEncoder.encode(file.getName(), "utf-8"))
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .contentLength(file.length())
                .cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS))
                .body(new FileSystemResource(file));
    }
}

具体现象

在点击下载的过程中,如果下载的文件ID为 1 时,默认下载的文件名则为 1, 而非Response header 中设置的filename 1.pdf

原因排查

确定排查范围

接口已经经过测试,除默认设置的文件名不生效外,暂未发现其他问题。而默认设置的文件名由 .header("Content-Disposition", "attachment; filename=" + URLEncoder.encode(file.getName(), "utf-8")) 这一行控制,则将范围设置为 Response header Content-Disposition 相关的问题

查询相关信息

在确定了排查范围之后,根据相关的关键词查询信息。

  1. 根据关键词 http response attachment filename 搜索相关的信息,在MDN Web Docs上找到了对于Content-Disposition的描述
  2. MDN Web Docs中描述的格式如下 Content-Disposition: attachment; filename="filename.jpg"
  3. 将接口返回的结果 Content-Disposition: attachment; filename=filename.pdf 与文档描述进行对比,发现 filename= 后面缺失了 " 符号。
  4. 修改相关的代码,添加 " 符号,将相关的代码修改如下后进行测试
    1
    2
    3
    4
    5
    6
    7
    
            return ResponseEntity
                 .ok()
                 .header("Content-Disposition", "attachment; filename=\"" + URLEncoder.encode(file.getName(), "utf-8") + "\"")
                 .contentType(MediaType.APPLICATION_OCTET_STREAM)
                 .contentLength(file.length())
                 .cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS))
                 .body(new FileSystemResource(file));
    

解决方案

修改代码,在 filename= 后面添加 "