实现分片上传文件与合并文件 前端使用vue3 ui组件为ant-design-vue 后端服务使用tornado异步框架实现

上传文件

什么是分片上传

分片上传功能是将一个文件切割为一系列特定大小的小数据片,分别将这些
小数据片分别上传到服务端,全部上传完后再由服务端将这些小数据片合并成为一个完整的资源。

为什么要分片上传

在上传普通大小文件时,分片上传和普通上传的效果体验相差不大。但是在上传大型文件时,普通传统文件上传速度缓慢分片上传 可以大大提高上传的速度  并且可以实现断点续传操作,也就是当某一片上传失败时可以记录下来,进行重传或者其他处理,分片的附带好处还能很方便的实现进度条
 


这里直接代码  展示我的实现过程

前端  

上传文件样式

 前端分片操作

    beforeUpload(){
        return false  //这里要返回 否则会上传失败
    },
     handleChange:function(file){  
      console.log(file.file)
        //获取文件大小     1k = 1024字节
        var size = file.file.size
        console.log(size)
        // 定义分片大小
        var shardSize = 1024 * 200
        // 总片数   向上去整
        this.shardCount = Math.ceil(size / shardSize)

        if(this.shardCount == 1){   // 如果只分了一片  就直接上传即可

          let data1 = new FormData();   // 将数据文件与数据表单中
          data1.append('file', file.file);
          data1.append('filename',file.file.name);
          // 参数与分片上传相比 少了分片个数  用于后端识别是否是分片上传
          axios.post(this.baseURL + 'upload/',data1).then(resp=>{
          console.log('上传文件',resp)
          if(resp.data.errcode==0){
                this.websrc = this.upload_dir + '/' + file.file.name
                this.src =  file.file.name
               } 
          })
/* A */
        }else{          // 否则进行切片操作
          // 切片操作
        for(var i=0;i<this.shardCount;i++){      // 循环切片次数
          //开始位置      每一次循环的开始位置
          var start = i * shardSize
          // 结束位置      取最小值    如 378,0+200取200  378,200+200取378 
          var end = Math.min(size,start+shardSize)

          //切片   slice 使用slice方法对文件进行分割
          var shardfile = file.file.slice(start,end)

         let data = new FormData();  // 将数据文件与数据表单中
          data.append('file', shardfile);
          data.append('count',i);
          data.append('filename',file.file.name);
          // 进行上传
          axios.post(this.baseURL + 'upload/',data).then(resp=>{
          console.log('分片上传',resp)
          if(resp.data.errcode==0){    // 如果这一次分片上传成功了
              this.finish +=1        // 记录一下上传次数
            if(this.finish == this.shardCount){    // 如果上传次数 等于 分片个数  代表所有分片都上传了  进行合并即可
              //合并分片
               this.myaxios(this.baseURL + 'upload/','put',{filename:file.file.name,"size":size,"count":(this.shardCount)}).then(resp=>{
                // this.myaxios('http://localhost:8888/scheduler/','get',{job_id:this.job_id,filename:file.file.name,"size":size,"count":this.shardCount}).then(resp=>{
               if(resp.errcode==0){
                this.websrc = this.upload_dir + '/' + file.file.name    // 合并成功 赋值给一个变量 用于展示文件
                this.src =  file.file.name   
               }  
              // }) 
               
              //  if(resp.errcode==0){
              //   this.websrc = this.upload_dir + '/' + file.file.name
              //   this.src =  file.file.name
              //  }  
              }) 
               this.finish = 0              // 合并后对上传次数清零  用于下次上传分片
            }
          }
     
          })
        }

       


        }


    },

 后端接口  上传文件接口

   async def post(self):
        # 获取文件实体
        file = self.request.files['file'][0]
        #获取分片次数下标    用来标记分片文件顺序
        count = self.get_argument('count',None)
        # 获取文件名
        filename = self.get_argument('filename',None)
        if not count:    # 如果没有传分片次数 代表文件比较小 不需要分片 普通上传即可
            content = file['body']  # 获取文件流
            async with aiofiles.open(f'./static/upload/{filename}', 'wb')as f:
                await f.write(content)
            res = {'errcode': 0, 'msg': '文件已上传'}
        # 获取文件内容
        else:
            content = file['body']  # 获取文件流
            async with aiofiles.open(f'./static/upload/{count}_{filename}','wb')as f: # 写入文件 用count做标记 用来记录分片顺序
                await f.write(content)
            res = {'errcode': 0, 'msg': '分片已上传'}
        return self.finish(res)

后端 合并分片接口

   async def put(self):
        filename = self.get_argument('filename', None)  # 文件名
        size = self.get_argument('size', None)  # 文件总大小
       
        try:       # 查询合并后的文件是否已存在 并获取他的大小
            f_size =  os.path.getsize(f'./static/upload/{filename}')
        except Exception as e:    # 如果报错 说明没有此文件
            f_size = 0
        if int(size) != f_size:    # 如果大小不相等 代表要么没有 要么之前合并错误
            # 打开文件句柄
            f_count = 0  # 循环次数
            async with aiofiles.open(f'./static/upload/{filename}','ab')as f: # 创建并打开文件 开始合并
                while True:
                    try:
                        # 读取分片   
                        shard_file = open(f'./static/upload/{f_count}_{filename}','rb') # 读取每个分片文件实体
                        await f.write(shard_file.read()) # 写入
                        shard_file.close() # 关闭文件

                    except Exception as e:  # 如果报错了 代表已经没有分片文件可以合并了
                        break
                    f_count+=1

        return self.finish({'errcode': 1, 'msg': '合并完成'})

这里还写了一个接口   用于触发合并后对文件合并后的处理 使用了apscheduler定时任务

import os
import re
import redis
from tornado.ioloop import IOLoop,PeriodicCallback
from tornado.web import RequestHandler,Application,url
from apscheduler.schedulers.tornado import TornadoScheduler

scheduler = None
r = redis.Redis(decode_responses=True)
# 初始化
def init_scheduler():  # 初始化scheduler 对象
    global scheduler
    scheduler = TornadoScheduler()
    scheduler.start() # 定时任务启动
    print('定时任务启动')

# 声明任务
         # 任务id
def task(job_id,filename,size,count):
    filelist = os.listdir('./static/upload')   # 获取文件夹下所有文件
    re_list = []
    for i in filelist:    
        res = re.match(r'^\d+_{}'.format(filename),i)  # 利用正则匹配出此文件的所有分片文件名 放入列表中
        if res:
            re_list.append(i)
    f_count = len(re_list)

    if int(count) == f_count:   # 如果分片文件个数等于分片次数
        try:                      # 获取文件大小
            f_size = os.path.getsize(f'./static/upload/{filename}')
        except Exception as e:   # 报错代表文件没有合并完成
            f_size = 0
        if int(size) == f_size: # 如果合并后文件大小等于上传的文件大小 代表合并完成
            for i in range(0, f_count):    # 循环次数下标
                os.remove(f'./static/upload/{i}_{filename}') # 删除每个分片文件
            r.lrem(filename,1,job_id)   # 删除redis中的 任务id
            scheduler.remove_job(job_id)  # 删除这个定时任务
            print('分片执行完毕,定时任务删除')
            return True
        print('合并未成功')
        return False
    print('分片传输未成功')
    return False



# 声明服务控制器
class SchedulerHandler(RequestHandler):
    async def get(self):
        job_id = self.get_argument('job_id',None)   # 获取任务id
        filename = self.get_argument('filename', None)  
        size = self.get_argument('size', None) # 文件总大小
        count = self.get_argument('count', None) # 分片个数
        job_ids = r.lrange(filename,0,-1)  # 获取redis中存的所有任务id
        print(job_ids)
        if job_id not in job_ids:    # 不存在 执行这个定时任务   存在 代表这个任务还没完成
            r.lpush(filename,job_id)  # 任务id加入redis      
                            # 任务函数名          间隔时间三秒           任务函数参数
            scheduler.add_job(task,'interval',seconds=3,id=job_id,args=(job_id,filename,size,count)) # 开启定时任务
            print('定时任务入队')
            res = {'errcode':0,'msg':'ok'}
        else:
            res = {'errcode':1, 'msg': 'failed'}
        return self.finish(res)

if __name__ == '__main__':
    routes = [url(r'/scheduler/',SchedulerHandler)]
    init_scheduler()

    application = Application(routes,debug=True)
    application.listen(8888)
    IOLoop.current().start() # 开启事件循环

    application = Application(routes,debug=True)
    application.listen(8888)
    IOLoop.current().start() # 开启事件循环

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值