实现步骤
-
使用分片下载: 将大文件分割成多个小块进行下载,可以降低内存占用和网络传输中断的风险。这样可以避免一次性下载整个大文件造成的性能问题。
-
断点续传: 实现断点续传功能,即在下载中途中断后,可以从已下载的部分继续下载,而不需要重新下载整个文件。
-
进度条显示: 在页面上展示下载进度,让用户清晰地看到文件下载的进度。
-
取消下载和暂停下载功能: 提供取消下载和暂停下载的按钮,让用户可以根据需要中止或暂停下载过程。
-
合并文件: 下载完成后,将所有分片文件合并成一个完整的文件。
以下是一个基本的前端大文件下载的实现示例:
js
class FileDownloader {
constructor(url, fileName, chunkSize = 2 * 1024 * 1024) {
this.url = url;
this.fileName = fileName;
this.chunkSize = chunkSize;
this.fileSize = 0;
this.totalChunks = 0;
this.currentChunk = 0;
this.downloadedSize = 0;
this.chunks = [];
this.abortController = new AbortController();
this.paused = false;
}
async getFileSize() {
const response = await fetch(this.url, { signal: this.abortController.signal });
const contentLength = response.headers.get("content-length");
this.fileSize = parseInt(contentLength);
this.totalChunks = Math.ceil(this.fileSize / this.chunkSize);
}
async downloadChunk(chunkIndex) {
const start = chunkIndex * this.chunkSize;
const end = Math.min(this.fileSize, (chunkIndex + 1) * this.chunkSize - 1);
const response = await fetch(this.url, {
headers: { Range: `bytes=${start}-${end}` },
signal: this.abortController.signal
});
const blob = await response.blob();
this.chunks[chunkIndex] = blob;
this.downloadedSize += blob.size;
if (!this.paused && this.currentChunk < this.totalChunks - 1) {
this.currentChunk++;
this.downloadChunk(this.currentChunk);
} else if (this.currentChunk === this.totalChunks - 1) {
this.mergeChunks();
}
}
async startDownload() {
if (this.chunks.length === 0) {
await this.getFileSize();
}
this.downloadChunk(this.currentChunk);
}
pauseDownload() {
this.paused = true;
}
resumeDownload() {
this.paused = false;
this.downloadChunk(this.currentChunk);
}
cancelDownload() {
this.abortController.abort();
this.chunks = [];
this.currentChunk = 0;
this.downloadedSize = 0;
}
async mergeChunks() {
const blob = new Blob(this.chunks, { type: "application/octet-stream" });
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = this.fileName;
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
}
}
// 使用示例
const url = "https://example.com/largefile.zip";
const fileName = "largefile.zip";
const downloader = new FileDownloader(url, fileName);
// 开始下载
downloader.startDownload();
// 暂停下载
// downloader.pauseDownload();
// 继续下载
// downloader.resumeDownload();
// 取消下载
// downloader.cancelDownload();
分片下载怎么实现断点续传?已下载的文件怎么存储?
在分片下载过程中,每个下载的文件块(chunk)都需要在客户端进行缓存或存储,方便实现断点续传功能,同时也方便后续将这些文件块合并成完整的文件。这些文件块可以暂时保存在内存中或者存储在客户端的本地存储(如 IndexedDB、LocalStorage 等)中。
一般情况下,为了避免占用过多的内存,推荐将文件块暂时保存在客户端的本地存储中。这样可以确保在下载大文件时不会因为内存占用过多而导致性能问题。
在上面提供的示例代码中,文件块是暂时保存在一个数组中的,最终在mergeChunks()
方法中将这些文件块合并成完整的文件。如果你希望将文件块保存在本地存储中,可以根据需要修改代码,将文件块保存到 IndexedDB 或 LocalStorage 中。
indexedDB本地存储
原生的indexedDB api 使用起来很麻烦,稍不留神就会出现各种问题,封装一下方便以后使用。
这个类封装了 IndexedDB 的常用操作,包括打开数据库、添加数据、通过 ID 获取数据、获取全部数据、更新数据、删除数据和删除数据表。
封装indexedDB类
js
class IndexedDBWrapper {
constructor(dbName, storeName) {
this.dbName = dbName;
this.storeName = storeName;
this.db = null;
}
openDatabase() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName);
request.onerror = () => {
console.error("Failed to open database");
reject();
};
request.onsuccess = () => {
this.db = request.result;
resolve();
};
request.onupgradeneeded = () => {
this.db = request.result;
if (!this.db.objectStoreNames.contains(this.storeName)) {
this.db.createObjectStore(this.storeName, { keyPath: "id" });
}
};
});
}
addData(data) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName], "readwrite");
const objectStore = transaction.objectStore(this.storeName);
const request = objectStore.add(data);
request.onsuccess = () => {
resolve();
};
request.onerror = () => {
console.error("Failed to add data");
reject();
};
});
}
getDataById(id) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName], "readonly");
const objectStore = transaction.objectStore(this.storeName);
const request = objectStore.get(id);
request.onsuccess = () => {
resolve(request.result);
};
request.onerror = () => {
console.error(`Failed to get data with id: ${id}`);
reject();
};
});
}
getAllData() {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName], "readonly");
const objectStore = transaction.objectStore(this.storeName);
const request = objectStore.getAll();
request.onsuccess = () => {
resolve(request.result);
};
request.onerror = () => {
console.error("Failed to get all data");
reject();
};
});
}
updateData(data) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName], "readwrite");
const objectStore = transaction.objectStore(this.storeName);
const request = objectStore.put(data);
request.onsuccess = () => {
resolve();
};
request.onerror = () => {
console.error("Failed to update data");
reject();
};
});
}
deleteDataById(id) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName], "readwrite");
const objectStore = transaction.objectStore(this.storeName);
const request = objectStore.delete(id);
request.onsuccess = () => {
resolve();
};
request.onerror = () => {
console.error(`Failed to delete data with id: ${id}`);
reject();
};
});
}
deleteStore() {
return new Promise((resolve, reject) => {
const version = this.db.version + 1;
this.db.close();
const request = indexedDB.open(this.dbName, version);
request.onupgradeneeded = () => {
this.db = request.result;
this.db.deleteObjectStore(this.storeName);
resolve();
};
request.onsuccess = () => {
resolve();
};
request.onerror = () => {
console.error("Failed to delete object store");
reject();
};
});
}
}
使用indexedDB类示例:
js
const dbName = "myDatabase";
const storeName = "myStore";
const dbWrapper = new IndexedDBWrapper(dbName, storeName);
dbWrapper.openDatabase().then(() => {
const data = { id: 1, name: "John Doe", age: 30 };
dbWrapper.addData(data).then(() => {
console.log("Data added successfully");
dbWrapper.getDataById(1).then((result) => {
console.log("Data retrieved:", result);
const updatedData = { id: 1, name: "Jane Smith", age: 35 };
dbWrapper.updateData(updatedData).then(() => {
console.log("Data updated successfully");
dbWrapper.getDataById(1).then((updatedResult) => {
console.log("Updated data retrieved:", updatedResult);
dbWrapper.deleteDataById(1).then(() => {
console.log("Data deleted successfully");
dbWrapper.getAllData().then((allData) => {
console.log("All data:", allData);
dbWrapper.deleteStore().then(() => {
console.log("Object store deleted successfully");
});
});
});
});
});
});
});
});
indexedDB的使用库 - localforage
这个库对浏览器本地存储的几种方式做了封装,自动降级处理。但是使用indexedDB上感觉不是很好,不可以添加索引。
文档地址: localforage