您的位置:

Spring Boot文件下载详解

Spring Boot 是一个非常好的Java Web框架,它作为一个微服务的框架,提供了很多基本工具和服务支持,而文件下载是其中一个相对简单的功能。下面我们将从多个方面详细阐述Spring Boot文件下载的实现。

一、下载方式

通常有两种主要的文件下载方式:使用HTTP协议下载和使用FTP协议下载。

HTTP协议下载:

@RequestMapping(value = "/download", method = RequestMethod.GET)
public ResponseEntity fileDownload(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 ResponseEntity download(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文件下载限制等多个方面进行了讲述。通过这些方式,我们可以实现各种场景下的文件下载功能。