Django REST API搭配React如何实现多视频文件批量上传
Django REST Framework + React 多视频拖放上传实现方案
多视频上传和多图片上传的核心逻辑完全一致,不存在场景适配差异,只需要调整模型结构、序列化规则、视图解析配置,再配合前端拖放表单逻辑即可实现,具体步骤如下:
第一步:调整数据模型
单个FileField只能存储单个文件,需要通过一对多关联拆分文件存储结构,不要把多个文件强行塞到同一个字段里:
from django.db import models from django.core.validators import FileExtensionValidator # 单条视频记录,存储每个视频的实际文件路径 class UploadedVideo(models.Model): file = models.FileField( upload_to='videos_uploaded', validators=[FileExtensionValidator( allowed_extensions=['mp4', 'm4v', 'mov', 'mpg', 'mpg2', 'mpeg'] )] ) collection = models.ForeignKey( 'FileCollection', on_delete=models.CASCADE, related_name='files' ) created_at = models.DateTimeField(auto_now_add=True) # 文件集合模型 class FileCollection(models.Model): name = models.CharField(max_length=150, null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True)
修改完模型后依次执行python manage.py makemigrations、python manage.py migrate完成数据库表更新。
第二步:配置DRF序列化器
通过嵌套序列化器支持多文件批量写入:
from rest_framework import serializers from .models import FileCollection, UploadedVideo class UploadedVideoSerializer(serializers.ModelSerializer): class Meta: model = UploadedVideo fields = ['id', 'file', 'created_at'] class FileCollectionSerializer(serializers.ModelSerializer): # 核心:指定many=True接收多文件列表 files = UploadedVideoSerializer(many=True, required=True) class Meta: model = FileCollection fields = ['id', 'name', 'files', 'created_at'] def create(self, validated_data): files_data = validated_data.pop('files') collection = FileCollection.objects.create(**validated_data) # 批量创建关联的视频记录 for file_data in files_data: UploadedVideo.objects.create(collection=collection, **file_data) return collection
第三步:调整视图配置
DRF默认解析器无法识别multipart/form-data格式的多文件 payload,需要手动指定解析器,同时提取请求中的多文件列表传给序列化器:
from rest_framework.parsers import MultiPartParser, FormParser from rest_framework import viewsets from .models import FileCollection from .serializers import FileCollectionSerializer class FileCollectionViewSet(viewsets.ModelViewSet): queryset = FileCollection.objects.prefetch_related('files').all() serializer_class = FileCollectionSerializer # 必须添加这两个解析器,才能正确解析表单上传的文件 parser_classes = [MultiPartParser, FormParser] def perform_create(self, serializer): # 提取同名字段下的所有上传文件 uploaded_files = self.request.FILES.getlist('files') serializer.save( name=self.request.data.get('name'), files=[{'file': f} for f in uploaded_files] )
第四步:React端拖放表单实现
拖放逻辑和图片上传完全一致,只需要注意FormData传参时把所有文件追加到同一个files字段下,大视频上传可按需调大请求超时时间:
import { useState } from 'react'; import axios from 'axios'; export default function VideoUploadForm() { const [dragActive, setDragActive] = useState(false); const [selectedFiles, setSelectedFiles] = useState([]); // 拖放事件统一处理,必须阻止默认行为避免浏览器直接打开文件 const handleDragEvent = (e) => { e.preventDefault(); e.stopPropagation(); const eventType = e.type; if (eventType === 'dragenter' || eventType === 'dragover') { setDragActive(true); } else if (eventType === 'dragleave' || eventType === 'drop') { setDragActive(false); } }; const handleFileDrop = (e) => { handleDragEvent(e); if (e.dataTransfer.files?.length) { setSelectedFiles(Array.from(e.dataTransfer.files)); } }; const handleFileSelect = (e) => { if (e.target.files?.length) { setSelectedFiles(Array.from(e.target.files)); } }; const handleSubmit = async (e) => { e.preventDefault(); const formData = new FormData(); formData.append('name', '我的视频合集'); // 所有文件追加到同名字段下 selectedFiles.forEach(file => formData.append('files', file)); try { const res = await axios.post('/api/file-collections/', formData, { headers: { 'Content-Type': 'multipart/form-data' }, timeout: 300000 // 5分钟超时,适配大视频上传 }); console.log('上传成功', res.data); setSelectedFiles([]); } catch (err) { console.error('上传失败', err); } }; return ( <form onSubmit={handleSubmit}> <div onDragEnter={handleDragEvent} onDragOver={handleDragEvent} onDragLeave={handleDragEvent} onDrop={handleFileDrop} style={{ border: dragActive ? '2px solid #1677ff' : '2px dashed #ccc', padding: '48px 24px', borderRadius: '8px', textAlign: 'center' }} > <input type="file" multiple accept=".mp4,.m4v,.mov,.mpg,.mpeg" onChange={handleFileSelect} /> <p style={{marginTop: '12px'}}> {dragActive ? '松开鼠标上传文件' : '拖拽视频文件到此处,或点击选择本地文件'} </p> {selectedFiles.length > 0 && ( <div style={{marginTop: '16px', textAlign: 'left'}}> <p>已选择 {selectedFiles.length} 个文件:</p> <ul> {selectedFiles.map((file, idx) => ( <li key={idx}> {file.name}({(file.size / 1024 / 1024).toFixed(2)}MB) </li> ))} </ul> </div> )} </div> <button type="submit" disabled={selectedFiles.length === 0} style={{marginTop: '16px', padding: '8px 24px'}} > 开始上传 </button> </form> ); }
额外配置注意事项
- 视频文件普遍体积较大,需要在Django配置中调整上传大小限制:
# settings.py 按需调整上限 DATA_UPLOAD_MAX_MEMORY_SIZE = 5 * 1024 * 1024 * 1024 # 总上传上限5G FILE_UPLOAD_MAX_MEMORY_SIZE = 1 * 1024 * 1024 * 1024 # 单文件内存缓存上限1G - 如果服务端用Nginx反向代理,需要同步调整
client_max_body_size配置,避免Nginx层拦截大文件请求 - 前端可提前校验文件后缀、大小,减少无效请求
- 若需要支持断点续传、分片上传,可在上述基础逻辑上扩展接口,基础批量上传功能上述代码可直接运行
内容的提问来源于stack exchange,提问作者Matthias
相关产品推荐
相关产品推荐

