如何用Java的HttpURLConnection实现文件传输进度展示与多用户追踪?
大文件下载进度追踪与多用户场景解决方案
1. 修改现有代码计算并展示传输进度
要实现进度计算,核心是先获取待下载文件的总大小,再实时统计已下载字节数,进而计算进度百分比。具体步骤与代码修改如下:
- 通过
HttpURLConnection.getContentLengthLong()获取文件总大小(需处理服务器未返回Content-Length的情况) - 维护累加变量记录已下载字节数
- 每次写入文件后更新变量并计算进度百分比
修改后的代码示例:
HttpURLConnection urlConnection = (HttpURLConnection) requestUrl.openConnection(); // 获取文件总大小 long totalFileSize = urlConnection.getContentLengthLong(); if (totalFileSize == -1) { throw new RuntimeException("服务器未返回文件大小,无法计算进度"); } long downloadedBytes = 0; try (BufferedInputStream bufferedInputStream = new BufferedInputStream(urlConnection.getInputStream()); BufferedOutputStream fileOutputStream = new BufferedOutputStream(new FileOutputStream(filePath, true))) { byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = bufferedInputStream.read(buffer)) != -1) { fileOutputStream.write(buffer, 0, bytesRead); downloadedBytes += bytesRead; // 计算并输出进度百分比 double progress = (double) downloadedBytes / totalFileSize * 100; System.out.printf("下载进度: %.2f%%%n", progress); } }
2. 开发供前端获取进度更新的API
完全可以实现,常用两种方案:
方案一:REST查询接口
前端通过轮询方式,携带任务ID调用接口获取当前进度:
@RestController @RequestMapping("/api/download") public class DownloadProgressController { // 后续会优化为多用户安全的进度存储结构 private final Map<String, DownloadProgress> progressMap = new ConcurrentHashMap<>(); @GetMapping("/progress/{taskId}") public ResponseEntity<DownloadProgress> getProgress(@PathVariable String taskId) { DownloadProgress progress = progressMap.get(taskId); if (progress == null) { return ResponseEntity.notFound().build(); } return ResponseEntity.ok(progress); } // 进度数据载体 public static class DownloadProgress { private double percentage; private long downloadedBytes; private long totalBytes; // getter、setter方法省略 } }
方案二:WebSocket实时推送
通过WebSocket建立长连接,后端主动向前端推送进度更新,实时性更强:
@Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(new DownloadProgressHandler(), "/ws/download/progress") .setAllowedOrigins("*"); } } public class DownloadProgressHandler extends TextWebSocketHandler { private final Map<String, WebSocketSession> taskSessions = new ConcurrentHashMap<>(); @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { // 从请求参数中获取任务ID,关联会话 String taskId = session.getUri().getQuery().split("taskId=")[1]; taskSessions.put(taskId, session); } // 下载线程中调用此方法推送进度 public void sendProgress(String taskId, double percentage) throws IOException { WebSocketSession session = taskSessions.get(taskId); if (session != null && session.isOpen()) { session.sendMessage(new TextMessage(String.format("{\"progress\": %.2f}", percentage))); } } }
3. 多用户传输的独立进度追踪
核心是用唯一任务标识区分不同用户的下载任务,并用线程安全的容器存储进度数据:
实现思路
- 为每个下载任务生成唯一ID(可组合用户ID+时间戳+随机数)
- 使用
ConcurrentHashMap保证多线程环境下的线程安全 - 下载过程中更新对应任务ID的进度数据
- 查询时通过任务ID精准获取用户专属进度
完整代码示例
1. 进度数据模型
public class DownloadTaskProgress { private String taskId; private String userId; private double progressPercentage; private long downloadedBytes; private long totalBytes; private boolean isCompleted; // 构造方法、getter、setter方法省略 }
2. 全局进度管理器(线程安全)
@Component public class DownloadProgressManager { private final ConcurrentHashMap<String, DownloadTaskProgress> taskProgressMap = new ConcurrentHashMap<>(); // 创建新任务进度记录 public String createTask(String userId, long totalBytes) { String taskId = userId + "_" + System.currentTimeMillis() + "_" + UUID.randomUUID().toString().substring(0, 8); DownloadTaskProgress progress = new DownloadTaskProgress(); progress.setTaskId(taskId); progress.setUserId(userId); progress.setTotalBytes(totalBytes); progress.setProgressPercentage(0.0); progress.setDownloadedBytes(0); progress.setIsCompleted(false); taskProgressMap.put(taskId, progress); return taskId; } // 更新任务进度 public void updateProgress(String taskId, long downloadedBytes) { DownloadTaskProgress progress = taskProgressMap.get(taskId); if (progress != null) { progress.setDownloadedBytes(downloadedBytes); progress.setProgressPercentage((double) downloadedBytes / progress.getTotalBytes() * 100); if (downloadedBytes >= progress.getTotalBytes()) { progress.setIsCompleted(true); } } } // 获取任务进度 public DownloadTaskProgress getProgress(String taskId) { return taskProgressMap.get(taskId); } // 定时清理已完成任务,避免内存泄漏 @Scheduled(fixedRate = 3600000) // 每小时执行一次 public void cleanCompletedTasks() { taskProgressMap.entrySet().removeIf(entry -> entry.getValue().isCompleted()); } }
3. 整合到下载逻辑中
@Autowired private DownloadProgressManager progressManager; public void downloadFile(URL requestUrl, String filePath, String userId) throws IOException { HttpURLConnection urlConnection = (HttpURLConnection) requestUrl.openConnection(); long totalFileSize = urlConnection.getContentLengthLong(); if (totalFileSize == -1) { throw new RuntimeException("服务器未返回文件大小,无法追踪进度"); } // 创建任务并获取唯一ID String taskId = progressManager.createTask(userId, totalFileSize); long downloadedBytes = 0; try (BufferedInputStream bufferedInputStream = new BufferedInputStream(urlConnection.getInputStream()); BufferedOutputStream fileOutputStream = new BufferedOutputStream(new FileOutputStream(filePath, true))) { byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = bufferedInputStream.read(buffer)) != -1) { fileOutputStream.write(buffer, 0, bytesRead); downloadedBytes += bytesRead; // 更新进度到管理器 progressManager.updateProgress(taskId, downloadedBytes); } } finally { urlConnection.disconnect(); } }
4. 前端查询接口
@RestController @RequestMapping("/api/download") public class DownloadProgressController { @Autowired private DownloadProgressManager progressManager; @GetMapping("/progress/{taskId}") public ResponseEntity<DownloadTaskProgress> getTaskProgress(@PathVariable String taskId) { DownloadTaskProgress progress = progressManager.getProgress(taskId); if (progress == null) { return ResponseEntity.notFound().build(); } return ResponseEntity.ok(progress); } }
内容的提问来源于stack exchange,提问作者Amit J.
相关产品推荐
相关产品推荐

