【从前端入门到全栈】Node.js之大文件分片上传

从前端入门到全栈-系列介绍

  1. 你会学到什么?

可能学不到什么东西,该系列是作者本人工作和学习积累,用于复习

  1. 作者介绍

江辰,前网易高级前端工程师

  1. 系列介绍

现在的 Web 前端已经离不开 Node.js,我们广泛使用的 Babel、Webpack、工程化都是基于 Node 的,各个互联网大厂也早已大规模落地 Node 项目。因此,想要成为一名优秀的前端工程师,提升个人能力、进入大厂,掌握 Node.js 技术非常有必要。

Node.js 不仅可以用来完善手头的开发环境,实现减少代码和 HTTP 请求,降低网页请求消耗的时间,提升服务质量。还可以扩展前端工程师的工作领域,用作 HTTP 服务,让前端也能完成一部分后端的工作,减少对后端的依赖,降低沟通成本,提升开发效率。

而且,Node.js 和浏览器的 JavaScript 只是运行时环境不同,编程语言都是 JavaScript ,所以掌握 Node.js 基础对前端工程师来说并不难,难点在于应用。由于浏览器的 JavaScript 主要是负责内容呈现与交互,而 Node.js 应用领域包括工具开发、Web 服务开发和客户端开发,这些都与传统的Web前端领域不一样,用来应对不同的问题。

  1. 适宜人群
  • 对 Node.js 感兴趣的 JavaScript 程序员
  • 希望拓展知识边界,往全栈方向发展的前端工程师

定义

分片上传是把一个大的文件分成若干块,一块一块的传输,这样做的好处就是在文件上传/下载过程中,遇到不可抗力,比如网络中断,服务器异常,或者其他原因导致操作中断;再次操作时,可以从已经上传/下载的部分开始继续上传/下载未完成的部分,而没有必要从头开始上传/下载。

步骤

前端部分

  1. 首先,我们需要使用 HTMLFile API,通过 input 标签获取用户上传的大文件。
  2. 使用 slice 方法将大文件分割成若干个小文件块,即分片。
  3. 再使用 fetchaxios 等方法向服务器发送 POST 请求,上传文件的分片。

后端部分

  1. 使用 express 或类似的框架接收前端上传的文件分片。
  2. 将接收到的文件分片保存到服务器的一个临时位置。
  3. 图所接收到所有的文件分片后,将这些分片合并成一个完整的文件。

流程图

实现

javascript 复制代码
import React, { useState } from 'react';
import axios from 'axios';

const FileUploader = () => {
    const [file, setFile] = useState(null);

    const onSubmit = async event => {
        event.preventDefault();

        if (!file) {
            // 没有文件则直接返回
            return;
        }

        // 以1MB为一个分片
        const blockSize = 1024 * 1024 * 1;
        const fileSize = file.size;

        let start = 0;
        let end = blockSize;

        while (start < fileSize) {
            const chunk = file.slice(start, end);

            const formData = new FormData();
            formData.append('file', chunk, file.name);

            // 分片上传
            await axios.post('/upload', formData);

            start = end;
            end = start + blockSize;
        }

        // 合并
        await axios.get(`/merge?filename=${file.name}`);
    };

    const onChange = event => {
        setFile(event.target.files[0]);
    };

    return (
        <form onSubmit={onSubmit}>
            <input type="file" onChange={onChange} />
            <button type="submit">上传</button>
        </form>
    );
};

export default FileUploader;
javascript 复制代码
const express = require('express');
const fs = require('fs');
const path = require('path');
const multer = require('multer');

const upload = multer({ dest: 'uploads/' });
const app = express();

app.post('/upload', upload.single('file'), (req, res) => {
    const chunk = req.file; // 获取上传的文件分片
    const { originalname } = req.file;
    const chunkDirPath = path.resolve(__dirname, 'chunks');
    const chunkPath = path.resolve(chunkDirPath, originalname);

    // 如果 chunks 文件夹不存在,就创建一个
    if (!fs.existsSync(chunkDirPath)) {
        fs.mkdirSync(chunkDirPath);
    }

    // 将文件分片保存到 chunks 文件夹下
    fs.renameSync(chunk.path, chunkPath);

    res.sendStatus(200);
});

app.get('/merge', (req, res) => {
    const { filename } = req.query;
    const chunkDirPath = path.resolve(__dirname, 'chunks');
    const filePath = path.resolve(__dirname, 'files', filename);

    fs.readdirSync(chunkDirPath).forEach(chunkPath => {
        // 读取每一个文件分片,然后将它们一起写入一个新文件,完成合并
        fs.appendFileSync(filePath, fs.readFileSync(path.resolve(chunkDirPath, chunkPath)));
        // 合并后删除文件分片
        fs.unlinkSync(path.resolve(chunkDirPath, chunkPath));
    });

    // 删除用于保存文件分片的文件夹
    fs.rmdirSync(chunkDirPath);

    res.sendStatus(200);
});

app.listen(8000, () => {
    console.log('Server listening on port 8000');
});

请注意这是一种简化的实现,无法处理有些复杂情况,如并发上传,断点续传,错误恢复等。如果你需要这些功能,可能需要引入更完善的库或服务,如 tus-js-client,Plupload,Resumable.js 或 UpChunk。

需要注意的点

1. 上传进度: 用户上传大文件时,提供一个进度条可以改善用户体验。你可以使用 XMLHttpRequest 或 Fetch 的 API 获得正在上传的进度信息,并实时展示。

2. 错误处理和重试: 你需要处理可能会发生的错误,比如网络问题或者服务器错误。为了避免用户再次上传文件,可以实现重试机制,当某个分片上传失败时,可以重新上传这个分片。

3. 并发控制: 如果你一次上传多个分片,可能会消耗大量的用户网络带宽。你需要控制同时上传分片的数量,比如使用 Promise.all。

4. 断点续传: 用户在上传大文件时,可能会中断,比如网络连接可能会断开,或者用户关闭了页面。你可以实现断点续传,让用户可以在重新打开页面时,继续上次的上传,而不需要从头开始。通常需要后台支持,记录已经上传的分片。

5. 安全性: 文件上传是一个重要的安全问题,你应该检查并限制用户上传的文件类型,防止上传恶意文件。同时,你应该在服务器端再次检查文件。

总结

文章同步更新平台:掘金、CSDN、知乎、思否、博客,公众号(野生程序猿江辰) 我的联系方式,v:Jiang9684,欢迎和我一起学习交流

相关推荐
橙子家3 小时前
浏览器缓存之【基础键值存储】:Local storage 和 Session storage
前端
星星在线5 小时前
MusicFree:一个「All in One」的个人音乐服务器,让听歌回归简单
前端·后端
IT_陈寒6 小时前
Redis的SETNX并发问题让我加了三天班
前端·人工智能·后端
demo007x6 小时前
Docling 文档转换以及技术架构分析
前端·后端·程序员
京东云开发者7 小时前
京东市民服务又“上新”!这次是黑龙江“龙易办”
前端
袋鱼不重8 小时前
我的神奇同事,AI 用多了居然写了个 Open In Codex
前端·后端·ai编程
竹林8188 小时前
Web3表单签名验证:我用 wagmi 和 ethers 给 DApp 加了一个“免密登录”,踩坑记录全在这了
javascript
用户6990304848758 小时前
try catch使用场景 处理同步代码错误兼容用的
javascript·uni-app
雪碧聊技术8 小时前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript
Fireworks8 小时前
深入vue3源码解读 -- 1、响应式的基础概念
前端