django+vue3實現前后端大文件分片下載

效果:
在這里插入圖片描述

大文件分片下載支持的功能:

  • 展示目標文件信息
  • 提高下載速度:通過并發請求多個塊,可以更有效地利用網絡帶寬
  • 斷點續傳:支持暫停后從已下載部分繼續,無需重新開始
  • 錯誤恢復:單個塊下載失敗只需重試該塊,而不是整個文件
  • 更好的用戶體驗:實時顯示下載進度、速度和預計剩余時間
  • 內存效率:通過分塊下載和處理,減少了一次性內存占用

大文件分片下載

前端處理流程:

用戶操作
重試次數<最大值
達到最大重試
取消當前請求
保存下載狀態
暫停下載
重新開始未完成分塊
恢復下載
取消所有請求
重置所有狀態
取消下載
開始
獲取文件信息
計算總分塊數
初始化分塊狀態
并發下載多個分塊
分塊下載完成?
下載下一個分塊
重試邏輯
錯誤處理
所有分塊下載完成?
合并所有分塊
創建完整文件
結束

后端處理流程:

后端
前端
API調用
API調用
返回文件元數據
file_info API
解析Range頭
download_large_file API
定位文件指針
流式讀取響應字節范圍
返回206狀態碼和數據
獲取文件信息
初始化下載器
計算分塊策略
創建并發下載隊列
發送Range請求
所有分塊下載完成?
合并分塊并下載

django代碼

1,代碼

# settings.py
# 指定文件訪問的 URL 前綴
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media/'# views.py
import os
import mimetypes
from django.conf import settings
from django.http import StreamingHttpResponse, JsonResponse, HttpResponse
from django.utils.http import http_date
from django.views.decorators.http import require_http_methodsdef get_file_info(file_path):"""獲取文件信息:- name: 文件名- size: 文件大小,單位字節- type: 文件類型"""if not os.path.exists(file_path):return Nonefile_size = os.path.getsize(file_path)file_name = os.path.basename(file_path)content_type, encoding = mimetypes.guess_type(file_path)return {'name': file_name,'size': file_size,'type': content_type or 'application/octet-stream'}@require_http_methods(["GET"])
def file_info(request):"""獲取文件信息API"""file_path = os.path.join(settings.MEDIA_ROOT, "user_info_big.csv")info = get_file_info(file_path)if info is None:return JsonResponse({"error": "File not found"}, status=404)return JsonResponse(info)@require_http_methods(["GET"])
def download_large_file(request):"""分片下載文件的API:param request: 請求對象:return: 文件流"""file_path = os.path.join(settings.MEDIA_ROOT, "user_info_big.csv")# 1,檢查文件是否存在if not os.path.exists(file_path):return HttpResponse("File not found", status=404)# 2,獲取文件信息file_size = os.path.getsize(file_path)file_name = os.path.basename(file_path)content_type, encoding = mimetypes.guess_type(file_path)content_type = content_type or 'application/octet-stream'# 3,獲取請求中的Range頭range_header = request.META.get('HTTP_RANGE', '').strip()# 格式:bytes=0-100range_match = range_header.replace('bytes=', '').split('-')# 起始位置range_start = int(range_match[0]) if range_match[0] else 0# 結束位置range_end = int(range_match[1]) if range_match[1] else file_size - 1# 4,確保范圍合法range_start = max(0, range_start)range_end = min(file_size - 1, range_end)# 5,計算實際要發送的數據大小content_length = range_end - range_start + 1# 6,創建響應:使用StreamingHttpResponse,將文件流式傳輸。206表示部分內容,200表示全部內容response = StreamingHttpResponse(file_iterator(file_path, range_start, range_end, chunk_size=8192),status=206 if range_header else 200,content_type=content_type)# 7,設置響應頭response['Content-Length'] = content_lengthresponse['Accept-Ranges'] = 'bytes'response['Content-Disposition'] = f'attachment; filename="{file_name}"'if range_header:response['Content-Range'] = f'bytes {range_start}-{range_end}/{file_size}'response['Last-Modified'] = http_date(os.path.getmtime(file_path))# 模擬處理延遲,方便測試暫停/繼續功能# time.sleep(0.1)  # 取消注釋以添加人為延遲# 8,返回響應return responsedef file_iterator(file_path, start_byte=0, end_byte=None, chunk_size=8192):"""文件讀取迭代器:param file_path: 文件路徑:param start_byte: 起始字節:param end_byte: 結束字節:param chunk_size: 塊大小"""with open(file_path, 'rb') as f:# 移動到起始位置f.seek(start_byte)# 計算剩余字節數remaining = end_byte - start_byte + 1 if end_byte else Nonewhile True:if remaining is not None:# 如果指定了結束位置,則讀取剩余字節或塊大小,取小的那個bytes_to_read = min(chunk_size, remaining)if bytes_to_read <= 0:breakelse:# 否則讀取指定塊大小bytes_to_read = chunk_sizedata = f.read(bytes_to_read)if not data:breakyield dataif remaining is not None:remaining -= len(data)# proj urls.py
from django.urls import path, includeurlpatterns = [# 下載文件path('download/', include(('download.urls', 'download'), namespace='download')),
]# download.urls.py
from django.urls import pathfrom download import viewsurlpatterns = [path('large_file/file_info/', views.file_info, name='file_info'),path('large_file/download_large_file/', views.download_large_file, name='download_large_file'),
]

2,核心功能解析

(1)file_info 端點 - 獲取文件元數據

這個端點提供文件的基本信息,讓前端能夠規劃下載策略:

  • 功能:返回文件名稱、大小和MIME類型
  • 用途:前端根據文件大小和設置的塊大小計算出需要下載的分塊數量

(2)download_large_file 端點 - 實現分片下載

這是實現分片下載的核心API,通過HTTP Range請求實現:
1,解析Range頭:從HTTP_RANGE頭部解析客戶端請求的字節范圍

range_header = request.META.get('HTTP_RANGE', '').strip()
range_match = range_header.replace('bytes=', '').split('-')
range_start = int(range_match[0]) if range_match[0] else 0
range_end = int(range_match[1]) if range_match[1] else file_size - 1

2,流式傳輸:使用StreamingHttpResponse和迭代器按塊讀取和傳輸文件,避免一次加載整個文件到內存

response = StreamingHttpResponse(file_iterator(file_path, range_start, range_end, chunk_size=8192),status=206 if range_header else 200,content_type=content_type
)

3,返回響應頭:設置必要的響應頭,包括Content-Range指示返回內容的范圍

response['Content-Range'] = f'bytes {range_start}-{range_end}/{file_size}'

(3)file_iterator 函數 - 高效的文件讀取

這個函數創建一個迭代器,高效地讀取文件的指定部分:

1,文件定位:將文件指針移動到請求的起始位置

f.seek(start_byte)

2,分塊讀取:按指定的塊大小讀取文件,避免一次性讀取大量數據

data = f.read(bytes_to_read)

3,邊界控制:確保只讀取請求范圍內的數據

remaining -= len(data)

HTTP狀態碼和響應頭的作用

1,206 Partial Content:

  • 表示服務器成功處理了部分GET請求
  • 分片下載的標準HTTP狀態碼

2,Content-Range: bytes start-end/total:

  • 指示響應中包含的字節范圍和文件總大小
  • 幫助客戶端確認接收的是哪部分數據

3,Accept-Ranges: bytes:

  • 表明服務器支持范圍請求
  • 讓客戶端知道可以使用Range頭請求部分內容

4,Content-Length:

  • 表示當前響應內容的長度
  • 不是文件總長度,而是本次返回的片段長度

vue3代碼

1,代碼

1,前端界面 (Vue組件):

  • 提供配置選項:并發塊數、塊大小
  • 顯示下載進度:進度條、已下載量、下載速度、剩余時間提供操作按鈕:開始、暫停、繼續、取消
  • 可視化顯示每個分塊的下載狀態
<template><div class="enhanced-downloader"><div class="card"><h2>大文件分塊下載</h2><div class="file-info" v-if="fileInfo"><p><strong>文件名:</strong> {{ fileInfo.name }}</p><p><strong>文件大小:</strong> {{ formatFileSize(fileInfo.size) }}</p><p><strong>類型:</strong> {{ fileInfo.type }}</p></div><div class="config-panel" v-if="!isDownloading && !isPaused"><div class="config-item"><label>并發塊數:</label><select v-model="concurrency"><option :value="1">1</option><option :value="2">2</option><option :value="3">3</option><option :value="5">5</option><option :value="8">8</option></select></div><div class="config-item"><label>塊大小:</label><select v-model="chunkSize"><option :value="512 * 1024">512 KB</option><option :value="1024 * 1024">1 MB</option><option :value="2 * 1024 * 1024">2 MB</option><option :value="5 * 1024 * 1024">5 MB</option></select></div></div><div class="progress-container" v-if="isDownloading || isPaused"><div class="progress-bar"><div class="progress" :style="{ width: `${progress}%` }"></div></div><div class="progress-stats"><div class="stat-item"><span class="label">進度:</span><span class="value">{{ progress.toFixed(2) }}%</span></div><div class="stat-item"><span class="label">已下載:</span><span class="value">{{ formatFileSize(downloadedBytes) }} / {{ formatFileSize(totalBytes) }}</span></div><div class="stat-item"><span class="label">速度:</span><span class="value">{{ downloadSpeed }}</span></div><div class="stat-item"><span class="label">已完成塊:</span><span class="value">{{ downloadedChunks }} / {{ totalChunks }}</span></div><div class="stat-item"><span class="label">剩余時間:</span><span class="value">{{ remainingTime }}</span></div></div></div><div class="chunk-visualization" v-if="isDownloading || isPaused"><div class="chunk-grid"><divv-for="(chunk, index) in chunkStatus":key="index"class="chunk-block":class="{'downloaded': chunk === 'completed','downloading': chunk === 'downloading','pending': chunk === 'pending','error': chunk === 'error'}":title="`塊 ${index + 1}: ${chunk}`"></div></div></div><div class="actions"><button@click="startDownload":disabled="isDownloading"v-if="!isDownloading && !isPaused"class="btn btn-primary">開始下載</button><button@click="pauseDownload":disabled="!isDownloading"v-if="isDownloading && !isPaused"class="btn btn-warning">暫停</button><button@click="resumeDownload":disabled="!isPaused"v-if="isPaused"class="btn btn-success">繼續</button><button@click="cancelDownload":disabled="!isDownloading && !isPaused"class="btn btn-danger">取消</button></div><div class="status-message" v-if="statusMessage">{{ statusMessage }}</div></div></div>
</template><script setup>
import {computed, onMounted, onUnmounted, ref, watch} from 'vue';
import {ChunkDownloader} from './downloadService';// API URL
const API_BASE_URL = 'http://localhost:8000/download/';// 下載配置
const concurrency = ref(3);
const chunkSize = ref(1024 * 1024); // 1MB
const downloader = ref(null);// 狀態變量
const fileInfo = ref(null);
const isDownloading = ref(false);
const isPaused = ref(false);
const downloadedBytes = ref(0);
const totalBytes = ref(0);
const downloadedChunks = ref(0);
const totalChunks = ref(0);
const statusMessage = ref('準備就緒');
const downloadStartTime = ref(0);
const lastUpdateTime = ref(0);
const lastBytes = ref(0);
const downloadSpeed = ref('0 KB/s');
const remainingTime = ref('計算中...');
const speedInterval = ref(null);// 塊狀態
const chunkStatus = ref([]);// 計算下載進度百分比
const progress = computed(() => {if (totalBytes.value === 0) return 0;return (downloadedBytes.value / totalBytes.value) * 100;
});// 初始化
onMounted(async () => {try {await fetchFileInfo();} catch (error) {console.error('獲取文件信息失敗:', error);statusMessage.value = `獲取文件信息失敗: ${error.message}`;}
});// 清理資源
onUnmounted(() => {if (downloader.value) {downloader.value.cancel();}clearInterval(speedInterval.value);
});// 獲取文件信息
async function fetchFileInfo() {const response = await fetch(`${API_BASE_URL}large_file/file_info/`);if (!response.ok) {throw new Error(`HTTP 錯誤! 狀態碼: ${response.status}`);}fileInfo.value = await response.json();totalBytes.value = fileInfo.value.size;// 根據文件大小初始化分塊狀態const initialTotalChunks = Math.ceil(fileInfo.value.size / chunkSize.value);chunkStatus.value = Array(initialTotalChunks).fill('pending');totalChunks.value = initialTotalChunks;
}// 開始下載
async function startDownload() {if (isDownloading.value) return;try {// 初始化下載器downloader.value = new ChunkDownloader(`${API_BASE_URL}`, {chunkSize: chunkSize.value,concurrency: concurrency.value,maxRetries: 3,onProgress: handleProgress,onComplete: handleComplete,onError: handleError,onStatusChange: handleStatusChange});// 初始化狀態downloadedBytes.value = 0;downloadedChunks.value = 0;isDownloading.value = true;isPaused.value = false;statusMessage.value = '準備下載...';// 獲取文件信息await downloader.value.fetchFileInfo();// 更新總塊數totalChunks.value = Math.ceil(downloader.value.fileSize / chunkSize.value);chunkStatus.value = Array(totalChunks.value).fill('pending');// 開始下載downloadStartTime.value = Date.now();lastUpdateTime.value = Date.now();lastBytes.value = 0;startSpeedCalculator();await downloader.value.start();} catch (error) {console.error('下載啟動失敗:', error);statusMessage.value = `下載啟動失敗: ${error.message}`;isDownloading.value = false;}
}// 暫停下載
function pauseDownload() {if (!isDownloading.value || !downloader.value) return;downloader.value.pause();isDownloading.value = false;isPaused.value = true;statusMessage.value = '下載已暫停';clearInterval(speedInterval.value);
}// 繼續下載
function resumeDownload() {if (!isPaused.value || !downloader.value) return;downloader.value.resume();isDownloading.value = true;isPaused.value = false;statusMessage.value = '繼續下載...';// 重新開始速度計算lastUpdateTime.value = Date.now();lastBytes.value = downloadedBytes.value;startSpeedCalculator();
}// 取消下載
function cancelDownload() {if (!downloader.value) return;downloader.value.cancel();isDownloading.value = false;isPaused.value = false;downloadedBytes.value = 0;downloadedChunks.value = 0;statusMessage.value = '下載已取消';clearInterval(speedInterval.value);// 重置塊狀態chunkStatus.value = Array(totalChunks.value).fill('pending');
}// 處理進度更新
function handleProgress(data) {downloadedBytes.value = data.downloadedBytes;downloadedChunks.value = data.downloadedChunks;// 更新塊狀態(這里僅是簡化的更新方式,實際上應該由downloader提供精確的塊狀態)const newChunkStatus = [...chunkStatus.value];const completedChunksCount = Math.floor(downloadedChunks.value);for (let i = 0; i < newChunkStatus.length; i++) {if (i < completedChunksCount) {newChunkStatus[i] = 'completed';} else if (i < completedChunksCount + concurrency.value && newChunkStatus[i] !== 'completed') {newChunkStatus[i] = 'downloading';}}chunkStatus.value = newChunkStatus;
}// 處理下載完成
function handleComplete(data) {isDownloading.value = false;isPaused.value = false;statusMessage.value = '下載完成';clearInterval(speedInterval.value);// 標記所有塊為已完成chunkStatus.value = Array(totalChunks.value).fill('completed');
}// 處理錯誤
function handleError(error) {console.error('下載錯誤:', error);statusMessage.value = `下載錯誤: ${error.message}`;
}// 處理狀態變化
function handleStatusChange(status, error) {switch (status) {case 'downloading':isDownloading.value = true;isPaused.value = false;statusMessage.value = '下載中...';break;case 'paused':isDownloading.value = false;isPaused.value = true;statusMessage.value = '下載已暫停';break;case 'completed':isDownloading.value = false;isPaused.value = false;statusMessage.value = '下載完成';break;case 'error':isDownloading.value = false;statusMessage.value = `下載錯誤: ${error?.message || '未知錯誤'}`;break;}
}// 啟動下載速度計算器
function startSpeedCalculator() {clearInterval(speedInterval.value);speedInterval.value = setInterval(() => {const now = Date.now();const timeElapsed = (now - lastUpdateTime.value) / 1000; // 轉換為秒const bytesDownloaded = downloadedBytes.value - lastBytes.value;if (timeElapsed > 0) {const speed = bytesDownloaded / timeElapsed; // 字節/秒downloadSpeed.value = formatFileSize(speed) + '/s';// 計算剩余時間if (speed > 0) {const bytesRemaining = totalBytes.value - downloadedBytes.value;const secondsRemaining = bytesRemaining / speed;remainingTime.value = formatTime(secondsRemaining);} else {remainingTime.value = '計算中...';}lastUpdateTime.value = now;lastBytes.value = downloadedBytes.value;}}, 1000);
}// 格式化文件大小
function formatFileSize(bytes) {if (bytes === 0) return '0 B';const k = 1024;const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];const i = Math.floor(Math.log(bytes) / Math.log(k));return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}// 格式化時間
function formatTime(seconds) {if (!isFinite(seconds) || seconds < 0) {return '計算中...';}if (seconds < 60) {return `${Math.ceil(seconds)}`;} else if (seconds < 3600) {const minutes = Math.floor(seconds / 60);const secs = Math.ceil(seconds % 60);return `${minutes}${secs}`;} else {const hours = Math.floor(seconds / 3600);const minutes = Math.floor((seconds % 3600) / 60);return `${hours}小時${minutes}分鐘`;}
}// 監聽配置改變,更新塊狀態
watch([chunkSize], () => {if (fileInfo.value && fileInfo.value.size) {totalChunks.value = Math.ceil(fileInfo.value.size / chunkSize.value);chunkStatus.value = Array(totalChunks.value).fill('pending');}
});
</script><style scoped>
.enhanced-downloader {max-width: 800px;margin: 0 auto;padding: 20px;
}.card {background: #fff;border-radius: 8px;box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);padding: 20px;
}h2 {margin-top: 0;color: #333;
}.file-info {margin-bottom: 20px;
}.progress-container {margin-bottom: 20px;
}.progress-bar {height: 20px;background-color: #f0f0f0;border-radius: 10px;overflow: hidden;margin-bottom: 10px;
}.progress {height: 100%;background-color: #4CAF50;transition: width 0.3s ease;
}.progress-stats {display: flex;justify-content: space-between;font-size: 14px;color: #666;
}.actions {display: flex;gap: 10px;margin-bottom: 20px;
}.btn {padding: 8px 16px;border: none;border-radius: 4px;cursor: pointer;font-weight: bold;transition: background-color 0.3s;
}.btn:disabled {opacity: 0.5;cursor: not-allowed;
}.btn-primary {background-color: #4CAF50;color: white;
}.btn-warning {background-color: #FF9800;color: white;
}.btn-success {background-color: #2196F3;color: white;
}.btn-danger {background-color: #F44336;color: white;
}.status {font-style: italic;color: #666;
}
</style>

2,下載服務 (ChunkDownloader類):

  • 負責管理整個下載過程
  • 處理文件信息獲取、分塊下載、進度追蹤
  • 實現并發控制、重試機制、暫停/繼續功能
// downloadService.js - 分塊下載實現/*文件分塊下載器*/
export class ChunkDownloader {constructor(url, options = {}) {this.url = url;this.chunkSize = options.chunkSize || 1024 * 1024; // 默認1MB每塊this.maxRetries = options.maxRetries || 3;this.concurrency = options.concurrency || 3; // 并發下載塊數this.timeout = options.timeout || 30000; // 超時時間this.fileSize = 0;this.fileName = '';this.contentType = '';this.chunks = [];this.downloadedChunks = 0;this.activeDownloads = 0;this.totalChunks = 0;this.downloadedBytes = 0;this.status = 'idle'; // idle, downloading, paused, completed, errorthis.error = null;this.onProgress = options.onProgress || (() => {});this.onComplete = options.onComplete || (() => {});this.onError = options.onError || (() => {});this.onStatusChange = options.onStatusChange || (() => {});this.abortControllers = new Map();this.pendingChunks = [];this.processedChunks = new Set();}// 獲取文件信息async fetchFileInfo() {try {const response = await fetch(this.url + 'large_file/file_info/');if (!response.ok) {throw new Error(`無法獲取文件信息: ${response.status}`);}const info = await response.json();this.fileSize = info.size;this.fileName = info.name;this.contentType = info.type;// 計算分塊數量this.totalChunks = Math.ceil(this.fileSize / this.chunkSize);return info;} catch (error) {this.error = error;this.status = 'error';this.onStatusChange(this.status, error);this.onError(error);throw error;}}// 開始下載async start() {if (this.status === 'downloading') {return;}try {// 如果還沒獲取文件信息,先獲取if (this.fileSize === 0) {await this.fetchFileInfo();}// 初始化狀態this.status = 'downloading';this.onStatusChange(this.status);// 如果是全新下載,初始化塊數組if (this.chunks.length === 0) {this.chunks = new Array(this.totalChunks).fill(null);this.pendingChunks = Array.from({length: this.totalChunks}, (_, i) => i);}// 開始并發下載this.startConcurrentDownloads();} catch (error) {this.error = error;this.status = 'error';this.onStatusChange(this.status, error);this.onError(error);}}// 開始并發下載startConcurrentDownloads() {// 確保同時只有指定數量的并發下載while (this.activeDownloads < this.concurrency && this.pendingChunks.length > 0) {const chunkIndex = this.pendingChunks.shift();this.downloadChunk(chunkIndex);}}// 下載指定的塊async downloadChunk(chunkIndex, retryCount = 0) {if (this.status !== 'downloading' || this.processedChunks.has(chunkIndex)) {return;}this.activeDownloads++;const startByte = chunkIndex * this.chunkSize;const endByte = Math.min(startByte + this.chunkSize - 1, this.fileSize - 1);// 創建用于取消請求的控制器const controller = new AbortController();this.abortControllers.set(chunkIndex, controller);try {const response = await fetch(this.url + 'large_file/download_large_file/',{method: 'GET',headers: {'Range': `bytes=${startByte}-${endByte}`},signal: controller.signal,timeout: this.timeout});if (!response.ok && response.status !== 206) {throw new Error(`服務器錯誤: ${response.status}`);}// 獲取塊數據const blob = await response.blob();this.chunks[chunkIndex] = blob;this.downloadedChunks++;this.downloadedBytes += blob.size;this.processedChunks.add(chunkIndex);// 更新進度this.onProgress({downloadedChunks: this.downloadedChunks,totalChunks: this.totalChunks,downloadedBytes: this.downloadedBytes,totalBytes: this.fileSize,progress: (this.downloadedBytes / this.fileSize) * 100});// 清理控制器this.abortControllers.delete(chunkIndex);// 檢查是否下載完成if (this.downloadedChunks === this.totalChunks) {this.completeDownload();} else if (this.status === 'downloading') {// 繼續下載下一個塊this.activeDownloads--;this.startConcurrentDownloads();}} catch (error) {this.abortControllers.delete(chunkIndex);if (error.name === 'AbortError') {// 用戶取消,不進行重試this.activeDownloads--;return;}// 重試邏輯if (retryCount < this.maxRetries) {console.warn(`${chunkIndex} 下載失敗,重試 ${retryCount + 1}/${this.maxRetries}`);this.activeDownloads--;this.downloadChunk(chunkIndex, retryCount + 1);} else {console.error(`${chunkIndex} 下載失敗,已達到最大重試次數`);this.error = error;this.status = 'error';this.onStatusChange(this.status, error);this.onError(error);this.activeDownloads--;}}}// 完成下載completeDownload() {if (this.status === 'completed') {return;}try {// 合并所有塊const blob = new Blob(this.chunks, {type: this.contentType});// 創建下載鏈接const url = URL.createObjectURL(blob);const a = document.createElement('a');a.href = url;a.download = this.fileName;document.body.appendChild(a);a.click();document.body.removeChild(a);// 清理資源setTimeout(() => URL.revokeObjectURL(url), 100);// 更新狀態this.status = 'completed';this.onStatusChange(this.status);this.onComplete({fileName: this.fileName,fileSize: this.fileSize,contentType: this.contentType,blob: blob});} catch (error) {this.error = error;this.status = 'error';this.onStatusChange(this.status, error);this.onError(error);}}// 暫停下載pause() {if (this.status !== 'downloading') {return;}// 取消所有正在進行的請求this.abortControllers.forEach(controller => {controller.abort();});// 清空控制器集合this.abortControllers.clear();// 更新狀態this.status = 'paused';this.activeDownloads = 0;this.onStatusChange(this.status);// 將當前處理中的塊重新加入待處理隊列this.pendingChunks = Array.from({length: this.totalChunks}, (_, i) => i).filter(i => !this.processedChunks.has(i));}// 繼續下載resume() {if (this.status !== 'paused') {return;}this.status = 'downloading';this.onStatusChange(this.status);this.startConcurrentDownloads();}// 取消下載cancel() {// 取消所有正在進行的請求this.abortControllers.forEach(controller => {controller.abort();});// 重置所有狀態this.chunks = [];this.downloadedChunks = 0;this.activeDownloads = 0;this.downloadedBytes = 0;this.status = 'idle';this.error = null;this.abortControllers.clear();this.pendingChunks = [];this.processedChunks.clear();this.onStatusChange(this.status);}// 獲取當前狀態getStatus() {return {status: this.status,downloadedChunks: this.downloadedChunks,totalChunks: this.totalChunks,downloadedBytes: this.downloadedBytes,totalBytes: this.fileSize,progress: this.fileSize ? (this.downloadedBytes / this.fileSize) * 100 : 0,fileName: this.fileName,error: this.error};}
}

2,核心技術原理

(1)HTTP Range請求

該實現通過HTTP的Range頭部實現分塊下載:

const response = await fetch(this.url + 'large_file/download_large_file/',{method: 'GET',headers: {'Range': `bytes=${startByte}-${endByte}`},signal: controller.signal,timeout: this.timeout});
  • 服務器會返回狀態碼206(Partial Content)和請求的文件片段。

(2)并發控制

代碼通過控制同時活躍的下載請求數量來實現并發:

while (this.activeDownloads < this.concurrency && this.pendingChunks.length > 0) {const chunkIndex = this.pendingChunks.shift();this.downloadChunk(chunkIndex);
}

(3)狀態管理和進度追蹤

  • 跟蹤每個塊的下載狀態(待下載、下載中、已完成、錯誤)
  • 計算并報告總體進度、下載速度和剩余時間

(4)錯誤處理和重試機制

對下載失敗的塊進行自動重試:

if (retryCount < this.maxRetries) {console.warn(`${chunkIndex} 下載失敗,重試 ${retryCount + 1}/${this.maxRetries}`);this.activeDownloads--;this.downloadChunk(chunkIndex, retryCount + 1);
}

(5)暫停/恢復功能

通過AbortController取消活躍的請求,并保存未完成的塊索引:

pause() {// 取消所有正在進行的請求this.abortControllers.forEach(controller => {controller.abort();});// 將當前處理中的塊重新加入待處理隊列this.pendingChunks = Array.from({length: this.totalChunks}, (_, i) => i).filter(i => !this.processedChunks.has(i));
}

(6)文件合并和下載

所有塊下載完成后,使用Blob API合并所有分塊并創建下載鏈接:

const blob = new Blob(this.chunks, {type: this.contentType});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = this.fileName;
a.click();

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/72539.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/72539.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/72539.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

matlab中如何集成使用python

在 MATLAB 中集成使用 Python 可以通過調用 Python 腳本或函數來實現。MATLAB 提供了 py 模塊來直接調用 Python 代碼。以下是一個簡單的示例&#xff0c;展示如何在 MATLAB 中調用 Python 函數。 示例&#xff1a;在 MATLAB 中調用 Python 函數 1. 編寫 Python 函數 首先&a…

ICMP、UDP以及IP、ARP報文包的仲裁處理

在之前的章節中&#xff0c;筆者就UDP、ICMP、IP、ARP、MAC層以及巨型幀等做了詳細介紹以及代碼實現及仿真&#xff0c;從本章節開始&#xff0c;筆者將就各個模塊組合在一起&#xff0c;實現UDP協議棧的整體收發&#xff0c;在實現模塊的整體組合之前&#xff0c;還需要考慮一…

【大模型學習】第十九章 什么是遷移學習

目錄 1. 遷移學習的起源背景 1.1 傳統機器學習的問題 1.2 遷移學習的提出背景 2. 什么是遷移學習 2.1 遷移學習的定義 2.2 生活實例解釋 3. 技術要點與原理 3.1 遷移學習方法分類 3.1.1 基于特征的遷移學習(Feature-based Transfer) 案例說明 代碼示例 3.1.2 基于…

基于大模型的分泌性中耳炎全流程預測與治療管理研究報告

目錄 一、引言 1.1 研究背景與意義 1.2 研究目的與目標 1.3 研究方法與創新點 二、分泌性中耳炎概述 2.1 疾病定義與特征 2.2 發病原因與機制 2.3 疾病危害與影響 三、大模型技術原理與應用現狀 3.1 大模型基本原理 3.2 在醫療領域的應用案例 3.3 選擇大模型預測分…

【NLP 38、實踐 ⑩ NER 命名實體識別任務 Bert 實現】

去做具體的事&#xff0c;然后穩穩托舉自己 —— 25.3.17 數據文件&#xff1a; 通過網盤分享的文件&#xff1a;Ner命名實體識別任務 鏈接: https://pan.baidu.com/s/1fUiin2um4PCS5i91V9dJFA?pwdyc6u 提取碼: yc6u --來自百度網盤超級會員v3的分享 一、配置文件 config.py …

藍橋杯學習-11棧

11棧 先進后出 例題–藍橋19877 用數組來設置棧 1.向棧頂插入元素--top位置標記元素 2.刪除棧頂元素--top指針減減 3.輸出棧頂元素--輸出top位置元素使用arraylist import java.util.ArrayList; import java.util.Scanner;public class Main {public static void main(Str…

Linux 藍牙音頻軟件棧實現分析

Linux 藍牙音頻軟件棧實現分析 藍牙協議棧簡介藍牙控制器探測BlueZ 插件系統及音頻插件藍牙協議棧簡介 藍牙協議棧是實現藍牙通信功能的軟件架構,它由多個層次組成,每一層負責特定的功能。藍牙協議棧的設計遵循藍牙標準 (由藍牙技術聯盟,Bluetooth SIG 定義),支持多種藍牙…

JetBrains(全家桶: IDEA、WebStorm、GoLand、PyCharm) 2024.3+ 2025 版免費體驗方案

JetBrains&#xff08;全家桶: IDEA、WebStorm、GoLand、PyCharm&#xff09; 2024.3 2025 版免費體驗方案 前言 JetBrains IDE 是許多開發者的主力工具&#xff0c;但從 2024.02 版本起&#xff0c;JetBrains 調整了試用政策&#xff0c;新用戶不再享有默認的 30 天免費試用…

1.8PageTable

頁表的作用 虛擬地址空間映射&#xff1a;頁表記錄了進程的虛擬頁號到物理頁號的映射關系。每個進程都有自己的頁表&#xff0c;操作系統為每個進程維護一個獨立的頁表。內存管理&#xff1a;頁表用于實現虛擬內存管理&#xff0c;支持進程的虛擬地址空間和物理地址空間之間的…

Prosys OPC UA Gateway:實現 OPC Classic 與 OPC UA 無縫連接

在工業自動化的數字化轉型中&#xff0c;設備與系統之間的高效通信至關重要。然而&#xff0c;許多企業仍依賴于基于 COM/DCOM 技術的 OPC 產品&#xff0c;這給與現代化的 OPC UA 架構的集成帶來了挑戰。 Prosys OPC UA Gateway 正是為解決這一問題而生&#xff0c;它作為一款…

數據結構------線性表

一、線性表順序存儲詳解 &#xff08;一&#xff09;線性表核心概念 1. 結構定義 // 數據元素類型 typedef struct person {char name[32];char sex;int age;int score; } DATATYPE;// 順序表結構 typedef struct list {DATATYPE *head; // 存儲空間基地址int tlen; …

【WPF】在System.Drawing.Rectangle中限制鼠標保持在Rectangle中移動?

方案一&#xff0c;在OnMouseMove方法限制 在WPF應用程序中&#xff0c;鼠標在移動過程中保持在這個矩形區域內&#xff0c;可以通過監聽鼠標的移動事件并根據鼠標的當前位置調整其坐標來實現。不過需要注意的是&#xff0c;WPF原生使用的是System.Windows.Rect而不是System.D…

基于銀河麒麟系統ARM架構安裝達夢數據庫并配置主從模式

達夢數據庫簡要概述 達夢數據庫&#xff08;DM Database&#xff09;是一款由武漢達夢公司開發的關系型數據庫管理系統&#xff0c;支持多種高可用性和數據同步方案。在主從模式&#xff08;也稱為 Master-Slave 或 Primary-Secondary 模式&#xff09;中&#xff0c;主要通過…

系統思考全球化落地

感謝加密貨幣公司Bybit的再次邀請&#xff0c;為全球團隊分享系統思考課程&#xff01;雖然大家來自不同國家&#xff0c;線上學習的形式依然讓大家充滿熱情與互動&#xff0c;思維的碰撞不斷激發新的靈感。 盡管時間存在挑戰&#xff0c;但我看到大家的討論異常積極&#xff…

Figma的漢化

Figma的漢化插件有客戶端版本與Chrome版本&#xff0c;大家可根據自己的需要進行選擇。 下載插件 進入Figma軟件漢化-Figma中文版下載-Figma中文社區使用客戶端&#xff1a;直接下載客戶端使用網頁版&#xff1a;安裝chrome瀏覽器漢化插件國外推薦前往chrome商店安裝國內推薦下…

【Go語言圣經2.5】

目標 了解類型定義不僅告訴編譯器如何在內存中存儲和處理數據&#xff0c;還對程序設計產生深遠影響&#xff1a; 內存結構&#xff1a;類型決定了變量的底層存儲&#xff08;比如占用多少字節、內存布局等&#xff09;。操作符與方法集&#xff1a;類型決定了哪些內置運算符…

IDEA 一鍵完成:打包 + 推送 + 部署docker鏡像

1、本方案要解決場景&#xff1f; 想直接通過本地 IDEA 將最新的代碼部署到遠程服務器上。 2、本方案適用于什么樣的項目&#xff1f; 項目是一個 Spring Boot 的 Java 項目。項目用 maven 進行管理。項目的運行基于 docker 容器&#xff08;即項目將被打成 docker image&am…

SpringBoot 第一課(Ⅲ) 配置類注解

目錄 一、PropertySource 二、ImportResource ①SpringConfig &#xff08;Spring框架全注解&#xff09; ②ImportResource注解實現 三、Bean 四、多配置文件 多Profile文件的使用 文件命名約定&#xff1a; 激活Profile&#xff1a; YAML文件支持多文檔塊&#xff…

深度解析React Native底層核心架構

React Native 工作原理深度解析 一、核心架構&#xff1a;三層異構協作體系 React Native 的跨平臺能力源于其獨特的 JS層-Shadow層-Native層 架構設計&#xff0c;三者在不同線程中協同工作&#xff1a; JS層 運行于JavaScriptCore&#xff08;iOS&#xff09;或Hermes&…

對話智能體的正確打開方式:解析主流AI聊天工具的核心能力與使用方式

一、人機對話的黃金法則 在與人工智能對話系統交互時&#xff0c;掌握以下七項核心原則可顯著提升溝通效率&#xff1a;文末有教程分享地址 意圖精準表達術 采用"背景需求限定條件"的結構化表達 示例優化&#xff1a;"請用Python編寫一個網絡爬蟲&#xff08…