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

如图所示,File对象原型为Blob,具备slice的分片方法,因此可以利用该方法实现文件的分片。
▼js复制代码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 }) })
▼js复制代码/** * 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 // 计算md5 const spark = new self.SparkMD5.ArrayBuffer() let current = 0 function 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() })
文件上传
▼js复制代码/** * 处理文件上传存放的位置 * 处理文件名字 */ 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 }) })
分片的合并
▼js复制代码/** * 合并切片(通过文件流合并) */ 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值的目录是否存在合并后的文件即可。
▼js复制代码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 }) })
断点续传
断点续传表示当网络中断时延续过去的进度继续上传。
▼js复制代码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 }) })
🎇秒传与断点续传都需要在文件上传前调用
▼js复制代码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}`)) // 文件上传
评论
问答助学
相关内容
0个评论
全部评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
作者分享
2025年度总结以及展望2026
5
新的一年继续努力学习
4
今天遇到了一件令人无语的事情,今天网站打不开,排查了很久,确实有点问题。解决了问题之后,还是打不开。于是开始不断的问ai,当我将部署流程重新走一遍保证正确的情况下网站还是打不开。为什么呢?因为云服务器欠费了😅
3
关于编程导航的问题:一打开首页就一直处于加载状态,然后切换菜单,出现内容。不知道什么原因。刷新也会出现。不知道有没有其他小伙伴遇到跟我一样的问题
2
今天尝试了以下投简历,发现一个沟通小技巧,当你发送编辑好的沟通语之后已读不回的,不用灰心,可以看下该hr的状态是否在线,在线的话问他已读不回有什么顾虑,告知他我的能力很强,考虑给个面试机会向他证明一下。然后我这样做的基本都告诉我原因以及向我要简历,小伙伴们可以多这样试试看,起码到最后简历发出去了
16
