大文件切片上傳

大文件上傳一次性上傳對(duì)于服務(wù)器壓力比較大柠并,所以對(duì)于大文件多用切片上傳

<template>
  <el-upload
    action
    drag
    :auto-upload="false"
    :show-file-list="false"
    :on-change="handleChange"
    v-bind="$attrs"
    v-on="$listeners"
  >
    <i class="el-icon-upload"></i>
    <div class="el-upload__text">
      將文件拖到此處仍侥,或
      <em>點(diǎn)擊上傳</em>
    </div>
    <div class="el-upload__tip" slot="tip">
      支持?jǐn)U展名:pdf.doc.pptx.mp4.mp3,視頻大小5G以?xún)?nèi)
      <div class="file_info" v-if="percent">
        <div class="ellipsis-one">{{ filename }}</div>
        <el-progress :percentage="Math.ceil(percent)"></el-progress>
      </div>
    </div>
  </el-upload>
</template>

<script>
import axios from "axios";
import SparkMD5 from "spark-md5";

export default {
  inheritAttrs: false, // 不繼承父組件非 props 屬性,如 class style
  data() {
    return {
      action: `${process.env.VUE_APP_API_PATH}/api/services/app/FileUpLoadService/FileUploadSingle`,
      percent: 0, // 總進(jìn)度
      percentCount: 0, // 進(jìn)度值
      isSuspend: true, // 是否暫停
      filename: "", // 文件名
    };
  },
  methods: {
    // * 處理文件變化
    async handleChange(file) {
      if (!file) return;
      this.$message.info("正在分析文件中...");
      this.filename = file.name;
      this.percent = 0;
      this.percentCount = 0;
      // 獲取文件并轉(zhuǎn)成 ArrayBuffer 對(duì)象
      const fileObj = file.raw;
      let buffer;
      try {
        buffer = await this.fileToBuffer(fileObj);
      } catch (error) {
        console.log("?? ~ handleChange ~ error:", error);
      }
      // 將文件按固定大小(5M)進(jìn)行切片合愈,注意此處同時(shí)聲明了多個(gè)常量
      const chunkSize = 5 * 1024 * 1024,
        chunkList = [], // 保存所有切片的數(shù)組
        chunkListLength = Math.ceil(fileObj.size / chunkSize), // 計(jì)算總共多個(gè)切片
        suffix = /\.([0-9A-z]+)$/.exec(fileObj.name)[1]; // 文件后綴名

      // 根據(jù)文件內(nèi)容生成 hash 值
      const spark = new SparkMD5.ArrayBuffer();
      spark.append(buffer);
      const hash = spark.end();

      // 生成切片,這里后端要求傳遞的參數(shù)為字節(jié)數(shù)據(jù)塊(chunk)和每個(gè)數(shù)據(jù)塊的文件名(fileName)
      let startPos = 0; // 切片時(shí)的初始位置
      for (let i = 0; i < chunkListLength; i++) {
        const item = {
          chunk: fileObj.slice(startPos, startPos + chunkSize),
          fileName: `${hash}_${i}.${suffix}`, // 文件名規(guī)則按照 hash_1.jpg 命名
        };
        startPos += chunkSize;
        chunkList.push(item);
      }
      this.chunkList = chunkList; // sendRequest 要用到
      this.hash = hash; // sendRequest 要用到
      this.sendRequest();
    },
    // * 發(fā)送切片
    async sendChunks(formData, index) {
      await axios({
        url: "http://127.0.0.1:3000/upload/bigFiles",
        method: "post",
        headers: { "Content-Type": "multipart/form-data", token: this.headers.token },
        data: formData,
      });
      // 成功
      if (this.percentCount === 0) {
        // 避免上傳成功后會(huì)刪除切片改變 chunkList 的長(zhǎng)度影響到 percentCount 的值
        this.percentCount = 100 / this.chunkList.length;
      }
      this.percent += this.percentCount; // 改變進(jìn)度
      this.chunkList.splice(index, 1); // 一旦上傳成功就刪除這一個(gè) chunk,方便斷點(diǎn)續(xù)傳
    },
    // * 發(fā)送請(qǐng)求
    sendRequest() {
      const requestList = []; // 請(qǐng)求集合
      this.chunkList.forEach((item, index) => {
        const fn = () => {
          const formData = new FormData();
          formData.set("name", item.fileName);
          formData.append("chunks", item.chunk);
          return this.sendChunks(formData, index);
        };
        requestList.push(fn);
      });
      let i = 0; // 記錄發(fā)送的請(qǐng)求個(gè)數(shù)
      // 文件切片全部發(fā)送完畢后适贸,需要請(qǐng)求 '/merge' 接口,把文件的 hash 傳遞給服務(wù)器
      const complete = async () => {
        const result = await axios({
          url: "http://127.0.0.1:3000/upload/merge",
          method: "get",
          headers: { token: this.headers.token },
          params: { hash: this.hash },
        });
        this.$message.success("上傳成功");
        console.log("上傳成功--臨時(shí)訪問(wèn)路徑", result.data.data.url);
      };
      const send = async () => {
        if (!this.isSuspend) return;
        if (i >= requestList.length) {
          return complete(); // 發(fā)送完畢
        }
        await requestList[i]();
        i++;
        send();
      };
      send(); // 發(fā)送請(qǐng)求
    },
    // * 按下暫停按鈕
    handleClickBtn() {
      this.isSuspend = !this.isSuspend;
      // 如果不暫停則繼續(xù)上傳
      if (this.isSuspend) this.sendRequest();
    },
    // * 將 File 對(duì)象轉(zhuǎn)為 ArrayBuffer
    fileToBuffer(file) {
      return new Promise((resolve, reject) => {
        const fr = new FileReader();
        fr.onload = event => {
          resolve(event.target.result);
        };
        fr.readAsArrayBuffer(file);
        fr.onerror = event => {
          reject(new Error("轉(zhuǎn)換文件格式發(fā)生錯(cuò)誤-->" + event.target.error));
        };
      });
    },
  },
  computed: {
    headers() {
      return {
        Authorization: `Bearer ${this.$store.state.user.session.accessToken}`,
        // 自己的token 測(cè)試大文件上傳
        token:
          "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuaWNrbmFtZSI6InBhbmdodSIsInBhc3N3b3JkIjoiMTIzNDU2IiwiaWF0IjoxNzA1NzU3Njk3LCJleHAiOjE3MDU4MDA4OTd9.JdNbM7Hed31opf2Ktagudy1FkGRh7jTIcIYedXJ_43U",
      };
    },
  },
};
</script>

<style lang="scss" scoped>
@import "@/styles/index.scss";
.file_info {
  margin-top: 7px;
  max-width: 360px;
}
.ellipsis-one {
  @include ellipsis-one;
}
</style>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末涝桅,一起剝皮案震驚了整個(gè)濱河市拜姿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌冯遂,老刑警劉巖蕊肥,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異蛤肌,居然都是意外死亡壁却,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)裸准,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)展东,“玉大人,你說(shuō)我怎么就攤上這事炒俱⊙嗡啵” “怎么了爪膊?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)砸王。 經(jīng)常有香客問(wèn)我推盛,道長(zhǎng),這世上最難降的妖魔是什么处硬? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任小槐,我火速辦了婚禮,結(jié)果婚禮上荷辕,老公的妹妹穿的比我還像新娘凿跳。我一直安慰自己,他們只是感情好疮方,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布控嗜。 她就那樣靜靜地躺著,像睡著了一般骡显。 火紅的嫁衣襯著肌膚如雪疆栏。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,475評(píng)論 1 312
  • 那天惫谤,我揣著相機(jī)與錄音壁顶,去河邊找鬼。 笑死溜歪,一個(gè)胖子當(dāng)著我的面吹牛若专,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蝴猪,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼调衰,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了自阱?” 一聲冷哼從身側(cè)響起嚎莉,我...
    開(kāi)封第一講書(shū)人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎沛豌,沒(méi)想到半個(gè)月后趋箩,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡加派,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年阁簸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哼丈。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖筛严,靈堂內(nèi)的尸體忽然破棺而出醉旦,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布车胡,位于F島的核電站檬输,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏匈棘。R本人自食惡果不足惜丧慈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望主卫。 院中可真熱鬧逃默,春花似錦、人聲如沸簇搅。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)瘩将。三九已至吟税,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間姿现,已是汗流浹背肠仪。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留备典,地道東北人异旧。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像熊经,于是被迫代替她去往敵國(guó)和親泽艘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容