首页 > 其他分享 >【Django DRF Apps】【文件上传】【断点上传】从零搭建一个普通文件上传,断点续传的App应用

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

时间:2025-01-22 15:27:54浏览次数:3  
标签:文件 hash os 上传 file path 断点

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

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

功能点说明

  1. 普通文件上传

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

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

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 文件夹下。

总结

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

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

该功能app已在github开源

标签:文件,hash,os,上传,file,path,断点
From: https://blog.csdn.net/qq_59344127/article/details/145302936

相关文章

  • 如何在Python中高效地读写大型文件?
    大家好,我是V哥。上一篇给大家介绍如何使用Python进行文件读写操作的方法,问题来了,如何读写的是大型文件,有没有什么方法来提高效率呢,不要捉急,这一篇来聊聊如何在Python中高效地读写大型文件。以下是在Python中高效读写大型文件的一些方法:一、逐行读取大型文件:defread_larg......
  • linux文件IO:select
    select电平触发#include<sys/time.h>#include<sys/types.h>#include<unistd.h>intselect(intn,fd_set*readfds,fd_set*writefds,fd_set*exceptfds,structtimeval*timeout);在指定的文件描述符准备好I/O之前或超过一定时间限制,select调用会被阻塞readfds文件描......
  • 如何在 Linux 服务器上设置 FTP 文件传输协议
    第一步:安装vsftpd要在Linux上设置FTP服务器,首先需要确保已安装vsftpd。对于Ubuntu/Debian系统:sudoaptupdatesudoaptinstallvsftpd-y对于CentOS/RHEL系统:sudoyuminstallvsftpd-y第二步:配置vsftpd配置vsftpd以允许基本的FTP连接并设置用户限......
  • 如何用vscode打开obj、glb文件,查看3D文件
    方案1:安装插件3DViewerforVSCode,安装完可以查看obj但是不懂为啥是白色的  glTFTools,安装完可以查看gltf启动位置在右上角:白色小山的图标 这个效果不错,看起来比较舒服。 但是gltf从哪里来呢?首先我们有一个glb文件,右键它,点击倒数第二行的“glTF:importfromG......
  • Debian解压zip文件时中文文件名称乱码
    使用unzip解压文件,如下:merit@tt-raspberrypi5:/home/pi$unzipmakerobo_code.zipArchive:makerobo_code.zipcreating:makerobo_code/creating:makerobo_code/.ipynb_checkpoints/inflating:makerobo_code/.ipynb_checkpoints/1.-▒+▒LED-▒▒▒-checkpoint.i......
  • python 读取word、pdf文件内容
    importdocx2txtimportfitzimportdocxfromdocx.oxmlimportparse_xmldefget_doc_content(filepath):"""获取word文本内容"""try:doc=docx.Document(filepath)content=[]forelementindoc.elem......
  • Csharp上传大文件到服务器指定文件夹问题
    功能:大文件上传下载,断点续传,文件夹上传下载,加密传输,加密存储,云对象存储要求:免费,开源,技术支持前端:vue2,vue3,react,vue-cli,html,jquery后端:asp.net,vb.net,.netcore,.netmvc,.netwebform平台:Windows,macOS,Linux,Ubuntu,RedHat,CentOS,中标麒麟,银河麒麟,统信UOS,信......
  • VUE分片上传大型视频文件到服务器解决方案
    要求:免费,开源,技术支持技术:百度webuploader,分块,切片,断点续传,秒传,MD5验证,纯JS实现,支持第三方软件集成前端:vue2,vue3,vue-cli,html5,webuploader后端:asp.net,.netmvc,.netcore,asp,jsp,java,springboot,php,数据库:MySQL,Oracle,SQLServer,达梦,人大金仓,国产数据库平......
  • 【linux】文件与目录命令 - vim
    文章目录1.基本用法2.常用参数3.用法举例4.多种模式5.注意事项vim是一款功能强大的文本编辑器,适用于代码编辑和日常文本处理。它是vi的增强版,支持多种模式(如普通模式、插入模式和命令模式)以及插件扩展。1.基本用法语法:vim[选项][文件]功能:编......
  • 在 Windows 中,通过修改注册表或者其他配置文件,跳过首次启动时的设置过程。这些设置通
    在Windows中,除了跳过InternetExplorer的第一次启动配置外,还有一些其他应用和服务,也可以通过修改注册表或者其他配置文件,跳过首次启动时的设置过程。这些设置通常用于让用户能够直接进入程序或系统界面,而不需要经历繁琐的初始配置步骤。以下是一些常见的跳过首次启动配置的示......