javascript 大文件下载,分片下载,断点续传

既然是断点续传,自然离不开分片下载。接下来,我们将一个文件分片并一个一个下载。

首先,我们需要获取文件的大小,以便更好地对其进行分段。

1:获取文件大小:

// status
const DONE = 4;

// get content length
function getContentLength(url) {
    return new Promise((resolve, reject) => {
        var xhr = new XMLHttpRequest();
        xhr.open('HEAD', url, true);
        xhr.onreadystatechange = function () {
            if (this.readyState == DONE) {
                resolve(this.getResponseHeader('Content-Length'));
            }
        }
        xhr.send();
    })
}

得到文件大小后,对其进行切片,得到对应切片的内容。

因为这里的测试文件不是很大,所以每个切片的大小设置为100。

具体大小视个人需求而定。

2:切片下载

// status
const DONE = 4;

// range size
const RANGE_SIZE = 100;
// get range content
function getRangeContent(startIndex, endIndex, url) {
    return new Promise((resolve, reject) => {
        var xhr = new XMLHttpRequest();
        xhr.open('GET', url, true);
        xhr.setRequestHeader('Range', `bytes=${startIndex}-${endIndex}`);
        xhr.responseType = 'arraybuffer';
        xhr.onreadystatechange = function () {
            if (this.readyState == DONE) {
                resolve(this.response);
            }
        }
        xhr.send();
    })
}

因为是arraybuffer类型的内容,这里我们使用Uint8Array视图来接受合并。

3:合并数据

// concat arraybuffer array
function concatArrayBuffer(arrayBufferArray) {
    let totalLength = 0;
    arrayBufferArray.forEach(arrayBuffer => {
        totalLength += arrayBuffer.byteLength;
    });
    const result = new Uint8Array(totalLength);
    let offset = 0;
    arrayBufferArray.forEach(arrayBuffer => {
        result.set(new Uint8Array(arrayBuffer), offset);
        offset += arrayBuffer.byteLength;
    });
    return result;
}

如果您有下载的需要,这里有一个示例,您可以将合并后的数据下载到本地。

4:下载到本地

// download arraybuffer file
function downloadArrayBufferFile(arrayBuffer, fileName) {
    const blob = new Blob([arrayBuffer], { type: 'application/octet-stream' });
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = fileName;
    a.click();
}

然后你就可以得到完整的内容了。

5:成功

// main methoeds
async function main() {
    const fileUrl = 'http://localhost:8083/public/m2.jpeg';
    const contentLength = await getContentLength(fileUrl);
    const numberRequest = Math.ceil(contentLength / RANGE_SIZE);
    const arrayBufferArray = [];
    for (let i = 0; i < numberRequest; i++) {
        const startIndex = i * RANGE_SIZE;
        const endIndex = startIndex + RANGE_SIZE - 1;
        const clip = await getRangeContent(startIndex, endIndex, fileUrl);
        arrayBufferArray.push(clip);
    }
    const result = concatArrayBuffer(arrayBufferArray);
    downloadArrayBufferFile(result, 'access.txt');
}

6:完整代码

// status
const DONE = 4;

// range size
const RANGE_SIZE = 100;

// get content length
function getContentLength(url) {
    return new Promise((resolve, reject) => {
        var xhr = new XMLHttpRequest();
        xhr.open('HEAD', url, true);
        xhr.onreadystatechange = function () {
            if (this.readyState == DONE) {
                resolve(this.getResponseHeader('Content-Length'));
            }
        }
        xhr.send();
    })
}

// get range content
function getRangeContent(startIndex, endIndex, url) {
    return new Promise((resolve, reject) => {
        var xhr = new XMLHttpRequest();
        xhr.open('GET', url, true);
        xhr.setRequestHeader('Range', `bytes=${startIndex}-${endIndex}`);
        xhr.responseType = 'arraybuffer';
        xhr.onreadystatechange = function () {
            if (this.readyState == DONE) {
                console.log(this.response)
                resolve(this.response);
            }
        }
        xhr.send();
    })
}

// download arraybuffer file
function downloadArrayBufferFile(arrayBuffer, fileName) {
    const blob = new Blob([arrayBuffer], { type: 'application/octet-stream' });
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = fileName;
    a.click();
}

// concat arraybuffer array
function concatArrayBuffer(arrayBufferArray) {
    let totalLength = 0;
    arrayBufferArray.forEach(arrayBuffer => {
        totalLength += arrayBuffer.byteLength;
    });
    const result = new Uint8Array(totalLength);
    let offset = 0;
    arrayBufferArray.forEach(arrayBuffer => {
        result.set(new Uint8Array(arrayBuffer), offset);
        offset += arrayBuffer.byteLength;
    });
    return result;
}

// main methoeds
async function main() {
    const fileUrl = 'http://localhost:8083/public/access.txt';
    const contentLength = await getContentLength(fileUrl);
    const numberRequest = Math.ceil(contentLength / RANGE_SIZE);
    const arrayBufferArray = [];
    for (let i = 0; i < numberRequest; i++) {
        const startIndex = i * RANGE_SIZE;
        const endIndex = startIndex + RANGE_SIZE - 1;
        const clip = await getRangeContent(startIndex, endIndex, fileUrl);
        arrayBufferArray.push(clip);
    }
    const result = concatArrayBuffer(arrayBufferArray);
    downloadArrayBufferFile(result, 'access.txt');
}

main();

至此,你应该有断点续传的想法了。
只需要对已经分片的数据进行标记,最后将标记好的分片数据进行合并,就可以得到一个完整的文件。
即使途中网络中断,只要正确标记了段数据,没有获取到哪个段数据,就可以从服务器重新获取丢失的段数据到本地,合并完整的内容并再次下载。
如果还是不知道怎么做,可以给我留言,接下来我会介绍前后端配合标记验证数据。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐