【Django DRF Apps】【文件上传】【断点上传】从零搭建一个普通文件上传,断点续传的App应用

Django DRF 应用搭建文档:普通文件上传与大文件断点上传

本文档将指导你从零搭建一个支持普通文件上传和大文件断点上传功能的 Django DRF 应用。我们将通过两部分内容进行说明:普通文件上传功能和分片上传功能。

功能点说明

  1. 普通文件上传

    • 处理普通文件上传,支持根据文件内容的哈希值重命名文件,避免文件重复。
    • 支持文件类型判断(如图片、视频、音频等),并限制上传文件的大小。
    • 如果文件已经存在于数据库中,返回文件路径而不是重新上传。
  2. 大文件断点上传

    • 将大文件拆分为多个分块进行上传,并且支持断点续传。
    • 通过上传文件的哈希值来区分每个上传任务,以确保上传的一致性。
    • 提供接口查询上传进度和已上传的分块,合并文件时根据文件哈希值进行操作。

1. 普通文件上传实现

1.1 视图实现
python 复制代码
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 断点上传视图
python 复制代码
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 查询已上传分块
python 复制代码
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 合并文件分块
python 复制代码
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

文件模型用于存储上传的文件信息,包括文件名、存储路径、哈希值等字段,并通过文件的哈希值确保文件的唯一性。

python 复制代码
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 中设置上传文件的大小限制。此配置将根据不同类型的文件设置不同的大小限制。

python 复制代码
# 设置上传文件大小限制

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。

python 复制代码
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 文件夹下。

总结

  1. File 模型:该模型用于存储上传文件的元数据,包括文件名称、路径、哈希值等,并通过哈希值防止重复上传。
  2. 上传文件大小限制 :在 settings.py 中设置了针对不同类型文件的上传大小限制。
  3. 文件存储配置 :配置了 MEDIA_URLMEDIA_ROOT 来指定文件存储路径和访问 URL。

这样设置完成后,你就可以通过 Django DRF 处理文件上传功能,并确保上传的文件有合理的大小限制以及存储路径的管理。

该功能app已在github开源

🚀 Django封装高复用高可移植的apps系列 - 快速集成与高效开发!

已在 GitHub 开源:Django-DRF-components

特点:

  • 高复用性:每个组件都经过精心设计,确保可在不同项目中无缝使用。
  • 高可移植性:简单集成,快速实现所需功能,节省开发时间。
  • 灵活扩展:可根据需求轻松定制和扩展。
  • 功能完备:包括常见的上传功能、文件管理、用户认证等多种实用功能,满足各种开发需求。

🔧 涵盖功能:

  • 文件上传(普通、分块上传)
  • 用户认证(RBAC)
  • JWT认证app
  • 邮箱登录、验证、重置密码
  • 导出Excel、PDF
  • 封装通用views类
  • SSE(服务器推送事件)
  • WebSocket
  • 文件管理与存储
  • 多种常见工具集(如权限验证、数据处理等)

开源优点:

  • 快速启动、低门槛接入
  • 大量实用的模板与示例代码
  • 高效的文档支持与社区维护

🌐 前端项目 - 完美衔接 Django 后端!

已在 GitHub 开源:Vue3-components

项目概述:

基于最新技术栈 Vue3 + Vite + TypeScript ,采用 vben 开源的后端管理系统,前后端完美衔接,为您的开发提供高效且现代的解决方案。

主要特点:

  • Vue3 + Vite + TypeScript:基于前沿技术开发,提升开发效率和性能。
  • 多套主题支持:提供可配置的主题系统,让应用界面与品牌风格完美匹配。
  • 内置国际化:支持多语言,满足全球化需求。
  • 动态路由权限生成:内置动态权限管理和路由生成方案,实现细粒度的权限控制。

功能亮点:

  • 快速集成 Django 后端,轻松调用 Django-DRF-components 系列功能
  • 完善的用户认证系统,支持 JWT 认证
  • 强大的文件管理系统,支持文件上传和下载
  • 高度可定制的界面和功能模块

立即访问 GitHub,开始使用前后端完美结合的 Django 与 Vue3 项目,提升开发效率和应用性能!【查看代码(后端)】【查看代码(前端)


相关推荐
向上的车轮26 分钟前
什么是时序数据库?有哪些时序数据库?常见的运用场景有哪些?
数据库·时序数据库
movee1 小时前
一台低配云主机也能轻松愉快地玩RDMA
linux·人工智能·后端
GDAL1 小时前
better-sqlite3之exec方法
javascript·sqlite
项目題供诗2 小时前
ES语法学习
学习·elasticsearch·django
岱宗夫up2 小时前
【Python】Django 中的算法应用与实现
数据库·python·opencv·django·sqlite
比花花解语2 小时前
使用数据库和缓存的时候,是如何解决数据不一致的问题的?
数据库·缓存·数据一致性
YGGP2 小时前
Redis篇:基础知识总结与基于长期主义的内容更新
数据库·redis·缓存
程序员清风2 小时前
什么时候会考虑用联合索引?如果只有一个条件查就没有建联合索引的必要了么?
java·后端·面试
Seven972 小时前
【设计模式】掌握建造者模式:如何优雅地解决复杂对象创建难题?
java·后端·设计模式
KINICH ahau3 小时前
数据库1-2章
数据库·oracle