Appearance
文件上传方案
概述
文件上传是前端开发中的常见需求,涉及文件选择、分片上传 / 断点续传 / 并发上传 / 秒传 / 进度条 / 安全与权限控制等技术点。
基础上传
1. 表单上传
html
<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="file" />
<button type="submit">上传</button>
</form>2. FormData 上传
javascript
async function uploadFile(file) {
const formData = new FormData()
formData.append('file', file)
formData.append('userId', '123')
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
})
return response.json()
}3. Axios 上传
javascript
import axios from 'axios'
async function uploadFile(file, onProgress) {
const formData = new FormData()
formData.append('file', file)
const response = await axios.post('/api/upload', formData, {
onUploadProgress: progressEvent => {
const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
)
onProgress(percent)
}
})
return response.data
}进度显示
上传进度条
vue
<template>
<div>
<input type="file" @change="handleFileChange" />
<div class="progress">
<div class="progress-bar" :style="{ width: progress + '%' }"></div>
</div>
<span>{{ progress }}%</span>
</div>
</template>
<script setup>
import { ref } from 'vue'
const progress = ref(0)
function handleFileChange(e) {
const file = e.target.files[0]
uploadFile(file, percent => {
progress.value = percent
})
}
</script>大文件上传
1. 文件分片
javascript
function createFileChunks(file, chunkSize = 5 * 1024 * 1024) {
const chunks = []
let cur = 0
let index = 0
while (cur < file.size) {
const chunk = file.slice(cur, cur + chunkSize)
chunks.push({
index,
chunk,
start: cur,
end: cur + chunkSize
})
cur += chunkSize
index++
}
return chunks
}2. 并发上传分片
javascript
async function uploadChunks(file, chunkSize = 5 * 1024 * 1024) {
const chunks = createFileChunks(file, chunkSize)
const hash = await calculateHash(file)
const requests = chunks.map(({ index, chunk }) => {
const formData = new FormData()
formData.append('chunk', chunk)
formData.append('hash', hash)
formData.append('index', index)
return fetch('/api/upload/chunk', {
method: 'POST',
body: formData
})
})
await Promise.all(requests)
// 合并分片
await fetch('/api/upload/merge', {
method: 'POST',
body: JSON.stringify({ hash, total: chunks.length })
})
}3. 断点续传
javascript
async function resumeUpload(file) {
const hash = await calculateHash(file)
// 检查已上传的分片
const response = await fetch(`/api/upload/check?hash=${hash}`)
const { uploadedChunks } = await response.json()
const chunks = createFileChunks(file)
// 过滤掉已上传的分片
const needUploadChunks = chunks.filter(
chunk => !uploadedChunks.includes(chunk.index)
)
// 上传剩余分片
for (const chunk of needUploadChunks) {
await uploadChunk(chunk, hash)
}
}文件类型验证
1. 前端验证
javascript
function validateFile(file) {
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif']
const maxSize = 10 * 1024 * 1024 // 10MB
if (!allowedTypes.includes(file.type)) {
throw new Error('不支持的文件类型')
}
if (file.size > maxSize) {
throw new Error('文件大小超过限制')
}
return true
}2. 文件预览
javascript
function previewFile(file) {
return new Promise(resolve => {
const reader = new FileReader()
reader.onload = e => {
resolve(e.target.result)
}
if (file.type.startsWith('image/')) {
reader.readAsDataURL(file)
} else if (file.type.startsWith('text/')) {
reader.readAsText(file)
}
})
}拖拽上传
实现拖拽区域
vue
<template>
<div
class="drop-zone"
@drop.prevent="handleDrop"
@dragover.prevent="isDragging = true"
@dragleave="isDragging = false"
:class="{ active: isDragging }"
>
<p>拖拽文件到此处上传</p>
<input type="file" @change="handleFileChange" multiple />
</div>
</template>
<script setup>
import { ref } from 'vue'
const isDragging = ref(false)
function handleDrop(e) {
isDragging.value = false
const files = e.dataTransfer.files
uploadFiles(files)
}
</script>
<style scoped>
.drop-zone {
border: 2px dashed #ccc;
padding: 40px;
text-align: center;
}
.drop-zone.active {
border-color: #1890ff;
background: #f0f8ff;
}
</style>最佳实践
1. 用户体验
- 显示上传进度
- 支持取消上传
- 提供错误提示
- 支持文件预览
2. 安全性
- 前端验证文件类型和大小
- 后端二次验证
- 防止恶意文件上传
- 限制上传频率
3. 性能优化
- 使用 Web Worker 计算文件 hash
- 并发上传分片
- 压缩图片后再上传
- 使用 CDN 加速
常见问题
如何处理大文件上传超时?
- 使用分片上传
- 增加服务器超时时间
- 实现断点续传
如何优化上传速度?
- 并发上传多个分片
- 使用 CDN 加速
- 压缩文件后再上传