Springboot 文件下载

1. 前言

本文介绍了Springboot应用中三种常见的文件下载场景:

  • 单个文件下载:需要获取文件的大小和媒体类型,并使用ContentDisposition工具类构建Content-Disposition头,避免下载文件名称乱码问题。
  • 使用Gzip压缩文件:需要告诉客户端Gzip编码,并且在响应中使用GZIPOutputStream进行压缩。
  • 一次性下载多个文件:需要把多个文件都打包到一个zip文件中,再响应给客户端,客户端可以自己解压zip文件获取多个文件

2. 单个文件下载

关键点如下:

  1. 获取文件大小
  2. 获取文件的媒体类型(Content-Type)
  3. 通过ContentDisposition工具构建Content-Disposition头,避免下载文件名乱码
  4. copy数据到客户端

具体实现如下:

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
39
40
41
42
43
44
45
46
47
48
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
* @author xiaoyuge
*/
@Slf4j
@RestController
public class DownloadController {

@GetMapping("/download")
public void downloadFile(HttpServletRequest request, HttpServletResponse response) {
Path file = Paths.get("/Users/xiaoyuge/Desktop/download.txt");
try {
// 获取文件的媒体类型
String contentType = Files.probeContentType(file);
if (StringUtils.isBlank(contentType)) {
contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
}
response.setContentType(contentType);
//文件大小
response.setContentLengthLong(Files.size(file));

//使用 ContentDisposition 构建 CONTENT_DISPOSITION 头,可以避免文件名称乱码的问题
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.builder("attachment")
.filename(file.getFileName().toString(), StandardCharsets.UTF_8).build()
.toString());

//响应给客户端
Files.copy(file, response.getOutputStream());
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}

3. 使用Gzip压缩文件

如果下载的文件特别大的话,可以考虑使用Gzip压缩后下载,可以减少传输字节,节省流量,但是因为使用Gzip编码会耗费额外的CPU

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.zip.GZIPOutputStream;
import java.util.zip.ZipFile;

/**
* @author xiaoyuge
*/
@Slf4j
@RestController
public class DownloadController {


@GetMapping("/gzip")
public void gzip(HttpServletRequest request, HttpServletResponse response) {
Path file = Paths.get("/Users/xiaoyuge/Desktop/download.txt");
try {
// 获取文件的媒体类型
String contentType = Files.probeContentType(file);
if (StringUtils.isBlank(contentType)) {
contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
}
response.setHeader(HttpHeaders.CONTENT_ENCODING, "gzip");
response.setContentType(contentType);

//使用 ContentDisposition 构建 CONTENT_DISPOSITION 头,可以避免文件名称乱码的问题
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.builder("attachment")
.filename(file.getFileName().toString(), StandardCharsets.UTF_8).build()
.toString());

//使用Gzip压缩后,响应给客户端; try()用来管理释放资源

gzip(request, response);
try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(response.getOutputStream())) {
Files.copy(file, gzipOutputStream);
}
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}

4. 一次性下载多个文件

如果需要一次性下载多个文件,那么徐亚服务器把多个文件都打包到一个zip文件中,在响应给客户端

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
@Slf4j
@RestController
public class DownloadController {

@GetMapping("/zip")
public void zip(HttpServletRequest request, HttpServletResponse response) {

List<Path> files = new ArrayList<Path>() {{
add(Paths.get("/Users/xiaoyuge/Desktop/download.txt"));
add(Paths.get("/Users/xiaoyuge/Desktop/download2.txt"));
}};
response.setContentType("application/zip");
//使用 ContentDisposition 构建 CONTENT_DISPOSITION 头,可以避免文件名称乱码的问题
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.builder("attachment")
.filename("download.zip", StandardCharsets.UTF_8).build()
.toString());
try (ZipOutputStream zipOutputStream = new ZipOutputStream(response.getOutputStream())) {
for (Path file : files) {
try (InputStream in = Files.newInputStream(file)) {
zipOutputStream.putNextEntry(new ZipEntry(file.getFileName().toString()));
StreamUtils.copy(in, zipOutputStream);
zipOutputStream.flush();
}
}
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}