大文件上传与流下载



前端必备工具推荐网站(免费图床、API和ChatAI等实用工具):
http://luckycola.com.cn/


前言

在现代网站中,越来越多的个性化图片,视频,去展示,因此我们的网站一般都会支持文件上传。今天我们以大文件上传和下载为主题来分享总结一下.


一、大文件上传

分片上传

将大文件切分成较小的片段(通常称为分片或块),然后逐个上传这些分片。这种方法可以提高上传的稳定性,因为如果某个分片上传失败,只需要重新上传该分片而不需要重新上传整个文件。同时,分片上传还可以利用多个网络连接并行上传多个分片,提高上传速度。

1、前端实现
  • 监听input的change事件获取文件对象file
  • 通过文件对象上的slice方法实现分片
  • 通过Promise.all和fetch结合实现统一的分片上传请求
  • 完成所有分片上传后请求后端实现分片融合
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>大文件上传</title></title>
</head>
<body>
    <input type="file" name="file" id="file">
    <script>
        let fileEle = document.getElementById('file');
        fileEle.onchange = function(e) {
            // console.log(e);
            const file = e.target.files[0];
            console.log('file=>', file);
            // 分片
            let mychunks = getChunks(file, 1 * 1024 * 100);
            console.log('chunks=>', mychunks);
            // 批量上传 这里用promise.all合适些  
            uploadFile(mychunks);
        }

        // 通过file对象上的slice方法进行分片
        var getChunks = function(file, step = 1 * 1024 * 1024) {
            let chunks = [];
            for (let i = 0; i < file.size; i += step) {
                chunks.push(file.slice(i , step + i));
            };
            return chunks;
        }

        // 文件上传方法
        var uploadFile = function(chunks) {
            let promiseFetchLis = [];
            chunks.forEach((chunk, index) => {
                let myformdata = new FormData();
                // 记录分片索引
                myformdata.append('index', index);
                // 记录大文件名称
                myformdata.append('fileName', 'testFile1');
                // 记录分片总数
                myformdata.append('total', chunks.length);
                // 记录分片信息
                myformdata.append('file', chunk);
                promiseFetchLis.push(fetch('http://127.0.0.1:3000/up', {
                    method: 'POST',
                    body: myformdata
                }))
            });
            console.log('promiseFetchLis:', promiseFetchLis);
            Promise.all(promiseFetchLis).then(res => {
                console.log('上传成功', res);
                // 上传成功后需要调用后端接口进行分片合并回原来的文件
                fetch('http://127.0.0.1:3000/merge', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({
                        fileName: 'testFileAll',
                    })
                }).then(res => {
                    alert('上传且合并成功');
                })
            }).catch(err => {
                console.log('上传失败', err);
            })
        }
        // 2、文件流下载
    </script>
</body>
</html>
2、node后端实现
  • 通过multer模块实现文件片段存储
  • 通过fs模块进行分片读取和融合
import express from 'express'
import multer from 'multer'
import cors from 'cors'
import fs from 'fs'
import path from 'path'

const storage = multer.diskStorage({
    destination: (req, file, cb) => {
        cb(null, 'uploads/')
    },
    filename: (req, file, 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('/up', upload.single('file'), (req, res) => {
    res.send('ok')
})

app.post('/merge', async (req, res) => {
    const uploadPath = './uploads'
    let files = fs.readdirSync(path.join(process.cwd(), uploadPath));
    files = files.sort((a, b) => a.split('-')[0] - b.split('-')[0]);
    const writePath = path.join(process.cwd(), `imgs`, `${req.body.fileName}.jpeg`)
    files.forEach((item) => {
        fs.appendFileSync(writePath, fs.readFileSync(path.join(process.cwd(), uploadPath, item)))
        fs.unlinkSync(path.join(process.cwd(), uploadPath, item))
    })

    res.send('ok')
})

app.listen(3000, () => {
    console.log('Server is running on port 3000')
})


二、大文件上传

大文件流式下载

文件流下载是一种通过将文件内容以流的形式发送给客户端,实现文件下载的方法。它适用于处理大型文件或需要实时生成文件内容的情况。

1、前端实现
  • 前端核心逻辑就是接受的返回值是流的方式arrayBuffer,转成blob,生成下载链接,模拟a标签点击下载
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>大文件流下载</title></title>
    <script src="./index.js"></script>
</head>
<body>
    <button id="btn">download</button>
    <script>
    // 文件流下载   
    // 前端核心逻辑就是接受的返回值是流的方式arrayBuffer,转成blob,生成下载链接,模拟a标签点击下载
    const btn = document.getElementById('btn');
    btn.addEventListener('click', function(e) {
        fetch('http://localhost:3000/download', {
            method: 'POST',
            body:JSON.stringify({
                fileName:'download.jpeg'
            }),
            headers:{
                "Content-Type":"application/json"
            }
        }).then(res => {
            res.arrayBuffer();
        }).then(res => {
            const blob = new Blob([res], {
                type: 'image/jpeg'
            });
            let url = URL.createObjectURL(blob);
            const a = document.createElement('a')
            a.href = url
            a.download = '1.png'
            a.click()
        })
    });
    </script>
</body>
</html>
2、后端node实现
核心点(响应头):
application/octet-stream(二进制流数据)

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('./static'))

app.post('/download', function (req, res) {
    const fileName = req.body.fileName
    const filePath = path.join(process.cwd(), './static', fileName)
    const content = fs.readFileSync(filePath)
    res.setHeader('Content-Type', 'application/octet-stream')
    res.setHeader('Content-Disposition', 'attachment;filename=' + fileName)
    res.send(content)
})

app.listen(3000, () => {
    console.log('http://localhost:3000')
})
  • 15
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
文件上传和下载在 Express 中可以通过使用中间件 `multer` 来实现。下面是一个基本的文件上传下载的示例代码: 文件上传: ```javascript const express = require('express'); const multer = require('multer'); const upload = multer({ dest: 'uploads/' }); const app = express(); app.post('/upload', upload.single('file'), (req, res) => { console.log(req.file); res.send('File uploaded successfully.'); }); app.listen(3000, () => { console.log('Server listening on port 3000'); }); ``` 文件下载: ```javascript const express = require('express'); const fs = require('fs'); const app = express(); app.get('/download', (req, res) => { const file = `${__dirname}/example.pdf`; const stat = fs.statSync(file); res.setHeader('Content-Length', stat.size); res.setHeader('Content-Type', 'application/pdf'); res.setHeader('Content-Disposition', 'attachment; filename=example.pdf'); const stream = fs.createReadStream(file); stream.pipe(res); }); app.listen(3000, () => { console.log('Server listening on port 3000'); }); ``` 文件切分可以使用 `stream.Transform` 类来实现。下面是一个将文件按照指定大小分块的示例代码: ```javascript const stream = require('stream'); class Chunker extends stream.Transform { constructor(options) { super(options); this.chunkSize = options.chunkSize || 1024; this.buffer = Buffer.alloc(0); } _transform(chunk, encoding, callback) { this.buffer = Buffer.concat([this.buffer, chunk]); while (this.buffer.length >= this.chunkSize) { const chunk = this.buffer.slice(0, this.chunkSize); this.buffer = this.buffer.slice(this.chunkSize); this.push(chunk); } callback(); } _flush(callback) { if (this.buffer.length > 0) { this.push(this.buffer); } callback(); } } const fs = require('fs'); const input = fs.createReadStream('example.pdf'); const output = fs.createWriteStream('example.chunked'); const chunker = new Chunker({ chunkSize: 1024 }); input.pipe(chunker).pipe(output); ``` 在上述代码中,`Chunker` 是一个继承自 `stream.Transform` 的类,用于将文件按照指定大小分块。在 `_transform` 方法中,我们将新的数据块加入到缓冲区中,并不断从缓冲区中取出指定大小的数据块,将其推入输出中。在 `_flush` 方法中,我们将剩余的数据块推入输出中。最后,我们使用 `fs.createReadStream` 和 `fs.createWriteStream` 创建输入和输出,并将数据经过 `Chunker` 实例处理后再写入到输出中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LuckyCola2023

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值