Django如何实现异步文件上传?Celery使用及报错问题咨询
Django大文件异步上传落地方案
核心问题澄清
大文件上传过程中客户端无法操作其他功能,本质是两个原因导致的:一是前端用同步表单提交,上传时会锁死页面交互;二是后端把上传、文件处理全放在同步请求逻辑里,长连接占住导致响应慢。
Celery的适用边界
Celery不适合直接承接文件上传的传输过程,仅适合处理文件上传完成后的后端耗时逻辑(比如转码、病毒扫描、元数据解析、缩略图生成)。
你之前遇到的Object of type module is not JSON serializable报错,根因是Celery任务参数只支持可JSON序列化的基础类型(字符串、数字、普通字典/列表),你直接传入Django表单实例、request对象、内存文件对象这类绑定了请求上下文、WSGI连接引用的复杂实例,跨进程传参时序列化必然失败。就算绕开序列化限制,请求结束后连接释放、内存回收,worker拿到的也是无效对象,逻辑依然跑不通。
完整可运行实现方案
整套方案拆成三层,完全可以实现上传时用户正常操作网站其他功能,无阻塞:
- 前端交互层(解决客户端阻塞的核心)
放弃默认表单同步submit提交,用异步请求+分片逻辑实现上传:- 选文件后给每个文件生成唯一ID,按固定大小(建议2MB-10MB/片)用
Blob.slice()方法切片,不要一次性把整个大文件读入内存,避免浏览器崩溃 - 用
XMLHttpRequest或fetch逐片发送分片请求,所有请求走独立异步线程,不会阻塞页面其他JS逻辑执行,用户可以正常点击、跳转网站其他功能 - 所有分片发送完成后,给后端发文件合并请求,轮询或用WebSocket接收后端处理完成的通知
- 选文件后给每个文件生成唯一ID,按固定大小(建议2MB-10MB/片)用
- 后端传输层
Django端直接写两个普通接口承接分片上传即可,不需要把传输逻辑扔给Celery:- 分片接收接口:接收前端传的文件唯一ID、分片序号、分片文件内容,存到服务器临时目录,直接返回成功响应
参考代码:
import os from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt TEMP_CHUNK_PATH = "/tmp/django_upload_chunks" os.makedirs(TEMP_CHUNK_PATH, exist_ok=True) @csrf_exempt def upload_chunk(request): if request.method != "POST": return JsonResponse({"code": 405, "msg": "方法不允许"}) file_id = request.POST.get("file_id") chunk_idx = request.POST.get("chunk_index") chunk = request.FILES.get("chunk") save_dir = os.path.join(TEMP_CHUNK_PATH, file_id) os.makedirs(save_dir, exist_ok=True) with open(os.path.join(save_dir, f"{chunk_idx}.part"), "wb") as f: for block in chunk.chunks(): f.write(block) return JsonResponse({"code": 0, "msg": "分片上传成功"})- 分片合并接口:收到前端合并请求后,校验所有分片是否上传完整,按分片序号顺序拼接成完整文件,存到正式文件存储路径,清理临时分片目录
- 分片接收接口:接收前端传的文件唯一ID、分片序号、分片文件内容,存到服务器临时目录,直接返回成功响应
- 后端异步处理层(Celery的正确使用位置)
文件合并完成后,不要在请求逻辑里执行耗时操作,只把可序列化的基础参数(比如文件正式存储路径、文件ID、上传用户ID)传给Celery任务,接口立刻给前端返回上传成功响应,耗时逻辑交给后台worker执行即可。
参考代码:
合并文件完成后,直接调用from celery import shared_task @shared_task def post_upload_process(file_path: str, file_id: str, upload_user_id: int): # 此处写耗时逻辑:比如视频转码、内容安全扫描、生成缩略图、写入数据库关联关系 # 禁止传入request、表单实例、内存文件对象这类非序列化参数 print(f"开始处理文件:{file_id},存储路径:{file_path}") return Truepost_upload_process.delay(final_save_path, file_id, request.user.id)即可触发异步任务,不会出现序列化报错。
注意:不要为了图省事把整个文件内容读成字符串传给Celery,大文件内容传参会占满Celery的消息队列带宽,严重时会导致Broker崩溃,只传文件存储路径让worker自己读文件是最优方案。
内容的提问来源于stack exchange,提问作者Rockhan
相关产品推荐
相关产品推荐

