Spring Boot 是一个非常好的Java Web框架,它作为一个微服务的框架,提供了很多基本工具和服务支持,而文件下载是其中一个相对简单的功能。下面我们将从多个方面详细阐述Spring Boot文件下载的实现。
一、下载方式
通常有两种主要的文件下载方式:使用HTTP协议下载和使用FTP协议下载。
HTTP协议下载:
@RequestMapping(value = "/download", method = RequestMethod.GET) public ResponseEntityfileDownload(HttpServletRequest request, String fileName) throws IOException { // 加载文件资源 File file = new File(fileName); InputStream in = new FileInputStream(file); byte[] body = new byte[in.available()]; in.read(body); // 设置响应头 HttpHeaders headers = new HttpHeaders(); headers.add("Content-Disposition", "attachment;filename=" + new String(file.getName().getBytes("UTF-8"), "iso-8859-1")); HttpStatus statusCode = HttpStatus.OK; // 构造请求实体对象 ResponseEntity entity = new ResponseEntity<>(body, headers, statusCode); return entity; }
FTP协议下载:
@RequestMapping(value = "/download", method = RequestMethod.GET) public void ftpDownload(HttpServletResponse response, String server, int port, String username, String password, String path, String fileName) throws IOException { FTPClient ftpClient = new FTPClient(); ftpClient.connect(server, port); ftpClient.login(username, password); ftpClient.setFileType(FTP.BINARY_FILE_TYPE); InputStream in = ftpClient.retrieveFileStream(path + "/" + fileName); OutputStream out = response.getOutputStream(); response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString())); response.setContentType("application/octet-stream"); byte[] buffer = new byte[1024 * 1024]; int len; while ((len = in.read(buffer)) > 0) { out.write(buffer, 0, len); } in.close(); out.close(); ftpClient.logout(); ftpClient.disconnect(); }
二、文件安全
文件下载涉及到的安全问题很重要,我们需要确保下载的文件是用户有权限获取的。文件安全应该在文件上传时进行验证,例如使用MD5或SHA256作为文件唯一标识,以便确保文件上传的时候是完整无损的。
例如使用MD5:
public String getFileMD5(File file) throws FileNotFoundException { FileInputStream fis = new FileInputStream(file); byte[] buffer = new byte[1024]; MessageDigest md5; try { md5 = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { return null; } int numRead; do { numRead = fis.read(buffer); if (numRead > 0) { md5.update(buffer, 0, numRead); } } while (numRead != -1); fis.close(); return toHexString(md5.digest()); } public String toHexString(byte[] b) { StringBuilder sb = new StringBuilder(b.length * 2); for (byte value : b) { sb.append(toHexString(value)); } return sb.toString(); } private static String toHexString(byte b) { int value = b & 0xFF; String hexString = Integer.toHexString(value); if (hexString.length() < 2) { hexString = "0" + hexString; } return hexString; }
三、大文件下载
如果文件比较大,会对网络和系统性能有很大的影响。一些方案可以被使用来缓解这个问题,如:
- 使用多个线程来下载文件;
- 使用流来减少内存使用;
- 使用压缩文件包来减少文件大小。
例如使用多个线程:
@RequestMapping("/download/bigfile") public void download(HttpServletResponse response) throws IOException { response.setContentType("application/vnd.ms-excel"); response.setHeader("content-disposition", "attachment;filename=test.xls"); InputStream inputStream = this.getClass().getResourceAsStream("/static/test.xls"); OutputStream os = response.getOutputStream(); byte[] b = new byte[1024]; int length; while ((length = inputStream.read(b)) != -1) { os.write(b, 0, length); } os.close(); inputStream.close(); }
四、断点续传
如果下载的文件比较大,有可能中途会出现网络断开或其他问题导致下载中断,断点续传是一个很有用的功能,可以在之前中断的位置重新开始下载。
使用 Range Header 实现断点续传:
@RequestMapping("/download") public ResponseEntitydownload(HttpServletRequest request) throws IOException { // ... String range = request.getHeader("Range"); Long start = 0L; Long end = file.length() - 1; if (range != null && range.contains("bytes=") && range.contains("-")) { String[] parts = range.split("=")[1].split("-"); start = Long.parseLong(parts[0]); end = parts.length > 1 ? Long.parseLong(parts[1]) : end; } Long contentLength = end - start + 1; HttpHeaders headers = new HttpHeaders(); headers.add("Content-Disposition", "attachment;filename=" + URLEncoder.encode(file.getName(), "UTF-8")); headers.add("Accept-Ranges", "bytes"); headers.add("Content-Length", String.valueOf(contentLength)); headers.add("Content-Range", "bytes " + start + "-" + end + "/" + file.length()); headers.add("Content-Type", "application/octet-stream"); RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r"); randomAccessFile.seek(start); InputStream inputStream = new FileInputStream(file); byte[] data = new byte[inputStream.available()]; randomAccessFile.read(data); ResponseEntity.BodyBuilder builder = ResponseEntity .status(HttpStatus.PARTIAL_CONTENT) .headers(headers); builder = builder.body(data); return builder.build(); }
五、Spring Security 限制文件下载
对于需要限制用户权限才能下载的文件,Spring Security框架是一个非常好的选择。
例如使用Spring Security:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/secure/**").hasRole("USER") .and() .formLogin() .and() .logout() .and() .csrf().disable() } }
其中, "secure" 路径可以通过用户角色进行访问控制。
总结
以上是对Spring Boot文件下载的详细分析,包括下载方式、文件安全、大文件下载、断点续传、Spring Security文件下载限制等多个方面进行了讲述。通过这些方式,我们可以实现各种场景下的文件下载功能。