Spring Boot 应用中的文件下载

本文介绍了Spring Boot应用中常见的文件下载场景。

单个文件下载

关键点如下:

  1. 获取文件的大小。
  2. 获取文件的媒体类型(Content-Type)。
  3. 通过 ContentDisposition 工具类构建 Content-Disposition 头,避免下载文件名称乱码的问题。
  4. 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();
				}
			}
		}
	}
}

完!

1 个赞