基于JavaScript Ajax和Django的2GB大文件分块上传问题求助
Django + JavaScript Ajax 2GB大文件分块上传问题解决方案
问题1:views.py无法拼接完整文件解决方案
核心原因大多为块顺序混乱、文件标识冲突、合并逻辑错误,可按以下逻辑修改views.py代码:
import os from django.conf import settings from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt @csrf_exempt def upload_chunk(request): if request.method == 'POST': # 前端必须传递的三个参数:文件唯一标识、当前块序号、总块数 file_md5 = request.POST.get('file_md5') chunk_index = int(request.POST.get('chunk_index', 0)) total_chunks = int(request.POST.get('total_chunks', 1)) # 当前块二进制内容 chunk_file = request.FILES.get('chunk') # 每个文件单独创建临时目录存储块,避免不同文件块冲突 temp_chunk_dir = os.path.join(settings.MEDIA_ROOT, 'temp_chunks', file_md5) os.makedirs(temp_chunk_dir, exist_ok=True) # 存储当前块 chunk_save_path = os.path.join(temp_chunk_dir, f'chunk_{chunk_index}') with open(chunk_save_path, 'wb+') as f: for seg in chunk_file.chunks(): f.write(seg) # 校验所有块是否上传完成 uploaded_chunk_count = len(os.listdir(temp_chunk_dir)) if uploaded_chunk_count == total_chunks: # 拼接生成最终文件 final_file_name = request.POST.get('file_name', f'{file_md5}.dat') final_file_path = os.path.join(settings.MEDIA_ROOT, 'completed_uploads', final_file_name) os.makedirs(os.path.dirname(final_file_path), exist_ok=True) with open(final_file_path, 'wb') as final_f: # 按数字序号顺序读取块,禁止直接按文件名排序,避免字符串排序导致顺序错误 for i in range(total_chunks): current_chunk_path = os.path.join(temp_chunk_dir, f'chunk_{i}') with open(current_chunk_path, 'rb') as chunk_f: final_f.write(chunk_f.read()) # 合并完成后删除已用块 os.remove(current_chunk_path) # 删除空的临时块目录 os.rmdir(temp_chunk_dir) return JsonResponse({'code': 0, 'msg': '文件上传完成', 'save_path': final_file_path}) return JsonResponse({'code': 0, 'msg': '当前块上传成功'}) return JsonResponse({'code': 1, 'msg': '请求方法不支持'})
特别注意:Django settings.py中需要新增配置DATA_UPLOAD_MAX_MEMORY_SIZE = 15 * 1024 * 1024,值要大于单块大小,避免请求被Django默认拦截
问题2:视频上传耗时过长优化方案
- 调整单块大小为5MB~10MB,平衡请求次数和单请求失败重传成本,2GB文件用10MB块仅需204次请求,综合效率最高
- 前端开启3~5并发上传,不要串行传块,示例JS逻辑如下:
const CONCURRENT_LIMIT = 4; // 并发上传数,不建议超过5 const CHUNK_SIZE = 10 * 1024 * 1024; // 单块10MB // 并发任务控制函数 async function runConcurrentTasks(tasks, limit) { const executing = []; for (const task of tasks) { const p = Promise.resolve().then(() => task()); executing.push(p); if (executing.length >= limit) { await Promise.race(executing); executing.splice(executing.findIndex(e => e === p), 1); } } await Promise.all(executing); } // 单块上传函数 async function uploadSingleChunk(chunk, chunkIndex, totalChunks, fileMd5, fileName) { const formData = new FormData(); formData.append('chunk', chunk); formData.append('chunk_index', chunkIndex); formData.append('total_chunks', totalChunks); formData.append('file_md5', fileMd5); formData.append('file_name', fileName); await fetch('/upload_chunk/', { method: 'POST', body: formData }); } // 总上传入口 async function handleFileUpload(file) { // 可引入spark-md5计算文件唯一md5,避免文件重名冲突 const fileMd5 = await calcFileMd5(file); const totalChunks = Math.ceil(file.size / CHUNK_SIZE); const tasks = []; for (let i = 0; i < totalChunks; i++) { const start = i * CHUNK_SIZE; const end = Math.min(start + CHUNK_SIZE, file.size); const chunk = file.slice(start, end); tasks.push(() => uploadSingleChunk(chunk, i, totalChunks, fileMd5, file.name)); } await runConcurrentTasks(tasks, CONCURRENT_LIMIT); alert('文件上传完成'); }
- 新增断点续传逻辑:上传前前端先请求后端查询当前文件已上传的块列表,跳过已上传的块,断网重连不需要从头上传
- Nginx部署的用户需在Nginx配置中新增
client_max_body_size 15M;,避免块请求被Nginx拦截
内容的提问来源于stack exchange,提问作者Subhra Kanti Ray
相关产品推荐
相关产品推荐

