【文件上传系列】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.mzph.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;三层体系结构…

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…

【基于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…

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 原文链接 源代码 - 本文中…

LeNet

概念 代码 model import torch.nn as nn import torch.nn.functional as Fclass LeNet(nn.Module):def __init__(self):super(LeNet, self).__init__() # super()继承父类的构造函数self.conv1 nn.Conv2d(3, 16, 5)self.pool1 nn.MaxPool2d(2, 2)self.conv2 nn.Conv2d(16…

Bash脚本处理ogg、flac格式到mp3格式的批量转换

现在下载的许多音乐文件是flac和ogg格式的&#xff0c;QQ音乐上下载的就是这样的&#xff0c;这些文件尺寸比较大&#xff0c;在某些场合使用不便&#xff0c;比如在车机上播放还是mp3格式合适&#xff0c;音质这些在车机上播放足够了&#xff0c;要求不高。比如本人就喜欢下载…

软件接口安全设计规范

《软件项目接口安全设计规范》 1.token授权机制 2.https传输加密 3.接口调用防滥用 4.日志审计里监控 5.开发测试环境隔离&#xff0c;脱敏处理 6.数据库运维监控审计

卷王开启验证码后无法登陆问题解决

问题描述 使用 docker 部署&#xff0c;后台设置开启验证&#xff0c;重启服务器之后&#xff0c;docker重启&#xff0c;再次访问系统&#xff0c;验证码获取失败&#xff0c;导致无法进行验证&#xff0c;也就无法登陆系统。 如果不了解卷王的&#xff0c;可以去官网看下。…

飞天使-linux操作的一些技巧与知识点3

http工作原理 http1.0 协议 使用的是短连接&#xff0c;建立一次tcp连接&#xff0c;发起一次http的请求&#xff0c;结束&#xff0c;tcp断开 http1.1 协议使用的是长连接&#xff0c;建立一次tcp的连接&#xff0c;发起多次http的请求&#xff0c;结束&#xff0c;tcp断开ngi…

ky10 server x86 设置网卡开机自启

输入命令查看网卡名称 ip a 输入命令编辑网卡信息 vi /etc/sysconfig/network-scripts/*33改成yes 按ESC键&#xff0c;输入:wq&#xff0c;保存