天天看點

Python實作大檔案分片上傳引言實施關鍵代碼結果

轉載請注明出處: http://blog.csdn.net/jinixin/article/details/77545140

引言

想借着這篇文章簡要談談WebUploader大檔案上傳與Python結合的實作。

WebUploader是百度團隊對大檔案上傳的前端實作,而後端需要根據不同的語言自己實作。這裡我采用Python語言的Flask架構搭建後端,配合使用Bootstrap前端架構渲染上傳進度條,效果圖在文章底部。

WebUploader官網:點這裡;WebUploader API:點這裡
;

實施

http協定并不是非常适合上傳大檔案,是以要考慮分片,即把大檔案分割後再上傳,而WebUploader所做的事,正是将一個大檔案分片,一部分一部分的上傳到伺服器。在上傳每個分片的http請求中,需要同時攜帶:

1)該檔案的唯一辨別:task_id;

2)該檔案的分片總數:chunks;

3)該分片在該檔案所有分片中的位置:chunk;

其中後兩個WebUploader已經替我們自動上傳了,而第一個task_id僅需要我們調用對應函數即可産生,然後再将其寫入form-data。

WebUploader是一個前端架構,是以接收檔案的部分需要我們自己實作,而我選用了Python和其的Flask架構。
後端要做的是接收這一大堆分片,然後将它們重新合并成一個檔案,那麼有如下三個問題:

1)如何判定某個分片上傳後,是不是整個檔案也上傳結束了?

WebUploader已經為我們解決了,詳見下面代碼。

<script type="text/javascript">
$(document).ready(function() {
    var task_id = WebUploader.Base.guid();        //産生task_id,唯一辨別該檔案
    var uploader = WebUploader.create({
        server: '/upload/accept',                 //伺服器接收并處理分片的url位址
        formData: {
            task_id: task_id,                     //上傳分片的http請求攜帶的資料
        },
    });

    uploader.on('uploadSuccess', function(file) { //當該檔案所有分片均上傳成功時調用該函數
        //上傳的資訊(檔案唯一辨別符,檔案字尾名)
        var data = { 'task_id': task_id, 'ext': file.source['ext'], 'type': file.source['type'] };
        $.get('/upload/complete', data);          //ajax攜帶data向該url發請求
    });
});
</script>
           

2)如何處理接收分片和将分片内容寫入檔案的關系?

方案一:開一個字元串,一邊接收分片,一邊将分片裡的内容讀取出來後添加到字元串末尾;全部分片接收完畢後,再将字元串寫入新檔案中。

方案二:建立一個檔案,一邊接收分片,一邊将分片裡的内容讀取出來寫入檔案末尾。

方案三:為每個分片建立一個新的臨時檔案來儲存其内容;待全部分片上傳完畢後,再按順序讀取所有臨時檔案的内容,将資料寫入新檔案中。

前兩個方案看似不錯,但其實有些問題。方案一因等待所有分片的時間過長容易造成記憶體溢出;由于分片不一定是按序上傳,是以方案二也不行;故隻能選擇方案三了。

3)後端如何區分不同使用者的檔案?如何區分同個檔案不同分片的先後順序?

通過http請求攜帶的task_id可以區分不同的檔案。同個檔案分片的先後順序,可以通過http請求攜帶的chunk來區分。是以,task_id+chunk的組合可以在衆多不同使用者不同檔案的分片中唯一标記某個分片,即某個檔案的某個分片名稱是task_id+chunk。

關鍵代碼


前端代碼

<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <script src="./static/jquery-1.11.1.min.js"></script>
    <script src="./static/bootstrap/js/bootstrap.min.js"></script>
    <script src="./static/webuploader/webuploader.min.js"></script>
    <link rel="stylesheet" type="text/css" href="./static/webuploader/webuploader.css" target="_blank" rel="external nofollow" >
    <link rel="stylesheet" type="text/css" href="./static/bootstrap/css/bootstrap.min.css" target="_blank" rel="external nofollow" >
</head>

<body>
    <div>
        <div id="picker">請選擇</div>   <!-- 上傳按鈕,必須指定id選擇器的值 -->
        <div class="progress">         <!-- 進度條 -->
            <div class="progress-bar progress-bar-striped active" role="progressbar" style="width:0%;"></div>
        </div>
    </div>
    <script type="text/javascript">
    $(document).ready(function() {
        var task_id = WebUploader.Base.guid();        //産生task_id
        var uploader = WebUploader.create({           //建立上傳控件
            swf: './static/webuploader/Uploader.swf', //swf位置,這個可能與flash有關
            server: '/upload/accept',                 //接收每一個分片的伺服器位址
            pick: '#picker',                          //填上傳按鈕的id選擇器值
            auto: true,                               //選擇檔案後,是否自動上傳
            chunked: true,                            //是否分片
            chunkSize: 20 * 1024 * 1024,              //每個分片的大小,這裡為20M
            chunkRetry: 3,                            //某分片若上傳失敗,重試次數
            threads: 1,                               //線程數量,考慮到伺服器,這裡就選了1
            duplicate: true,                          //分片是否自動去重
            formData: {                               //每次上傳分片,一起攜帶的資料
                task_id: task_id,
            },
        });

        uploader.on('startUpload', function() {       //開始上傳時,調用該方法
            $('.progress-bar').css('width', '0%');
            $('.progress-bar').text('0%');
        });

        uploader.on('uploadProgress', function(file, percentage) { //一個分片上傳成功後,調用該方法
            $('.progress-bar').css('width', percentage * 100 - 1 + '%');
            $('.progress-bar').text(Math.floor(percentage * 100 - 1) + '%');
        });

        uploader.on('uploadSuccess', function(file) { //整個檔案的所有分片都上傳成功,調用該方法
            //上傳的資訊(檔案唯一辨別符,檔案名)
            var data = {'task_id': task_id, 'filename': file.source['name'] };
            $.get('/upload/complete', data);          //ajax攜帶data向該url發請求
            $('.progress-bar').css('width', '100%');
            $('.progress-bar').text('上傳完成');
        });

        uploader.on('uploadError', function(file) {   //上傳過程中發生異常,調用該方法
            $('.progress-bar').css('width', '100%');
            $('.progress-bar').text('上傳失敗');
        });

        uploader.on('uploadComplete', function(file) {//上傳結束,無論檔案最終是否上傳成功,該方法都會被調用
            $('.progress-bar').removeClass('active progress-bar-striped');
        });

    });
    </script>
</body>

</html>
           

後端代碼

@app.route('/', methods=['GET', 'POST'])
def index():                                        # 一個分片上傳後被調用
    if request.method == 'POST':
        upload_file = request.files['file']
        task = request.form.get('task_id')          # 擷取檔案唯一辨別符
        chunk = request.form.get('chunk', 0)        # 擷取該分片在所有分片中的序号
        filename = '%s%s' % (task, chunk)           # 構成該分片唯一辨別符
        upload_file.save('./upload/%s' % filename)  # 儲存分片到本地
    return rt('./index.html')


@app.route('/success', methods=['GET'])
def upload_success():                               # 所有分片均上傳完後被調用
    target_filename = request.args.get('filename')  # 擷取上傳檔案的檔案名
    task = request.args.get('task_id')              # 擷取檔案的唯一辨別符
    chunk = 0                                       # 分片序号
    with open('./upload/%s' % target_filename, 'wb') as target_file:  # 建立新檔案
        while True:
            try:
                filename = './upload/%s%d' % (task, chunk)
                source_file = open(filename, 'rb')                    # 按序打開每個分片
                target_file.write(source_file.read())                 # 讀取分片内容寫入新檔案
                source_file.close()
            except IOError:
                break
            chunk += 1
            os.remove(filename)                     # 删除該分片,節約空間
    return rt('./index.html')
           

結果

效果圖

Python實作大檔案分片上傳引言實施關鍵代碼結果
Python實作大檔案分片上傳引言實施關鍵代碼結果

測試

三台計算機,一台做伺服器,分别在另兩台上同時各上傳一本電影,大小為2.6G與3.8G;上傳完畢後,兩本電影均可在伺服器上正常播放。


備注

如果有想要源碼的朋友,可以移步這裡。對該項目還會不斷改進,如果你感興趣,不妨star一下,謝謝。