Django DRF 应用搭建文档:普通文件上传与大文件断点上传
本文档将指导你从零搭建一个支持普通文件上传和大文件断点上传功能的 Django DRF 应用。我们将通过两部分内容进行说明:普通文件上传功能和分片上传功能。
功能点说明
-
普通文件上传:
- 处理普通文件上传,支持根据文件内容的哈希值重命名文件,避免文件重复。
- 支持文件类型判断(如图片、视频、音频等),并限制上传文件的大小。
- 如果文件已经存在于数据库中,返回文件路径而不是重新上传。
-
大文件断点上传:
- 将大文件拆分为多个分块进行上传,并且支持断点续传。
- 通过上传文件的哈希值来区分每个上传任务,以确保上传的一致性。
- 提供接口查询上传进度和已上传的分块,合并文件时根据文件哈希值进行操作。
1. 普通文件上传实现
1.1 视图实现
from django.core.files.uploadedfile import SimpleUploadedFile
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.parsers import MultiPartParser
from django.conf import settings
import hashlib
import os
from .models import File
from .serializers import FileSerializer
class UploadFileView(APIView):
"""
视图作用:处理文件上传,按文件内容的哈希值重命名文件,并保存到分类目录。
"""
parser_classes = [MultiPartParser]
def post(self, request):
uploaded_file = request.FILES.get('file')
if not uploaded_file:
return Response({'error': 'No file uploaded'}, status=400)
file_type = get_file_type(uploaded_file)
max_size = settings.MAX_UPLOAD_SIZES.get(file_type, settings.MAX_UPLOAD_SIZES['others'])
if uploaded_file.size > max_size:
return Response({'error': f'The file is too large. Maximum allowed size for {file_type} is {max_size / 1024 / 1024} MB.'}, status=400)
file_data = uploaded_file.read()
file_hash = hashlib.sha256(file_data).hexdigest()
existing_file = File.objects.filter(file_hash=file_hash).first()
if existing_file:
serializer = FileSerializer(existing_file)
return Response({
'message': 'File already exists',
'filePath': serializer.data['file_path'],
'fileHash': serializer.data['file_hash'],
'fileName': serializer.data['file_name']
})
output_dir = os.path.join(settings.MEDIA_ROOT, 'uploads', 'files', file_type)
os.makedirs(output_dir, exist_ok=True)
output_file_path = os.path.join(output_dir, f"{file_hash}{os.path.splitext(uploaded_file.name)[1]}")
with open(output_file_path, 'wb') as f:
f.write(file_data)
file_record = File.create_file_record(uploaded_file, output_file_path)
serializer = FileSerializer(file_record)
return Response({
'message': 'File uploaded successfully',
'filePath': output_file_path,
'fileHash': file_record.file_hash,
'fileName': file_record.file_name
})
def get_file_type(file) -> str:
"""
根据文件的扩展名判断文件类型
"""
file_extension = file.name.split('.')[-1].lower()
if file_extension in ['jpg', 'jpeg', 'png', 'gif', 'bmp']:
return 'images'
elif file_extension in ['mp4', 'avi', 'mov', 'mkv']:
return 'videos'
elif file_extension in ['mp3', 'wav', 'flac']:
return 'audio'
elif file_extension in ['pdf', 'doc', 'docx', 'xls', 'xlsx']:
return 'documents'
elif file_extension in ['zip', 'tar', 'rar']:
return 'archives'
else:
return 'others'
1.2 功能说明
- 文件类型判断:根据文件的扩展名判断文件所属类型,并限制不同类型文件的大小。
- 哈希值判断:通过文件的 SHA-256 哈希值进行文件去重,如果文件已存在,直接返回文件路径。
2. 大文件断点上传实现
2.1 断点上传视图
class UploadChunkView(APIView):
parser_classes = [MultiPartParser]
def post(self, request):
"""
上传文件分块
"""
file = request.FILES.get('file')
chunk_index = request.data.get('chunkIndex')
file_hash = request.data.get('fileHash')
total_chunks = request.data.get('totalChunks')
if not all([file, chunk_index, file_hash, total_chunks]):
return Response({'error': 'Missing required parameters'}, status=400)
temp_dir = os.path.join(settings.MEDIA_ROOT, 'temp', file_hash)
os.makedirs(temp_dir, exist_ok=True)
chunk_path = os.path.join(temp_dir, f'chunk_{chunk_index}')
with open(chunk_path, 'wb') as f:
for chunk in file.chunks():
f.write(chunk)
return Response({'message': 'Chunk uploaded successfully'})
2.2 查询已上传分块
class GetUploadedChunksView(APIView):
def get(self, request):
"""
查询已上传的分块索引
"""
file_hash = request.query_params.get('fileHash')
if not file_hash:
return Response({'error': 'Missing fileHash parameter'}, status=400)
temp_dir = os.path.join(settings.MEDIA_ROOT, 'temp', file_hash)
if not os.path.exists(temp_dir):
return Response({'uploadedChunks': []})
uploaded_chunks = [
int(filename.split('_')[1])
for filename in os.listdir(temp_dir)
if filename.startswith('chunk_')
]
uploaded_chunks.sort()
return Response({'uploadedChunks': uploaded_chunks})
2.3 合并文件分块
class CompleteUploadView(APIView):
def post(self, request):
"""
合并分块文件并保存到数据库
"""
file_hash = request.data.get('fileHash')
file_extension = request.data.get('fileExtension')
file_name = request.data.get('fileName')
if not all([file_hash, file_extension, file_name]):
return Response({'error': 'Missing required parameters'}, status=400)
temp_dir = os.path.join(settings.MEDIA_ROOT, 'temp', file_hash)
if not os.path.exists(temp_dir):
return Response({'error': 'No uploaded chunks found'}, status=400)
file_type = get_file_type(SimpleUploadedFile(f'temp.{file_extension}', b''))
output_dir = os.path.join(settings.MEDIA_ROOT, 'uploads', file_type)
os.makedirs(output_dir, exist_ok=True)
output_file_path = os.path.join(output_dir, f'{file_hash}.{file_extension}')
chunk_files = sorted(
[os.path.join(temp_dir, f) for f in os.listdir(temp_dir)],
key=lambda x: int(os.path.basename(x).split('_')[1])
)
with open(output_file_path, 'wb') as output_file:
for chunk_file in chunk_files:
with open(chunk_file, 'rb') as f:
output_file.write(f.read())
for chunk_file in chunk_files:
os.remove(chunk_file)
os.rmdir(temp_dir)
file_record = File.objects.create(
file_name=file_name,
file_path=output_file_path.replace(settings.MEDIA_ROOT, '').lstrip('/'),
file_hash=file_hash
)
file_serializer = FileSerializer(file_record)
return Response({
'message': 'Upload complete',
'file': file_serializer.data
})
2.4 功能说明
- 上传分块:接收分块上传请求,并将每个分块存储到临时目录。
- 查询上传进度:通过
fileHash
查询已上传的分块,支持断点续传。 - 合并分块:将所有上传的分块按顺序合并,生成完整的文件,并保存到数据库。
文件模型与设置配置
以下代码段包含了文件模型 File
的定义和上传文件大小限制的设置。
1. 文件模型 File
文件模型用于存储上传的文件信息,包括文件名、存储路径、哈希值等字段,并通过文件的哈希值确保文件的唯一性。
from django.db import models
import hashlib
class File(models.Model):
file_name = models.CharField(max_length=255) # 文件原始名称
file_path = models.FileField(upload_to='uploads/files/') # 文件存储路径,使用 FileField 来处理
file_hash = models.CharField(max_length=64, unique=True) # 文件的哈希值,唯一约束
def __str__(self):
return self.file_name
@classmethod
def create_file_record(cls, uploaded_file, file_path):
"""
根据文件哈希值创建或返回文件记录
:param uploaded_file: 上传的文件对象
:param file_path: 文件的存储路径
:return: 文件记录对象
"""
# 计算文件的哈希值
uploaded_file.seek(0) # 重新将文件指针移动到开头,准备下一步操作
file_hash = hashlib.sha256(uploaded_file.read()).hexdigest()
# 查找是否已存在该文件
file_record, created = cls.objects.get_or_create(
file_hash=file_hash,
defaults={'file_name': uploaded_file.name, 'file_path': file_path}
)
# 如果文件已存在,返回已存在的记录
if not created:
return file_record
# 如果是新文件,则返回新的记录
return file_record
1.1 说明
file_name
: 存储文件的原始名称。file_path
: 使用 Django 的FileField
存储文件的实际路径。file_hash
: 存储文件的哈希值,确保文件唯一性,避免重复上传。
create_file_record
方法用于根据文件哈希值检查数据库中是否已经存在相同的文件记录。如果存在,返回已存在的记录;如果文件是新的,则保存并返回新记录。
2. 上传文件大小限制配置
在 settings.py
中设置上传文件的大小限制。此配置将根据不同类型的文件设置不同的大小限制。
# 设置上传文件大小限制
MAX_UPLOAD_SIZES = {
'images': 5 * 1024 * 1024, # 最大图片上传大小:5MB
'videos': 50 * 1024 * 1024, # 最大视频上传大小:50MB
'audio': 20 * 1024 * 1024, # 最大音频上传大小:20MB
'documents': 10 * 1024 * 1024, # 最大文档上传大小:10MB
'archives': 50 * 1024 * 1024, # 最大压缩文件上传大小:50MB
'others': 10 * 1024 * 1024, # 其他文件类型上传大小:10MB
}
MAX_UPLOAD_SIZES
: 设置了不同文件类型的最大上传大小,单位为字节(Byte)。例如,图片文件最大允许上传 5MB,视频文件最大允许上传 50MB。
2.1 媒体文件存储配置
为了让用户上传的文件能够被正确存储和访问,以下配置将决定文件存储的路径和 URL。
import os
# MEDIA_URL 用于生成可公开访问的文件 URL
MEDIA_URL = '/media/'
# MEDIA_ROOT 是实际文件存储的路径
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL
: 用于生成文件的访问 URL,客户端通过该 URL 访问上传的文件。MEDIA_ROOT
: 设置文件存储的实际路径,BASE_DIR
通常是 Django 项目的根目录,这样上传的文件将存储在media
文件夹下。
总结
File
模型:该模型用于存储上传文件的元数据,包括文件名称、路径、哈希值等,并通过哈希值防止重复上传。- 上传文件大小限制:在
settings.py
中设置了针对不同类型文件的上传大小限制。 - 文件存储配置:配置了
MEDIA_URL
和MEDIA_ROOT
来指定文件存储路径和访问 URL。
这样设置完成后,你就可以通过 Django DRF 处理文件上传功能,并确保上传的文件有合理的大小限制以及存储路径的管理。