文件上传 切片与断点续传

17 篇文章 0 订阅
5 篇文章 0 订阅

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

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

然后使用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);
 

这里使用了一个内置类为 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'
                }
            });

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

后端处理

// 单文件上传处理「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);
        });
    });
};

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

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}`
                });
            };
        });
    };

这样后端就只要判断就行,不会进行处理
后端处理
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('很遗憾,文件上传失败,请您稍后再试~~');

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

// 延迟函数
const delay = function delay(interval) {
    typeof interval !== "number" ? interval = 1000 : null;
    return new Promise(resolve => {
        setTimeout(() => {
            resolve();
        }, interval);
    });
};

这里设置了一个延时函数,是因为过渡是有一个动画效果 所以为了顺滑再最后一个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);
    });

这里主要是使用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();
            });
        });
    });

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

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值