【Node.js】大文件上传

概述

大文件上传通常采用分片上传。如果因为某些原因上传突然中断,解决问题之后可以接着之前的分片上传,而不需要从头开始上传,也就是断点续传。此外还可以利用多个网络连接并行上传多个分片,提高上传速度。

注:前端不能使用 live-server 去启动, live-server 启动会在上传文件时自动刷新页面,阻止默认事件。所以使用 npm i http-server 去启动 http-server -p 9999这里用了 9999 端口。

html 复制代码
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <input id="file" type="file">
    <script>
        const file = document.querySelector('#file')
        file.addEventListener('change', e => {
            let file = e.target.files[0]  // file 对象
            console.log(file)
        })
    </script>
</body>
</html>

此时上传一个 map4 文件。

分片上传。

完整代码

前端

html 复制代码
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<input id="file" type="file">
<script>
    const file = document.querySelector('#file')
    // 2 MB 的文件 我这里 以 1MB 为一个切片
    const chunksFn = (file, size = 1024 * 1024) => {
        const chunks = []
        for (let i = 0; i < file.size; i += size) {
            chunks.push(file.slice(i, i + size))
        }
        return chunks
    }
    const uploadFiles = (chunks) => {
        // 批量上传 Promise.all
        const list = []
        for (let i = 0; i < chunks.length; i++) {
            const formData = new FormData()
            formData.append('index', i)  // 标识
            formData.append('filename', 'videoTest')
            formData.append('file', chunks[i])  // 必须写最后,因为multer读到file,之后的index,filename等(自定义的)就不会处理了
            list.push(fetch('http://localhost:3000/upload', {
                method: 'POST',
                body: formData
            }))
        }
        // 等待所有请求发送完成,通知后端合并切片
        Promise.all(list).then(res => {
            fetch('http://localhost:3000/merge', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    filename: 'videoTest'
                })
            })
            console.log('上传成功')
        })
    }
    file.addEventListener('change', e => {
        // files 对象 底层继承 blob 可以调用 slice 方法进行切割
        let file = e.target.files[0]
        const chunks = chunksFn(file)
        uploadFiles(chunks)
    })
</script>
</body>
</html>

后端

js 复制代码
import multer from 'multer'
import express from 'express'
import cors from 'cors'
import fs from 'fs'
import path from "path";

// 初始化 multer
const storage = multer.diskStorage({
    destination: function (req, res, cb) {
        cb(null, 'upload/') // 分片存储的目录
    },
    filename(req, res, 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('/upload', upload.single('file'), (req, res) => {
    res.send('ok')
})
app.post('/merge', (req, res) => {
    // 读取目录下面的所有切片
    const uploadDir = path.join(process.cwd(), 'upload')
    const uploadArr = fs.readdirSync(uploadDir)
    // 排序,再进行拼接
    uploadArr.sort((a, b) => a.split('-')[0] - b.split('-')[0])
    const videoDir = path.join(process.cwd(), 'video', `${req.body.filename}.mp4`)
    uploadArr.forEach(item => {
        fs.appendFileSync(videoDir, fs.readFileSync(path.join(uploadDir, item)))
        // 分片合并后就可以清除了
        fs.unlinkSync(path.join(uploadDir, item))
    })
    res.send('ok')
})
app.listen(3000, (req, res) => {
    console.log('3000端口已启动')
})
相关推荐
摸鱼的春哥11 小时前
春哥的Agent通关秘籍07:5分钟实现文件归类助手【实战】
前端·javascript·后端
Victor35611 小时前
MongoDB(2)MongoDB与传统关系型数据库的主要区别是什么?
后端
JaguarJack11 小时前
PHP 应用遭遇 DDoS 攻击时会发生什么 从入门到进阶的防护指南
后端·php·服务端
BingoGo11 小时前
PHP 应用遭遇 DDoS 攻击时会发生什么 从入门到进阶的防护指南
后端
Victor35611 小时前
MongoDB(3)什么是文档(Document)?
后端
牛奔13 小时前
Go 如何避免频繁抢占?
开发语言·后端·golang
想用offer打牌18 小时前
MCP (Model Context Protocol) 技术理解 - 第二篇
后端·aigc·mcp
KYGALYX20 小时前
服务异步通信
开发语言·后端·微服务·ruby
掘了20 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
爬山算法20 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate