• 文件上传 切片与断点续传 持续更新


    主要讲前端 ,后端使用node。会写一下后端的处理
    1.单个文件上传
    请求头为 multipart/form-data
    数据为 from-data格式

        let formData = new FormData();
            formData.append('file', _file);
            formData.append('filename', _file.name);
    
    • 1
    • 2
    • 3

    然后使用post直接给后端。后端直接存起来

    2.单个文件使用base64格式上传(针对于图片)
    在拿到文件后

      // 把选择的文件读取成为BASE64
        const changeBASE64 = file => {
            return new Promise(resolve => {
                let fileReader = new FileReader();
                fileReader.readAsDataURL(file);
                fileReader.onload = ev => {
                    resolve(ev.target.result);
                };
            });
        };
     BASE64 = await changeBASE64(file);
     
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这里使用了一个内置类为 FileReader ,readAsDataURL方法是编为base64,也还有别的编码 比如二进制
    fileReader.onload 是一个异步事件。这里封装了一个promise

    instance 是一个axios实例
    为了防止乱码,这里为了防止乱码使用encodeURIComponent做了一个编码,记得让后端解码呀

     data = await instance.post('/upload_single_base64', {
                    file: encodeURIComponent(BASE64),
                    filename: file.name
                }, {
                    headers: {
                        'Content-Type': 'application/x-www-form-urlencoded'
                    }
                });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    针对这个功能一个小提示 就是如果你同一个文件 上传第二次的时候,后端会提示你文件存在(这是优化点,后端可以不做也行)

    后端处理

    // 单文件上传处理「BASE64」
    app.post('/upload_single_base64', async (req, res) => {
        let file = req.body.file,
            filename = req.body.filename,
            spark = new SparkMD5.ArrayBuffer(),  //SparkMD5 第三方 实例化一个对象
            suffix = /\.([0-9a-zA-Z]+)$/.exec(filename)[1], // 获取文件类型
            isExists = false,
            path;
        file = decodeURIComponent(file);  //  解码
        file = file.replace(/^data:image\/\w+;base64,/, ""); // 提取base64的内容
        file = Buffer.from(file, 'base64'); // 二进制转换
        spark.append(file); // 添加文件进行分析
        path = `${uploadDir}/${spark.end()}.${suffix}`; //生成唯一hash文件名 spark.end()
        await delay();
        // 检测是否存在
        isExists = await exists(path); // 根据文件名判断是否存在
        if (isExists) {
            res.send({
                code: 0,
                codeText: 'file is exists',
                originalFilename: filename,
                servicePath: path.replace(__dirname, HOSTNAME)
            });
            return;
        }
        writeFile(res, path, file, filename, false);
    });
    
    
    // 检测文件是否存在
    const exists = function exists(path) {
        return new Promise(resolve => {
            fs.access(path, fs.constants.F_OK, err => {
                if (err) {
                    resolve(false);
                    return;
                }
                resolve(true);
            });
        });
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    一般这个是后端处理,但是不排除让你自己来。了解一下机制就行

    3.缩略图处理 ,并上传

    请求头为 multipart/form-data
    数据为 from-data格式
    1.缩略图 的话是前端把文件转成base64地址,让后使用src属性
    在这里插入图片描述
    上传的时候,前端进行文件名的hash 处理,后端不处理

    主要是先把文件转成二进制 再 使用 SparkMD5插件

       const changeBuffer = file => {
            return new Promise(resolve => {
                let fileReader = new FileReader();
                fileReader.readAsArrayBuffer(file);
                fileReader.onload = ev => {
                    let buffer = ev.target.result,
                        spark = new SparkMD5.ArrayBuffer(),
                        HASH,
                        suffix;
                    spark.append(buffer);
                    HASH = spark.end();
                    suffix = /\.([a-zA-Z0-9]+)$/.exec(file.name)[1];
                    resolve({
                        buffer,
                        HASH,
                        suffix,
                        filename: `${HASH}.${suffix}`
                    });
                };
            });
        };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    这样后端就只要判断就行,不会进行处理
    后端处理
    multiparty_upload 用来解析 fromdata 格式 。
    在这里插入图片描述
    4 文件上传进度监控
    这里主要是用到了axios中的 onuploadProgress 实现

            try {
                let formData = new FormData();
                formData.append('file', file);
                formData.append('filename', file.name);
                data = await instance.post('/upload_single', formData, {
                    // 文件上传中的回调函数 xhr.upload.onprogress
                    onUploadProgress(ev) {
                        let {
                            loaded,
                            total
                        } = ev;
                        upload_progress.style.display = 'block';
                        upload_progress_value.style.width = `${loaded/total*100}%`;
                    }
                });
                if (+data.code === 0) {
                    upload_progress_value.style.width = `100%`;
                    await delay(300);
                    alert(`恭喜您,文件上传成功,您可以基于 ${data.servicePath} 访问该文件~~`);
                    return;
                }
                throw data.codeText;
            } catch (err) {
                alert('很遗憾,文件上传失败,请您稍后再试~~');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    主要代码和单一文件的上传没有太大区别,只是多了一个axios回调
    loaded是已上传 大小 total是总大小

    // 延迟函数
    const delay = function delay(interval) {
        typeof interval !== "number" ? interval = 1000 : null;
        return new Promise(resolve => {
            setTimeout(() => {
                resolve();
            }, interval);
        });
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这里设置了一个延时函数,是因为过渡是有一个动画效果 所以为了顺滑再最后一个100% 动画完成后,再进行提示 (这个自己根据情况去设置)

    5 多文件上传,进度监控
    input 要设置 多选属性

    在这里插入图片描述
    主要是一个循环去发送请求,去监听上传进度,再用promise.all 去进行后续的一些处理工作
    在这里插入图片描述

    6 拖拽文件上传

    这里用到了几个特殊事件

    // 拖拽获取 dragenter dragleave dragover drop
        /* upload.addEventListener('dragenter', function () {
            console.log('进入');
        });
        upload.addEventListener('dragleave', function () {
            console.log('离开');
        }); */
        upload.addEventListener('dragover', function (ev) {
            ev.preventDefault();
        });
        upload.addEventListener('dragover', function (ev) {
            ev.preventDefault();
            let file = ev.dataTransfer.files[0];
            if (!file) return;
            uploadFile(file);
        });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    这里主要是使用drop事件,这里注意需要禁止dragover drop默认事件。因为浏览器会默认打开文件
    文件得获取为 ev.dataTransfer.files

    后续得上传操作就没啥,和别的没啥大区别

    7 大文件得切片上传和断点续传

    1. 断点续传是要基于切片上传
    2. 切片上传后,文件的合并时由服务端根据 文件的hash名来进行的
    3. 这个文件的hash 可以由客户端来做,当然后端来也是可以的,这里是客户端来做
    4. 每次上传之前。我们都需要先把需要上传的文件hash 名给后端。后端返回我们已经上传过的切片数组,然后我们固定大小或者固定数量来进行切片 。这里是把两者结合了
    5. 然后开始循环切片数组,发送请求之前 先判断是否在已上传过的数组里面,如果纯在的话我们就不需要进行请求
    6. 在所有的请求都完成后,也就是我们的进度变为100% 我们再告知客户端,可以进行切片的合并了
      upload_inp.addEventListener('change', async function () {
            let file = upload_inp.files[0];
            if (!file) return;
            upload_button_select.classList.add('loading');
            upload_progress.style.display = 'block';
    
            // 获取文件的HASH
            let already = [],
                data = null,
                {
                    HASH,
                    suffix
                } = await changeBuffer(file);
    
            // 获取已经上传的切片信息
            try {
                data = await instance.get('/upload_already', {
                    params: {
                        HASH
                    }
                });
                if (+data.code === 0) {
                    already = data.fileList;
                }
            } catch (err) {}
    
            // 实现文件切片处理 「固定数量 & 固定大小」
            let max = 1024 * 100,
                count = Math.ceil(file.size / max),
                index = 0,
                chunks = [];
            if (count > 100) {
                max = file.size / 100;
                count = 100;
            }
            while (index < count) {
                chunks.push({
                    file: file.slice(index * max, (index + 1) * max),
                    filename: `${HASH}_${index+1}.${suffix}`
                });
                index++;
            }
    
            // 上传成功的处理
            index = 0;
            const clear = () => {
                upload_button_select.classList.remove('loading');
                upload_progress.style.display = 'none';
                upload_progress_value.style.width = '0%';
            };
            const complate = async () => {
                // 管控进度条
                index++;
                upload_progress_value.style.width = `${index/count*100}%`;
    
                // 当所有切片都上传成功,我们合并切片
                if (index < count) return;
                upload_progress_value.style.width = `100%`;
                try {
                    data = await instance.post('/upload_merge', {
                        HASH,
                        count
                    }, {
                        headers: {
                            'Content-Type': 'application/x-www-form-urlencoded'
                        }
                    });
                    if (+data.code === 0) {
                        alert(`恭喜您,文件上传成功,您可以基于 ${data.servicePath} 访问该文件~~`);
                        clear();
                        return;
                    }
                    throw data.codeText;
                } catch (err) {
                    alert('切片合并失败,请您稍后再试~~');
                    clear();
                }
            };
    
            // 把每一个切片都上传到服务器上
            chunks.forEach(chunk => {
                // 已经上传的无需在上传
                if (already.length > 0 && already.includes(chunk.filename)) {
                    complate();
                    return;
                }
                let fm = new FormData;
                fm.append('file', chunk.file);
                fm.append('filename', chunk.filename);
                instance.post('/upload_chunk', fm).then(data => {
                    if (+data.code === 0) {
                        complate();
                        return;
                    }
                    return Promise.reject(data.codeText);
                }).catch(() => {
                    alert('当前切片上传失败,请您稍后再试~~');
                    clear();
                });
            });
        });
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102

    基本上就这么多了,主要是对文件的处理 以及几个获取文件的事件 思路基本上都差不多,很好理解

  • 相关阅读:
    Jsp-九大内置对象
    UG NX二次开发(C#)-计算直线到各个坐标系轴向的投影角度
    AI计算机视觉进阶项目(一)——带口罩识别检测(4)
    Java-jar包,反编译为:.java文件
    【C语言刷LeetCode】717. 1 比特与 2 比特字符(E)
    C#多线程编程技术——多线程操作(没看懂)
    Redis数据类型-需求实战记录
    SaaSBase:什么是捷讯通信?
    二叉树练习
    (附源码)ssm介绍信智能实现系统 毕业设计 260930
  • 原文地址:https://blog.csdn.net/weixin_49210760/article/details/126116320