【文件上傳系列】No.2 秒傳(原生前端 + Node 后端)

上一篇文章

【文件上傳系列】No.1 大文件分片、進度圖展示(原生前端 + Node 后端 & Koa)


秒傳效果展示

請添加圖片描述


秒傳思路

整理的思路是:根據文件的二進制內容生成 Hash 值,然后去服務器里找,如果找到了,說明已經上傳過了,所以又叫做秒傳(笑)


整理文件夾、path.resolve() 介紹

接著上一章的內容,因為前端和后端的服務都寫在一起了,顯得有點凌亂,所以我打算分類一下

在這里插入圖片描述

改了文件路徑的話,那么各種引用也要修改,引用就很好改了,這里就不多說了

這里講一下 path 的修改,為了方便修改 path,引用了 path 依賴,使用 path.resolve() 方法就很舒服的修改路徑,常見的拼接方法如下圖測試:(如果不用這個包依賴的話,想一下如何返回上一個路徑呢?可能使用 split('/)[1] 類似這種方法吧。)

在這里插入圖片描述

會使用這個包依賴之后就可以修改服務里的代碼了:

在這里插入圖片描述

200 頁面正常!資源也都加載了!

在這里插入圖片描述

前端

思路

具體思路如下

  1. 計算文件整體 hash ,因為不同的文件,名字可能相同,不具有唯一性,所以根據文件內容計算出來的 hash 值比較靠譜,并且為下面秒傳做準備。
  2. 利用 web-worker 線程:因為如果是很大的文件,那么分塊的數量也會很多,讀取文件計算 hash 是非常耗時消耗性能的,這樣會使頁面阻塞卡頓,體驗不好,解決的一個方法是,我們開一個新線程來計算 hash

工作者線程簡介

《高級JavaScript程序設計》27 章簡介: JavaScript 環境實際上是運行在托管操作系統中的虛擬環境。在瀏覽器中每打開一個頁面,就會分配一個它自己的環境。這樣,每個頁面都有自己的內存、事件循環、DOM,等等。每個頁面就相當于一個沙盒,不會干擾其他頁面。
對于瀏覽器來說,同時管理多個環境是非常簡單的,因為所有這些環境都是并行執行的。

工作者線程的數據傳輸如下:

在這里插入圖片描述

注意在 worker 中引入的腳本也是個請求!

在這里插入圖片描述

// index.html
function handleCalculateHash(fileChunkList) {let worker = new Worker('./hash.js');worker.postMessage('你好 worker.js');worker.onmessage = function (e) {console.log('e:>>', e);};
}
handleCalculateHash();
// worker.js
self.onmessage = (work_e) => {console.log('work_e:>>', work_e);self.postMessage('你也好 index.html');
};

計算整體文件 Hash

前端拿到 Blob,然后通過 fileReader 轉化成 ArrayBuffer,然后用 append() 方法灌入 SparkMD5.ArrayBuffer() 實例中,最后 SparkMD5.ArrayBuffer().end() 拿到 hash 結果在這里插入圖片描述

在這里插入圖片描述

SparkMD5 計算 Hash 性能簡單測試

js-spark-md5 的 github 地址

配置 x99 2643v3 六核十二線程 基礎速度:3.4GHz,睿頻 3.6GHz只測試了一遍

請添加圖片描述

// 計算時間的代碼
self.onmessage = (e) => {const { data } = e;self.postMessage('你也好 index.html');const spark = new SparkMD5.ArrayBuffer();const fileReader = new FileReader();const blob = data[0].file;fileReader.readAsArrayBuffer(blob);fileReader.onload = (e) => {console.time('append');spark.append(e.target.result);console.timeEnd('append');spark.end();};
};

在這里插入圖片描述

工作者線程:計算 Hash

這里有個注意點,就是我們一定要等到 fileReader.onload 讀完一個 chunk 之后再去 append 下一個塊,一定要注意這個順序,我之前想當然寫了個如下的錯誤版本,就是因為回調函數 onload 還沒被調用(文件沒有讀完),我這里只是定義了回調函數要干什么,但沒有保證順序是一塊一塊讀的。

// 錯誤版本
const chunkLength = data.length;
let curr = 0;
while (curr < chunkLength) {const blob = data[curr].file;curr++;const fileReader = new FileReader();fileReader.readAsArrayBuffer(blob);fileReader.onload = (e) => {spark.append(e.target.result);};
}
const hash = spark.end();
console.log(hash);

如果想保證在回調函數內處理問題,我目前能想到的辦法:一種方法是遞歸,另一種方法是配合 await

這個是非遞歸版本的,比較好理解。

// 非遞歸版本
async function handleBlob2ArrayBuffer(blob) {return new Promise((resolve) => {const fileReader = new FileReader();fileReader.readAsArrayBuffer(blob);fileReader.onload = function (e) {resolve(e.target.result);};});
}
self.onmessage = async (e) => {const { data } = e;self.postMessage('你也好 index.html');const spark = new SparkMD5.ArrayBuffer();for (let i = 0, len = data.length; i < len; i++) {const eachArrayBuffer = await handleBlob2ArrayBuffer(data[i].file);spark.append(eachArrayBuffer);   // 這個是同步的,可以 debugger 打斷點試一試。}const hash = spark.end();
};

遞歸的版本代碼比較簡潔

// 遞歸版本
self.onmessage = (e) => {const { data } = e;console.log(data);self.postMessage('你也好 index.html');const spark = new SparkMD5.ArrayBuffer();function loadNext(curr) {const fileReader = new FileReader();fileReader.readAsArrayBuffer(data[curr].file);fileReader.onload = function (e) {const arrayBuffer = e.target.result;spark.append(arrayBuffer);curr++;if (curr < data.length) {loadNext(curr);} else {const hash = spark.end();console.log(hash);return hash;}};}loadNext(0);
};

我們在加上計算 hash 進度的變量 percentage就差不多啦

官方建議用小切塊計算體積較大的文件,點我跳轉官方包說明

在這里插入圖片描述

ok 這個工作者線程的整體代碼如下:

importScripts('./spark-md5.min.js');
/*** 功能:blob 轉換成 ArrayBuffer* @param {*} blob* @returns*/
async function handleBlob2ArrayBuffer(blob) {return new Promise((resolve) => {const fileReader = new FileReader();fileReader.readAsArrayBuffer(blob);fileReader.onload = function (e) {resolve(e.target.result);};});
}/*** 功能:求整個文件的 Hash* - self.SparkMD5 和 SparkMD5 都一樣* - 1. FileReader.onload	處理 load 事件。該事件在讀取操作完成時觸發。* - 流程圖展示* - 注意這里的 percentage += 100 / len; 的位置,要放到后面* - 因為如果是小文件的話,塊的個數可能是1,最后 100/1 就直接是 100 了* ┌────┐                                   ┌───────────┐                                     ┌────┐* │    │   Object      fileReader          │           │      new SparkMD5.ArrayBuffer()     │    │* │Blob│ ────────────────────────────────? │ArrayBuffer│ ───────────────┬──────────────────? │Hash│* │    │   Method   readAsArrayBuffer      │           │       append() └────?  end()        │    │* └────┘                                   └───────────┘                                     └────┘*/
self.onmessage = async (e) => {const { data } = e;const spark = new SparkMD5.ArrayBuffer();let percentage = 0;for (let i = 0, len = data.length; i < len; i++) {const eachArrayBuffer = await handleBlob2ArrayBuffer(data[i].file);percentage += 100 / len;self.postMessage({percentage,});spark.append(eachArrayBuffer);}const hash = spark.end();self.postMessage({percentage: 100,hash,});self.close();
};

主線程調用 Hash 工作者線程

把處理 hash 的函數包裹成 Promise,前端處理完 hash 之后傳遞給后端

把每個chunk 的包裹也精簡了一下,只傳遞 Blobindex

在這里插入圖片描述

再把后端的參數調整一下

在這里插入圖片描述

最后我的文件結構如下:

在這里插入圖片描述

添加 hash 進度

簡單寫一下頁面,效果如下:
請添加圖片描述

在這里插入圖片描述

后端

接口:判斷秒傳

寫一個接口判斷一下是否存在即可

/*** 功能:驗證服務器中是否存在文件* - 1. 主要是拼接的任務* - 2. ext 的值前面是有 . 的,注意一下。我之前合并好的文件 xxx..mkv 有兩個點...* - 導致 fse.existsSync 怎么都找不到,哭* @param {*} req* @param {*} res* @param {*} MERGE_DIR*/
async handleVerify(req, res, MERGE_DIR) {const postData = await handlePostData(req);const { fileHash, fileName } = postData;const ext = path.extname(fileName);const willCheckMergedName = `${fileHash}${ext}`;const willCheckPath = path.resolve(MERGE_DIR, willCheckMergedName);if (fse.existsSync(willCheckPath)) {res.end(JSON.stringify({code: 0,message: 'existed',}));} else {res.end(JSON.stringify({code: 1,message: 'no exist',}));}
}

前端這邊在 hash 計算后把結果傳給后端,讓后端去驗證

在這里插入圖片描述

秒傳就差不多啦!請添加圖片描述

參考文章

  1. path.resolve() 解析
  2. 字節跳動面試官:請你實現一個大文件上傳和斷點續傳
  3. 《高級JavaScript設計》第四版:第 27 章
  4. Spark-MD5
  5. 布隆過濾器

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/211040.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/211040.shtml
英文地址,請注明出處:http://en.pswp.cn/news/211040.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【智能家居】七、人臉識別 翔云平臺編程使用(編譯openSSL支持libcurl的https訪問、安裝SSL依賴庫openSSL)

一、翔云 人工智能開放平臺 API文檔開發示例下載 二、編譯openSSL支持libcurl的https訪問 安裝SSL依賴庫openSSL(使用工具wget)libcurl庫重新配置&#xff0c;編譯&#xff0c;安裝運行&#xff08;運行需添加動態庫為環境變量&#xff09; 三、編程實現人臉識別 四、Base6…

12.4每日一題(備戰藍橋杯順序結構程序設計)

12.4每日一題&#xff08;備戰藍橋杯順序結構程序設計&#xff09; 題目1000: 【入門】AB Problem題目描述輸入輸出樣例輸入樣例輸出來源/分類 題解 1000: 【入門】AB Problem題目 2124: 計算(ab)c的值題目描述輸入輸出樣例輸入樣例輸出來源/分類 題解 2124: 計算(ab)c的值題目…

UML案例分析

首先需要花大約20分鐘來思考解決這個問題&#xff0c;如果對問題不是很熟悉&#xff0c;也可以在完成題目之后&#xff0c;找相關的資料翻閱&#xff08;例如看UML類圖的基本情況&#xff0c;UML狀態圖的基本情況&#xff0c;然后結合這些信息 做一個自我評價&#xff0c;看這個…

matlab 最小二乘擬合空間直線(方法三)

目錄 一、算法原理1、算法過程2、參考文獻二、代碼實現三、結果展示四、相關鏈接博客長期更新,GPT與爬蟲自重,你也未必能爬到最新版本。 一、算法原理 1、算法過程 空間直線的點向式方程為:

poe與chatgpt那個功能更強大

在當前的人工智能領域&#xff0c;Poe Al Chat以其卓越的聊天能力和實用的功能&#xff0c;受到了大家的廣泛關注和喜愛。本文好為您個紹Poe Al Chat的功能&#xff0c;以及我們國內用戶如何進行充值訂閱。Poe Al Chat是一個基于OpenAl的GPT模型開發的人工智能聊天工具。它能夠…

基于Springboot的校園失物招領系統(有報告)。Javaee項目,springboot項目。

演示視頻&#xff1a; 基于Springboot的校園失物招領系統&#xff08;有報告&#xff09;。Javaee項目&#xff0c;springboot項目。 項目介紹&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三層體系結構…

Docker鏡像和容器的簡單操作

1.鏡像管理 搜索鏡像&#xff1a; 這種方法只能用于官方鏡像庫 搜索基于 centos 操作系統的鏡像 # docker search centos 按星級搜索鏡像&#xff1a; 查找 star 數至少為 100 的鏡像&#xff0c;默認不加 s 選項找出所有相關 ubuntu 鏡像&#xf…

為 setTimeout 或 setInterval 提供一個字符串作為第一個參數(js的問題)

首先&#xff0c;需要知道的是為 setTimeout 或 setInterval 提供一個字符串作為第一個參數&#xff0c;這本身并不是一個錯誤。它是完全合法的JavaScript代碼。這里的問題更多的是性能和效率的問題。很少有人解釋的是&#xff0c;如果你把字符串作為setTimeout或setInterval的…

46.0/基本的 HTML 標簽(詳細版)

目錄 46.1 標題 46.2 段落 Paragraph, 46.3 換行 46.4 HTML 注釋 46.5 空格 46.6 水平線 46.7 控制網頁中文字的標記 46.8 居中標記 46.9 預格式化文本 46.10 網頁背景 46.1 標題 標記解釋 標題使用 <h1> 至 <h6> 標簽進行定義。 <h1> 定…

uniapp如何制作一個收縮通訊錄(布局篇)

html&#xff1a; <view class"search"><view class"search_padding"><u-search change"search" placeholder"請輸入成員名稱" v-model"keyword"></u-search></view></view> <view…

C語言有哪些預處理操作?

C語言的預處理是在編譯之前對源代碼進行處理的階段&#xff0c;它主要由預處理器完成。預處理器是一個獨立的程序&#xff0c;它負責對源代碼進行一些文本替換和處理&#xff0c;生成經過預處理的代碼。以下是C語言預處理的一些重要特性&#xff1a; 1&#xff0c;頭文件包含 #…

Ansible中執行流控制

1.ansible中的迭代循環 創建目錄和文件 vim createfile.yaml - name: create file playbook hosts: all tasks: - name: create file file: path: "/mnt/{{item[name]}}" state: …

Unity 自定義窗口

放在Editor文件夾下&#xff1b; #if UNITY_EDITORusing System; using UnityEditor; using UnityEngine;namespace EditorCustumTool {/// <summary>/// 自定義窗口/// </summary>public class CustomWindow : EditorWindow{public enum FlagType{Flag1 101,Fl…

前端知識筆記(三十六)———HTTP 緩存機制

一、強制緩存 只要瀏覽器判斷緩存沒有過期&#xff0c;則直接使用瀏覽器的本地緩存而無需再請求服務器。 強制緩存是利用下面這兩個 HTTP 響應頭部&#xff08;Response Header&#xff09;字段實現的&#xff0c;它們都用來表示資源在客戶端緩存的有效期&#xff1a; Cache-…

java中對象和Map互相轉換的幾種方式

在Java中&#xff0c;將對象和Map相互轉換是常見的操作&#xff0c;可以通過不同的方式實現這種轉換。以下是幾種常見的方法以及示例說明&#xff1a; 1. 使用Hutool工具類 Hutool是一個優秀的Java工具包&#xff0c;提供了豐富的工具方法&#xff0c;其中就包括對象和Map之間…

【基于ESP32無線藍牙上傳電腦Excel透傳數據】

【基于ESP32無線藍牙上傳電腦透傳數據】 1. 引言2. 環境搭建2.1 硬件準備:2.2 軟件準備:2.3. 配置Excel端口接收功能3. 測試代碼4. 連接電腦和 ESP324.1 燒錄程序4.2 啟動藍牙服務4.3 測試數據透傳5. 總結1. 引言 隨著物聯網技術的發展,越來越多的設備開始支持無線通信,其…

fl studio2024官方體驗版如何破解?

fl studio2024全稱Fruity Loops Studio2024&#xff0c;這款軟件也被人們親切的稱之為水果&#xff0c;它是一款功能強大的音樂創作編輯軟件&#xff0c;擁有全功能的錄音室&#xff0c;大混音盤以及先進的音樂制作工具&#xff0c;用戶通過使用該軟件&#xff0c;就可以輕松制…

輕量封裝WebGPU渲染系統示例<43>- 材質組裝流水線(MaterialPipeline)之燈光和陰影(源碼)

目標: 數據化&#xff0c;模塊化&#xff0c;自動化 備注: 從這個節點開始整體設計往系統規范的方向靠攏。之前的都算作是若干準備。所以會和之前的版本實現有些差異。 當前示例源碼github地址: https://github.com/vilyLei/voxwebgpu/blob/feature/material/src/voxgpu/sa…

redis過期提醒

redis過期提醒 有一次看redis的配置文件發現一個notify-keyspace-events配置&#xff0c;注釋里邊長篇大論的&#xff0c;那我得看看這是干啥的&#xff0c;看完注釋內容&#xff0c;發現不得了了&#xff0c;redis竟然還有過期提醒的功能 接下來得大家解釋一下&#xff1a; 首…

EfficientNet: Rethinking Model Scaling for Convolutional Neural Networks(2020)

文章目錄 -Abstract1. Introductiondiss former methodour method 2. Related Work3. Compound Model Scaling3.1. 問題公式化3.2. Scaling Dimensions3.3. Compound Scaling 4. EfficientNet Architecture5. Experiments6. Discussion7. Conclusion 原文鏈接 源代碼 - 本文中…