js文件上传 分片上传/断点续传/极速秒传

(极速秒传)利用md5判断上传的文件是否存在

MD5信息摘要算法,一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。

每一个文件都会生成一个不同的md5编码 例:【3a94b8ca53dea8524d16c4e39dd43a69】

优点

服务器中不会出现重复文件

极速秒传

传输文件先校验md5,减小服务器压力

思路是
在文件上传到服务器前,将文件进行MD5转换,然后将转换完后的MD5码传给服务器,服务器判断当前MD5码是否存在,如果存在就代表着服务器上已经有了跟该文件相同的文件,不再需要上传文件

在这里插入图片描述

// 获取md5
import SparkMD5 from 'spark-md5';

const getMd5 = async (file: File) => {
    let text = await file.text(); // 将文件转换成text文本
    return SparkMD5.hashBinary(text); // 通过插件进行MD5加密
}

分片上传

分片上传就是把一个大文件,按照一定的大小,分割成多个小文件分别进行上传,文件上传结束后,服务器对所有的文件进行合并(前端负责拆分、后端负责整合)

在这里插入图片描述

优点(分片上传/断点续传)

上传速度快

上传稳定性强

降低传输断开风险

前端拆分文件的方法

前端拿到【file】文件后,可以调用file.slice方法(Blob的方法)对文件进行拆分

在这里插入图片描述

const BIGSIZE = 1024 * 1024 * 2 // 2mb

// 3. 切割文件
const getFileList = (file: File): FileList[] => {
    let cur = 0; // 当前分割到的位置
    const fileChunkList: any[] = [] // 最终的blob数组
    while (cur < file.size) {
        let chunk = file.slice(cur, cur + BIGSIZE)
        fileChunkList.push({
            chunk: chunk,
            index: fileChunkList.length
        })
        cur += BIGSIZE;
    }
    return fileChunkList
}
服务器合并文件的方法(node示例)
filesNameList = ['分片文件a','分片文件b',...] // 文件路径
// 创建可写流
let writeStream = fs.createWriteStream('最终合并后的文件路径', {
  flags: 'w+',
  encoding: 'base64',
})
// 合并文件
for (let i = 0; i < filesName.length; i++) {
  // 读取文件
  let val = await getFile(filesNameList[i])
  // 写入文件
  writeStream.write(val)
}
writeStream.end();

// 读取文件函数
const getFile = (path) => {
  return new Promise(resolve => {
    // 创建可读流
    const readStream = fs.createReadStream(path, {
      flags: 'r',
      encoding: 'base64',
    });
    var count = 0;
    var str = '';
    readStream.on('data', (data) => {  //监听
      str += data
      count++
    })
    readStream.on('end', () => {   //读取结束
      resolve(str)
    })
  });
}

断点续传

断点续传就是在分片上传逻辑的基础上,增加断开后继续上传的逻辑

一种实现思路是:可以将分片的文件命名为对应的索引,在文件断开后,根据索引判断有多少分片已经上传成功了,然后继续传输没有上传成功的分片

在这里插入图片描述

附代码

前端:

bigUpFile(file)
/***********************样式与提示***********************/
// 大文件上传中
const bigFileLoading = ref(false)
const loadingTextArr = ref<string[]>([])

const addText = (str: string, number: undefined | number = undefined, type = 'default') => {
    loadingTextArr.value.push(str)
    if (number || number === 0) {
        if (type === 'add') {
            progressNumber.value += number
        } else {
            progressNumber.value = number
        }
    }
}

/***********************上传代码***********************/
// 开始函数
const bigUpFile = async (file: File) => {
    addText('大文件上传中...', 0)
    bigFileLoading.value = true

    // 1 获取文件的md5
    addText('获取文件md5...', 5)
    const fileMD5 = await getMd5(file) // md5

    // 2. 极速秒传
    addText('判断是否可以进行极速秒传...', 10)
    let upEnd = await fastUpFile(file, fileMD5)
    if (upEnd) {
        addText('极速秒传成功!', 100)
        message.success('极速秒传成功!')
        emit("getList");
        return
    }

    addText('开始分割文件...', 15)
    // 3. 切割文件
    let fileChunkList = await getFileList(file)
    let fileNumber = fileChunkList.length // 文件分段总数

    addText('校验断点续传...', 18)
    // 4. 校验大文件分片上传文件数量
    let execFileList = await getExecFileList(fileMD5)
    if (execFileList && execFileList?.length) {
        let fl: FileList[] = []
        fileChunkList.forEach(item => {
            if (!execFileList.includes(item.index) && !execFileList.includes(item.index + '')) {
                fl.push(item)
            }
        })
        fileChunkList = fl
        addText('校验断点续传成功,继续传输...', 18)
    }


    addText('开始上传文件...', 20)
    // 5. 开始上传任务
    await createUpBigFile(fileChunkList, fileMD5, file.name, fileNumber)
    addText('结束...', 100)
}


/**********************************************/

// 1 获取文件的md5
const getMd5 = async (file: File | Blob) => {
    let buffer = await file.text(); // 获取buffer
    return SparkMD5.hashBinary(buffer);
}

// 2. 极速秒传
const fastUpFile = async (file: File, fileMD5: string) => {
    // 发送md5到服务器判断是否已经存在
    const res = await api.upPage.execFile({
        md5: fileMD5
    });
    // md5已存在,更新标签
    if (res) {
        await api.upPage.updateMD5({
            md5: fileMD5,
            name: formState.name,
        })
        // 极速秒传成功
        return true
    }
}

// 3. 切割文件
const getFileList = (file: File): FileList[] => {
    let cur = 0;
    const fileChunkList: any[] = [] // blob数组
    while (cur < file.size) {
        let chunk = file.slice(cur, cur + BIGSIZE)
        fileChunkList.push({
            chunk: chunk,
            index: fileChunkList.length
        })
        cur += BIGSIZE;
    }
    return fileChunkList
}

// 4. 校验大文件分片上传文件数量
const getExecFileList = async (fileMD5: string): Promise<string | number[]> => {
    let res = await api.upPage.execFileList({
        md5: fileMD5
    });
    console.log('res', res)
    return res
}

// 5. 开始上传任务
const createUpBigFile = async (fileList: any[], fileMD5: string, fileName: string, fileNumber: number) => {
    console.log('---fileList', fileList)
    let promiseList: any[] = []
    fileList.forEach(async (item, i) => {
        promiseList.push(up(item.chunk, item.index, fileMD5))
    })
    let num = Math.floor(70 / promiseList.length)
    promiseList.forEach(p => p.then(res => {
        addText('上传成功1个分片...', num, 'add')
        return res
    }))

    await Promise.all(promiseList)
    // 5.2 合并文件
    await mergeFile(fileMD5, fileNumber, fileName, fileList)
}

// 5.1 上传
const up = async (file, i, fileMD5) => {
    let formData = new FormData();
    formData.append("fileName", fileMD5);
    formData.append("fileIndex", `${i}`);
    formData.append("file", file);
    let res = await api.upPage.saveBigFile(formData);
}

// 5.2 合并文件
const mergeFile = async (fileMD5: string, fileNumber: number, fileName: string, fileList: any[]) => {
    addText('合并文件中...', 90)
    let res = await api.upPage.mergeFile({
        fileNumber,
        md5: fileMD5,
        name: formState.name,
        fileName,
    });
    console.log('res', res)
    if (res.type === 'add') {
        let indexList = res.fileList
        let fl: any[] = []
        fileList.forEach(item => {
            if (!indexList.includes(item.index) && !indexList.includes(item.index + '')) {
                fl.push(item)
            }
        })
        createUpBigFile(fl, fileMD5, fileName, fileNumber)
    }
}

nodejs

const filePath = path.join(__dirname, './static') // 文件上传后保存的路径

// 方法函数:添加文件到服务器
const addFileToServer = (file, filePath) => {
    // 转成文件流
    var _file = fs.createReadStream(file.filepath)
    // 存储文件
    _file.pipe(fs.createWriteStream(filePath))
}

// 方法函数:获取文件可读流
const getFile = (path) => {
    return new Promise(resolve => {
        const readStream = fs.createReadStream(path, {
            flags: 'r',
            encoding: 'base64',
        });
        var count = 0;
        var str = '';
        readStream.on('data', (data) => {  //监听
            str += data
            count++
        })

        readStream.on('end', () => {   //读取结束
            resolve(str)
        })
    });
}

// 返回参数格式化
cosnt sendVal = (val) => return {...,val}

/*************************************************************************/
// 校验大文件分片上传文件数量
router.post('/execFileList', async (req, res) => {
    const data = getReq(req) // 获取传参
    try {
        // 文件路径
        let dirPath = path.join(filePath, data.md5)
        // 判断是否有该文件夹
        if (fs.existsSync(dirPath)) {
            // 列出文件夹中文件名称
            const filesName = fs.readdirSync(dirPath);
            return res.send(sendVal(filesName))
        } else {
            return res.send(sendVal(false))
        }
    } catch (error) {
        return res.send(sendErr(error))
    }
})

// 大文件上传
router.post('/saveBigFile', async (req, res) => {
    try {
        var form = new formidable.IncomingForm();
        form.encoding = 'utf-8';
        form.keepExtensions = true;//保留后缀
        form.parse(req, async (err, fields, files) => {
            if (err) {
                return res.send(sendErr(err))
            }
            let _filePath = path.join(filePath, fields.fileName)
            createPath(_filePath) // 如果没有该路径,创建路径
            // 添加文件到服务器
            addFileToServer(files.file, `${_filePath}/${fields.fileIndex}`)

            return res.send(sendVal(1))
        })
    } catch (error) {
        return res.send(sendErr(error))
    }
})

// 大文件合并
router.post('/mergeFile', async (req, res) => {
    const data = getReq(req) // 获取参数
    // 字段校验
    let field = fieldVerification({
        md5: 'string',
        name: 'string',
        fileName: 'string',
        fileNumber: 'number'
    }, data)
    if (field) return res.send(sendErr(3, field))
    // 登陆权限校验+获取用户信息
    const userInfo = validateErr(req, "imgUp")
    if (typeof (userInfo) !== 'object') return res.send(sendErr(userInfo))

    try {
        // 获取后缀
        let extensions = data.fileName.replace(/^.*(\..*?)$/, '$1')
        // 文件路径
        let dirPath = path.join(filePath, data.md5)
        let fileName = path.join(filePath, `${data.md5}${extensions}`)
        const filesName = fs.readdirSync(dirPath);
        // 校验文件完整性
        if (filesName?.length !== data.fileNumber) {
            return res.send(sendVal({
                fileList: filesName,
                type: 'add'
            }))
        }
        // 创建合并流
        let writeStream = fs.createWriteStream(fileName, {
            flags: 'w+',
            encoding: 'base64',
        })
        writeStream.on('finish', async () => {
            console.log('成功');
            await addFileToSQL(data.md5, data.name, data.fileName, extensions, true)
            res.send(sendVal({
                type: 'success'
            }))
        })
        // 合并文件
        for (let i = 0; i < filesName.length; i++) {
            let _filePath = path.join(dirPath, `${i}`)
            // 读取文件
            let val = await getFile(_filePath)
            // 写入文件
            writeStream.write(val)
        }
        writeStream.end();
    } catch (error) {
        return res.send(sendErr(error))
    }
})

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/288253.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

电磁波的信号加载说明

电磁波的信号加载电磁波(Electromagnetic wave)是由同相振荡 且互相垂直的电场与磁场在空间中衍生发射的振荡粒子波&#xff0c;是以波动的形式传播的电磁场&#xff0c;具有波粒二象性&#xff0c;其粒子形态称为光子&#xff0c;电磁波与光子不是非黑即白的关系&#xff0c;而…

Kubernetes 核心实战之一(精华篇 1/2)

文章目录 1&#xff0c;资源创建方式1.1 yaml1.2 命令行 2&#xff0c;NameSpace命名空间2.1 命令行创建ns2.2 yaml 创建ns 3&#xff0c;Pod3.1 命令行 创建pod3.2 yaml 创建pod3.3 可视化界面 创建3.3.1 Pod nginx3.3.2 Pod nginx tomcat3.3.3 Pod 2ngnix 1&#xff0c;资源…

日文游戏翻译 ,如何做好本地化翻译?

相关调查显示&#xff0c;日本游戏占据全球游戏市场约20%的份额&#xff0c;其销量一直都不错。市场上对于日语游戏翻译的需求也很大。那么&#xff0c;针对日文游戏翻译&#xff0c;如何做好本地化翻译&#xff1f; 首先、做好语言和文化上的本地化。这要求译员从翻译的文本到…

一篇关于大模型在信息抽取(实体识别、关系抽取、事件抽取)的研究进展综述

信息提取&#xff08;IE&#xff09;旨在从普通自然语言文本中提取结构化知识&#xff08;如实体、关系和事件&#xff09;。最近&#xff0c;生成式大型语言模型&#xff08;LLMs&#xff09;展现了在文本理解和生成方面的卓越能力&#xff0c;使得它们能够广泛应用于各种领域…

VINS-MONO拓展1----手写后端求解器,LM3种阻尼因子策略,DogLeg,构建Hessian矩阵

文章目录 0. 目标及思路1. 非线性优化求解器2. 基于VINS-MONO的Marginalization框架构建Hessian矩阵2.1 estimator.cpp移植2.2 solve.cpp/preMakeHessian()2.3 solve.cpp/makeHessian() 3. solve.cpp/solveLinearSystem()求解正规方程4. 更新状态5. 迭代求解6. EVO评估结果7. 待…

Certum ev多域名证书的优势

多域名证书作为一种能够为多个域名提供安全保护的证书类型&#xff0c;越来越受到企业的青睐。Certum作为一个成立了二十几年的CA认证机构&#xff0c;旗下的EV多域名SSL证书产品已经保护了多家企业的网站。Certum旗下的EV多域名证书作为一种能够为多个域名提供安全保护的证书类…

《Linux C编程实战》笔记:实现自己的myshell

ok&#xff0c;考完试成功复活 这次是自己的shell命令程序的示例 流程图&#xff1a; 关键函数 1.void print_prompt() 函数说明&#xff1a;这个函数打印myshell提示符&#xff0c;即“myshell$$”. 2.void get_input(char *buf) 函数说明&#xff1a;获得一条指令&#…

Dora-rs 机器人框架学习教程(1)—— Dora-rs安装

1、dora简介 Dora-rs[1] 是一个基于 Rust 实现的化机器人框架&#xff0c;其具有极高的实时性能。Dora-rs使用Rust语言做数据流的传输和调度管理&#xff0c;可以大大减少了数据的重复拷贝和传输。它提供了Rust语言和Python语言之间的无缝集成&#xff0c;减少了跨语言的性能代…

C++上位软件通过LibModbus开源库和西门子S7-1200/S7-1500/S7-200 PLC进行ModbusTcp 和ModbusRTU 通信

前言 一直以来上位软件比如C等和西门子等其他品牌PLC之间的数据交换都是大家比较头疼的问题&#xff0c;尤其是C上位软件程序员。传统的方法一般有OPC、Socket 等&#xff0c;直到LibModbus 开源库出现后这种途径对程序袁来说又有了新的选择。 Modbus简介 Modbus特点 1 &#…

高压继电器,未来几年市场将保持稳定增长

高压继电器是一种用于控制大功率电气设备的开关装置&#xff0c;广泛应用于电力系统、轨道交通、工业自动化等领域。随着各行业对电气控制需求的不断增加&#xff0c;高压继电器市场也在不断扩大。全球高压继电器市场分析&#xff1a; 在全球市场中&#xff0c;目前主要的高压继…

拒绝纸张浪费,Paperless-ngx开源文档管理系统将纸质版转换成可搜索的电子版档案

GitHub&#xff1a;GitHub - paperless-ngx/paperless-ngx: A community-supported supercharged version of paperless: scan, index and archive all your physical documents 在线演示&#xff1a;https://demo.paperless-ngx.com 官网&#xff1a;https://docs.paperless-n…

【力扣100】46.全排列

添加链接描述 class Solution:def permute(self, nums: List[int]) -> List[List[int]]:# 思路是使用回溯if not nums:return []def dfs(path,depth,visited,res):# 出递归的条件是当当前的深度已经和nums的长度一样了&#xff0c;把path加入数组&#xff0c;然后出递归if …

手机流量卡推广分销网站php源码,多功能的号卡推广分销管理系统

源码简介 拥有多个接口&#xff0c;包括运营商接口&#xff0c;并支持无限三级代理。 最简单易用的PHP系统&#xff0c;它自带自动安装向导&#xff0c;可以让你轻松安装和部署。 该系统集成了多个第三方接口资源&#xff0c;能够满足你的不同需求。采用全系统双色主题&…

Python 自学(二) 之流程控制语句

目录 1. if ... elif ... else 语句 P62 2. True False 3. for 数值循环 in range() P69 4. for 遍历字符串&#xff0c;列表&#xff0c;元组&#xff0c;集合和字典 in obj P70 5. pass 空语句 1. if ... elif ... else 语句 P62 每个判断语句后面要加 :elif …

机器学习中的监督学习基本算法-逻辑回归简单介绍

逻辑回归 逻辑回归&#xff08;Logistic Regression&#xff09;是一种用于解决二分类问题的统计学习方法&#xff0c;尽管名字中带有"回归"一词&#xff0c;但实际上它是一种分类算法。逻辑回归的主要目标是通过学习从输入特征到一个离散的输出&#xff08;通常是0…

log4cplus visual c++ 编译及调试小记

简介 最近在调试一款SATA加密设备&#xff0c;发现设备有时加密出来的数据&#xff0c;再解密时与明文对不上&#xff0c;怀疑是通信问题。因此&#xff0c;急需要在测试工具中加入通信日志。由于对第三方日志库都不熟悉&#xff0c;所以随便选了个log4cplus软件集成到现有工具…

以STM32为例,实现按键的短按和长按

以STM32为例&#xff0c;实现按键的短按和长按 目录 以STM32为例&#xff0c;实现按键的短按和长按1 实现原理2 实现代码3 测试结束语 1 实现原理 简单来说就是通过设置一个定时器来定时扫描几个按键的状态&#xff0c;并分别记录按键按下的持续时间&#xff0c;通过时间的长短…

百度百科词条创建多久可以通过?

一个优质的百度百科词条&#xff0c;能提升个人或企业的品牌形象。因此&#xff0c;越来越多的人希望创建自己的百度百科词条&#xff0c;那么&#xff0c;创建一个百度百科词条到底需要多久才能通过审核呢&#xff1f;接下来伯乐网络传媒就来给大家分享一下。 一、百度百科词条…

【Qt之Quick模块】7. Quick基础、常用组件Item、Rectangle、Text、TextInput、TextEdit等

1. 概述 Qt Quick模块是编写QML应用程序的标准库。Qt QML模块提供QML引擎和语言基础结构&#xff0c;Qt Quick模块提供用QML创建用户界面所需的所有基本类型。它提供了一个可视化画布&#xff0c;包括用于创建和动画化可视化组件、接收用户输入、创建数据模型和视图以及延迟对…

静态网页设计——海贼王

前言 使用前端经典三件套HTMLCSSJS实现的海贼王静态网页课程设计&#xff0c;适合我们的童年&#xff01; 主要内容 首页 首页最上方有一个轮播图&#xff0c;可以自动切换图片&#xff0c;使用js实现。 轮播图往下&#xff0c;就是列出一些比较经典的海贼王影片&#xf…