菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
0
0

前端如何分片上传大文件

原创
05/13 14:22
阅读数 304

课程推荐:学习猿地大量免费体验课等你来哦~点击进入

前端如何分片上传大文件
最近做了一个需求,让用户本地上传一个最大 300M 的视频文件,下面是前端部分的记录。

需求分析点
校验用户上传的文件类型,以及内存大小,还有视频的播放时长检查
视频过大需要分片上传,记录本次是第几片数据,重新上传则可以实现接着上次的片段继续上传而非重头上传
同个视频需要标记,不可重复上传,减小服务器的内存压力
交互样式
接下来首先如何让用户上传文件,input 标签中type=file可以让用户上传文件,此处我是在 Vue 项目里。


复制代码
此时已经可以让用户在本地上传文件了,但是这里有个问题,在该用户已经有上传过的视频文件时,如果再次上传需要首先展示提示信息,重复上传会覆盖原有视频。但是用户点了该 input 按钮,就已经触发选择文件的弹窗了,并不能拦截,加之原生的 input 样式巨丑还自带选择的文件名,这不是我想要的,跟我们的 ui 样式不符,所以我的想法是,隐藏这个 input, 取而代之的是自己重新写一个按钮,给这个按钮加上点击事件,在该点击事件里处理额外逻辑,然后用 js 代码主动触发该 input 的点击事件。

this.$messageBox.confirm("重新上传视频会替换原有视频,是否继续?").then(() => {
this.$refs.videoInput.click();
});
复制代码
标记视频
接下来就是如何标记视频,以免重复上传相同的视频。方案是获取视频文件的哈希 md5 值。因为每个文件的 md5 是唯一的,我们在做文件上传的时候,就只要在前端先获取要上传的文件 md5,并把文件 md5 传到服务器,对比之前文件的 md5,如果存在相同的 md5,我们只要把文件的名字传到服务器关联之前的文件即可,并不需要再次去上传相同的文件,再去耗费存储资源、上传的时间、网络带宽。

let file = this.$refs.videoInput.files[0];
console.log(file);
复制代码
注意这里拿到的file数据,下面会再提到它 此处可以拿到文件 file,它自身可以获取文件的类型type,以及文件的内存大小size,单位是字节。同时也有slice方法用于截取,分片获得子片段数据 file。 但是直接通过它去获取 md5 值发现跟后端(Java)获取不一致。接下来怎么办?

首先要说到的是spark-md5依赖包

SparkMD5 是 MD5 算法的快速实现。此脚本基于 JKM md5 库,这最适合浏览器使用。

它的原理:(化简后的核心代码)

import SparkMD5 from 'spark-md5';

const fileReader = new FileReader();
fileReader.onload = function (e) {
let spark = new SparkMD5.ArrayBuffer();
spark.append(e.target.result); // Append array buffer
console.info("computed hash", spark.end()); // Compute hash
};
fileReader.readAsArrayBuffer(file);
复制代码
关于FileReader请移步? FileReader

使用browser-md5-file获取文件 md5 值, 它是进一步的封装
import BMF from "browser-md5-file";
const bmf = new BMF();
bmf.md5(
file,
(err, md5) => {
if (err) {
console.log(err);
}
console.log(md5); // md5值
},
(process) => {
//计算进度
}
);
复制代码
此时对文件的md5值已经跟后端的保持一致了?!

获取视频的播放时长
这里有两种场景获取视频时长

通过File数据
通过播放地址
function getVideoDuration(file) {
return new Promise((resolve, reject) => {
var audioElement;
var url;
if (typeof file == 'string') {
audioElement = new Audio();
audioElement.src = file
} else {
url = window.URL.createObjectURL(file);
audioElement = new Audio(url);
}

audioElement.addEventListener("loadedmetadata", function() {
  var duration = audioElement.duration;
  // console.log("视频时长", duration);
  window.URL.revokeObjectURL(url);

  resolve(duration)
});

})
}
复制代码
到这里有必要说一说通过用户上传的文件直接获取到的数据File到底是什么了?

File在前端中用于文件操作的二进制对象类,它的父类是Blob。它们是针对文件的,或者可以说它就是一个文件对象。

关于Blob请移步? 你不知道的 Blob

上传文件
上传文件的数据格式{"content-type": "multipart/form-data"},html5 提供了 api

FormData 接口提供了一种表示表单数据的键值对 key/value 的构造方式,并且可以轻松的将数据通过 XMLHttpRequest.send() 方法发送出去,本接口和此方法都相当简单直接。如果送出时的编码类型被设为 "multipart/form-data",它会使用和表单一样的格式。

let formData = new FormData();
formData.append("file", item);
axios({
url: "xxx",
method: "post",
headers: { "content-type": "multipart/form-data" },
data: formData,
});
复制代码
下面是项目中关于上传的代码逻辑,给大家一个参考吧!

/**

  • id
  • fileList 文件分片后的列表
  • hashList 文件分片后对应的md5值列表
  • startIndex 在上一次上传的起点 继续上传 默认第一段
  • processFn 上传进度
    /
    function async startMediaPartUpload(id, fileList, hashList, startIndex = 1, processFn) {
    let point = Math.floor(((startIndex - 1) / fileList.length)
    100);
    processFn && processFn(point)
    // 上传
    for (let i = startIndex - 1; i < fileList.length; i++) {
    const item = fileList[i];
    let formData = new FormData();
    formData.append("id", id);
    formData.append("option", "slice");
    formData.append("hash", hashList[i]);
    formData.append("part", i + 1);
    formData.append("file", item);

    let { code, memo, content } = await mediaPartUpload(formData);
    let point = Math.floor(((i + 1) / fileList.length) * 100);
    processFn && processFn(point)

    if (code == "000000") {
    // this.$message.success(上传ok ${content.part});
    } else {
    this.$message.error(memo);

    return Promise.reject("上传失败了");
    }
    }
    }

文章来自:https://juejin.im/post/6889626317160906766

发表评论

0/200
0 点赞
0 评论
收藏
为你推荐 换一批