文件上传和优化
文件格式判断和大小判断
判断格式
方式一 在 input[type="file"] 添加
accept=".png,.jpg,.jpeg"
选择文件时限制,限制不了拖拽上传方式二 通过 file.type 正则限制
!/(jpg|jpeg|png)/i.test(file.type)
方式三 通过二进制头信息
const fileReader = new FileReader()
fileReader.readAsArrayBuffer(file)
fileReader.onload = function (e) {
const result = new Uint8Array(e.target.result)
if (result.join('').indexOf('255216') === 0) {
console.log('jpg文件')
} else if (result.join('').indexOf('13780') === 0) {
console.log('png文件')
}
}
文件上传【form-data,文件唯一hash,进度条,多文件】
- form-data 方式上传
- 设置
Content-Type:multipart/form-data
头信息 - 采用
(new FormData()).formData.append('file', file)
方式上传
- 设置
- 文件唯一 hash 名称
- 采用
(new FileReader()).readAsArrayBuffer(file)
读取文件为 ArrayBuffer 格式 - 使用
spark-md5
生成 hash 名称
- 采用
- 上传进度条
- 监听 axios 中
onUploadProgress(e)
方式 - 其实际就是 ajax 中
xhr.upload.onprogress(e)
方法 - 百分比计算
e.loaded / e.total
- 监听 axios 中
- 多文件上传
- 在 input[type="file"] 添加
multiple
属性 - 利用 input[type="file"] 对象中的
files
获取到所有文件对象,循环上传
- 在 input[type="file"] 添加
文件上传【base64,缩略图,拖拽上传,大文件断点续传】
- base64 & 缩略图
- 一般用于处理小的图片,可以预览缩略图
- 采用
(new FileReader()).readAsDataURL(file)
读取文件为 DataURL 格式,可以在浏览器上直接访问
- 拖拽上传
- 监听 html5 元素
dragover
和drop
事件,需要阻止默认事件e.preventDefault()
- 在 drop 事件中通过
e.dataTransfer.files
获取到文件对象,即可上传
- 监听 html5 元素
- 大文件断点续传
- 使用
固定数量 & 固定大小
方案生成切片,如果切片数量大于最大值,就根据数量生产切片大小 - 使用文件对象中
file.slice
进行切割,切片名称为${hash}_${i}
(spark-md5
生成文件唯一 hash,i
为切片序号) - 上传之前,调接口获取已上传切片数组
- 循环使用
FormData
上传未上传的切片,上传失败的切片放到失败数组里,重新上传 - 上传完成后,调接口发送上传完成指令
- 使用
使用 webwork 优化大文件 hash 生产,防止页面假死
onmessage = function (e) {
importScripts('../node_modules/spark-md5/spark-md5.min.js');
const file = e.data
const fileReader = new FileReader()
fileReader.readAsArrayBuffer(file)
fileReader.onload = ev => {
const buffer = ev.target.result
const spark = new SparkMD5.ArrayBuffer()
spark.append(buffer)
const hash = spark.end()
const suffix = /\.([a-zA-Z0-9]+)$/.exec(file.name)[1]
postMessage({
buffer, hash, suffix, filename: `${hash}.${suffix}`
})
}
}
完整代码
源码预览
打开窗口查看源码
后端处理文件上传的 nodejs 测试代码
- 利用 express 框架
- 使用 multer 处理
form-data
上传 - 使用 body-parser 处理
x-www-form-urlencoded
参数 - 使用 spark-md5 处理 base64 文件唯一 hash 名称
- getData 方法用于获取切片数量、接收上传完成合并切片
- upload 方法用于处理上传普通文件、base64格式文件
const express = require('express')
const app = express()
const multer = require('multer')
const bodyParser = require('body-parser')
const sparkMD5 = require('spark-md5')
//引入 path 和 fs
const path = require('path')
const fs = require('fs')
const upload = multer({dest: './uploads/'})
const sleep = () => new Promise(resolve => {
setTimeout(resolve, 1000)
})
app.use(upload.any())
app.use(bodyParser.urlencoded({extended: false, limit: '2100000kb'}))
//设置跨域访问
app.all('*', (req, res, next) => {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "X-Requested-With");
res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
res.header("X-Powered-By", ' 3.2.1')
res.header("Content-Type", "application/json;charset=utf-8");
next();
});
app.post('/getData', async function (req, res) {
const body = req.body
if (body.hash && body.suffix) {
const newDir = path.join(__dirname, `/uploads/${body.hash}`)
if (!fs.existsSync(newDir)) {
fs.mkdirSync(newDir)
}
const files = fs.readdirSync(newDir)
res.send({
code: 0,
codeText: '请求成功',
fileList: files
})
} else if (body.hash && body.count) {
const newDir = path.join(__dirname, `/uploads/${body.hash}`)
const files = fs.readdirSync(newDir)
const extname = path.extname(files[0])
const newFile = newDir + extname
files.sort((a, b) => {
const reg = /_(\d+)/
return reg.exec(a)[1] - reg.exec(b)[1]
}).forEach(file => {
fs.appendFileSync(newFile, fs.readFileSync(newDir + '/' + file))
fs.unlinkSync(newDir + '/' + file)
})
fs.rmdirSync(newDir)
res.send({
code: 0,
codeText: '合并成功',
originalFilename: body.filename,
servicePath: 'http://localhost:8888/uploads/' + body.hash + extname
})
}
})
app.post('/upload', function (req, res) {
// 处理 multipart/form-data 方式
if (req.files) {
const file = req.files[0]
const body = req.body
// 拿到后缀名
let extname = path.extname(file.originalname);
//拼接新的文件路径,文件加上后缀名
let newPath = file.path + extname;
if (body.hash && body.filename) {
const newDir = `uploads/${body.hash}`
newPath = `${newDir}/${body.filename}`
}
//重命名
fs.rename(file.path, newPath, async function (err) {
// await sleep()
if (err) {
res.send({
code: 1,
codeText: err
})
} else {
res.send({
code: 0,
codeText: '上传成功',
originalFilename: file.originalname,
servicePath: 'http://localhost:8888/' + newPath
})
}
})
} else if (req.body) { // 处理 application/x-www-form-urlencoded 方式
const body = req.body
//过滤data:URL
const base64Data = decodeURIComponent(body.file).replace(/^data:image\/\w+;base64,/, "");
const dataBuffer = Buffer.from(base64Data, 'base64');
// 利用扩展生产唯一 md5 名字
const spark = new sparkMD5.ArrayBuffer()
spark.append(base64Data)
// 拿到后缀名
const extname = path.extname(body.filename);
// 新路径
const newPath = '/uploads/' + spark.end() + extname
// base64 写入文件
fs.writeFile(path.join(__dirname) + newPath, dataBuffer, async function (err) {
// await sleep()
if (err) {
res.send({
code: 1,
codeText: err
})
} else {
res.send({
code: 0,
codeText: '上传成功',
originalFilename: body.filename,
servicePath: 'http://localhost:8888' + newPath
})
}
})
}
})
app.listen(8888, function () {
console.log('Server is running at http://localhost:8888');
})