欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

前端大文件分片下載具體實(shí)現(xiàn)方法(看這一篇就夠了)

 更新時(shí)間:2024年10月18日 09:18:35   作者:littleMonster_LC  
本文介紹了在瀏覽器中下載大文件的技術(shù)方案,包括分片下載、斷點(diǎn)續(xù)傳、進(jìn)度條顯示、取消及暫停下載和文件合并等功能,分片下載可以降低網(wǎng)絡(luò)傳輸中斷的風(fēng)險(xiǎn),并減少內(nèi)存占用,需要的朋友可以參考下

概要

本文從前端方面出發(fā)實(shí)現(xiàn)瀏覽器下載大文件的功能。不考慮網(wǎng)絡(luò)異常、關(guān)閉網(wǎng)頁(yè)等原因造成傳輸中斷的情況。分片下載采用串行方式(并行下載需要對(duì)切片計(jì)算hash,比對(duì)hash,丟失重傳,合并chunks的時(shí)候需要按順序合并等,很麻煩。對(duì)傳輸速度有追求的,并且在帶寬允許的情況下可以做并行分片下載)

整體架構(gòu)流程

1, 使用分片下載: 將大文件分割成多個(gè)小塊進(jìn)行下載,可以降低內(nèi)存占用和網(wǎng)絡(luò)傳輸中斷的風(fēng)險(xiǎn)。這樣可以避免一次性下載整個(gè)大文件造成的性能問(wèn)題。

2, 斷點(diǎn)續(xù)傳: 實(shí)現(xiàn)斷點(diǎn)續(xù)傳功能,即在下載中途中斷后,可以從已下載的部分繼續(xù)下載,而不需要重新下載整個(gè)文件。

3, 進(jìn)度條顯示: 在頁(yè)面上展示下載進(jìn)度,讓用戶清晰地看到文件下載的進(jìn)度。如果一次全部下載可以從process中直接拿到參數(shù)計(jì)算得出(很精細(xì)),如果是分片下載,也是計(jì)算已下載的和總大小,只不過(guò)已下載的會(huì)成片成片的增加(不是很精細(xì))。

4, 取消下載和暫停下載功能: 提供取消下載和暫停下載的按鈕,讓用戶可以根據(jù)需要中止或暫停下載過(guò)程。

5, 合并文件: 下載完成后,將所有分片文件合并成一個(gè)完整的文件。

具體實(shí)現(xiàn)

一,分片下載之本地儲(chǔ)存(localForage)

前言:

瀏覽器的安全策略禁止網(wǎng)頁(yè)(JS)直接訪問(wèn)和操作用戶計(jì)算機(jī)上的文件系統(tǒng)。

在分片下載過(guò)程中,每個(gè)下載的文件塊(chunk)都需要在客戶端進(jìn)行緩存或存儲(chǔ),方便實(shí)現(xiàn)斷點(diǎn)續(xù)傳功能,同時(shí)也方便后續(xù)將這些文件塊合并成完整的文件。這些文件塊可以暫時(shí)保存在內(nèi)存中或者存儲(chǔ)在客戶端的本地存儲(chǔ)(如 IndexedDB、LocalStorage 等)中。

使用封裝,直接上代碼

import axios from 'axios';
import ElementUI from 'element-ui'
import Vue from 'vue'
import localForage from 'localforage'
import streamSaver from 'streamsaver';
import store from "@/store/index"
/**
 * localforage–是一個(gè)高效而強(qiáng)大的離線存儲(chǔ)方案。
 * 它封裝了IndexedDB, WebSQL, or localStorage,并且提供了一個(gè)簡(jiǎn)化的類似localStorage的API。
 * 在默認(rèn)情況下會(huì)優(yōu)先采用使用IndexDB、WebSQL、localStorage進(jìn)行后臺(tái)存儲(chǔ),
 * 即瀏覽器不支持IndexDB時(shí)嘗試采用WebSQL,既不支持IndexDB又不支持WebSQL時(shí)采用
 * localStorage來(lái)進(jìn)行存儲(chǔ)。
 * 
 */
/**
 * @description 創(chuàng)建數(shù)據(jù)庫(kù)實(shí)例,創(chuàng)建并返回一個(gè) localForage 的新實(shí)例。每個(gè)實(shí)例對(duì)象都有獨(dú)立的數(shù)據(jù)庫(kù),而不會(huì)影響到其他實(shí)例
 * @param {Object} dataBase 數(shù)據(jù)庫(kù)名
 * @return {Object} storeName 實(shí)例(倉(cāng)庫(kù)實(shí)例)
 */
function createInstance(dataBase){
  return localForage.createInstance({
    name: dataBase
  });
}

/**
 * @description 保存數(shù)據(jù)
 * @param {Object} name 鍵名
 * @param {Object} value 數(shù)據(jù)
 * @param {Object} storeName 倉(cāng)庫(kù)
 */
async function saveData(name, value, storeName){
  await storeName.setItem(name, value).then(() => {
    // 當(dāng)值被存儲(chǔ)后,可執(zhí)行其他操作
    console.log("save success");
  }).catch(err => {
    // 當(dāng)出錯(cuò)時(shí),此處代碼運(yùn)行
    console.log(err)
  })
}

/**
 * @description 獲取保存的數(shù)據(jù)
 * @param {Object} name 鍵名
 * @param {Object} storeName 倉(cāng)庫(kù)
 */
async function getData(name, storeName, callback){
  await storeName.getItem(name).then((val) => {
    // 獲取到值后,可執(zhí)行其他操作
    callback(val);
  }).catch(err => {
    // 當(dāng)出錯(cuò)時(shí),此處代碼運(yùn)行
    callback(false);
  })
}

/**
 * @description 移除保存的數(shù)據(jù)
 * @param {Object} name 鍵名
 * @param {Object} storeName 倉(cāng)庫(kù)
 */
async function removeData(name, storeName){
  await storeName.removeItem(name).then(() => {
    console.log('Key is cleared!');
  }).catch(err => {
    // 當(dāng)出錯(cuò)時(shí),此處代碼運(yùn)行
    console.log(err);
  })
}

/**
 * @description 刪除數(shù)據(jù)倉(cāng)庫(kù),將刪除指定的 “數(shù)據(jù)庫(kù)”(及其所有數(shù)據(jù)倉(cāng)庫(kù))。
 * @param {Object} dataBase 數(shù)據(jù)庫(kù)名
 */
async function dropDataBase(dataBase){
  await localForage.dropInstance({
    name: dataBase
  }).then(() => {
     console.log('Dropped ' + dataBase + ' database');
  }).catch(err => {
    // 當(dāng)出錯(cuò)時(shí),此處代碼運(yùn)行
    console.log(err);
  })
}

/**
 * @description 刪除數(shù)據(jù)倉(cāng)庫(kù)
 * @param {Object} dataBase 數(shù)據(jù)庫(kù)名
 * @param {Object} storeName 倉(cāng)庫(kù)名(實(shí)例)
 */
async function dropDataBaseNew(dataBase, storeName){
  await localForage.dropInstance({
    name: dataBase,
    storeName: storeName
  }).then(() => {
     console.log('Dropped',storeName)
  }).catch(err => {
    // 當(dāng)出錯(cuò)時(shí),此處代碼運(yùn)行
    console.log(err);
  })
}



二,本地?cái)?shù)據(jù)獲?。ǐ@取下載列表)

/**
 * @description 獲取下載列表
 * @param {String} page 頁(yè)面
 * @param {String} user 用戶
 */
export async function getDownloadList(page, user, callback){
  // const key = user + "_" + page + "_";
  // const key = user + "_";
  const key = "_"; // 因?yàn)橛脩魰?huì)過(guò)期 所以不保存用戶名  直接以頁(yè)面為鍵即可
  // 基礎(chǔ)數(shù)據(jù)庫(kù)
  const baseDataBase = createInstance(baseDataBaseName);
  await baseDataBase.keys().then(async function(keys) {
      // 包含所有 key 名的數(shù)組
      let fileList = [];
      for(let i = 0; i < keys.length; i++){
        if(keys[i].indexOf(key)>-1){
          // 獲取數(shù)據(jù)
          await getData(keys[i], baseDataBase, async (res) =>{
            fileList.push(
              {
                'fileName': res, // 文件名
                'dataInstance': keys[i] // 文件對(duì)應(yīng)數(shù)據(jù)庫(kù)實(shí)例名
              }
            );
          })
        }
      }
      // 獲取進(jìn)度
      for(let i = 0; i < fileList.length; i++){
        const dataBase = createInstance(fileList[i].dataInstance);
        await getData(progressKey, dataBase,  async (progress) => {
          if(progress){
            fileList[i].fileSize = progress.fileSize;
            fileList[i].progress = progress.progress ? progress.progress : 0;
            fileList[i].status = progress.status ? progress.status : "stopped";
            fileList[i].url = progress.url;
          }
        });
      }
      callback(fileList);
  }).catch(function(err) {
      // 當(dāng)出錯(cuò)時(shí),此處代碼運(yùn)行
      callback(err);
  });
}

三,操作JS

1,下載進(jìn)度監(jiān)聽(tīng)

/**
 * 文件下載進(jìn)度監(jiān)聽(tīng)
 */
const onDownloadProgres = (progress) =>{
  // progress對(duì)象中的loaded表示已經(jīng)下載的數(shù)量,total表示總數(shù)量,這里計(jì)算出百分比
  let downProgress = Math.round(100 * progress.loaded / progress.total); 
  store.commit('setProgress', {
      dataInstance: uniSign,
      fileName: data.downLoad,
      progress: downProgress,
      status: downProgress == 100 ? 'success' : 'downloading',
      cancel: cancel
  })
}

2,文件下載狀態(tài)控制

// 數(shù)據(jù)庫(kù)進(jìn)度數(shù)據(jù)主鍵
const progressKey = "progress";
/**
 * @description 狀態(tài)控制
 * @param {String} fileName 文件名
 * @param {String} type 下載方式 continue:續(xù)傳  again:重新下載  cancel:取消
 * @param {String} dataBaseName 數(shù)據(jù)庫(kù)名 每個(gè)文件唯一
 * 
 */
async function controlFile(fileName, type, dataBaseName){
  if(type == 'continue'){
    
  } else if(type == 'again'){
    // 刪除文件數(shù)據(jù)
    await dropDataBase(dataBaseName);
    // 基礎(chǔ)數(shù)據(jù)庫(kù)
    const baseDataBase = createInstance(baseDataBaseName);
    // 基礎(chǔ)數(shù)據(jù)庫(kù)刪除數(shù)據(jù)庫(kù)實(shí)例
    removeData(dataBaseName, baseDataBase);
  } else if(type == 'cancel'){
    // 刪除文件數(shù)據(jù)
    await dropDataBase(dataBaseName);
    // 基礎(chǔ)數(shù)據(jù)庫(kù)
    const baseDataBase = createInstance(baseDataBaseName);
    // 基礎(chǔ)數(shù)據(jù)庫(kù)刪除數(shù)據(jù)庫(kù)實(shí)例
    await removeData(dataBaseName, baseDataBase);
    store.commit('delProgress', {
        dataInstance: dataBaseName
    })
  } else if(type == 'stop'){
    store.commit('setProgress', {
        dataInstance: dataBaseName,
        status: 'stopped',
    })
  }
}

3,分片下載

/**
 * @description 分片下載
 * @param {String} fileName 文件名
 * @param {String} url 下載地址
 * @param {String} dataBaseName 數(shù)據(jù)庫(kù)名 每個(gè)文件唯一
 * @param {Object} progress 下載進(jìn)度 type: continue:續(xù)傳  again:重新下載  cancel:取消
 * 
 */
export async function downloadByBlock(fileName, url, dataBaseName, progress) {
  //調(diào)整下載狀態(tài)
  if(progress.type){
    await controlFile(fileName, progress.type, dataBaseName);
  }
  
  // 判斷是否超過(guò)最大下載數(shù)量
  let downloadNum = store.state.progressList;
  if(downloadNum){
    if(!progress.type && downloadNum.length == downloadMaxNum){
      ElementUI.Message.error("已超過(guò)最大下載量,請(qǐng)等待其他文件下載完再嘗試!");
      return;
    }
  }
  
  // 基礎(chǔ)數(shù)據(jù)庫(kù)
  const baseDataBase = createInstance(baseDataBaseName);
  // 判斷數(shù)據(jù)庫(kù)中是否已存在該文件的下載任務(wù)
  let isError = false;
  await getData(dataBaseName, baseDataBase, async (res) =>{
    if(res && !progress.type){
      ElementUI.Message.error("該文件已在下載任務(wù)列表,無(wú)需重新下載!");
      isError = true;
    }
  });
  if(isError){
    return;
  }
  
  // 儲(chǔ)存數(shù)據(jù)的數(shù)據(jù)庫(kù)
  const dataBase = createInstance(dataBaseName);
  
  // 獲取文件大小
  const response = await axios.head(url);
  // 文件大小
  const fileSize = +response.headers['content-length'];
  // 分片大小
  const chunkSize = +response.headers['buffer-size'];
  // 開(kāi)始節(jié)點(diǎn)
  let start = 0;
  // 結(jié)束節(jié)點(diǎn)
  let end = chunkSize - 1;
  if (fileSize < chunkSize) {
    end = fileSize - 1;
  }
  // 所有分片
  let ranges = [];
  // 計(jì)算需要分多少次下載
  const numChunks = Math.ceil(fileSize / chunkSize);
  // 回寫(xiě)文件大小
  progress.fileSize = fileSize;
  progress.url = url;
  
  // 保存數(shù)據(jù)庫(kù)實(shí)例
  await saveData(dataBaseName, fileName, baseDataBase);
  if(!progress.progress){
    // 保存進(jìn)度數(shù)據(jù)
    await saveData(progressKey, progress, dataBase);
  }
  // 保存下載狀態(tài)至localstorage  如果頁(yè)面刷新 可重新讀取狀態(tài) 判斷是否需要繼續(xù)下載
  sessionStorage.setItem(dataBaseName,"downloading");
  
  // 組轉(zhuǎn)參數(shù)
  for (let i = 0; i < numChunks; i++) {
    // 創(chuàng)建請(qǐng)求控制器 
    const controller = new AbortController();
    
    const range = `bytes=${start}-${end}`;
    // 如果是續(xù)傳 先判斷是否已下載
    if(progress.type == 'continue'){
      // 先修改狀態(tài)
      store.commit('setProgress', {
          dataInstance: dataBaseName,
          status: 'downloading'
      })
      let isContinue = false;
      await getData(range, dataBase, async function(res){ if(res) isContinue = true});
      if(isContinue){
        ranges.push(range);
        // 重置開(kāi)始節(jié)點(diǎn)
        start = end + 1;
        // 重置結(jié)束節(jié)點(diǎn)
        end = Math.min(end + chunkSize, fileSize - 1);
        continue;
      }
    }

    let cancel;
    const config = {
      headers: {
        Range: range
      },
      responseType: 'arraybuffer',
      // 綁定取消請(qǐng)求的信號(hào)量
      signal: controller.signal, 
      // 文件下載進(jìn)度監(jiān)聽(tīng)
      onDownloadProgress: function (pro){
        if(progress.type == 'stop' || progress.type == 'cancel'){
           // 終止請(qǐng)求
          controller.abort();
        }
        
        // 已加載大小
        // progress對(duì)象中的loaded表示已經(jīng)下載的數(shù)量,total表示總數(shù)量,這里計(jì)算出百分比
        let downProgress = (pro.loaded / pro.total); 
        downProgress = Math.round( (i / numChunks) * 100 + downProgress  / numChunks * 100);
        progress.progress = downProgress;
        // 設(shè)置為異常  如果是正常下載完 這個(gè)記錄會(huì)被刪除 如果合并失敗  則會(huì)顯示異常
        progress.status = downProgress == 100 ? 'error' : 'stopped';
        store.commit('setProgress', {
            dataInstance: dataBaseName,
            fileName: fileName,
            progress: downProgress,
            status: 'downloading',
            cancel: cancel,
            url: url,
            dataBaseName: dataBaseName
        })
      }
    };
    try {
      // 開(kāi)始分片下載
      const response = await axios.get(url, config);
      // 保存分片數(shù)據(jù)
      await saveData(range, response.data, dataBase);
        
      ranges.push(range);
      // 重置開(kāi)始節(jié)點(diǎn)
      start = end + 1;
      // 重置結(jié)束節(jié)點(diǎn)
      end = Math.min(end + chunkSize, fileSize - 1);
        
    } catch (error) {
      console.log("終止請(qǐng)求時(shí)catch的error---", error);
      // 判斷是否為取消上傳
      if (error.message == "canceled"){
          // 進(jìn)行后續(xù)處理
          if(progress.type == 'stop'){
            // 暫停
            await controlFile(fileName, progress.type, dataBaseName);
            sessionStorage.setItem(dataBaseName,"stop");
             // 終止請(qǐng)求
            console.log("stopped");
            return;
          } else if(progress.type == 'cancel'){
            // 取消
            await controlFile(fileName, progress.type, dataBaseName);
            sessionStorage.removeItem(dataBaseName);
            // 終止請(qǐng)求
            console.log("canceled");
            return;
          }
      };
    }
  }

  console.log("開(kāi)始合入");
  // 流操作
  const fileStream = streamSaver.createWriteStream(fileName, {size: fileSize});
  const writer = fileStream.getWriter();
  // 合并數(shù)據(jù)
  // 循環(huán)取出數(shù)據(jù)合并
  for (let i=0; i < ranges.length; i++) {
    var range = ranges[i];
    // 從數(shù)據(jù)庫(kù)獲取分段數(shù)據(jù)
    await getData(range, dataBase, async function(res){
      if(res){
        // 讀取流
        const reader = new Blob([res]).stream().getReader();
        while (true) {
          const { done, value } = await reader.read();
          if (done) break;
          // 寫(xiě)入流
          writer.write(value)
        }
        // 結(jié)束寫(xiě)入
        if(i==ranges.length-1){
          writer.close();
          // 清空數(shù)據(jù)庫(kù)
          dropDataBase(dataBaseName);
          // 基礎(chǔ)數(shù)據(jù)庫(kù)刪除數(shù)據(jù)庫(kù)實(shí)例
          removeData(dataBaseName, baseDataBase);
          // 清除store
          store.commit('delProgress', {
            dataInstance: dataBaseName
          })
        } 
      }
    }); 
  }
}

四,VUE頁(yè)面

<script>
import { downloadByBlock } from "上面的js";
export default {
  name: "suspension",
  data() {
    return {
      // 下載列表
      downloadDialog: false,
      fileStatus: {}
    };
  },
  watch: {
    "$store.state.progressList": function() {
      this.$nextTick(function() {
        let progressList = this.$store.state.progressList;
        progressList.forEach(item => {
          // 獲取之前下載狀態(tài) 還原操作
          const status = sessionStorage.getItem(item.dataInstance);
          if (status == "downloading" && item.status != status) {
            this.startDownload(item);
          }
        });
      });
    }
  },
  methods: {
    /**
     * @description 重試
     * @param {Object} row
     */
    retryDownload(row) {
      this.startDownload(row);
    },
    /**
     * @description 開(kāi)始下載
     * @param {Object} row
     */
    startDownload(row) {
      this.fileStatus[row.dataInstance] = {
        type: "continue",
        progress: row.progress
      };
      downloadByBlock(
        row.fileName,
        row.url,
        row.dataInstance,
        this.fileStatus[row.dataInstance]
      );
    },
    /**
     * @description 暫停下載
     * @param {Object} row
     */
    stopDownload(row) {
      this.$set(this.fileStatus[row.dataInstance], "type", "stop");
    },
    /**
     * @description 刪除下載
     * @param {Object} row
     */
    delDownload(row) {
      if (this.fileStatus[row.dataInstance] && row.status != "stopped") {
        this.$set(this.fileStatus[row.dataInstance], "type", "cancel");
      } else {
        this.fileStatus[row.dataInstance] = { type: "cancel" };
        downloadByBlock(
          row.fileName,
          row.url,
          row.dataInstance,
          this.fileStatus[row.dataInstance]
        );
      }
    },
    /**
     * @description 分片下載文件
     */
    downloadByBlock(fileName, url, dataBaseName, type) {
      this.fileStatus[dataBaseName] = { type: type };
      downloadByBlock(
        fileName,
        url,
        dataBaseName,
        this.fileStatus[dataBaseName]
      );
      this.btnDownload();
    },
    /**
     * @description 下載列表
     */
    btnDownload() {
      // 通過(guò)路由信息判斷當(dāng)前處于哪一個(gè)頁(yè)面
      const page = this.$route.name;
      this.downloadDialog = true;
    },



    /**
     * @description 取消彈窗
     */
    downloadBtnCancel() {
      this.downloadDialog = false;
    }
  }
};
</script>

小結(jié)

這只是一個(gè)基本示例。在實(shí)際應(yīng)用中,你可能需要考慮以下問(wèn)題:

并發(fā)下載: 如果多個(gè)用戶同時(shí)下載相同的大文件,可能會(huì)對(duì)服務(wù)器造成很大的壓力??梢允褂貌l(fā)下載來(lái)提高性能。

安全性: 確保只有授權(quán)用戶可以下載文件,并且不會(huì)泄漏敏感信息。

性能優(yōu)化: 使用緩存、壓縮等技術(shù)來(lái)提高下載速度和用戶體驗(yàn)。

服務(wù)器資源管理: 下載大文件可能會(huì)占用服務(wù)器的網(wǎng)絡(luò)帶寬和內(nèi)存資源,需要適當(dāng)?shù)墓芾怼?/p>

總之,大文件分片下載和合并是一個(gè)復(fù)雜的任務(wù),需要綜合考慮性能、安全性和用戶體驗(yàn)。在實(shí)際項(xiàng)目中,你可能會(huì)使用更高級(jí)的工具和技術(shù)來(lái)處理這些問(wèn)題。

總結(jié)

到此這篇關(guān)于前端大文件分片下載具體實(shí)現(xiàn)方法的文章就介紹到這了,更多相關(guān)前端大文件分片下載內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論