大文件下载防内存溢出简单实现
摘要:利用StreamingResponseBody来防止内存溢出,实现大文件下载。
一、配置异步请求超时时间
1 /** 2 * @Description: 异步请求配置类 3 * @Date: Created in 17:19 2025/1/5 4 * @Author: Cenobitor 5 * @Modified By: 6 * @since 0.1.0 7 */ 8 @Configuration 9 @EnableWebMvc 10 public class WebConfig implements WebMvcConfigurer { 11 12 @Override 13 public void configureAsyncSupport(AsyncSupportConfigurer configurer) { 14 configurer.setTaskExecutor(taskExecutor()); 15 // 超时时间设置为6000s 16 configurer.setDefaultTimeout(TimeUnit.SECONDS.toMillis(6000)); 17 } 18 19 private AsyncTaskExecutor taskExecutor() { 20 ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); 21 executor.setCorePoolSize(10); // 核心线程数 22 executor.setMaxPoolSize(20); // 最大线程数 23 executor.setQueueCapacity(500); // 队列容量 24 executor.setThreadNamePrefix("Async-"); 25 executor.initialize(); 26 return executor; 27 } 28 }
二、本地、远程文件下载请求接口
1 private static final int MAX_CONCURRENT_DOWNLOADS = 5; // 最大并发下载数 2 private final Semaphore semaphore = new Semaphore(MAX_CONCURRENT_DOWNLOADS); 3 4 @RequestMapping( "/downloadFile") 5 public ResponseEntity<StreamingResponseBody> downloadFile() { 6 long start = System.currentTimeMillis(); 7 try { 8 Path filePath = Paths.get("E:\\macOS Catalina 10.15 正式版 19A583 macOShome.com.dmg"); 9 StreamingResponseBody responseBody = outputStream -> { 10 try (InputStream inputStream = Files.newInputStream(filePath)) { 11 StreamUtils.copy(inputStream, outputStream); 12 } 13 }; 14 HttpHeaders header = new HttpHeaders(); 15 header.setContentDispositionFormData("attachment", "macOS.dmg"); 16 header.setContentType(MediaType.APPLICATION_OCTET_STREAM); 17 return new ResponseEntity<>(responseBody,header, HttpStatus.OK); 18 } catch (Exception e) { 19 throw new RuntimeException(e); 20 } finally { 21 long end = System.currentTimeMillis(); 22 System.out.println("downloadFile cost: " + (end - start) + "ms"); 23 } 24 } 25 26 @RequestMapping( "/downloadRemoteFile") 27 public ResponseEntity<StreamingResponseBody> downloadRemoteFile() { 28 long start = System.currentTimeMillis(); 29 try { 30 StreamingResponseBody responseBody = outputStream -> { 31 URL resourceUrl = new URL("https://cdn.mysql.com/Downloads/MySQL-9.1/mysql-9.1.0-winx64-debug-test.zip"); 32 try (InputStream inputStream = new UrlResource(resourceUrl).getInputStream()) { 33 StreamUtils.copy(inputStream, outputStream); 34 } 35 }; 36 HttpHeaders header = new HttpHeaders(); 37 header.setContentDispositionFormData("attachment", "mysql.zip"); 38 header.setContentType(MediaType.APPLICATION_OCTET_STREAM); 39 return new ResponseEntity<>(responseBody,header, HttpStatus.OK); 40 } catch (Exception e) { 41 throw new RuntimeException(e); 42 } finally { 43 long end = System.currentTimeMillis(); 44 System.out.println("downloadFile cost: " + (end - start) + "ms"); 45 } 46 } 47 48 @RequestMapping( "/downloadRemoteFileLimit") 49 public ResponseEntity<StreamingResponseBody> downloadRemoteFileLimit() { 50 51 if (!semaphore.tryAcquire()) { 52 // 如果无法获取信号量,则返回503服务不可用状态 53 return new ResponseEntity<>(HttpStatus.SERVICE_UNAVAILABLE); 54 } 55 56 try { 57 StreamingResponseBody responseBody = outputStream -> { 58 URL resourceUrl = new URL("https://cdn.mysql.com/Downloads/MySQL-9.1/mysql-9.1.0-winx64-debug-test.zip"); 59 try (InputStream inputStream = new UrlResource(resourceUrl).getInputStream()) { 60 byte[] buffer = new byte[1024]; 61 int bytesRead; 62 Instant lastWrite = Instant.now(); 63 long bytesPerSecond = 1024 * 100; // 例如,限制为100KB/s 64 65 while ((bytesRead = inputStream.read(buffer)) != -1) { 66 outputStream.write(buffer, 0, bytesRead); 67 68 long now = Instant.now().toEpochMilli(); 69 long elapsed = now - lastWrite.toEpochMilli(); 70 long expectedDelay = (bytesRead * 1000L / bytesPerSecond) - elapsed; 71 72 if (expectedDelay > 0) { 73 TimeUnit.MILLISECONDS.sleep(expectedDelay); 74 } 75 76 lastWrite = Instant.now(); 77 } 78 } catch (Exception e) { 79 // 处理异常 80 throw new RuntimeException("下载失败", e); 81 }finally { 82 semaphore.release(); // 释放信号量 83 } 84 }; 85 86 HttpHeaders header = new HttpHeaders(); 87 header.setContentDispositionFormData("attachment", "mysql.zip"); 88 header.setContentType(MediaType.APPLICATION_OCTET_STREAM); 89 return new ResponseEntity<>(responseBody, header, HttpStatus.OK); 90 } catch (Exception e) { 91 // 处理其他可能的异常,并释放信号量(如果需要) 92 semaphore.release(); 93 throw e; 94 } 95 }
三、前端页面代码
1 <template> 2 <div class="hello"> 3 <h1>{{ msg }}</h1> 4 <p> 5 For a guide and recipes on how to configure / customize this project,<br> 6 check out the 7 <a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>. 8 </p> 9 </div> 10 <div class="download-wrap"> 11 <el-button type="primary" icon="el-icon-download" size="sm>ll" @click="downloadFile">下载文件</el-button> 12 <el-button type="primary" icon="el-icon-download" size="sm>ll" @click="downloadRemoteFile">下载远程文件</el-button> 13 <el-button type="primary" icon="el-icon-download" size="sm>ll" @click="downloadRemoteFileLimit">下载远程文件限速</el-button> 14 </div> 15 </template> 16 17 <script> 18 import axios from "axios"; 19 20 export default { 21 name: 'HelloWorld', 22 props: { 23 msg: String 24 }, 25 26 methods:{ 27 downloadFile() { 28 console.log('下载文件开始'); 29 const downloadUrl = 'http://localhost:8080/downloadFile'; // 文件下载的链接 30 window.open(downloadUrl); 31 console.log('下载文件完成'); 32 }, 33 downloadRemoteFile() { 34 console.log('下载文件开始'); 35 const downloadUrl = 'http://localhost:8080/downloadRemoteFile'; // 文件下载的链接 36 window.open(downloadUrl); 37 console.log('下载文件完成'); 38 }, 39 downloadRemoteFileLimit() { 40 console.log('下载文件开始'); 41 const downloadUrl = 'http://localhost:8080/downloadRemoteFileLimit'; // 文件下载的链接 42 window.open(downloadUrl); 43 console.log('下载文件完成'); 44 }, 45 }, 46 mounted() { 47 axios.get('http://localhost:8080/hello').then(res => { 48 console.log(res); 49 }); 50 }, 51 } 52 </script> 53 54 <!-- Add "scoped" attribute to limit CSS to this component only --> 55 <style scoped> 56 h3 { 57 margin: 40px 0 0; 58 } 59 ul { 60 list-style-type: none; 61 padding: 0; 62 } 63 li { 64 display: inline-block; 65 margin: 0 10px; 66 } 67 a { 68 color: #42b983; 69 } 70 </style>

浙公网安备 33010602011771号