大文件上传的实现

准备阶段:
大文件上传的过程中需要用到发布订阅模式监听上传的进度
发布订阅模式的实现

export default class {    // 事件栈    eventStacks = [{        eventType: '',        handlers: []    }];    /**     * 获取事件对应栈的索引     *     * @param {string} eventType 事件类型     * @return {number} stackIndex 对应栈的索引 不存在为-1     */    indexOf(eventType) {        const eventStacks = this.eventStacks;        // 已有事件类型处理栈        let stackIndex = -1;        for (let i = 0; i < eventStacks.length; i++) {            const eventStack = eventStacks[i];            if (eventStack.eventType === eventType) {                stackIndex = i;                break;            }        }        return stackIndex;    };    /**     * 监听事件     *     * @param {string} eventType 自定义事件类型     * @param {Function} handler 事件处理函数     */    on(eventType, handler) {        const index = this.indexOf(eventType);        if (index >= 0) {            // 已存在事件类型处理 直接把相应的处理函数入栈            this.eventStacks[index].handlers.push(handler);        }        else {            // 不存在事件,把对应的事件处理入栈            const newEventStack = {                eventType,                handlers: [handler]            };            this.eventStacks.push(newEventStack);        }    };    /**     * 触发对应的事件     *     * @param {string} eventType 自定义事件类型     * @param {Object} params 参数对象     */    emit(eventType, params = {}) {        this.execEvent(eventType, params);    };    /**     * 执行对应的事件     *     * @param {string} eventType 自定义事件类型     * @param {Object} params 参数对象     */    execEvent(eventType, params = {}) {        const index = this.indexOf(eventType);        if (index < 0) {            return;        }        const handlers = this.eventStacks[index].handlers;        for (let i = 0; i < handlers.length; i++) {            const currentHandler = handlers[i];            if (currentHandler && typeof currentHandler === 'function') {                currentHandler(params);            }        }    };    /**     * 解除对应的事件     *     * @param {string} eventType 事件类型     * @param {Function} handler 事件处理器 必须是引用传进来 使用对象引用相等判断     */    offHandler(eventType, handler) {        const index = this.indexOf(eventType);        if (index >= 0 && this.eventStacks[index].handlers.length) {            // 存在,并且已经入栈            const handlers = this.eventStacks[index].handlers;            this.eventStacks[index].handlers = handlers.filter(currentHandler => {                return currentHandler !== handler            });        }    }}

文件是基于Blob BlobMDN文档
Blob 类型可以使用slice截取一段 基于这点 我们就可以吧大文件拆分成很多小的文件块上传 前端实现如下:

import superagent from 'superagent';import eventEmitter from '../util/eventEmitter';// 上传单个文件export const uploadFile = (file, url) => {    const formData = new FormData();    formData.set('image', file);    console.log(file.size);    return superagent.post(url)        .send(formData);};// 获取文件分块之后的数量const getFragmentNum = (file, fragmentSize) => {    // fragmentSize 拆分文件的大小    return Math.ceil(file.size / fragmentSize);};// 获取文件碎片const getFileFragment = (file, start, end, fragmentSize) => {    return file.slice(start * fragmentSize, end * fragmentSize);};const getSlicedFiles = (file, fragmentSize) => {    const slicedFiles = [];    // 获取文件分块的数量    const fragmentNum = getFragmentNum(file, fragmentSize);    // 获取所有的分块文件    for (let i = 0; i < fragmentNum; i++) {        const currentFileFragment = getFileFragment(file, i, i + 1, fragmentSize);        slicedFiles.push(currentFileFragment);    }    return slicedFiles;};// 多文件上传export class uploadBigFile extends eventEmitter {    constructor(file, url, fragmentSize) {        super();        // 拆分的文件数组        const slicedFiles = getSlicedFiles(file, fragmentSize);        // 正在上传的模块        this.currentIndex = 0;        this.uploadSlicedFiles(slicedFiles, url);    };    /**     * 上传拆分后的文件     *     * @param {Array} files 文件数组     * @param {string} url 上传的地址     */    uploadSlicedFiles(files, url) {        uploadFile(files[this.currentIndex], url)            .then(({body}) => {                this.emit('process', {                    currentIndex: this.currentIndex,                    res: body,                    progress: (this.currentIndex + 1) / files.length                });                this.currentIndex++;                if (this.currentIndex < files.length) {                    this.uploadSlicedFiles(files, url);                }            })            .catch(err => {                console.log(err);            });    };}

后端实现如下:

const express = require('express');const router = express.Router();const multer = require('multer');const upload = multer();// multer用于接受formdatarouter.post('/upload', upload.single('image'), (req, res) => {    res.send({        code: 0,        file: req.file.size    });});module.exports = router;

效果如图 :