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

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

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

概要

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

整體架構(gòu)流程

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

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

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

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

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

具體實現(xiàn)

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

前言:

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

在分片下載過程中,每個下載的文件塊(chunk)都需要在客戶端進行緩存或存儲,方便實現(xiàn)斷點續(xù)傳功能,同時也方便后續(xù)將這些文件塊合并成完整的文件。這些文件塊可以暫時保存在內(nèi)存中或者存儲在客戶端的本地存儲(如 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–是一個高效而強大的離線存儲方案。
 * 它封裝了IndexedDB, WebSQL, or localStorage,并且提供了一個簡化的類似localStorage的API。
 * 在默認情況下會優(yōu)先采用使用IndexDB、WebSQL、localStorage進行后臺存儲,
 * 即瀏覽器不支持IndexDB時嘗試采用WebSQL,既不支持IndexDB又不支持WebSQL時采用
 * localStorage來進行存儲。
 * 
 */
/**
 * @description 創(chuàng)建數(shù)據(jù)庫實例,創(chuàng)建并返回一個 localForage 的新實例。每個實例對象都有獨立的數(shù)據(jù)庫,而不會影響到其他實例
 * @param {Object} dataBase 數(shù)據(jù)庫名
 * @return {Object} storeName 實例(倉庫實例)
 */
function createInstance(dataBase){
  return localForage.createInstance({
    name: dataBase
  });
}

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

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

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

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

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



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

/**
 * @description 獲取下載列表
 * @param {String} page 頁面
 * @param {String} user 用戶
 */
export async function getDownloadList(page, user, callback){
  // const key = user + "_" + page + "_";
  // const key = user + "_";
  const key = "_"; // 因為用戶會過期 所以不保存用戶名  直接以頁面為鍵即可
  // 基礎(chǔ)數(shù)據(jù)庫
  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] // 文件對應(yīng)數(shù)據(jù)庫實例名
              }
            );
          })
        }
      }
      // 獲取進度
      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)出錯時,此處代碼運行
      callback(err);
  });
}

三,操作JS

1,下載進度監(jiān)聽

/**
 * 文件下載進度監(jiān)聽
 */
const onDownloadProgres = (progress) =>{
  // progress對象中的loaded表示已經(jīng)下載的數(shù)量,total表示總數(shù)量,這里計算出百分比
  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ù)庫進度數(shù)據(jù)主鍵
const progressKey = "progress";
/**
 * @description 狀態(tài)控制
 * @param {String} fileName 文件名
 * @param {String} type 下載方式 continue:續(xù)傳  again:重新下載  cancel:取消
 * @param {String} dataBaseName 數(shù)據(jù)庫名 每個文件唯一
 * 
 */
async function controlFile(fileName, type, dataBaseName){
  if(type == 'continue'){
    
  } else if(type == 'again'){
    // 刪除文件數(shù)據(jù)
    await dropDataBase(dataBaseName);
    // 基礎(chǔ)數(shù)據(jù)庫
    const baseDataBase = createInstance(baseDataBaseName);
    // 基礎(chǔ)數(shù)據(jù)庫刪除數(shù)據(jù)庫實例
    removeData(dataBaseName, baseDataBase);
  } else if(type == 'cancel'){
    // 刪除文件數(shù)據(jù)
    await dropDataBase(dataBaseName);
    // 基礎(chǔ)數(shù)據(jù)庫
    const baseDataBase = createInstance(baseDataBaseName);
    // 基礎(chǔ)數(shù)據(jù)庫刪除數(shù)據(jù)庫實例
    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ù)庫名 每個文件唯一
 * @param {Object} progress 下載進度 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);
  }
  
  // 判斷是否超過最大下載數(shù)量
  let downloadNum = store.state.progressList;
  if(downloadNum){
    if(!progress.type && downloadNum.length == downloadMaxNum){
      ElementUI.Message.error("已超過最大下載量,請等待其他文件下載完再嘗試!");
      return;
    }
  }
  
  // 基礎(chǔ)數(shù)據(jù)庫
  const baseDataBase = createInstance(baseDataBaseName);
  // 判斷數(shù)據(jù)庫中是否已存在該文件的下載任務(wù)
  let isError = false;
  await getData(dataBaseName, baseDataBase, async (res) =>{
    if(res && !progress.type){
      ElementUI.Message.error("該文件已在下載任務(wù)列表,無需重新下載!");
      isError = true;
    }
  });
  if(isError){
    return;
  }
  
  // 儲存數(shù)據(jù)的數(shù)據(jù)庫
  const dataBase = createInstance(dataBaseName);
  
  // 獲取文件大小
  const response = await axios.head(url);
  // 文件大小
  const fileSize = +response.headers['content-length'];
  // 分片大小
  const chunkSize = +response.headers['buffer-size'];
  // 開始節(jié)點
  let start = 0;
  // 結(jié)束節(jié)點
  let end = chunkSize - 1;
  if (fileSize < chunkSize) {
    end = fileSize - 1;
  }
  // 所有分片
  let ranges = [];
  // 計算需要分多少次下載
  const numChunks = Math.ceil(fileSize / chunkSize);
  // 回寫文件大小
  progress.fileSize = fileSize;
  progress.url = url;
  
  // 保存數(shù)據(jù)庫實例
  await saveData(dataBaseName, fileName, baseDataBase);
  if(!progress.progress){
    // 保存進度數(shù)據(jù)
    await saveData(progressKey, progress, dataBase);
  }
  // 保存下載狀態(tài)至localstorage  如果頁面刷新 可重新讀取狀態(tài) 判斷是否需要繼續(xù)下載
  sessionStorage.setItem(dataBaseName,"downloading");
  
  // 組轉(zhuǎn)參數(shù)
  for (let i = 0; i < numChunks; i++) {
    // 創(chuà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);
        // 重置開始節(jié)點
        start = end + 1;
        // 重置結(jié)束節(jié)點
        end = Math.min(end + chunkSize, fileSize - 1);
        continue;
      }
    }

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

  console.log("開始合入");
  // 流操作
  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ù)庫獲取分段數(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;
          // 寫入流
          writer.write(value)
        }
        // 結(jié)束寫入
        if(i==ranges.length-1){
          writer.close();
          // 清空數(shù)據(jù)庫
          dropDataBase(dataBaseName);
          // 基礎(chǔ)數(shù)據(jù)庫刪除數(shù)據(jù)庫實例
          removeData(dataBaseName, baseDataBase);
          // 清除store
          store.commit('delProgress', {
            dataInstance: dataBaseName
          })
        } 
      }
    }); 
  }
}

四,VUE頁面

<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 開始下載
     * @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() {
      // 通過路由信息判斷當(dāng)前處于哪一個頁面
      const page = this.$route.name;
      this.downloadDialog = true;
    },



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

小結(jié)

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

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

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

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

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

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

總結(jié)

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

相關(guān)文章

  • JavaScript判斷數(shù)據(jù)類型的四種方式總結(jié)

    JavaScript判斷數(shù)據(jù)類型的四種方式總結(jié)

    JavaScript 作為一門動態(tài)語言,其靈活性是把雙刃劍,一方面帶來了開發(fā)的便利性,另一方面也給我們在類型判斷時帶來了挑戰(zhàn),特別是在處理類型轉(zhuǎn)換和隱式轉(zhuǎn)換的時候,所以本篇文章我們將探討 JavaScript 中的數(shù)據(jù)類型判斷方式及在實際項目中的應(yīng)用,需要的朋友可以參考下
    2025-04-04
  • JavaScript獲取table中某一列的值的方法

    JavaScript獲取table中某一列的值的方法

    這篇文章主要介紹了JavaScript獲取table中某一列的值的方法,需要的朋友可以參考下
    2014-05-05
  • JavaScript實現(xiàn)點擊切換驗證碼及校驗

    JavaScript實現(xiàn)點擊切換驗證碼及校驗

    這篇文章主要為大家詳細介紹了JavaScript實現(xiàn)點擊切換驗證碼及校驗,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-01-01
  • JS優(yōu)雅的使用function實現(xiàn)一個class

    JS優(yōu)雅的使用function實現(xiàn)一個class

    這篇文章主要為大家介紹了JS優(yōu)雅的使用function實現(xiàn)一個class示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-12-12
  • JavaScript處理數(shù)組數(shù)據(jù)的示例詳解

    JavaScript處理數(shù)組數(shù)據(jù)的示例詳解

    這篇文章主要為大家詳細介紹了JavaScript如何處理數(shù)組數(shù)據(jù),包括數(shù)據(jù)匹配和剔除,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起了解一下
    2023-10-10
  • 微信小程序在Tab的icon上顯示消息數(shù)量的方法

    微信小程序在Tab的icon上顯示消息數(shù)量的方法

    這篇文章主要介紹了微信小程序在Tab的icon上顯示消息數(shù)量的方法,首先,在app.json文件中,找到對應(yīng)的tabBar配置,本文通過示例代碼給大家介紹的非常詳細,需要的朋友可以參考下
    2024-08-08
  • 很棒的Bootstrap選項卡切換效果

    很棒的Bootstrap選項卡切換效果

    這篇文章主要為大家分享了一款很棒的Bootstrap選項卡切換效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-07-07
  • 最簡單的JavaScript驗證整數(shù)、小數(shù)、實數(shù)、有效位小數(shù)正則表達式

    最簡單的JavaScript驗證整數(shù)、小數(shù)、實數(shù)、有效位小數(shù)正則表達式

    這篇文章主要介紹了最簡單的JavaScript驗證整數(shù)、小數(shù)、實數(shù)、有效位小數(shù)正則表達式,其中包含保留1位小數(shù)、保留2位小數(shù)、保留3位小數(shù)等正則,需要的朋友可以參考下
    2015-04-04
  • JS中Map、WeakMap和Object的區(qū)別解析

    JS中Map、WeakMap和Object的區(qū)別解析

    Map、WeakMap和Object都是JavaScript中用于存儲鍵值對的數(shù)據(jù)結(jié)構(gòu),它們在鍵類型、垃圾回收、可枚舉性、方法和操作、以及繼承等方面存在一些區(qū)別,適用于不同的場景,本文給大家詳細講解js map、weakmap和object區(qū)別,需要的朋友可以參考下
    2023-04-04
  • 采用call方式實現(xiàn)js繼承

    采用call方式實現(xiàn)js繼承

    這篇文章主要介紹了如何采用call方式實現(xiàn)js繼承,需要的朋友可以參考下
    2014-05-05

最新評論