You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 17:45:54