如何在Django中直接将文件上传至Cloudinary、S3等第三方存储服务器
解决Django大文件上传耗时问题:自定义UploadHandler直接对接存储
我之前也碰到过一模一样的大文件上传瓶颈——Django默认的上传流程会先把文件暂存到内存或者磁盘,再交给Storage保存,大文件的时候这个中间步骤简直是噩梦。自定义UploadHandler是完美的解决方案,核心思路就是在文件流式上传的过程中,直接把收到的字节块写入目标存储,完全跳过本地暂存环节。
下面是具体的实现步骤和注意事项:
1. 编写自定义UploadHandler
创建一个继承自FileUploadHandler的类,重写关键方法来接管文件上传的数据流处理:
# myapp/uploadhandlers.py from django.core.files.uploadhandler import FileUploadHandler, StopFutureHandlers from myapp.storage import YourCustomStorage # 替换成你实际使用的Storage类 class DirectStorageUploadHandler(FileUploadHandler): def __init__(self, request=None): super().__init__(request) self.storage = YourCustomStorage() self.target_file = None self.file_name = "" def new_file(self, field_name, file_name, content_type, content_length, charset=None, content_type_extra=None): # 初始化要上传的文件,打开Storage中的写入句柄 self.file_name = file_name self.target_file = self.storage.open(file_name, 'wb') # 阻止后续默认handler处理,避免重复操作 raise StopFutureHandlers() def receive_data_chunk(self, raw_data, start): # 把收到的每一块字节直接写入Storage if self.target_file: self.target_file.write(raw_data) return None # 返回None告诉Django我们自己处理了数据,不用缓存 def file_complete(self, file_size): # 上传完成后关闭文件句柄,返回关联到Storage的文件对象 self.target_file.close() # 这样request.FILES里拿到的就是已经在Storage中的文件了 return self.storage.open(self.file_name, 'rb')
2. 配置并启用自定义Handler
你可以全局配置,也可以仅在特定视图中使用:
全局配置(settings.py)
把自定义Handler放到FILE_UPLOAD_HANDLERS的最前面,确保优先被调用:
FILE_UPLOAD_HANDLERS = [ 'myapp.uploadhandlers.DirectStorageUploadHandler', 'django.core.files.uploadhandler.MemoryFileUploadHandler', 'django.core.files.uploadhandler.TemporaryFileUploadHandler', ]
视图级动态配置
如果只需要在特定上传视图中使用,就在视图函数开头覆盖上传处理器:
# views.py from django.http import HttpResponse from django.views.decorators.http import require_POST from myapp.uploadhandlers import DirectStorageUploadHandler @require_POST def large_file_upload(request): # 仅当前视图使用自定义上传处理器 request.upload_handlers = [DirectStorageUploadHandler(request)] + request.upload_handlers # 后续正常处理request.FILES就行,文件已经直接存在Storage里了 uploaded_file = request.FILES['file'] # 这里不需要再调用storage.save()!Handler已经完成了上传操作 return HttpResponse(f"文件 {uploaded_file.name} 已成功上传到存储")
3. 关键注意事项
- Storage的流式支持:确保你的Storage类支持流式写入(比如S3的boto3分块上传、本地文件系统都没问题),如果是第三方存储,要确认SDK是否支持边接收边写入。
- 错误处理:一定要在
receive_data_chunk和file_complete里加异常捕获,比如网络中断时要删除Storage里的不完整文件,避免垃圾数据。 - 进度跟踪(可选):如果需要做上传进度条,可以在
receive_data_chunk里累计已上传字节数,再通过某种方式(比如WebSocket)同步给前端。 - 权限验证:别忘记在Handler或者视图里加权限校验,防止未授权的文件上传。
这么改造之后,大文件上传时完全绕开了Django的本地暂存,直接把数据流怼到目标存储,速度和服务器资源占用都会改善很多。
内容的提问来源于stack exchange,提问作者Derek
相关产品推荐
相关产品推荐

