断点续传之上传

2020/01/09 计算机网络

这篇文章主要讲断点续传的上传原理。

断点续传指的是在文件传输时将文件进行切分,每个部分的传输都是独立的,并且在遇到网络故障时,可以在网络通畅之后继续传输的未曾传输的部分,并不需要重新传输。

根据以上描述,Web 端的文件上传步骤如下。为了思路的清晰,以下代码十分简陋,没有进行相应的错误处理,而且是非并发安全的,千万不要在实际项目中这么写!

读取文件

var fileEle = document.querySelector('#file');
var readerFile;
var file;

fileEle.onchange = function() {
    var rd = new FileReader();
    file = this.files[0]; // 这里默认只有一个文件,多文件的话可以自行扩展
    
    rd.onload = function(e) {
        readerFile = e.target.result;
    }
    
    rd.readAsDataURL(file);
}

切分文件

var start = 0; // 切片头部偏移量
var count = 0; // 切片序号
var step = 10240; // 切片大小
var piece;

function getPiece() {
    piece = readerFile.slice(start, start + step);
    return piece;
}

上传文件

function upload() {
    var fm = new FormData();
    fm.append('name', file.name);
    fm.append('cap', file.size);
    fm.append('len', step);
    fm.append('seq_' + String(count), getPiece());
    
    var xhr = new XMLHttpRequest();
    xhr.onload = function (e) {
        if (e.target.status == 200 && e.target.readyState == 4) {
            count ++;
            start += step;
            if (start < readerFile.length) {
                // 这里自己决定怎么续传
                setTimeout(function() {
                    upload();
                }, 1000);
            } else {
                alert('上传成功');
            }
        }
    }
    
    xhr.open('post', 'http://localhost:8888/upload');
    xhr.send(fm);
}

上传完成,合并文件

将前端传来的seq_num(num 为序列号)保存到数据库中,可以根据文件的大小来判断是否上传完毕,待确认文件上传完毕,即可进行合并。以下代码将这些复杂的步骤进行了简化,将每一段切片都直接追回到同一个临时文件中,并以一个特别的请求表示文件已经上传完毕。

<?php

foreach ($_POST as $k => $v) {
    if (preg_match('/\d+$/', $k) != 0) {
        if (preg_match('/[^\d]0$/', $k)) {
            $fd = fopen('test.txt', "wb+");
            $v = preg_replace('/data:.*?,/', '', $v);
        } else {
            $fd = fopen('test.txt', "ab+");
        }

        fwrite($fd, $v);
        fclose($fd);
    }

    if ($k == 'end') {
        $content = file_get_contents('test.txt');
        $content = base64_decode($content);
        file_put_contents('test.jpg', $content);
        
        header('Content-type: image/jpeg');
        echo $content;
    }
}

完整的前端代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="">
    <input type="file" id="file">
    <button type="button" id="sm">提交</button>
</form>
<script>
    var fileEle = document.querySelector('#file');
    var readerFile;
    var file;

    var start = 0; // 切片偏移量
    var count = 0; // 切片序号
    var step = 10240; // 切片大小
    var piece; // 切片内容
    
    var url = 'http://localhost:8888/upload';

    fileEle.onchange = function () {
        var rd = new FileReader();
        file = this.files[0]; // 这里默认只有一个文件,多文件的话可以自行扩展

        rd.onload = function (e) {
            readerFile = e.target.result;
        }

        rd.readAsDataURL(file);
    }

    function getPiece() {
        piece = readerFile.slice(start, start + step);
        return piece;
    }

    function upload() {
        var fm = new FormData();
        fm.append('name', file.name);
        fm.append('cap', file.size);
        fm.append('len', step);
        fm.append('seq_' + String(count), getPiece());

        var xhr = new XMLHttpRequest();
        xhr.onload = function (e) {
            if (e.target.status == 200 && e.target.readyState == 4) {
                count++;
                start += step;
                if (start < readerFile.length) {
                    // 这里自己决定怎么续传
                    setTimeout(function () {
                        upload();
                    }, 1000);
                } else {
                    end();
                    alert('上传成功');
                }
            }
        }

        xhr.open('post', url);
        xhr.send(fm);
    }

    function end() {
        var fm = new FormData();
        fm.append('end', 'end');

        var xhr = new XMLHttpRequest();
        xhr.onload = function (e) {
            if (e.target.status == 200 && e.target.readyState == 4) {

            }
        };

        xhr.open('post', url);
        xhr.send(fm);
    }

    var sm = document.querySelector('#sm');
    sm.onclick = function () {
        count = 0;
        start = 0;
        upload();
    }
</script>
</body>
</html>

Search

    Table of Contents