django关于文件分块上传的简单实现(template+view)

django关于文件分块上传的简单实现(template+view)

关于文件分块上传技术以及所谓的断点续存。逻辑其实很简单,就是将大文件切割成一个个小文件,一个个上传,给每个块打上id,然后单独校验完整性,上传如果失败,也只是重新上传上传失败的部分文件。该功能已经有功能相对完善的项目实践:
django-chunked-upload
https://github.com/juliomalegria/django-chunked-upload
但越是完善的东西学习起来越麻烦,因此笔者借助RACCON助手按自己的理解进行了关键功能的简单实现。

该功能需要前后端进行配合以实现:
前端负责文件的切分,分块文件的编号,分块文件的hash计算,以及分块文件的上传。
后端负责接收分块文件,hash值的校验,分块文件的存储,接收成功的响应,以及最终所有分块文件的合并。理论上应该还有传输失败文件id的响应,但本文没有去实现。

前端template

  1. 相关依赖(js库以及MD5hash计算api)
  <script type="text/javascript" src="https://cdn.staticfile.org/twitter-bootstrap/5.1.1/js/bootstrap.min.js"></script>
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/spark-md5/3.0.2/spark-md5.min.js"></script>
  1. 文件分块以及上传
<script>
//file=document.getElementById("File").files[0];
//filename='filename'
//chunkSize=1024*1024
//uploadurl="{% url 'djangourlname' %}"
//callbackelement=document.getElementById("message");
//uploadFile(file,filename,chunkSize,uploadurl,callbackelement)

  function uploadFile(file,filename,chunkSize,uploadurl,callbackelement) {

    var chunks = Math.ceil(file.size / chunkSize);//计算分块数
    var currentChunk = 0;
  
  	//分块上传
    function sendChunk() {
      var start = currentChunk * chunkSize;
      var end = Math.min(file.size, start + chunkSize);
      var chunk = file.slice(start, end);//分块截取
      
      //为了计算分块文件的hash值,使用FileReader读取文件为ArrayBuffer类型
      var reader = new FileReader();
      reader.readAsArrayBuffer(chunk);
      reader.onload = function(evt) {
        md5 = SparkMD5.ArrayBuffer.hash(evt.target.result);
        
        var formData = new FormData();//创建表单对象
        formData.append('file', chunk);
        formData.append('chunk', currentChunk);
        formData.append('chunks', chunks);
        formData.append('hash', md5);
        formData.append('filename', filename);
      
    
        //上传表单
        fetch(uploadurl, {
          method: 'POST',
          body: formData,
          headers: {'X-CSRFToken': '{{ csrf_token }}'},//django csrf验证
        })
          .then(response => response.json())
          .then(data => {
            if (data.success) {
              //加一个进度条
              var progress = parseInt(currentChunk / chunks * 100, 10);
              callbackelement.innerHTML = '文件上传进度:' + progress + '%';
              currentChunk++;//上传成功则继续上传下一个文件块
              if (currentChunk < chunks) {
                sendChunk();

              } else {
                callbackelement.innerHTML = '文件上传成功!';
              }
            } else {
            //失败则继续上传,可以修改为继续上传下一个,但是记录失败分块,后续重传
              var progress = parseInt(currentChunk / chunks * 100, 10);
              callbackelement.innerHTML = '文件上传进度:' + progress + '%';			
              sendChunk();
            }
          })
          .catch(error => {
            var progress = parseInt(currentChunk / chunks * 100, 10);
            callbackelement.innerHTML = '文件上传进度:' + progress + '%';
            callbackelement.innerHTML += '文件上传出错!';
          });
      };
      

    }
    sendChunk();
  }
 </script>

后端视图

from django.http import JsonResponse
from django.views import View
import os
import hashlib

def calculate_md5(file_chunk):
        md5 = hashlib.md5()
        file_chunk.seek(0)  # 重置文件块的读取位置,这很关键
        while chunk := file_chunk.read(8192):
            md5.update(chunk)
        
        return md5.hexdigest()

class ChunkedUploadView(View):
    def post(self, request):
        # 获取文件名
        file_name = request.POST.get('filename', None)

        # 指定存储文件块的目录
        chunk_dir = os.path.join('media/uploads/', file_name)
        if not os.path.exists(chunk_dir):
            os.makedirs(chunk_dir)

        # 获取文件块数据
        file_chunk = request.FILES['file']
        print(file_chunk.size)

        # 计算文件块的MD5哈希值
        calculated_hash = calculate_md5(file_chunk)

        # 获取文件的MD5哈希值
        file_hash = request.POST['hash']
        if calculated_hash != file_hash:
            return JsonResponse({'success': False})
        
        # 获取当前文件块的索引
        current_chunk = int(request.POST['chunk'])
        
        # 获取文件块的总数
        total_chunks = int(request.POST['chunks'])
        
        # 将文件块保存到指定目录
        file_path = os.path.join(chunk_dir, f'chunk_{current_chunk}')
        with open(file_path, 'wb') as f:
            file_chunk.seek(0)#重置读取位置,这很关键
            f.write(file_chunk.read())
        
        # 如果所有文件块都已接收,则合并文件
        if current_chunk == total_chunks - 1:
            merged_file_path = os.path.join(chunk_dir, file_name+'.json')
            with open(merged_file_path, 'wb') as merged_file:
                for i in range(total_chunks):
                    chunk_path = os.path.join(chunk_dir, f'chunk_{i}')
                    with open(chunk_path, 'rb') as chunk_file:
                        merged_file.write(chunk_file.read())
                        chunk_file.close()#关闭文件,因为下面要删除,必须提取进行关闭
                        os.remove(chunk_path)  # 删除临时文件块
            
            return JsonResponse({'success': True})
        else:
            return JsonResponse({'success': True})
        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

朝凡FR

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值