前端环境准备及目录结构:
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'
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}) => {
const chunkSize = 4 * 1024 * 1024;
const fileSize = file.size;
const chunks = Math.ceil(fileSize / chunkSize);
const sha256Promise = sha256(file.name);
const checkUploadedChunks = () => {
return axios.post('http://127.0.0.1:8000/api/check', {
sha256Promise: sha256Promise
}).then(response => {
return response.data;
});
};
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.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
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",
'django.middleware.common.CommonMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
......
]
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOW_ALL_ORIGINS = True
CORS_ALLOW_HEADERS = ('*')
STATIC_URL = 'statics/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "../statics"),
]
"""
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
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = 'django-insecure-%sa^&p^%m3+m0ex%@y%la0(zzt4y4k3l%0=p#tipx-kz6w*#=d'
DEBUG = True
ALLOWED_HOSTS = []
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.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'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
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',
},
]
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
STATIC_URL = 'statics/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "../statics"),
]
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'
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":
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 = settings.STATIC_URL + "upload_files/" + sha256_promise
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)
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)
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文件下的小分片中的片段产生和合并过程,也可以在上传到一半时随机关闭浏览器,下次打开重新上传,则会跳过之前上传的继续进行上传。上传完成后的效果: