Win、JDK 17、 Spring Boot 3.1.2
以下内容来自 mdn web docs
在常规的 HTTP 应答中,Content-Disposition 响应标头指示回复的内容该以何种形式展示,是以内联的形式(即网页或者页面的一部分),还是以附件的形式下载并保存到本地。
在 multipart/form-data 类型的应答消息体中,Content-Disposition 通用标头可以被用在 multipart 消息体的子部分中,用来给出其对应字段的相关信息。各个子部分由在 Content-Type 中定义的边界(boundary)分隔。用在消息体自身则无实际意义。
Content-Disposition 标头最初是在 MIME 标准中定义的,HTTP 表单及 POST 请求只用到了其所有参数的一个子集。只有 form-data 以及可选的 name 和 filename 三个参数可以应用在 HTTP 上下文中。
在 HTTP 场景中,第一个参数或者是 inline(默认值,表示回复中的消息体会以页面的一部分或者整个页面的形式展示),或者是 attachment(意味着消息体应该被下载到本地;大多数浏览器会呈现一个“保存为”的对话框,将 filename 的值预填为下载后的文件名,假如它存在的话)。
Content-Disposition: inline
Content-Disposition: attachment
Content-Disposition: attachment; filename=“filename.jpg”
@GetMapping("/download-0/{filename}")
public void download0(@PathVariable("filename") String filename, HttpServletResponse resp) throws IOException {
Path path = Paths.get("upload", filename);
File file = path.toFile();
// 检查文件是否存在并可读
if (!file.exists() || !file.canRead()) {
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
// 设置下载响应头
resp.setContentType("application/octet-stream");
resp.setHeader("Content-Disposition", "attachment; filename=" + filename);
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
ServletOutputStream sos = resp.getOutputStream()) {
sos.write(bis.readAllBytes());
}
}
设置 Content-Disposition 硬编码较多,不够优雅,使用 Spring 提供的 ContentDisposition 类优化:
ContentDisposition contentDisposition = ContentDisposition
.attachment()
.filename(filename,StandardCharsets.UTF_8)
.build();
resp.setHeader(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString());
@GetMapping("/download-1/{filename}")
public void download1(@PathVariable("filename") String filename, HttpServletResponse resp) throws IOException {
Path path = Paths.get("upload", filename);
File file = path.toFile();
// 检查文件是否存在并可读
if (!file.exists() || !file.canRead()) {
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
resp.setContentType("application/octet-stream");
ContentDisposition contentDisposition = ContentDisposition
.attachment()
.filename(filename, StandardCharsets.UTF_8)
.build();
resp.setHeader(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString());
Files.copy(path, resp.getOutputStream());
}
@GetMapping("/download-2/{filename}")
public ResponseEntity<Resource> download2(@PathVariable String filename) throws IOException {
// 构建文件路径
Path filePath = Paths.get("upload", filename);
Resource resource = new UrlResource(filePath.toUri());
// 检查文件是否存在并可读
if (!resource.exists() || !resource.isReadable()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
// 设置下载响应头
ContentDisposition contentDisposition = ContentDisposition
.attachment()
.filename(filename, StandardCharsets.UTF_8)
.build();
HttpHeaders headers = new HttpHeaders();
headers.setContentDisposition(contentDisposition);
return ResponseEntity.ok()
.headers(headers)
.body(resource);
}
不指定默认也为 inline
@GetMapping("/download-3/{fileName}")
public ResponseEntity<Resource> download3(@PathVariable String fileName) throws IOException {
// 构建文件路径
Path filePath = Paths.get("upload").resolve(fileName);
Resource resource = new UrlResource(filePath.toUri());
// 检查文件是否存在并可读
if (!resource.exists() || !resource.isReadable()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
// 设置下载响应头,可省略
ContentDisposition contentDisposition = ContentDisposition.inline().build();
HttpHeaders headers = new HttpHeaders();
String mimeType = Files.probeContentType(filePath);
// 不可省略
headers.setContentType(MediaType.valueOf(mimeType + "; charset=UTF-8"));
// 可省略
headers.setContentDisposition(contentDisposition);
return ResponseEntity.ok()
.headers(headers)
.body(resource);
}
请求该 url 文件将不再下载,改为预览形式(浏览器不支持预览的类型依然下载)