Nodejs大文件分片上传、断点续传(HTTP/WebSocket)

本文介绍了使用Node.js实现大文件分片上传和断点续传的技术,通过分析流程图和具体开发流程,详细讲解了如何利用Blob、FileReader、fs和stream等工具进行文件处理。项目亮点包括HTTP/WebSocket协议选择、断点续传支持和友好的用户交互。测试部分展示了HTTP上传4M图片和WebSocket上传1G电影的案例。
摘要由CSDN通过智能技术生成

在这里插入图片描述

前言

文件上传的场景经常碰到,如果我们上传的文件是一个很大的文件,那么上传的时间应该会比较久,再加上网络不稳定各种因素的影响,很容易导致传输中断,用户除了重新上传文件外没有其他的办法,但是我们可以使用分片上传来解决这个问题。通过分片上传技术,如果网络传输中断,我们重新选择文件只需要传剩余的分片。而不需要重传整个文件,大大减少了重传的开销。

项目

分析流程图

在这里插入图片描述

  • 流程简述
  1. 选择或拖拽文件,选择适当的分片大小
  2. 生成哈希加密散列
  3. 哈希校验(未上传,断点续传,已上传)
  4. 选择对应请求协议并发上传
  5. 文件分片上传完成,服务器合并分片
项目地址

https://github.com/xbc30/large-file-upload

详细目录介绍
├─client                       # vue client根目录
│  ├─src                       # 组件根目录
│    ├─assets                  # 静态文件svg
│    ├─components              # 组件目录
│      ├─upload.vue            # 上传组件
│    ├─App.vue                 # 根组件
├─server                       # eggjs server根目录
│  ├─app                       # 请求处理目录
│    ├─controller              # controller目录
│      ├─home.js               # 请求处理controller
│    ├─io                      # io目录
│      ├─controller            # io/controller目录
│        ├─nsp.js              # io请求处理controller
│    ├─router.js               # 路由
│    ├─utils.js                # 工具类函数
│  ├─config                    # 配置
│  ├─uploads                   # 上传文件目录
│    ├─e4e4-2097152            # 上传文件对应哈希目录
|    |—build.jpg               # 最终合并文件
├─README.md                    # README
亮点
  • HTTP/WebSocket协议可供选择
  • 选择或拖拽文件,并显示文件具体信息
  • 进度条显示实时进度,友好的操作提示信息
  • 根据文件大小自动匹配合适的分片大小,也可自定义分片大小
  • 支持断点续传
  • 少于等于10个分片请求就执行并行上传,大于10个分片请求就执行顺序同步上传
  • 管道流合并分片减轻服务器压力

基本知识点

浏览器对象
Blob

将读取的文件进行分片切割,以拼凑准备上传的数据对象

const blobSlice =
  File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;

blobSlice.call(file, start, end) // 赋值到FormData示例的file属性
FileReader

使用FileReader.readAsArrayBuffer()读取分片内容,以计算文件哈希

fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
// readAsArrayBuffer() 方法用于启动读取指定的 Blob 或 File 内容
NodeJS模块
fs

fs负责读取文件路径,检索文件数量,创建可读流,可写流

stream

管道流的stream使用少量内存处理多个文件分片的合并

  • highWaterMark
    默认是16kb,调整为每个分片大小的值,使得合并过程中数据不会丢失或损坏,充当缓存池

  • pipe
    数据流中的积压问题

const input = fs.createReadStream(path, {
   
    highWaterMark: chunkSize
});
input.pipe(output);

具体开发流程

哈希生成与校验

Blob负责将文件按分片大小切分,FileReader会按字节读取文件内容,并转换为ArrayBuffer对象(二进制缓冲区),SparkMD5负责计算文件最终哈希

// 计算文件哈希
calFileHash(file) {
   
    return new Promise((resolve, reject) => {
   
        const chunks = Math.ceil(file.size / this.chunkSize);

        const spark = new SparkMD5.ArrayBuffer();
        const fileReader = new FileReader();
        const that = this;
        let currentChunk = 0;

        fileReader.onload = function (e) {
   
            console.log("read chunk nr", currentChunk + 1, "of", chunks);
            spark.append(e.target.result); // Append array buffer
            currentChunk++;

            if (currentChunk < chunks) {
   
                loadNext();
            } else {
   
                that.chunkTotal = currentChunk;
                const hash = spark.end();
                that.updateTips("success", "加载文件成功,文件哈希为" + hash);
                resolve(hash);
            }
        };

        fileReader.onerror
  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,以下是一个使用 Node.js 实现大文件切片上传断点续传的功能示例: ```javascript const http = require('http'); const fs = require('fs'); const path = require('path'); const PORT = 8000; const UPLOAD_DIR = path.resolve(__dirname, './uploads'); // 创建上传目录 if (!fs.existsSync(UPLOAD_DIR)) { fs.mkdirSync(UPLOAD_DIR); } // 上传文件处理函数 const handleUpload = (req, res) => { const { filename, chunkIndex, totalChunks } = req.headers; const chunkDir = path.resolve(UPLOAD_DIR, filename); // 如果是第一个分片,创建文件夹 if (chunkIndex === '0') { fs.mkdirSync(chunkDir); } // 获取上传分片数据 const chunksData = []; req.on('data', (chunk) => { chunksData.push(chunk); }); req.on('end', () => { const buffer = Buffer.concat(chunksData); // 写入分片文件 fs.writeFileSync(path.resolve(chunkDir, chunkIndex), buffer); // 如果当前分片是最后一个分片,则合并文件 if (Number(chunkIndex) === Number(totalChunks) - 1) { const filePath = path.resolve(UPLOAD_DIR, filename); const chunks = fs.readdirSync(chunkDir); const writeStream = fs.createWriteStream(filePath); chunks.forEach((chunk) => { const chunkPath = path.resolve(chunkDir, chunk); const chunkBuffer = fs.readFileSync(chunkPath); fs.unlinkSync(chunkPath); // 删除分片文件 writeStream.write(chunkBuffer); }); writeStream.end(() => { res.end('upload success'); }); } else { res.end('chunk upload success'); } }); }; // 断点续传处理函数 const handleResumeUpload = (req, res) => { const { filename } = req.headers; const filePath = path.resolve(UPLOAD_DIR, filename); const fileStat = fs.statSync(filePath); res.setHeader('Content-Length', fileStat.size); res.setHeader('Content-Type', 'application/octet-stream'); res.setHeader('Accept-Ranges', 'bytes'); const range = req.headers.range || 'bytes=0-'; const positions = range.replace(/bytes=/, '').split('-'); const start = parseInt(positions[0], 10); const end = positions[1] ? parseInt(positions[1], 10) : fileStat.size - 1; const chunkSize = end - start + 1; res.setHeader('Content-Range', `bytes ${start}-${end}/${fileStat.size}`); res.setHeader('Cache-Control', 'no-cache'); const readStream = fs.createReadStream(filePath, { start, end }); readStream.on('open', () => { readStream.pipe(res); }); readStream.on('error', () => { res.end('Error'); }); }; // 创建 HTTP 服务器 const server = http.createServer((req, res) => { if (req.url === '/upload' && req.method === 'POST') { handleUpload(req, res); } else if (req.url === '/resume-upload' && req.method === 'GET') { handleResumeUpload(req, res); } else { res.end('Hello World!'); } }); // 启动服务器 server.listen(PORT, () => { console.log(`Server is listening on port ${PORT}`); }); ``` 使用示例: 1. 开启服务器:`node server.js` 2. 上传文件:使用 POST 请求发送文件分片(每个分片的大小可以自定义),请求头需要包含 `filename`(文件名)、`chunkIndex`(当前分片索引,从 0 开始)、`totalChunks`(总分片数)三个字段。 3. 断点续传:使用 GET 请求获取已上传文件,请求头需要包含 `filename`(文件名)字段和 `range`(请求的字节范围)字段。如果请求头中没有 `range` 字段,则返回整个文件的内容。如果请求头中有 `range` 字段,则只返回指定字节范围内的文件内容。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值