文件上传是现代Web应用中常见的功能,使用纯AJAX/JS和Django实现可以实现无刷新上传体验。这种方式避免了传统表单提交导致的页面刷新,提升了用户体验。
<!-- HTML部分 -->
<input type="file" id="fileInput">
<button onclick="uploadFile()">上传文件</button>
<div id="progressBar"></div>
<div id="status"></div>
<script>
function uploadFile() {
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];
if (!file) {
alert('请选择文件');
return;
}
const formData = new FormData();
formData.append('file', file);
formData.append('csrfmiddlewaretoken', getCookie('csrftoken'));
const xhr = new XMLHttpRequest();
// 上传进度事件
xhr.upload.addEventListener('progress', function(e) {
if (e.lengthComputable) {
const percent = (e.loaded / e.total) * 100;
document.getElementById('progressBar').innerHTML =
`上传进度: ${percent.toFixed(2)}%`;
}
});
// 上传完成事件
xhr.addEventListener('load', function(e) {
if (xhr.status === 200) {
const response = JSON.parse(xhr.responseText);
document.getElementById('status').innerHTML =
`上传成功: ${response.message}`;
} else {
document.getElementById('status').innerHTML =
'上传失败: ' + xhr.statusText;
}
});
// 上传错误事件
xhr.addEventListener('error', function() {
document.getElementById('status').innerHTML = '上传出错';
});
xhr.open('POST', '/upload/', true);
xhr.send(formData);
}
// 获取CSRF token的函数
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
</script>
# views.py
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.core.files.storage import default_storage
import os
@csrf_exempt # 仅用于测试,生产环境应使用CSRF保护
def upload_file(request):
if request.method == 'POST':
file = request.FILES.get('file')
if not file:
return JsonResponse({'error': '未接收到文件'}, status=400)
try:
# 保存文件到指定位置
file_path = os.path.join('uploads', file.name)
file_name = default_storage.save(file_path, file)
return JsonResponse({
'message': '文件上传成功',
'file_path': file_name,
'file_size': file.size,
'file_type': file.content_type
})
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)
return JsonResponse({'error': '仅支持POST请求'}, status=405)
# urls.py
from django.urls import path
from . import views
urlpatterns = [
path('upload/', views.upload_file, name='upload_file'),
]
原因:Django默认启用CSRF保护,AJAX请求需要包含CSRF token
解决方案:
@csrf_exempt
装饰器(仅限测试环境)原因:Django默认有2.5MB的文件上传大小限制
解决方案:
# settings.py
DATA_UPLOAD_MAX_MEMORY_SIZE = 10485760 # 10MB
FILE_UPLOAD_MAX_MEMORY_SIZE = 10485760 # 10MB
解决方案:在前端和后端都进行验证
前端验证:
// 检查文件类型
const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];
if (!allowedTypes.includes(file.type)) {
alert('不支持的文件类型');
return;
}
后端验证:
ALLOWED_EXTENSIONS = ['jpg', 'jpeg', 'png', 'pdf']
extension = file.name.split('.')[-1].lower()
if extension not in ALLOWED_EXTENSIONS:
return JsonResponse({'error': '不支持的文件类型'}, status=400)
解决方案:分片上传
// 分片上传示例
function uploadInChunks(file) {
const chunkSize = 1024 * 1024; // 1MB
let offset = 0;
function uploadNextChunk() {
const chunk = file.slice(offset, offset + chunkSize);
const formData = new FormData();
formData.append('chunk', chunk);
formData.append('filename', file.name);
formData.append('offset', offset);
formData.append('totalSize', file.size);
const xhr = new XMLHttpRequest();
xhr.open('POST', '/upload_chunk/', true);
xhr.onload = function() {
if (xhr.status === 200) {
offset += chunkSize;
if (offset < file.size) {
uploadNextChunk();
} else {
console.log('上传完成');
}
}
};
xhr.send(formData);
}
uploadNextChunk();
}