学习node.js十三,文件的上传于下载
文件上传
文件上传的方案:
大文件上传
:将大文件切分成较小的片段(通常称为分片或块),然后逐个上传这些分片。这种方法可以提高上传的稳定性,因为如果某个分片上传失败,只需要重新上传该分片而不需要重新上传整个文件。同时,分片上传还可以利用多个网络连接并行上传多个分片,提高上传速度断点续传
:在上传过程中,如果网络中断或上传被中止,断点续传技术可以记录已成功上传的分片信息,以便在恢复上传时继续上传未完成的部分,而不需要重新上传整个文件。这种技术可以大大减少上传失败的影响,并节省时间和带宽。
前端实现
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
input {
background-color: #f5f5f5;
border: 1px solid #ccc;
border-radius: 5px;
cursor: pointer;
outline: none;
font-size: 14px;
color: #333;
text-align: center;
line-height: 30px;
font-size: 40px;
}
</style>
</head>
<body>
<!-- 上传文件 -->
<input type="file" id="file" name="file" value="上传文件" />
</body>
第一步:获取元素,监听change事件。获取到文件的信息之后,利用file原型上面的 blob对象的slice方法来进行分割
// 获取文件,监听有无上传
const file = document.getElementById('file');
file.addEventListener('change', function (e) {
// 获取文件信息
const file = e.target.files[0];
const chunks = sliceFile(file);
uploadFile(chunks)
})
// 分片
function sliceFile(file, chunkSize = 1024 * 1024 * 3) {
let chunks = []
for (let i = 0; i < file.size; i+= chunkSize) {
chunks.push(file.slice(i, i + chunkSize))
}
return chunks
}
第二步:将这些分片的文件片,编入编号和文件名后以formData的格式上传,并且将结果放入promise.all这个方法中,如果全部成功的化,那么就调用合并函数,将这个视频进行合并
// 上传
function uploadFile(chunks) {
let list = []
for (let i = 0; i < chunks.length; i++) {
let formData = new FormData();
formData.append('index', i)
formData.append('name', "wenjian")
formData.append('file', chunks[i])
list.push(fetch("http://localhost:8080/upload", {
method: 'POST',
body: formData
}))
}
// 监听事件是否成功
Promise.all(list).then(res => {
// 发送合并请求
fetch("http://localhost:8080/merge", {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: "ceshi.gif",
})
}).then(res => {
console.log(res)
}).catch(e => {
console.log(e)
})
}).catch(e=> {
console.log(e)
})
}
nodejs端实现
安装依赖
- express 帮我们启动服务,并且提供接口
- multer 读取文件,存储
- cors 解决跨域
初始化 multer.diskStorage
- destination 存储的目录
- filename 存储的文件名(我是通过index-文件名存储的你也可以改)
// 1. 初始化multer
const storage = multer.diskStorage({
destination:function (req,file,cb) {
cb(null,'./upload')
},
filename:function (req,file,cb) {
cb(null,`${req.body.index}-${req.body.name}`)
}
})
放到接口上面,就可以将分完片的文件上传
// 2. 配置multer
const upload = multer({storage:storage})
// 3. 创建上传接口
app.post('/upload',upload.single('file'),(req,res) => {
res.send('上传成功')
})
合并文件:先读取分片文件的文件名,然后把这些文件重新的进行排序,合成一个新的文件
完整代码:
import express from 'express'
import multer from 'multer'
import cors from 'cors'
import fs from 'node:fs'
import path from 'node:path'
// 1. 初始化multer
const storage = multer.diskStorage({
destination:function (req,file,cb) {
cb(null,'./upload')
},
filename:function (req,file,cb) {
cb(null,`${req.body.index}-${req.body.name}`)
}
})
const app = express()
app.use(cors())
app.use(express.json())
// 2. 配置multer
const upload = multer({storage:storage})
// 3. 创建上传接口
app.post('/upload',upload.single('file'),(req,res) => {
res.send('上传成功')
})
// 4. 合并文件
app.post("/merge",(req,res) => {
if(!req.body.name) return res.send('文件名不能为空')
let uploadDir = "./upload"
// 读取分片文件
let files = fs.readdirSync(path.join(process.cwd(), uploadDir))
// 重新排序
files = files.sort((a,b) => a.split('-')[0] - b.split('-')[0])
// 合并文件
let writeDir = path.join(process.cwd(),"./video",`${req.body.name}`)
files.forEach(item => {
fs.appendFileSync(writeDir,fs.readFileSync(path.join(process.cwd(),uploadDir,item)))
fs.unlinkSync(path.join(process.cwd(),uploadDir,item))
})
res.send('合并成功')
})
app.listen(8080,() => console.log('Server is running on port 8080'))
文件流下载
文件流下载是一种通过将文件内容以流的形式发送给客户端,实现文件下载的方法。它适用于处理大型文件或需要实时生成文件内容的情况。
nodejs端实现
响应头
Content-Type
指定下载文件的 MIME 类型application/octet-stream
(二进制流数据)application/pdf
:Adobe PDF 文件。application/json
:JSON 数据文件image/jpeg
:JPEG 图像文件
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(path.join(process.cwd(),"static")))
app.post("/upload", (req, res) => {
let fileName = req.body.fileName
if(!fileName) return res.send({message: "File name is required"})
let filePath = path.join(process.cwd(),"static", fileName)
let readStream = fs.readFileSync(filePath)
// 设置响应头
res.setHeader('Content-Type', 'application/octet-stream')
res.setHeader('Content-Disposition', 'attachment;filename=' + fileName)
res.send(readStream)
})
app.listen(3000,() => console.log('Server is running on port 3000'))
前端逻辑
前端核心逻辑就是接受的返回值是流的方式arrayBuffer
,转成blob,生成下载链接,模拟a标签点击下载
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
input {
background-color: #f5f5f5;
border: 1px solid #ccc;
border-radius: 5px;
cursor: pointer;
outline: none;
font-size: 14px;
color: #333;
text-align: center;
line-height: 30px;
font-size: 40px;
}
</style>
</head>
<body>
<!-- 上传文件 -->
<input type="button" value="下载文件" />
</body>
<script>
let btn = document.querySelector('input');
btn.onclick = function () {
fetch("http://localhost:3000/upload",{
method:'POST',
headers:{
'Content-Type':'application/json'
},
body:JSON.stringify({
fileName:'1.png',
})
}).then(res => res.arrayBuffer()).then(res => {
// 1. 转为blob
let blob = new Blob([res],{type:'image/png'})
// 2. 创建a标签
let a = document.createElement('a')
// 4. 设置a标签的href属性为blob地址
a.href = URL.createObjectURL(blob)
// 3. 设置下载文件名
a.download = '1.png'
// 5. 模拟点击a标签
a.click()
// 6. 移除a标签
a.remove()
})
}
</script>
</html>