亚洲乱色熟女一区二区三区丝袜,天堂√中文最新版在线,亚洲精品乱码久久久久久蜜桃图片,香蕉久久久久久av成人,欧美丰满熟妇bbb久久久

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

WEB前端開發(fā)中如何實現(xiàn)大文件上傳?

admin
2024年7月25日 12:51 本文熱度 2506



一、前言

大文件上傳是個非常普遍的場景,在面試中也會經(jīng)常被問到,大文件上傳的實現(xiàn)思路和流程。在日常開發(fā)中,無論是云存儲、視頻分享平臺還是企業(yè)級應(yīng)用,大文件上傳都是用戶與服務(wù)器之間交互的重要環(huán)節(jié)。隨著現(xiàn)代網(wǎng)絡(luò)應(yīng)用的日益復(fù)雜化,大文件上傳已經(jīng)成為前端開發(fā)中不可或缺的一部分。

然而,在實現(xiàn)大文件上傳時,我們通常會面臨以下幾個挑戰(zhàn):

  1. 上傳超時一般前端請求都會限制最大請求時長,比如axios設(shè)置timeout,或者是 nginx(或其它代理/網(wǎng)關(guān)) 限制了最大請求時長。

  2. 服務(wù)器壓力:大文件上傳會給服務(wù)器帶來較大的壓力,甚至可能導(dǎo)致服務(wù)器崩潰。

  3. 文件大小超限:一般后端都會對上傳文件的大小做限制,比如nginx和server都會限制。

  4. 用戶體驗:上傳過程中用戶需要等待較長時間,用戶體驗差。

  5. 網(wǎng)絡(luò)波動各種網(wǎng)絡(luò)原因?qū)е律蟼魇?,比?/span>網(wǎng)絡(luò)不穩(wěn)定可能導(dǎo)致上傳過程中斷,且失敗之后需要從頭開始。

對于前三點,雖說可以通過一定的配置來解決,但有時候也相當(dāng)麻煩,或者服務(wù)器就規(guī)定不允許上傳大型文件,需要兼顧實際場景。上傳慢的話倒是無傷大雅,忍一忍是可以接受的,只是體驗不好,但是失敗后在重頭開始上傳,在網(wǎng)絡(luò)環(huán)境差的時候簡直就是災(zāi)難。為了應(yīng)對以上挑戰(zhàn),我們就需要用到切片上傳、斷點續(xù)傳等技術(shù)手段。





二、實現(xiàn)思路分析

整體流程圖如下:

思路如下:

  1. 每個文件要有自己唯一的標識,因此在進行分片上傳前,需要對整個文件進行MD5加密,生成MD5碼,在后面上傳文件每次調(diào)用接口時以formData格式上傳給后端??梢允褂?span style="color: rgb(33, 37, 41); ">spark-md5 計算文件的內(nèi)容hash,以此來確定文件的唯一性將文件hash發(fā)送到服務(wù)端進行查詢。以此來確定該文件在服務(wù)端的存儲情況,這里可以分為三種:未上傳、已上傳、上傳部分。

  2. 根據(jù)服務(wù)端返回的狀態(tài)執(zhí)行不同的上傳策略。已上傳:執(zhí)行秒傳策略,即快速上傳,實際上沒有對該文件進行上傳,因為服務(wù)端已經(jīng)有這份文件了。未上傳、上傳部分:執(zhí)行計算待上傳分塊的策略并發(fā)上傳還未上傳的文件分塊。當(dāng)傳完最后一個文件分塊時,向服務(wù)端發(fā)送合并的指令,即完成整個大文件的分塊合并,實現(xiàn)在服務(wù)端的存儲。

上傳過程:

  1. 分割文件:將要上傳的文件切割成多個小文件片段。主要使用JavaScript的File API中的slice方法來實現(xiàn)。

  2. 上傳文件分片:使用XMLHttpRequest或者Fetch API將分片信息以formData格式,并攜帶相關(guān)信息,如文件名、文件ID、當(dāng)前片段序號等參數(shù)傳給分片接口。

  3. 后端接收并保存文件片段:后端接收到每個文件片段后,將其保存在臨時位置,并記錄文件片段的序號、文件ID和文件MD5 hash值等信息。

  4. 續(xù)傳處理:如果上傳過程中斷,下次繼續(xù)上傳時,通過查詢后端已保存的文件片段信息,得知需要上傳的文件片段,從斷點處繼續(xù)上傳剩余的文件片段。

  5. 合并文件:當(dāng)所有文件片段都上傳完成后,后端根據(jù)文件ID將所有片段合并成完整的文件。





三、切片上傳

切片上傳原理:通過使用JavaScript的File API中的slice方法將大文件分割成多個小片段(chunk),然后逐個上傳每個片段,在上傳完切片后,前端通知后臺再將文件片段拼接為一個完整的文件。

這樣做的優(yōu)點是可以并行多個請求一起上傳文件,提高上傳效率,并且在上傳過程中如果某個片段因為某些原因上傳失敗,也不會影響其它文件切片,只需要重新上傳該失敗片段即可,不必重新上傳整個文件。

實現(xiàn)思路:

在JavaScript中,文件File對象是Blob對象的子類,Blob對象包含了slice方法,通過這個方法,可以對二進制文件進行拆分。循環(huán)發(fā)送多個上傳請求,然后返回結(jié)果后計數(shù),當(dāng)計數(shù)達到file片段長度后終止上傳。

<input type="file" name="file" id="file" />

const eleFile = document.getElementById('file');

eleFile.addEventListener('change', (event) => {

    const file = event.target.files[0];

    // 上傳分塊大小,單位Mb

    const chunkSize = 1024 * 1024 * 1;

    // 當(dāng)前已執(zhí)行分片數(shù)位置

    let currentPosition = 0;

    //初始化分片方法,兼容問題

    let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;

    while(currentPosition < file.size) {

        const chunk = blobSlice.call(file, currentPosition, currentPosition + chunkSize);

        uploadChunk(chunk);

        currentPosition += chunkSize;

    }

})


function uploadChunk(chunk) {

    // 將分片信息以formData格式作為參數(shù)傳給分片接口

    let formData = new FormData();

    formData.append('fileChunk', chunk);

    // 根據(jù)項目實際情況

    axios.post(

        '/api/oss/upload/file', 

        formData, 

        {

            headers: { 'Content-Type': 'multipart/form-data' },

            timeout: 600000,

        }

    ).then(res => {

        // 上傳成功

        console.log('分片上傳成功', res)

    }).catch(error => {

        // 上傳失敗

        console.log('分片上傳失敗', error)

    })

}






四、并發(fā)上傳

并發(fā)上傳相對要優(yōu)雅一下,將文件分割成小片段后,使用Promise.all()把所有請求都放到一個Promise.all里,它會自動判斷所有請求都完成然后觸發(fā) resolve 方法。并發(fā)上傳可以同時上傳多個片段而不是依次上傳,進一步提高效率。

實現(xiàn)思路:

1、使用slice方法對二進制文件進行拆分,并把拆分的片段放到chunkList里面。

2、使用map將chunkList里面的每個chunk映射到一個Promise上傳方法。

3、把所有請求都放到一個Promise.all里,它會自動判斷所有請求都完成然后觸發(fā) resolve 方法,上傳成功后通知后端合并分片文件。

代碼實現(xiàn)如下:

const eleFile = document.getElementById('file');

eleFile.addEventListener('change', (event) => {

    const file = event.target.files[0];

    // 上傳分塊大小,單位Mb

    const chunkSize = 1024 * 1024 * 1;

    // 當(dāng)前已執(zhí)行分片數(shù)位置

    let currentPosition = 0;

    // 存儲文件的分片

    let chunkList = [];

    //初始化分片方法,兼容問題

    let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;

    while(currentPosition < file.size) {

        const chunk = blobSlice.call(file, currentPosition, currentPosition + chunkSize);

        chunkList.push(chunk);

        currentPosition += chunkSize;

    }

    uploadChunk(chunkList, file.name)

})


function uploadChunk(chunkList, fileName) {

    const uploadPromiseList = chunkList.map((chunk, index) => {

        // 將分片信息以formData格式作為參數(shù)傳給分片接口

        let formData = new FormData();

        formData.append('fileChunk', chunk);

        // 可以根據(jù)實際的需要添加其它參數(shù),比如切片的索引

        formData.append('index', index);

        // 根據(jù)項目實際情況

        return axios.post(

            '/api/oss/upload/file', 

            formData, 

            {

                headers: { 'Content-Type': 'multipart/form-data' },

                timeout: 600000,

            }

        )

    })


    Promise.all(uploadPromiseList).then(res => {

        // 上傳成功并通知后端合并分片文件

        axios.post(

            '/api/oss/file/merge', 

            {

                message: fileName

            },

            {

                headers: { 'Content-Type': 'application/json' },

                timeout: 600000,

            }

        ).then(data => {

            console.log('文件合并成功', data)

        })

    }).catch(error => {

        // 上傳錯誤

        console.log('上傳失敗', error)

    })

}





五、斷點續(xù)傳之1

斷點續(xù)傳允許在網(wǎng)絡(luò)中斷或其它原因?qū)е律蟼魇r,從上次上傳中斷的位置繼續(xù)上傳,而不是重新從頭上傳整個文件。

實現(xiàn)斷點續(xù)傳需要后端配合記錄上傳的進度,并且在前端重新上傳時,需要先查詢已上傳的進度,讓后從斷點處繼續(xù)上傳。

const eleFile = document.getElementById('file');

eleFile.addEventListener('change', (event) => {

    const file = event.target.files[0];

    // 上傳分塊大小,單位Mb

    const chunkSize = 1024 * 1024 * 1;

    // 當(dāng)前已執(zhí)行分片數(shù)位置

    let currentPosition = 0;

    // 存儲文件的分片

    let chunkList = [];

    //初始化分片方法,兼容問題

    let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;

    while(currentPosition < file.size) {

        const chunk = blobSlice.call(file, currentPosition, currentPosition + chunkSize);

        chunkList.push(chunk);

        currentPosition += chunkSize;

    }


    axios.post(

        '/api/upload/file/history',

        {

            fileName: file.name

        },

        {

            headers: { 'Content-Type': 'multipart/form-data' },

            timeout: 600000,

        }

    ).then(res => {

        const historyChunks = res.uploadedChunks;

        const remainChunks = chunkList.filter((item, index) => !historyChunks.includes(index));

        // 并發(fā)上傳剩余分片

        uploadChunk(remainChunks, file.name)

    })

})


function uploadChunk(chunkList, fileName) {

    const uploadPromiseList = chunkList.map((chunk, index) => {

        // 將分片信息以formData格式作為參數(shù)傳給分片接口

        let formData = new FormData();

        formData.append('fileChunk', chunk);

        // 可以根據(jù)實際的需要添加其它參數(shù),比如切片的索引

        formData.append('index', index);

        // 根據(jù)項目實際情況

        return axios.post(

            '/api/oss/upload/file', 

            formData, 

            {

                headers: { 'Content-Type': 'multipart/form-data' },

                timeout: 600000,

            }

        )

    })


    Promise.all(uploadPromiseList).then(res => {

        // 剩余分片上傳成功并通知后端合并分片文件

        axios.post(

            '/api/oss/file/merge', 

            {

                message: fileName

            },

            {

                headers: { 'Content-Type': 'application/json' },

                timeout: 600000,

            }

        ).then(data => {

            console.log('文件合并成功', data)

        })

    }).catch(error => {

        // 上傳錯誤

        console.log('上傳失敗', error)

    })

}

以上是一個簡易版的斷點續(xù)傳實現(xiàn)流程代碼,但在實際場景應(yīng)用中我們還需要更嚴謹?shù)奶幚韥韺崿F(xiàn)斷點續(xù)傳功能。不如,上傳文件前通常需要生成文件的唯一標識,比如文件名與文件大小的組合、文件的hash值或者文件hash值與文件大小的組合來支持斷點續(xù)傳的邏輯。請繼續(xù)看下面的代碼實現(xiàn)?。。?/p>





六、斷點續(xù)傳之2

已上傳的執(zhí)行秒傳策略,即快速上傳,實際上沒有對該文件進行上傳,因為服務(wù)端已經(jīng)有這份文件了。

秒傳的關(guān)鍵在于計算文件的唯一性標識。文件的不同不是命名的差異,而是內(nèi)容的差異,所以我們將整個文件的二進制碼作為入?yún)?,計?Hash 值,將其作為文件的唯一性標識。一般而言,這樣做就夠了,但是摘要算法是存在碰撞概率的,我們?nèi)绻胍賴乐旤c的話,可以將文件大小也作為衡量指標,只有文件摘要和文件大小同時相等,才認為是相同的文件。

<input type="file" name="file" id="file" @change="changeFile" />

計算文件hash值可以使用spark-md5。

import SparkMD5 from 'spark-md5'

通過input的change事件獲取要上傳的文件。

function changeFile(event) {

  const file = event.target.files[0];

  handleUploadFile(file, 1)

}

接下來對文件進行分片和hash計算:

/**

 * @param {File} file 目標上傳文件

 * @param {number} size 上傳分塊大小,單位Mb

 * @returns {filelist:ArrayBuffer,fileHash:string}

 */

async function handleSliceFile(file, size = 1) {

    return new Promise((resolve, reject) => {

        // 上傳分塊大小,單位Mb

        const chunkSize = 1024 * 1024 * size;

        // 分片數(shù)

        const totalChunkCount = file && Math.ceil(file.size / chunkSize);

        // 當(dāng)前已執(zhí)行分片數(shù)位置

        let currentChunkCount = 0;

        // 存儲文件的分片

        let fileList = [];

        //初始化分片方法,兼容問題

        let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;

        // 文件讀取對象

        const fileReader = new FileReader();

        // spark-md5 計算文件hash值SparkMD5對象

        const spark = new SparkMD5.ArrayBuffer();

        // 存儲計算后的文件hash值

        let fileHash = "";


        // 錯誤

        fileReader.onerror = function () {

            reject('Error reading file');

        };


        fileReader.onload = (e) => {

            //當(dāng)前讀取的分塊結(jié)果 ArrayBuffer

            const curChunk = e.target.result;

            //將當(dāng)前分塊追加到spark對象中

            spark.append(curChunk);

            currentChunkCount++;

            fileList.push(curChunk);

            //判斷分塊是否完成

            if (currentChunkCount >= totalChunkCount) {

                // 全部讀取,獲取文件hash

                fileHash = spark.end();

                resolve({ fileList, fileHash });

              } else {

                readNext();

              }

          };

          //讀取下一個分塊

          const readNext = () => {

              //計算分片的起始位置和終止位置

              const start = chunkSize * currentChunkCount;

              let end = start + chunkSize;

              if (end > file.size) {

                end = file.size

              }

              //讀取文件,觸發(fā)onLoad

              fileReader.readAsArrayBuffer(blobSlice.call(file, start, end))

            }

            readNext()

      })

}

文件上傳,首選調(diào)用接口獲取需要上傳的文件index,返回的集合length等于0執(zhí)行秒傳,如果返回的集合length不等于0執(zhí)行需要過濾得到需要上傳的remainingChunks,使用map將remainingChunks里面的每個chunk映射為一個Promise上傳方法,把所有請求都放到一個Promise.all里,上傳成功后通知后端合并分片文件。

sync function handleUploadFile(file, chunkSize) {

      const { fileList, fileHash } = await handleSliceFile(file, chunkSize);

      // 存放切片

      let chunkList = fileList;

      // 顯示上傳的進度條

      let process = 0;

      // 獲取文件上傳狀態(tài)

      const { data } = await axios.post('/api/upload/file/history', {

          fileHash,

          totalCount: chunkList.length,

          extname: file.name,

      })

      // 返回已經(jīng)上傳的

      const { needUploadChunks } = data;

      // 已上傳,無待上傳文件,秒傳

      if (!needUploadChunks.length) {

          process = 100;

          return;

      } 

      // 此處包含了未上傳和上傳部分的情況

      // 過濾剩余需要上傳的分片序列

      const remainingChunks = chunkList.filter((item, index) => needUploadChunks.includes(index + 1));

      // 同步上傳進度,斷點續(xù)傳情況下

      progress = ((chunkList.length - needUploadChunks.length) / chunkList.length) * 100;

      // 上傳

      if (remainingChunks.length) {

          const uploadPromiseList = remainingChunks.map(async (chunk, index) => {

              const response = await uploadChunk(chunk, index + 1, fileHash);

              //更新進度

              progress += Math.ceil(100 / allChunkList.length);

              if (progress >= 100) progress = 100;

              return response;

          });

          Promise.all(uploadPromiseList).then(() => {

              // 清空已上傳的切片

              chunkList = [];

              //發(fā)送請求,通知后端進行合并

              axios.post(

                  '/api/file/merge', 

                  {

                      fileHash,

                      extname: 'fileName.mp4'

                  }, 

                  {

                      headers: { 'Content-Type': 'multipart/form-data' },

                      timeout: 600000,

                  }

              ).then(res => {

                  console.log('合并完成', res)

              }).catch(error => {

                  // 合并錯誤

                  console.log('合并錯誤', error)

              })

          }).catch(error => {

              // 上傳錯誤

              console.log('上傳錯誤', error)

          })

      }

}

上傳函數(shù)返回一個promise,參數(shù)為formData。

function uploadChunk(chunk, index, fileHash) {

      // 將分片信息以formData格式作為參數(shù)傳給分片接口

      let formData = new FormData();

      formData.append('fileChunk', new Blob([chunk]));

      // 可以根據(jù)實際的需要添加其它參數(shù),比如切片的索引

      formData.append('index', index);

      // 文件的標識hash值

      formData.append('fileHash', fileHash);

      // 根據(jù)項目實際情況

      return axios.post(

          '/api/upload/file', 

          formData, 

          {

              headers: { 'Content-Type': 'multipart/form-data' },

              timeout: 600000,

          }

      )

}

我們在 fileReader 里面使用了 readAsArrayBuffer 方法做轉(zhuǎn)換并分割,因此傳入的chunk的類型是ArrayBuffer,而formData中文件的類型應(yīng)該是Blob,所以需要時用new Blob() 將每一個chunk轉(zhuǎn)為Blob類型。





七、總結(jié)

斷點續(xù)傳的重點是文件的切片與合并,整個上傳流程需要前后端配合好,細節(jié)較多。

注意事項:

  1. 計算整個文件的 MD5 值,當(dāng)大文件比較大時會比較慢,耗時,更好地做法是將這部分任務(wù)放在 Web Worker 中執(zhí)行。Web Worker 是 HTML5 標準的一部分,它允許一段 JavaScript 程序運行在主線程之外的另外一個線程中。這樣計算任務(wù)就不會影響到當(dāng)前線程的渲染任務(wù)??梢院彤?dāng)前線程間使用 postMessage 的方式進行通訊。

  2. 可以根據(jù)文件切片的狀態(tài),發(fā)送上傳請求,由于存在并發(fā)限制,需要限制 request 創(chuàng)建個數(shù),避免頁面卡死。

  3. 在上傳大文件時,應(yīng)提供適當(dāng)?shù)倪M度反饋和錯誤處理以確保良好的用戶體驗。

  4. 對于文件切片、并發(fā)上傳和斷點續(xù)傳,后端需要能夠接受文件片段,并能夠處理并發(fā)請求和斷點數(shù)據(jù),因此需要合后端人員密切配合。


該文章在 2024/7/25 15:15:29 編輯過
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點晴ERP是一款針對中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國內(nèi)大量中小企業(yè)的青睞。
點晴PMS碼頭管理系統(tǒng)主要針對港口碼頭集裝箱與散貨日常運作、調(diào)度、堆場、車隊、財務(wù)費用、相關(guān)報表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點,圍繞調(diào)度、堆場作業(yè)而開發(fā)的。集技術(shù)的先進性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點晴WMS倉儲管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質(zhì)期管理,貨位管理,庫位管理,生產(chǎn)管理,WMS管理系統(tǒng),標簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務(wù)都免費,不限功能、不限時間、不限用戶的免費OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved