当前位置: 首页 > news >正文

大文件上传

大文件上传

分片上传

web worker

Web Worker 为 Web 内容在后台线程中运行脚本提供了一种简单的方法。线程可以执行任务而不干扰用户界面。这是MDN对于Web Worker的描述。简单来说就是不会阻塞页面中的UI渲染,因此非常适合用来处理耗时计算操作,即文件的hash计算

文件分片

file-prototype

如图所示,File对象原型为Blob,具备slice的分片方法,因此可以利用该方法实现文件的分片。

const uploadBtn = document.getElementById('uploadBtn')
const fileInput = document.getElementById('file')
// 切片大小10MB
const chunkSize = 1024 * 1024 * 10
const chunks = []
const worker = new Worker('./worker.js')
uploadBtn.addEventListener('click', () => {// 读取文件内容const file = fileInput.files[0]const { size } = file// 计算总切片数total = Math.ceil(size / chunkSize)chunks.push(...Array.from({ length: total }, (_, i) => file.slice(i * chunkSize, (i + 1) * chunkSize)))// 向 worker 线程发送消息worker.postMessage({chunks,filename: file.name})
})
/*** web worker 浏览器的多线程脚本* 是运行在后台的js 不会阻塞页面* 可以进行计算 可以进行io操作 不能操作DOM* 不能访问window alert document location等浏览器对象* self代表worker全局对象* spark-md5.js用于计算文件的唯一hash值*/
self.importScripts('./utils/spark-md5.js')
self.addEventListener('message', e => {const { chunks, filename } = e.data// 计算md5const spark = new self.SparkMD5.ArrayBuffer()let current = 0function loadNext() {const reader = new FileReader()reader.onload = e => {spark.append(e.target.result)current++if (current < chunks.length) {loadNext()} else {// 全部读取完毕 发送结果给主线程self.postMessage({filename,hash: spark.end()})}}reader.readAsArrayBuffer(chunks[current])}loadNext()
})

文件上传

/*** 处理文件上传存放的位置* 处理文件名字*/
const storage = multer.diskStorage({destination: (req, file, cb) => {fs.mkdirSync(`uploads/${req.body.hash}`, { recursive: true }) // 确保上传目录存在cb(null, `uploads/${req.body.hash}/`)},filename: (req, file, cb) => {cb(null, `${req.body.filename}-${req.body.index}`) // 使用hash值和切片下标作为文件名}
})const upload = multer({ storage })/*** 文件上传接口* file字段名需和前端保持一致*/
app.post('/upload', upload.single('file'), (req, res) => {res.json({ message: 'File uploaded successfully', success: true })
})

分片的合并

/*** 合并切片(通过文件流合并)*/
app.get('/merge', async (req, res) => {const { filename, hash } = req.query// 获取切片所在目录const files = fs.readdirSync(`uploads/${hash}`)// 根据index排序const fileArraySort = files.sort((a, b) => {const indexA = parseInt(a.split('-').pop())const indexB = parseInt(b.split('-').pop())return indexA - indexB})// 创建存放最终合并的文件 目录const filePath = path.join(__dirname, hash)fs.mkdirSync(filePath, { recursive: true })// 创建写入流const writeStream = fs.createWriteStream(path.join(filePath, filename))for (const file of fileArraySort) {await new Promise((resolve, reject) => {// 创建读取流(读取每一个切片)const readStream = fs.createReadStream(path.join(__dirname, 'uploads', hash, file))readStream.pipe(writeStream, { end: false }) // 管道流写入但不关闭写入流readStream.on('end', () => {fs.unlinkSync(path.join(__dirname, 'uploads', hash, file)) // 删除切片resolve()})readStream.on('error', reject)})}// 手动关闭writeStream.end()res.json({success: true})
})

秒传

秒传表示上传过的文件不会再次上传,因此只需验证某个hash值的目录是否存在合并后的文件即可。

app.get('/verify', (req, res) => {const { hash, filename } = req.query// 判断是否秒传const filePath = path.join(__dirname, hash, filename)if (fs.existsSync(filePath)) {return res.json({ success: true, fileExist: true })}return res.json({ success: true, fileExist: false })
})

断点续传

断点续传表示当网络中断时延续过去的进度继续上传。

app.get('/verify', (req, res) => {const { hash, filename } = req.query// 判断是否秒传const filePath = path.join(__dirname, hash, filename)if (fs.existsSync(filePath)) {return res.json({ success: true, fileExist: true })}// 断点续传(返回已上传的文件列表)const isExist = fs.existsSync(path.join(__dirname, 'uploads', hash))if (!isExist) {return res.json({ success: true, files: [] })}const files = fs.readdirSync(path.join(__dirname, 'uploads', hash))res.json({ success: true, files })
})

🎇秒传与断点续传都需要在文件上传前调用

const res = await fetch(`http://localhost:3000/verify?hash=${hash}&filename=${filename}`)const { files, fileExist } = await res.json()if (fileExist) {return}const uploadedSet = new Set(files)const tasks = chunks.map((chunk, index) => ({ chunk, index })).filter(({ index }) => !uploadedSet.has(`${filename}-${index}`))// 文件上传
http://www.dtcms.com/a/560869.html

相关文章:

  • 做ppt找图片网站推广网发布的信息准确吗
  • Linux内核POSIX文件锁机制深度解析
  • 从“CPU 烧开水“到优雅暂停:Go 里 sync.Cond 的正确打开方式
  • 大模型系列——Excel数据治理新思路:引入智能体实现自动纠错【Python+Agent】
  • Pyppeteer 使用教程
  • React性能优化:useMemo vs useCallback
  • Onsemi展示了垂直GaN-on-GaN半导体
  • 专业机票网站建设禅城区建设局网站
  • Java 日志演进:一文读懂主流框架
  • 第3章 变量与数据类型
  • pyside6 qt 事件循环
  • Secertpad搭建
  • 吞吐量、延迟、内存:深入理解垃圾回收的“三元悖论”
  • List接口和常用方法
  • 计算机一级考试网站怎么做用织梦系统做网站产权
  • Java 数据结构第二十八期:反射、枚举以及 lambda 表达式
  • Linux 磁盘分区与系统监控完全指南
  • 是普通网站地图好还是rss地图好一点网站建设申请费用
  • 使用Graphics2D创建滑块验证码
  • Flutter provide框架内部实现原理刨析
  • 关于rpm,yum,apt
  • 15.6.Bat脚本编写
  • 景德镇网站制作广告法
  • 可以做头像的网站网站区域名是什么
  • 新手记录使用uniapp-x开发鸿蒙应用
  • Linux+Apache+MySQL+PHP 架构下搭建 Discuz 社区论坛
  • 可替代Github Copilot的插件分享CodeGeeX
  • Ubuntu学习笔记
  • 双非大学生自学鸿蒙5.0零基础入门到项目实战 - 歌曲列表
  • 双非大学生自学鸿蒙5.0零基础入门到项目实战 -ArkTs核心