直接用request.FILES上传大文件会失败,因Django默认将整个请求体读入内存或临时文件,而分片上传的每个chunk是独立HTTP请求,request.FILES不触发;且Nginx/uWSGI默认限制单次请求体大小(如client_max_body_size=1MB),大文件直接返回413,根本无法到达Django视图层。

为什么直接用 Django 的 request.FILES 上传大文件会失败
因为默认配置下,Django 会把整个上传体读入内存或临时文件,而分片上传的每个 chunk 是独立 HTTP 请求,request.FILES 根本不会触发——它只对完整表单提交生效。更关键的是,Nginx 或 uWSGI 默认限制单次请求体大小(如 client_max_body_size 100M),大文件一发就 413,根本到不了 Django。
- 必须绕过
request.FILES,直接读取request.body - 前端必须按固定大小切片(如 5MB/片),并携带标识:文件名、总片数、当前序号、唯一
upload_id - Nginx 需显式放宽限制:
client_max_body_size 100M(要比单片大) - uWSGI 要配
--buffer-size=32768防止 body 截断
如何在视图中安全接收并落盘分片
不能依赖 request.POST 解析参数——分片请求通常是 application/octet-stream 或 multipart/form-data 但结构不标准。最稳妥方式是统一走 JSON body 传元数据,二进制流走 request.body。
- 前端发送时用
fetch+FormData或ArrayBuffer,后端用json.loads(request.body.decode())提取upload_id、chunk_index等 - 分片文件名建议生成为
{upload_id}_{chunk_index},存到临时目录(如/tmp/uploads/),避免冲突 - 务必校验
chunk_index是否越界、是否重复上传,可用 Redis 记录已接收的索引集:redis.sadd(f"upload:{upload_id}:chunks", chunk_index) - 写入用
open(..., "ab")追加,但注意:分片顺序不一定按序到达,所以不能直接追加;应先写独立文件,合并时再按序拼接
合并分片时如何避免竞态和磁盘爆满
多个请求可能同时触发合并逻辑(比如前端重试),且合并过程耗时、占 I/O。必须加锁,并做原子性保障。
安全 Python 代码沙箱:在隔离的沙箱中执行任意 Python 代码,预装 requests、NumPy、pandas 库。返回 stdout、stderr。
- 用 Redis 分布式锁:
redis.set(f"merge_lock:{upload_id}", "1", ex=300, nx=True),获取失败则返回 409 - 合并前先检查所有分片是否齐备:
len(redis.smembers(f"upload:{upload_id}:chunks")) == total_chunks - 合并不用 Python 读全量再写——用
shutil.copyfileobj流式拼接,或直接调cat chunk_* > final_file(Linux) - 合并成功后立即删分片,但要确保最终文件
os.path.getsize()等于预期总大小,否则删了就不可逆
如何支持断点续传和秒传
断点续传靠服务端记录已传分片;秒传靠文件指纹(如 MD5 或 blake3)提前查重。两者可共存,但秒传需在首片上传前就计算并查询。
立即学习“Python免费学习笔记(深入)”;
- 前端在切片前计算整个文件的
blake3(file_bytes)(比 MD5 快且抗碰撞性强),随首片一起发给后端 - 后端查
redis.get(f"file_hash:{hash_hex}"),命中则直接返回{"uploaded": true, "url": "/media/xxx"},跳过所有分片 - 未命中则正常走分片流程,合并完成后写入
redis.setex(f"file_hash:{hash_hex}", 3600*24*30, file_path) - 断点状态查询接口应返回已传分片列表,前端据此跳过已传片:
redis.smembers(f"upload:{upload_id}:chunks")
实际部署时最容易被忽略的是 Nginx 缓冲行为:即使关了 proxy_buffering,它仍可能缓存整个请求体再转发。必须加 proxy_buffering off; 和 proxy_request_buffering off;(后者仅 Nginx ≥ 1.7.11)。否则你以为在流式处理,其实 Nginx 已经把 2GB 文件全读进内存了。

















