前端必备工具推荐网站(免费图床、API和ChatAI等实用工具):
http://luckycola.com.cn/
前言
在现代网站中,越来越多的个性化图片,视频,去展示,因此我们的网站一般都会支持文件上传。今天我们以大文件上传和下载为主题来分享总结一下.
一、大文件上传
分片上传
将大文件切分成较小的片段(通常称为分片或块),然后逐个上传这些分片。这种方法可以提高上传的稳定性,因为如果某个分片上传失败,只需要重新上传该分片而不需要重新上传整个文件。同时,分片上传还可以利用多个网络连接并行上传多个分片,提高上传速度。
1、前端实现
- 监听input的change事件获取文件对象file
- 通过文件对象上的slice方法实现分片
- 通过Promise.all和fetch结合实现统一的分片上传请求
- 完成所有分片上传后请求后端实现分片融合
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>大文件上传</title></title>
</head>
<body>
<input type="file" name="file" id="file">
<script>
let fileEle = document.getElementById('file');
fileEle.onchange = function(e) {
// console.log(e);
const file = e.target.files[0];
console.log('file=>', file);
// 分片
let mychunks = getChunks(file, 1 * 1024 * 100);
console.log('chunks=>', mychunks);
// 批量上传 这里用promise.all合适些
uploadFile(mychunks);
}
// 通过file对象上的slice方法进行分片
var getChunks = function(file, step = 1 * 1024 * 1024) {
let chunks = [];
for (let i = 0; i < file.size; i += step) {
chunks.push(file.slice(i , step + i));
};
return chunks;
}
// 文件上传方法
var uploadFile = function(chunks) {
let promiseFetchLis = [];
chunks.forEach((chunk, index) => {
let myformdata = new FormData();
// 记录分片索引
myformdata.append('index', index);
// 记录大文件名称
myformdata.append('fileName', 'testFile1');
// 记录分片总数
myformdata.append('total', chunks.length);
// 记录分片信息
myformdata.append('file', chunk);
promiseFetchLis.push(fetch('http://127.0.0.1:3000/up', {
method: 'POST',
body: myformdata
}))
});
console.log('promiseFetchLis:', promiseFetchLis);
Promise.all(promiseFetchLis).then(res => {
console.log('上传成功', res);
// 上传成功后需要调用后端接口进行分片合并回原来的文件
fetch('http://127.0.0.1:3000/merge', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
fileName: 'testFileAll',
})
}).then(res => {
alert('上传且合并成功');
})
}).catch(err => {
console.log('上传失败', err);
})
}
// 2、文件流下载
</script>
</body>
</html>
2、node后端实现
- 通过multer模块实现文件片段存储
- 通过fs模块进行分片读取和融合
import express from 'express'
import multer from 'multer'
import cors from 'cors'
import fs from 'fs'
import path from 'path'
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/')
},
filename: (req, file, cb) => {
cb(null, `${req.body.index}-${req.body.fileName}`)
}
})
const upload = multer({ storage })
const app = express()
app.use(cors())
app.use(express.json())
app.post('/up', upload.single('file'), (req, res) => {
res.send('ok')
})
app.post('/merge', async (req, res) => {
const uploadPath = './uploads'
let files = fs.readdirSync(path.join(process.cwd(), uploadPath));
files = files.sort((a, b) => a.split('-')[0] - b.split('-')[0]);
const writePath = path.join(process.cwd(), `imgs`, `${req.body.fileName}.jpeg`)
files.forEach((item) => {
fs.appendFileSync(writePath, fs.readFileSync(path.join(process.cwd(), uploadPath, item)))
fs.unlinkSync(path.join(process.cwd(), uploadPath, item))
})
res.send('ok')
})
app.listen(3000, () => {
console.log('Server is running on port 3000')
})
二、大文件上传
大文件流式下载
文件流下载是一种通过将文件内容以流的形式发送给客户端,实现文件下载的方法。它适用于处理大型文件或需要实时生成文件内容的情况。
1、前端实现
- 前端核心逻辑就是接受的返回值是流的方式arrayBuffer,转成blob,生成下载链接,模拟a标签点击下载
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>大文件流下载</title></title>
<script src="./index.js"></script>
</head>
<body>
<button id="btn">download</button>
<script>
// 文件流下载
// 前端核心逻辑就是接受的返回值是流的方式arrayBuffer,转成blob,生成下载链接,模拟a标签点击下载
const btn = document.getElementById('btn');
btn.addEventListener('click', function(e) {
fetch('http://localhost:3000/download', {
method: 'POST',
body:JSON.stringify({
fileName:'download.jpeg'
}),
headers:{
"Content-Type":"application/json"
}
}).then(res => {
res.arrayBuffer();
}).then(res => {
const blob = new Blob([res], {
type: 'image/jpeg'
});
let url = URL.createObjectURL(blob);
const a = document.createElement('a')
a.href = url
a.download = '1.png'
a.click()
})
});
</script>
</body>
</html>
2、后端node实现
核心点(响应头):
application/octet-stream(二进制流数据)
Content-Disposition 指定服务器返回的内容在浏览器中的处理方式。它可以用于控制文件下载、内联显示或其他处理方式
attachment:指示浏览器将响应内容作为附件下载。通常与 filename 参数一起使用,用于指定下载文件的名称
inline:指示浏览器直接在浏览器窗口中打开响应内容,如果内容是可识别的文件类型(例如图片或 PDF),则在浏览器中内联显示
import express from 'express'
import fs from 'fs'
import path from 'path'
import cors from 'cors'
const app = express()
app.use(cors())
app.use(express.json())
app.use(express.static('./static'))
app.post('/download', function (req, res) {
const fileName = req.body.fileName
const filePath = path.join(process.cwd(), './static', fileName)
const content = fs.readFileSync(filePath)
res.setHeader('Content-Type', 'application/octet-stream')
res.setHeader('Content-Disposition', 'attachment;filename=' + fileName)
res.send(content)
})
app.listen(3000, () => {
console.log('http://localhost:3000')
})