当前位置: 首页 > article >正文

django vue3实现大文件分段续传(断点续传)

前端环境准备及目录结构:

 npm create vue 并取名为big-file-upload-fontend
通过 npm i  安装以下内容
  "dependencies": {
    "axios": "^1.7.9",
    "element-plus": "^2.9.1",
    "js-sha256": "^0.11.0",
    "vue": "^3.5.13"
  },

在这里插入图片描述

main.js中的内容

import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
// app.use(ElementPlus)
const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')

App.vue 中的内容:

<template>
  <div class="button">
    <el-upload
        ref="uploadRef"
        class="upload-demo"
        :http-request="uploadFile"
        :show-file-list="false"
    >
      <el-button type="primary">点击上传文件</el-button>
    </el-upload>
  </div>
</template>
<script setup>
import axios from 'axios'
import {sha256} from 'js-sha256'

const uploadFile = ({file}) => {
  // 每4MB为一个小文件
  const chunkSize = 4 * 1024 * 1024; // 4MB
  // 文件总大小
  const fileSize = file.size;
  // 分成了多少个片段
  const chunks = Math.ceil(fileSize / chunkSize);
  // 保证文件唯一
  const sha256Promise = sha256(file.name); // sha256的参数只接收字符串
  // 询问已经上传了几个片段
  const checkUploadedChunks = () => {
    return axios.post('http://127.0.0.1:8000/api/check', {
      sha256Promise: sha256Promise
    }).then(response => {
      return response.data; // response.data 就是下边的 uploadedChunks
    });
  };

  return checkUploadedChunks().then(async uploadedChunks => {
    if (uploadedChunks.length === chunks) {
      console.log("已经上传完成就不需要再重复上传")
      return Promise.resolve();
    }
    for (let i = 0; i < chunks; i++) {
      const formData = new FormData();
      // 将之前上传过的片段过滤掉,即不上传之前上传过的内容
      if (uploadedChunks.includes(i + 1)) {
        continue;
      }
      const start = i * chunkSize;
      // 将文件分片
      const chunk = file.slice(start, start + chunkSize);
      // 使用FormData形式上传文件
      formData.append('chunk', chunk);
      formData.append('chunkNumber', i + 1);
      formData.append('chunksTotal', chunks);
      formData.append('sha256Promise', sha256Promise);
      formData.append('filename', file.name);
      // 一次只上传一个片段,本次上传完成后才上传下一个
      const res = await axios.post('http://127.0.0.1:8000/api/upload', formData)
    }
  });
};
</script>

<style >
html, body{
  height: 100%;
  width: 100%;
  background-color: pink;
}
</style>

django后端环境及目录:

django-admin startproject big_file_upload_backend # 创建一个big_file_upload_backend项目
python版本:
Python 3.11.11
pip 需要安装:
Django                        5.0.6 
django-cors-headers           4.6.0  # 用于解决跨域 

在这里插入图片描述

big_file_upload_backend/settings.py 中的配置如下:

MIDDLEWARE = [
	......
	'django.contrib.sessions.middleware.SessionMiddleware',
    "corsheaders.middleware.CorsMiddleware",  # CorsMiddleware一定要在CommonMiddleware之前
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware', #  注释掉这个
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    ......
]
# 并在文件最后添加上允许所有跨域
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOW_ALL_ORIGINS = True
CORS_ALLOW_HEADERS = ('*')
# 将STATIC_URL = 'statics/' 替换为下边这三行
STATIC_URL = 'statics/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, "../statics"),
]








# 完整的setting设置如下:
"""
Django settings for big_file_upload_backend project.

Generated by 'django-admin startproject' using Django 4.2.

For more information on this file, see
https://docs.djangoproject.com/en/4.2/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.2/ref/settings/
"""
import os
from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-%sa^&p^%m3+m0ex%@y%la0(zzt4y4k3l%0=p#tipx-kz6w*#=d'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    "corsheaders.middleware.CorsMiddleware",
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'big_file_upload_backend.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'big_file_upload_backend.wsgi.application'


# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}


# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/

STATIC_URL = 'statics/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, "../statics"),
]

# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOW_ALL_ORIGINS = True
CORS_ALLOW_HEADERS = ('*')

big_file_upload_backend/urls.py中的配置如下:

from django.contrib import admin
from django.urls import path
from big_file_upload_backend.views import checks, upload

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/check', checks),
    path('api/upload', upload),
]

big_file_upload_backend/views.py中的配置如下:

import json
import os
from django.http import JsonResponse
from big_file_upload_backend import settings


def checks(request):
    if request.method == "POST":
        body = json.loads(request.body.decode("utf-8"))
        filename = body.get("sha256Promise", None)
        base_path = settings.STATIC_URL+'record_files/'+filename+'.txt'
        # base_path = '../statics/record_files/'+filename+'.txt'
        file_is_exits = os.path.exists(base_path)
        # 判断文件是否存在,存在则说明之前至少上传过一次
        if file_is_exits:
            with open(base_path, 'r') as f:
                check_list = json.loads(f.readline())
        else:
            # 不存在就返回空
            check_list = []
        return JsonResponse(check_list, safe=False)


def upload(request):
    if request.method == "POST":
        # 注意这里用的是request.FILES 因为chunk为文件流形式
        chunk = request.FILES.get("chunk")
        # 当前的切片编号
        chunk_number = request.POST.get("chunkNumber")
        # 总切片数量
        chunks_total = int(request.POST.get("chunksTotal"))
        # 文件名唯一标识
        sha256_promise = request.POST.get("sha256Promise")
        # 文件名称
        filename = request.POST.get("filename")
        # base_path = '../statics/upload_files/'+sha256_promise # 这样写无效
        base_path = settings.STATIC_URL + "upload_files/" + sha256_promise  # 必须这样写
        # record_path中的txt文件用于记录已经上传过的切片
        record_path = settings.STATIC_URL + "record_files/" + sha256_promise+'.txt'  # 必须这样写
        os.makedirs(base_path, exist_ok=True)
        # 后缀名
        ext = filename.split(".")[-1]
        # 小切片的文件名称
        order_file = chunk_number+'.'+ext
        fname = base_path+"/" + order_file

        with open(fname, 'wb') as f:
            for line in chunk:
                # 将上传的文件写入小切片中,等上传完成后进行合并
                f.write(line)
        # 等写完了才做判断
        chunk_number_int = int(chunk_number)  # 将字符串转成int
        line_list = [int(chunk_number)]  # 默认先添加一个切片片段
        if os.path.exists(record_path):
            with open(record_path, 'r') as f:
                line_data = f.readline()  # 读取已经上传的小切片
                if line_data == '':
                    pass
                else:
                    line_list = json.loads(line_data)  # 将字符串形式的数组转为python数组
                if chunk_number_int not in line_list:
                    line_list.append(chunk_number_int)  # 如果当前切片号不在已上传的数组中则添加
        with open(record_path, 'w') as f:
            f.write(json.dumps(line_list))  # 将已上传的片段列表重新写回记录文件中
        #  合并小切片片段段
        if len(line_list) == chunks_total:
            with open(base_path+"/"+filename, "wb") as f:
                # 按照升序一个一个合并
                for num in sorted(line_list):
                    with open(base_path+"/"+str(num)+"."+ext, 'rb') as r:
                        f.write(r.read())
                    # 读取完毕将片段删除,只保留合并后的文件
                    os.remove(base_path+"/"+str(num)+"."+ext)
        # 返回没啥用
        check_list = {"chunk_number": chunk_number, "code": 200}
        return JsonResponse(check_list)

在项目根目录下要新建一个statics目录,且其下边要有两个目录:

record_files
upload_files

最后分别运行前后端项目

前端:npm run dev
后端: python manage.py runserver

点击上传文件,选择一个较大的文件进行上传,可以看到右侧一直再分片上传,上传完成后会在上述两个文件中分别多出两个文件

在这里插入图片描述

如果是上传过程中还可以看到upload_files文件下的小分片中的片段产生和合并过程,也可以在上传到一半时随机关闭浏览器,下次打开重新上传,则会跳过之前上传的继续进行上传。上传完成后的效果:

在这里插入图片描述


http://www.kler.cn/a/467348.html

相关文章:

  • axios和fetch的实现原理以及区别,与XMLHttpRequest的关系,并结合react封装统一请求示例
  • 移动硬盘无法访问:全面解析、恢复方案与预防策略
  • Linux:操作系统不朽的传说
  • 深入理解Java面向对象:类与对象、匿名对象、成员变量与局部变量
  • redis源码系列--(四)--redis cluster
  • 【Rust自学】10.6. 生命周期 Pt.2:生命周期的语法与例子
  • unity学习3:如何从github下载开源的unity项目
  • 数字0、字符‘0’、转义字符‘\0’、NULL有什么区别?
  • Profinet转EtherNet/IP网关连接AB PLC的应用案例
  • Word如何设置整段背景色
  • 成立一家无人机培训机构需要哪些基础配置
  • 机器学习 - 线性回归
  • Http基础认证摘要认证
  • uniapp实现后端数据i18n国际化
  • [CTF/网络安全] 攻防世界 get_post 解题详析
  • 基于Java+Netty搞了个类似teamviewer、向日葵、ToDesk的远程桌面工具
  • 【顶刊TPAMI 2025】多头编码(MHE)之极限分类 Part 1:背景动机
  • Ruby 中文编码
  • 【竞技宝】CS2:HLTV 2024 TOP11-w0nderful
  • Tailwind CSS 使用简介
  • 基于SpringBoot在线竞拍平台系统功能实现十五
  • 【Linux】定时运行shell脚本
  • DNS分块矩阵的理解
  • 遇到复杂的 递归查询sql 需要oracle 转pgsql 可以把数据表结构给ai
  • 挑战春招找到java后端实习第三天(1.4)
  • C++语言编程————C++的输入与输出