fastapi实现大文件上传

网上很多说的,包括官网说的,用UploadFile可以解决大文件上传,

基本上都是吹牛逼,

现在的文件,动不动就是几百兆,几G的文件,

UploadFile能解决啥,

用一台2核4G的服务器测试过,UploadFile最多只能支持100多兆的文件上传。

文件超过150兆~200兆就悬了,看命,命好的话,服务器刚好缓存比较多,上传成功。

命差的话,直接撑爆缓存,上传失败

所以,大文件上传,有且只有一条路子,分片上传

废话不说了,直接上代码

一、文件上传接口,如下:


@router.post("/upload/uploadfile")
async def upload_big_file(file: UploadFile = File(...), chunknumber: str = Form(...), identifier: str = Form(...)):#分片上传文件【用唯一标志符+分片序号】作为文件名
    if len(chunknumber) == 0 or len(identifier) == 0:
        return {"eroor": "没有传递相关参数"}
    task = identifier                           # 获取文件唯一标识符
    chunk = chunknumber                         # 获取该分片在所有分片中的序号【客户端设定】
    filename = '%s%s' % (task,chunk)           # 构成该分片唯一标识符
    contents = await file.read()                #异步读取文件
    with open(f"{settings.STATIC_DIR}/uploads/{filename}", "wb") as f:
        f.write(contents)
    return {"filename": file.filename}

传递参数有:文件、序号、唯一标志符

这段代码,可以用来接收分片文件,分片文件其实就是把大文件切割成一个一个的小文件,上传到服务器,服务器接收到这些文件以后,保存到服务器上,所以,分片文件上传,和普通文件上传是一样的,只是多了【唯一标志符】,把上传的文件命名为【唯一标志符】+【序号】就可以了,这个序号也就是用来区分这个上传的文件是第几段文件的。

建议用Unix时间戳用作唯一标志符,网上有很多分片的代码,很扯淡,什么调用这库那库进行加密啊,扯一堆有的没的,不就是怕文件重名嘛,至于那么弯弯绕绕吗,用Unix时间戳+需要命名文件,就行了

另外,不要后缀名,后缀名在合并分片文件的时候才需要

二、合并分片文件接口,如下:

@router.post("/upload/mergefile")
async def mergefile(identifier: str = Form(...), filename: str = Form(...), chunkstar: int = Form(...)):#根据唯一标识符合并文件
    if len(filename) == 0 or len(identifier) == 0:
        return {"eroor": "没有传递相关参数"}
    target_filename = filename      # 获取上传文件的文件名【保存的文件名】
    task = identifier              # 获取文件的唯一标识符
    chunk = chunkstar              # 分片序号开始的序号默认=0
    if os.path.isfile(f"{settings.STATIC_DIR}/uploads/{target_filename}"):#如果客户端传递过来的文件名在服务器上已经存在,那么另外新建一个【时间戳.后缀名】文件
        t = time.time()                                         #时间戳
        timeUnix = str(round(t * 1000))                         #毫秒级时间戳
        filesuffix = os.path.splitext(target_filename)[1]       #后缀名
        target_filename = timeUnix + filesuffix                 #新文件名【时间戳.后缀名】
    error_i = 0
    chunkfilename = ""
    with open(f"{settings.STATIC_DIR}/uploads/{target_filename}", 'wb') as target_file:  # 创建新文件
        while True:#循环把分片文件写入新建的文件
            if os.path.isfile(f"{settings.STATIC_DIR}/uploads/{task}{chunk}"):#存在这个文件
                try:
                    #分片文件名
                    chunkfilename = f"{settings.STATIC_DIR}/uploads/{task}{chunk}"
                    # 按序打开每个分片
                    source_file = open(chunkfilename, 'rb')
                    # 读取分片内容写入新文件
                    target_file.write(source_file.read())
                    source_file.close()
                except IOError:#当分片标志chunk累加到最后,文件夹里面不存在{task}{chunk}文件时,退出循环
                    break
                os.remove(f"{settings.STATIC_DIR}/uploads/{task}{chunk}")#删除分片文件
            else:#【如果分片文件上传中途出错,导致中间缺少某个分片文件,跳过它,不退出循环,直到累计缺少次数大于3次,再跳出循环】
                error_i += 1
                if error_i > 3:
                    break
            chunk += 1
    os.chmod(f"{settings.STATIC_DIR}/uploads/{target_filename}",0o664)#linux设置权限0o664、0o400
    return {"code":200, "filename": f"{settings.STATIC_DIR}/uploads/{target_filename}"}

传递参数有:【唯一标志符】、文件名、【起始分片序号】

根据【唯一标志符+序号】,去查保存目录下面对应的文件名,循环查找,找到了,就把分片文件合并,直到找不到了为止,退出循环

最后返回文件名

三、客户端:

把大文件分片

例如:我用的是vue3+typescript+vite+antd的前端,

文件分片代码:

let blobSlice = File.prototype.slice;
let chunkFile = blobSlice.call(file, start, end);

这两句话就是文件分片,start,end是文件分片起点和终点,例如0,3*1024*1024就是文件的0~3M

然后获取一下当前时间戳,用做唯一标志符,

然后把分片,一片片的传给接口【使用Axios】,第几片就传参数:序号=几

上传完全部的分片后,访问合并分片接口,传递【唯一标志符】+【分片序号】+【文件名】

最后获取接口返回的服务器文件地址,展示给用户,或者调用接口保存到后端数据库

类似的分片代码还有,例如:原生JS

blob = file.slice(start,end);

原生JS用XMLHttpRequest上传到服务器接口

最后总结,不管前端用哪种,都是:前端进行分片,然后一片一片的传给服务器文件上传接口,等服务器接收完毕后,再访问服务器文件合并接口,最后得到服务器接口返回的合并后的文件地址。

PS:前端分片的话,你最好看情况,服务器很垃圾的话,分片分小一点,例如:每片文件3M左右,然后上传,【上传中途要动态提示用户,第几片上传成功,总共多少片文件等待上传,当然,你不提示用户也可以,看谁用,自己公司人用的话,完全可以怼回去,你们等一等有啥关系啊,正好摸鱼,如果是客户用的话,你最好把动态提示的代码给加上,防止用户等待时间太长,心态爆炸】,如果你服务器是那种超级计算机的话,分啥片啊,直接整个文件上传就好。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值