Rest服务实现文件下载完成/中断后自动删除的解决方案
解决文件下载完成/中断后自动删除的问题
直接在创建Resource后删除文件会报错,因为Spring返回ResponseEntity后才会开始向客户端传输文件内容,此时文件已被删除就会导致读取失败。必须等文件流完全传输完毕(或传输中断)后再执行删除操作,以下是两种可行方案:
方案一:自定义Resource包装类,流关闭时触发删除
通过包装原始UrlResource,重写getInputStream方法返回一个装饰过的输入流,在流关闭时自动删除文件。
自定义AutoDeleteResource类
import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; public class AutoDeleteResource implements Resource { private final Resource delegate; private final Path filePath; public AutoDeleteResource(Path filePath) throws IOException { this.delegate = new UrlResource(filePath.toUri()); this.filePath = filePath; } @Override public InputStream getInputStream() throws IOException { InputStream originalStream = delegate.getInputStream(); // 包装输入流,关闭时执行删除 return new InputStream() { @Override public int read() throws IOException { return originalStream.read(); } @Override public void close() throws IOException { try { originalStream.close(); } finally { // 无论流关闭是否成功,都尝试删除文件 try { Files.deleteIfExists(filePath); } catch (IOException e) { logger.error("删除文件失败: " + filePath, e); } } } // 重写其他read方法提升性能 @Override public int read(byte[] b) throws IOException { return originalStream.read(b); } @Override public int read(byte[] b, int off, int len) throws IOException { return originalStream.read(b, off, len); } }; } // 以下方法直接委托给原始Resource实现 @Override public boolean exists() { return delegate.exists(); } @Override public boolean isReadable() { return delegate.isReadable(); } @Override public Resource createRelative(String relativePath) throws IOException { return delegate.createRelative(relativePath); } @Override public String getFilename() { return delegate.getFilename(); } @Override public String getDescription() { return delegate.getDescription(); } @Override public boolean isOpen() { return delegate.isOpen(); } @Override public java.net.URL getURL() throws IOException { return delegate.getURL(); } @Override public java.net.URI getURI() throws IOException { return delegate.getURI(); } @Override public java.io.File getFile() throws IOException { return delegate.getFile(); } @Override public long contentLength() throws IOException { return delegate.contentLength(); } @Override public long lastModified() throws IOException { return delegate.lastModified(); } }
修改原下载接口
@GetMapping("/download/{fileName:.+}") public ResponseEntity<Resource> downloadFile(@PathVariable String fileName) { try { Path filePath = Paths.get(directoryPath).resolve(fileName); AutoDeleteResource resource = new AutoDeleteResource(filePath); if (resource.exists() && resource.isReadable()) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); headers.setContentDispositionFormData("attachment", fileName); return ResponseEntity.ok().headers(headers).body(resource); } else { logger.error("文件不存在: " + fileName ); return ResponseEntity.notFound().build(); } } catch (Exception e) { logger.error("下载文件出错: " + fileName, e); return ResponseEntity.notFound().build(); } }
方案二:用StreamingResponseBody手动控制流传输
直接通过StreamingResponseBody手动处理文件流的读写,在传输完成(或异常中断)后执行删除,代码更简洁。
@GetMapping("/download/{fileName:.+}") public ResponseEntity<StreamingResponseBody> downloadFile(@PathVariable String fileName) { try { Path filePath = Paths.get(directoryPath).resolve(fileName); if (!Files.exists(filePath) || !Files.isReadable(filePath)) { logger.error("文件不存在: " + fileName ); return ResponseEntity.notFound().build(); } HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); headers.setContentDispositionFormData("attachment", fileName); StreamingResponseBody responseBody = outputStream -> { try (InputStream inputStream = Files.newInputStream(filePath)) { byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } outputStream.flush(); } finally { // 流关闭后执行删除 try { Files.deleteIfExists(filePath); } catch (IOException e) { logger.error("删除文件失败: " + fileName, e); } } }; return ResponseEntity.ok().headers(headers).body(responseBody); } catch (Exception e) { logger.error("下载文件出错: " + fileName, e); return ResponseEntity.notFound().build(); } }
方案对比
- 方案一基于Spring的Resource抽象,适配Spring生态,适合需要复用Resource逻辑的场景。
- 方案二更直接,无需实现大量接口方法,代码量更少,适合简单的下载删除场景。
内容的提问来源于stack exchange,提问作者Владимир Гичев
相关产品推荐
相关产品推荐

