本文介绍了Spring Boot应用中常见的文件下载场景。
单个文件下载
关键点如下:
- 获取文件的大小。
- 获取文件的媒体类型(
Content-Type
)。 - 通过
ContentDisposition
工具类构建Content-Disposition
头,避免下载文件名称乱码的问题。 - copy数据到客户端。
package io.springboot.controller;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@Controller
@RequestMapping("/download")
public class DownloadController {
@GetMapping
public void download (HttpServletRequest request, HttpServletResponse response) throws IOException {
// 要下载的文件
Path file = Paths.get("E:\\test.mp4");
// 获取文件的媒体类型
String contentType = Files.probeContentType(file);
if (contentType == null) {
// 如果获取失败,则使用通用的文件类型
contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
}
response.setContentType(contentType);
// 文件大小
response.setContentLengthLong(Files.size(file));
/**
* 使用 ContentDisposition 构建 CONTENT_DISPOSITION 头,可以避免文件名称乱码的问题
*/
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.attachment()
.filename(file.getFileName().toString(), StandardCharsets.UTF_8)
.build()
.toString());
// 响应数据给客户端
Files.copy(file, response.getOutputStream());
}
}
使用Gzip压缩文件
如果下载的文件特别大的话,可以考虑使用gzip压缩后下载,可以减少传输字节,节省流量,但是因为使用gzip编码会耗费额外的CPU。
package io.springboot.controller;
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 org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@Controller
@RequestMapping("/download")
public class DownloadController {
@GetMapping("/gzip")
public void gzipDownload (HttpServletRequest request, HttpServletResponse response) throws IOException {
Path file = Paths.get("E:\\test.mp4");
String contentType = Files.probeContentType(file);
if (contentType == null) {
contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
}
// 告诉客户端,文件使用了 gzip 编码,客户端会自动解码
response.setHeader(HttpHeaders.CONTENT_ENCODING, "gzip");
response.setContentType(contentType);
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.attachment()
.filename(file.getFileName().toString(), StandardCharsets.UTF_8)
.build()
.toString());
// 使用Gzip压缩后,响应给客户端
try(GZIPOutputStream gzipOutputStream = new GZIPOutputStream(response.getOutputStream())){
Files.copy(file, gzipOutputStream);
}
}
}
一次性下载多个文件
如果需要一次性下载多个文件,那么需要服务器把多个文件都打包到一个zip文件中,再响应给客户端,客户端可以自己解压zip文件来获取多个文件。
package io.springboot.controller;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Controller;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@GetMapping("/zip")
public void zipDownload (HttpServletRequest request, HttpServletResponse response) throws IOException {
// 要下载的文件列表
List<Path> files = Arrays.asList(Paths.get("E:\\test.mp4"),
Paths.get("E:\\node.txt"),
Paths.get("E:\\keys.txt"));
response.setContentType("application/zip"); // zip压缩
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.attachment()
.filename("download.zip", StandardCharsets.UTF_8)
.build()
.toString());
// 压缩多个文件到zip文件中,并且响应给客户端
try(ZipOutputStream zipOutputStream = new ZipOutputStream(response.getOutputStream())){
for (Path file : files) {
try (InputStream inputStream = Files.newInputStream(file)) {
zipOutputStream.putNextEntry(new ZipEntry(file.getFileName().toString()));
StreamUtils.copy(inputStream, zipOutputStream);
zipOutputStream.flush();
}
}
}
}
}
完!