java大文件分段下载

后端代码

package com.jy.jy.controller;import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.io.*;
import java.util.regex.Pattern;
import org.springframework.http.*;
import java.io.IOException;@RestController
@CrossOrigin(origins = "*", maxAge = 3600) // 解决跨域问题
@RequestMapping("/api/common/config/download")
public class FileDownloadController {// 实际生产环境中应通过配置文件设置private static final String DOWNLOAD_DIR = "/path/to/download/files";private static final String FILE_PATH = "D:\\wmxy_repository.rar";// 解析Range请求头的正则表达式private static final Pattern RANGE_PATTERN = Pattern.compile("bytes=(\\d+)-(\\d*)");@GetMapping("/file")public ResponseEntity<byte[]> downloadFile(@RequestParam(value = "name", required = false) String fileName,@RequestHeader(value = "Range", required = false) String rangeHeader) throws IOException {File file = new File(FILE_PATH);if (!file.exists()){return ResponseEntity.notFound().build();}long fileSize = file.length();HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);headers.setContentDisposition(ContentDisposition.attachment().filename(fileName).build());// 关键响应头:声明支持范围请求headers.set("Accept-Ranges", "bytes");try (FileInputStream fis = new FileInputStream(file)){if (rangeHeader == null){// 完整文件下载(200 OK)byte[] content = new byte[(int) fileSize];fis.read(content);headers.setContentLength(fileSize);return new ResponseEntity<>(content, headers, HttpStatus.OK);} else{// 处理分片请求(206 Partial Content)long[] range = parseRange(rangeHeader, fileSize);long start = range[0];long end = range[1];long contentLength = end - start + 1;// 验证范围有效性if (start > end || start >= fileSize){return ResponseEntity.status(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE).header("Content-Range", "bytes */" + fileSize).build();}// 设置分片响应头headers.setContentLength(contentLength);headers.set("Content-Range", "bytes " + start + "-" + end + "/" + fileSize);// 读取分片数据byte[] buffer = new byte[(int) contentLength];fis.skip(start);fis.read(buffer);return new ResponseEntity<>(buffer, headers, HttpStatus.PARTIAL_CONTENT);}}}/*** 解析 Range 请求头,返回 [start, end]*/private long[] parseRange(String rangeHeader, long fileSize) {long start = 0;long end = fileSize - 1;try{String[] parts = rangeHeader.replace("bytes=", "").split("-");start = Long.parseLong(parts[0]);if (parts.length > 1 && !parts[1].isEmpty()){end = Math.min(Long.parseLong(parts[1]), fileSize - 1);}// 防止负数start = Math.max(start, 0);// 防止越界end = Math.min(end, fileSize - 1);} catch (Exception e){// 解析失败时返回完整范围start = 0;end = fileSize - 1;}return new long[]{start, end};}
}

前端代码

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>文件分块下载示例</title><script src="https://cdn.tailwindcss.com"></script><link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"><script>tailwind.config = {theme: {extend: {colors: {primary: '#165DFF',success: '#00B42A',warning: '#FF7D00',danger: '#F53F3F',},fontFamily: {inter: ['Inter', 'system-ui', 'sans-serif'],},},}}</script><style type="text/tailwindcss">@layer utilities {.content-auto {content-visibility: auto;}.download-btn {@apply px-4 py-2 bg-primary text-white rounded-md transition-all duration-300 hover:bg-primary/90 active:scale-95 focus:outline-none focus:ring-2 focus:ring-primary/50;}.control-btn {@apply px-3 py-1.5 rounded-md transition-all duration-300 hover:bg-gray-100 active:scale-95 focus:outline-none;}.progress-bar {@apply h-2 rounded-full bg-gray-200 overflow-hidden;}.progress-value {@apply h-full bg-primary transition-all duration-300 ease-out;}}</style>
</head>
<body class="bg-gray-50 font-inter min-h-screen flex flex-col"><header class="bg-white shadow-sm py-4 px-6"><div class="container mx-auto flex justify-between items-center"><h1 class="text-2xl font-bold text-gray-800">文件分块下载</h1></div></header><main class="flex-grow container mx-auto px-4 py-8"><div class="max-w-3xl mx-auto bg-white rounded-lg shadow-md p-6"><div class="mb-6"><h2 class="text-xl font-semibold text-gray-800 mb-4">选择下载文件</h2><div class="grid grid-cols-1 md:grid-cols-2 gap-4"><div class="flex flex-col"><label class="text-sm font-medium text-gray-700 mb-2">文件列表</label><select id="fileSelect" class="border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary"><option value="file1.zip">大型文件1.zip (2.4GB)</option><option value="file2.zip">大型文件2.zip (1.7GB)</option><option value="file3.zip">大型文件3.zip (3.1GB)</option></select></div><div class="flex flex-col"><label class="text-sm font-medium text-gray-700 mb-2">块大小</label><select id="chunkSizeSelect" class="border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary"><option value="1048576">1MB</option><option value="5242880" selected>5MB</option><option value="10485760">10MB</option><option value="52428800">50MB</option></select></div></div></div><div id="downloadSection" class="hidden"><div class="flex items-center justify-between mb-3"><h3 class="text-lg font-medium text-gray-800">下载进度</h3><div class="flex space-x-2"><button id="pauseBtn" class="control-btn text-warning hidden"><i class="fa fa-pause mr-1"></i> 暂停</button><button id="resumeBtn" class="control-btn text-primary hidden"><i class="fa fa-play mr-1"></i> 继续</button><button id="cancelBtn" class="control-btn text-danger"><i class="fa fa-times mr-1"></i> 取消</button></div></div><div class="mb-3"><div class="flex justify-between text-sm text-gray-600 mb-1"><span id="fileName">大型文件1.zip</span><span id="progressText">0%</span></div><div class="progress-bar"><div id="progressBar" class="progress-value" style="width: 0%"></div></div></div><div class="grid grid-cols-2 gap-4 text-sm text-gray-600 mb-4"><div><span class="font-medium">已下载:</span> <span id="downloadedSize">0 MB</span></div><div><span class="font-medium">总大小:</span> <span id="totalSize">2.4 GB</span></div><div><span class="font-medium">下载速度:</span> <span id="downloadSpeed">0 KB/s</span></div><div><span class="font-medium">剩余时间:</span> <span id="remainingTime">计算中...</span></div></div><div id="downloadComplete" class="hidden text-success mb-4"><i class="fa fa-check-circle mr-2"></i> 下载完成!</div></div><div class="flex justify-center mt-8"><button id="startDownloadBtn" class="download-btn"><i class="fa fa-download mr-2"></i> 开始下载</button></div></div></main><footer class="bg-gray-800 text-white py-6 px-4"><div class="container mx-auto text-center text-sm"><p>© 文件分块下载示例 | 使用 Tailwind CSS 构建</p></div></footer><script>document.addEventListener('DOMContentLoaded', () => {// DOM 元素const startDownloadBtn = document.getElementById('startDownloadBtn');const pauseBtn = document.getElementById('pauseBtn');const resumeBtn = document.getElementById('resumeBtn');const cancelBtn = document.getElementById('cancelBtn');const downloadSection = document.getElementById('downloadSection');const progressBar = document.getElementById('progressBar');const progressText = document.getElementById('progressText');const downloadedSize = document.getElementById('downloadedSize');const totalSize = document.getElementById('totalSize');const downloadSpeed = document.getElementById('downloadSpeed');const remainingTime = document.getElementById('remainingTime');const downloadComplete = document.getElementById('downloadComplete');const fileSelect = document.getElementById('fileSelect');const chunkSizeSelect = document.getElementById('chunkSizeSelect');// 下载状态let isDownloading = false;let isPaused = false;let downloadedBytes = 0;let totalBytes = 0;let chunks = [];let currentChunk = 0;let startTime = 0;let lastUpdateTime = 0;let timer = null;let abortController = null;// 文件映射(实际项目中应由后端提供)const fileMap = {'file1.zip': { size: 5.51 * 1024 * 1024 * 1024, url: 'http://localhost:89/api/common/config/download/file?name=file1.zip' },'file2.zip': { size: 1.7 * 1024 * 1024 * 1024, url: '/download/file?name=file2.zip' },'file3.zip': { size: 3.1 * 1024 * 1024 * 1024, url: '/download/file?name=file3.zip' },};// 格式化文件大小function formatBytes(bytes, decimals = 2) {if (bytes === 0) return '0 Bytes';const k = 1024;const dm = decimals < 0 ? 0 : decimals;const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];const i = Math.floor(Math.log(bytes) / Math.log(k));return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];}// 格式化剩余时间function formatTime(seconds) {if (seconds < 60) {return `${Math.round(seconds)} 秒`;} else if (seconds < 3600) {const minutes = Math.floor(seconds / 60);const secs = Math.round(seconds % 60);return `${minutes} 分 ${secs} 秒`;} else {const hours = Math.floor(seconds / 3600);const minutes = Math.floor((seconds % 3600) / 60);return `${hours} 时 ${minutes} 分`;}}// 更新下载进度function updateProgress() {const now = Date.now();const elapsed = (now - lastUpdateTime) / 1000; // 秒lastUpdateTime = now;if (elapsed <= 0) return;// 计算下载速度 (KB/s)const speed = ((downloadedBytes - (chunks.length > 0 ? chunks.reduce((sum, chunk) => sum + chunk.size, 0) - chunks[chunks.length - 1].size : 0)) / elapsed) / 1024;// 计算剩余时间const remainingBytes = totalBytes - downloadedBytes;const eta = remainingBytes > 0 ? remainingBytes / (speed * 1024) : 0;// 更新UIconst percent = Math.min(100, Math.round((downloadedBytes / totalBytes) * 100));progressBar.style.width = `${percent}%`;progressText.textContent = `${percent}%`;downloadedSize.textContent = formatBytes(downloadedBytes);downloadSpeed.textContent = `${speed.toFixed(1)} KB/s`;remainingTime.textContent = formatTime(eta);// 下载完成if (downloadedBytes >= totalBytes) {finishDownload();}}// 下载完成处理function finishDownload() {isDownloading = false;isPaused = false;clearInterval(timer);timer = null;// 显示完成消息downloadComplete.classList.remove('hidden');pauseBtn.classList.add('hidden');resumeBtn.classList.add('hidden');// 合并所有块并触发下载const blob = new Blob(chunks.map(chunk => chunk.data), { type: 'application/octet-stream' });const url = URL.createObjectURL(blob);const a = document.createElement('a');a.href = url;a.download = fileSelect.value;document.body.appendChild(a);a.click();document.body.removeChild(a);URL.revokeObjectURL(url);console.log('下载完成!');}// 下载单个块async function downloadChunk(start, end) {if (!isDownloading || isPaused) return;try {abortController = new AbortController();const signal = abortController.signal;const headers = {'Range': `bytes=${start}-${end}`};const response = await fetch(fileMap[fileSelect.value].url, {method: 'GET',headers,signal});if (!response.ok) {throw new Error(`下载失败: ${response.status} ${response.statusText}`);}const contentRange = response.headers.get('content-range');const contentLength = parseInt(response.headers.get('content-length'), 10);const arrayBuffer = await response.arrayBuffer();// 存储块数据chunks.push({start,end,size: contentLength,data: arrayBuffer});// 更新已下载字节数downloadedBytes += contentLength;// 更新进度updateProgress();// 继续下载下一个块currentChunk++;if (currentChunk < chunksInfo.length) {await downloadChunk(chunksInfo[currentChunk].start, chunksInfo[currentChunk].end);}} catch (error) {if (error.name === 'AbortError') {console.log('下载已取消');} else {console.error('下载出错:', error);alert(`下载出错: ${error.message}`);}isDownloading = false;}}// 开始下载async function startDownload() {const selectedFile = fileSelect.value;const chunkSize = parseInt(chunkSizeSelect.value, 10);// 重置状态isDownloading = true;isPaused = false;downloadedBytes = 0;chunks = [];currentChunk = 0;downloadComplete.classList.add('hidden');// 获取文件总大小totalBytes = fileMap[selectedFile].size;totalSize.textContent = formatBytes(totalBytes);// 显示下载区域downloadSection.classList.remove('hidden');pauseBtn.classList.remove('hidden');resumeBtn.classList.add('hidden');// 计算块信息const fileSize = totalBytes;const numChunks = Math.ceil(fileSize / chunkSize);chunksInfo = [];for (let i = 0; i < numChunks; i++) {const start = i * chunkSize;const end = Math.min((i + 1) * chunkSize - 1, fileSize - 1);chunksInfo.push({ start, end });}// 开始计时startTime = Date.now();lastUpdateTime = startTime;// 启动进度更新计时器clearInterval(timer);timer = setInterval(updateProgress, 1000);// 开始下载第一个块await downloadChunk(chunksInfo[0].start, chunksInfo[0].end);}// 暂停下载function pauseDownload() {isPaused = true;if (abortController) {abortController.abort();}pauseBtn.classList.add('hidden');resumeBtn.classList.remove('hidden');}// 恢复下载async function resumeDownload() {isPaused = false;pauseBtn.classList.remove('hidden');resumeBtn.classList.add('hidden');// 继续下载当前块if (currentChunk < chunksInfo.length) {await downloadChunk(chunksInfo[currentChunk].start, chunksInfo[currentChunk].end);}}// 取消下载function cancelDownload() {isDownloading = false;isPaused = false;if (abortController) {abortController.abort();}clearInterval(timer);timer = null;// 重置UIdownloadSection.classList.add('hidden');pauseBtn.classList.add('hidden');resumeBtn.classList.add('hidden');progressBar.style.width = '0%';progressText.textContent = '0%';downloadedSize.textContent = '0 MB';downloadSpeed.textContent = '0 KB/s';remainingTime.textContent = '计算中...';// 清除块数据chunks = [];downloadedBytes = 0;currentChunk = 0;console.log('下载已取消');}// 事件监听startDownloadBtn.addEventListener('click', startDownload);pauseBtn.addEventListener('click', pauseDownload);resumeBtn.addEventListener('click', resumeDownload);cancelBtn.addEventListener('click', cancelDownload);});</script>
</body>
</html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/news/909028.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

antd-vue - - - - - a-table排序

antd-vue - - - - - a-table排序 1. 重点代码:2. 代码示例&#xff1a;3. 进阶版写法 1. 重点代码: sorter: {compare: (a, b) > a.columnsKeys - b.columnsKeys,multiple: 1, },解析&#xff1a; compare: 自定义排序函数&#xff0c;用于比较两个对象。 multiple: 排序优…

【AI】模型vs算法(以自动驾驶为例)

模型vs算法&#xff08;以自动驾驶为例&#xff09; 一、自动驾驶的核心任务二、以自动驾驶为例&#xff0c;模型vs算法的实际分工1. 感知环节&#xff1a;“看懂”周围环境&#xff08;如识别行人、车道线、车辆&#xff09;2. 预测环节&#xff1a;“预判”其他交通参与者的行…

机器学习与深度学习19-线性代数02

目录 前文回顾6.协方差矩阵与主成分分析7.矩阵的奇异值分解8.神经网络的前向传播和反向传播9.矩阵的迹10.特征工程的多项式特征扩展 前文回顾 上一篇文章链接&#xff1a;地址 6.协方差矩阵与主成分分析 协方差矩阵是一个对称矩阵&#xff0c;用于衡量随机变量之间的线性相关…

青藏高原ASTER_GDEM数据集(2011)

共享方式&#xff1a;开放获取数据大小&#xff1a;73.69 GB数据时间范围&#xff1a;2012-04-08 — 2012-05-08元数据更新时间&#xff1a;2021-10-15 数据集摘要 ASTER Global Digital Elevation Model &#xff08;ASTER GDEM&#xff09;是美国航空航天局 &#xff08;NAS…

代码随想录训练营二十六天| 654.最大二叉树 617.合并二叉树 700.二叉搜索树的搜索 98.验证二叉搜索树

654.最大二叉树&#xff1a; 文档讲解&#xff1a;代码随想录|654.最大二叉树 视频讲解&#xff1a;又是构造二叉树&#xff0c;又有很多坑&#xff01;| LeetCode&#xff1a;654.最大二叉树_哔哩哔哩_bilibili 状态&#xff1a;已做出 思路&#xff1a; 这道题目要求使用给定…

临时抱佛脚v2

术语解释 多范式 (Multi-paradigm) 指支持多种编程范式&#xff0c;如面向对象编程和函数式编程&#xff0c;允许开发者根据需求选择最合适的风格。 函数式编程 (Functional Programming) 一种编程范式&#xff0c;将计算视为数学函数的求值&#xff0c;强调不变性、无副作用…

MCGS和1200plc变量表格式编辑

设备编辑窗口---设备信息导出---另存为xx.CSV文件 在上面导出的表格基础上编辑 本体位的编辑&#xff1a; db数据块位编辑 db数据块int类型 (4.14应改为4.140,0不省略) db数据块real类型 通道号&#xff0c;地址均按顺序排列 &#xff0c;寄存期地址最后一位0不能省略&#…

Android高性能音频与图形开发:OpenSL ES与OpenGL ES最佳实践

引言 在移动应用开发中&#xff0c;音频和图形处理是提升用户体验的关键要素。本文将深入探讨Android平台上两大核心多媒体API&#xff1a;OpenSL ES&#xff08;音频&#xff09;和OpenGL ES&#xff08;图形&#xff09;&#xff0c;提供经过生产环境验证的优化实现方案。 …

GaussDB分布式数据库调优方法总结:从架构到实践的全链路优化指南

GaussDB分布式数据库调优方法总结&#xff1a;从架构到实践的全链路优化指南 GaussDB作为华为自主研发的分布式数据库&#xff0c;基于MPP&#xff08;大规模并行处理&#xff09;架构设计&#xff0c;支持存储与计算分离、列存/行存混合引擎、向量化执行等核心技术&#xff0…

NLP学习路线图(三十九):对话系统

在人工智能领域,自然语言处理(NLP)无疑是推动人机交互革命的核心引擎。当清晨的闹钟响起,你轻声一句“小爱同学,关掉闹钟”;当开车迷路时说“嘿Siri,导航到最近加油站”;当深夜向客服机器人询问订单状态时——我们已在不知不觉中与对话系统建立了千丝万缕的联系。这类系…

Cambridge Pixel为警用反无人机系统(C-UAS)提供软件支持

警用 C-UAS 系统受益于 Cambridge Pixel 和 OpenWorks Engineering 的技术合作。 作为雷达数据处理和雷达目标跟踪的专家公司&#xff0c;Cambridge Pixel宣布与OpenWorks Engineering 合作&#xff0c;为警用系统提供先进的C-UAS系统。OpenWorks Engineering以创新的光学系统和…

【ArcGIS Pro微课1000例】0072:如何自动保存编辑内容及保存工程?

文章目录 一、自动保存编辑内容二、自动保存工程在使用ArcGIS或者ArcGIS Pro时,经常会遇到以下报错,无论点击【发送报告】,还是【不发送】,软件都会强制退出,这时如果对所操作没有保存,就会前功尽弃。 此时,自动保存工作就显得尤为重要,接下来讲解两种常见的自动保存方…

进行性核上性麻痹健康护理指南:全方位照护之道

进行性核上性麻痹&#xff08;PSP&#xff09;是一种罕见的神经系统变性疾病&#xff0c;会严重影响患者的生活质量。做好健康护理&#xff0c;能在一定程度上缓解症状&#xff0c;提高患者生活质量。 ​饮食护理是基础。患者常伴有吞咽困难&#xff0c;饮食应选择质地均匀、易…

第二节:Vben Admin v5 (vben5) Python-Flask 后端开发详解(附源码)

目录 前言项目准备项目结构应用创建应用工厂`vben5-admin-backend/app/__init__.py` 文件`vben5-admin-backend/app/config.py` 文件`vben5-admin-backend/app/.env` 文件`vben5-admin-backend/app/logging_config.py` 文件`vben5-admin-backend/app/start.py` 文件`vben5-admi…

从零打造前沿Web聊天组件:从设计到交互

作者现在制作一款网页端聊天室&#xff08;青春版&#xff09;&#xff0c;之前一直有这个想法&#xff0c;现在总算是迈出了第一步开始制作了… 雄关漫道真如铁&#xff0c;而今迈步从头越&#xff01; 启程 当前已经完成左侧聊天室列表显示&#xff0c;通过http://localhos…

计算机网络 : 传输层协议UDP与TCP

计算机网络 &#xff1a; 传输层协议UDP与TCP 目录 计算机网络 &#xff1a; 传输层协议UDP与TCP引言1. 传输层协议UDP1.2 UDP协议段格式1.3 UDP的特点1.4 面向数据报1.5 UDP的缓冲区1.6 基于UDP的应用层协议及使用注意事项 2. 传输层协议TCP2.1 再谈端口号2.2 TCP协议段格式2.…

Java高频面试之并发编程-27

hello啊&#xff0c;各位观众姥爷们&#xff01;&#xff01;&#xff01;本baby今天又来报道了&#xff01;哈哈哈哈哈嗝&#x1f436; 面试&#xff1a;详细说说AtomicInteger 的原理 AtomicInteger 的原理详解 AtomicInteger 是 Java 并发包 (java.util.concurrent.atomic)…

冒险岛的魔法果实-多重背包

问题描述 在冒险岛的深处&#xff0c;小萌探索到了一个传说中的魔法果实园。这里满是各种神奇的魔法果实&#xff0c;吃了可以增加不同的魔法能量。 小萌想带一些魔法果实回去&#xff0c;但是他的背包空间有限。看着这些琳琅满目的魔法果实&#xff0c;小萌很是纠结&#xf…

atomicity of memory accesses

文章目录 atomicity of memory accesses✅ 正确认识原子性的边界对于 **Load**&#xff1a;✅ 正确的原子性边界是&#xff1a;对于 **Store**&#xff1a;✅ 正确的原子性边界是&#xff1a; &#x1f504; 修正原文中的说法&#xff08;对照分析&#xff09;✅ 原子性边界最终…

VScode安装配置PYQT6

开始是准备安装PYQT5的&#xff0c;但是安装不下去&#xff0c;就改成安装PYQT6 一.安装pyqt5&#xff0c;成功。 c:\PYQT>pip install pyqt5 Defaulting to user installation because normal site-packages is not writeable Collecting pyqt5 Downloading PyQt5-5.15.…