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

news2024/11/20 19:25:08

(极速秒传)利用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))
    }
})

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

杰发科技AC7840——Eclipse环境DMA注意事项

0.序 用 户 使 用 DMA 时 &#xff0c; 所 有 DMA 搬 运 的 SRAM 数 据 都 必 须 存 放 在 SRAM_U 区 (0x20000000~0x2000EFFF) 。 1. 修改办法 第一步&#xff1a; RAM定义 /* Specify the memory areas */ MEMORY {FLASH (rx) : ORIGIN 0x00000000, LENGT…

【零基础入门TypeScript】类型和变量

目录 任意类型 内置类型 Null 和 undefined ─ 它们是一样的吗&#xff1f; 用户定义类型 TypeScript 中的变量声明 示例&#xff1a;TypeScript 中的变量 TypeScript 中的类型断言 例子 TypeScript 中的推断类型 示例&#xff1a;推断类型 TypeScript 变量作用域 …

认识机器学习【woodwhales.cn】

为了更好的阅读体验&#xff0c;建议移步至笔者的博客阅读&#xff1a;认识机器学习 生活中的问题1&#xff1a;居民家庭生活用气价格 北京燃气小程序在线咨询&#xff0c;查询北京居民家庭生活用气价格 上图价格梯度&#xff0c;可以由文字转换成表格&#xff1a; 第一档用气…

DrGraph原理示教 - OpenCV 4 功能 - 二值化

二值化&#xff0c;也就是处理结果为0或1&#xff0c;当然是针对图像的各像素而言的 1或0&#xff0c;对应于有无&#xff0c;也就是留下有用的&#xff0c;删除无用的&#xff0c;有用的部分&#xff0c;就是关心的部分 在图像处理中&#xff0c;也不仅仅只是1或0&#xff0c;…

Linux 命令echo

命令作用 输出一行字符串在shell中&#xff0c;可以打印变量的值输出结果写入到文件在显示器上显示一段文字&#xff0c;起到提示的作用 语法 echo [选项] [字符串] 参数 字符含义-n不自动换行-e解释转义字符-E不解释转义字符 如果-e有效&#xff0c;则识别以下序列&…

【Unity入门】RequireComponent的使用

目录 RequireComponent的作用构造函数 RequireComponent的作用 RequireComponent 属性自动将所需的组件添加为依赖项。 当某个脚本必须依赖其他脚本或者组件共同使用时&#xff0c;为了避免人为添加过程的操作失误&#xff0c;可以在代码中使用RequireComponent&#xff0c;它…

c语言:设计投票小程序|练习题

一、题目 设计一个投票小程序 如图&#xff1a; 二、代码图片【带注释】 三、源代码【带注释】 #include <stdio.h> #include<string.h> void win(int,int,int); int main() { char ch[5]; int countLili0; int countjp0; int countzx0; int …

OS 7--DNS配置+Apache发布网站

环境准备 centOS 7 1.配置DNS 1.1 域名为lianxi.com 1.2 为WWW服务器、FTP服务器、NEWS服务器做域名解析 1)安装DNS yum -y install bind bind-utils (如果安装不上&#xff0c;就把磁盘在重洗挂载一下&#xff09; 2&#xff09;修改DNS配置文件 vim /etc/resolv.conf…

【一文入门】Git常用命令集锦--分支操作和版本管理篇

前言 Git 是一种分布式版本控制系统&#xff0c;可以帮助团队协作开发、管理和维护代码&#xff0c;提高代码质量和效率&#xff0c;掌握常用版本管理命令可以帮助我们更好地管理代码变更和历史记录。下面我将介绍开发中常用的一些Git分支操作和版本管理命令 1 分支操作 1.1 …

Linux 系统编程:文件系统

文件类型 Linux 文件分为 3 类&#xff1a; 普通文件&#xff1a;文本文件、二进制文件&#xff0c;要学习如何创建、复制、移动、重命名和删除这样的文件。目录&#xff08;Windows 中的“文件夹”与之类似&#xff09;伪文件&#xff1a;设备文件、命名管道、proc 文件&…

Kafka安全认证机制详解之SASL_PLAIN

一、概述 官方文档&#xff1a; https://kafka.apache.org/documentation/#security 在官方文档中&#xff0c;kafka有五种加密认证方式&#xff0c;分别如下&#xff1a; SSL&#xff1a;用于测试环境SASL/GSSAPI (Kerberos) &#xff1a;使用kerberos认证&#xff0c;密码是…

DevSecOps研讨会: 2023年DevOps有哪些值得关注的发展与挑战

近日&#xff0c;龙智DevSecOps研讨会年终专场”趋势展望与实战探讨&#xff1a;如何打好DevOps基础、赋能创新”在上海圆满落幕。来自清晖、Jama Software、CloudBees和中新赛克的嘉宾&#xff0c;以及龙智技术与顾问咨询团队代表分别发表了主题演讲&#xff0c;分享他们在Dev…

(已解决)word如何制作和引用参考文献

文章目录 正文其他 一般使用latex&#xff0c;但是有的时候会遇到使用word的情况&#xff0c;这里记录一下word如何弄参考文献。 正文 1.首先复制你的参考文献到word里面&#xff0c;然后要编号&#xff0c;记住&#xff0c;一定要编号&#xff0c;否则到时候无法引用。 那么…

Note: Balanced Diet

Balanced Diet 平衡膳食 diet balanced Wang Peng earned his living by running a barbecue restaurant, which served delicious bacon, fried chiken breast and mutton roasted with pepper and garlic. 王鹏经营一家烧烤餐厅来谋生&#xff0c;它提供美味的培根&#xf…

C语言注意点(2)

1.使用pow函数的相关问题 局部变量n0 while(num/pow(10,n)) n; 为什么不可行 printf("%d",num/pow(10,4)%10) 为什么要提前用temp先引出来 答&#xff1a;pow函数的返回值为double类型&#xff0c;1.终止条件不会满足 2.num/pow(10,4)结果为浮点型&#xff0c;浮…

Spark一:Spark介绍、技术栈与运行模式

一、Spark简介 Spark官网 https://spark.apache.org/ 1.1 Spark是什么 Spark是一种通用的大数据计算框架&#xff0c;是基于RDD(弹性分布式数据集)的一种计算模型。 是一种由 Scala 语言开发的快速、通用、可扩展的大数据分析引擎。 1.2 Spark作用 中间结果输出 Spark的Jo…

从零开始配置kali2023环境:配置jupyter的多内核环境

在kali2023上面尝试用anaconda3&#xff0c;anaconda2安装实现配置jupyter的多内核环境时出现各种问题&#xff0c;现在可以通过镜像方式解决 1. 搜索镜像 ┌──(holyeyes㉿kali2023)-[~] └─$ sudo docker search anaconda ┌──(holyeyes㉿kali2023)-[~] └─$ sudo …

Redis数据删除策略(惰性删除+定期删除)

文章目录 Redis数据删除策略1. 惰性删除2. 定期删除3. Redis过期删除策略用的哪种&#xff1f; Redis数据删除策略 1. 惰性删除 设置key过期时间后&#xff0c;不管它&#xff0c;需要用该key时&#xff0c;再检查是否过期&#xff0c;过期就删掉她&#xff0c;没过期返回 set …

车载 Android之 核心服务 - CarPropertyService 的VehicleHAL

前言: 本文是车载Android之核心服务-CarPropertyService的第二篇&#xff0c;了解一下CarPropertyService的VehicleHAL, 第一篇在车载 Android之 核心服务 - CarPropertyService 解析-CSDN博客&#xff0c;有兴趣的 朋友可以去看下。 本节介绍 AndroidAutomotiveOS中对于 Veh…

如何在 Ubuntu 20.04 上以独立模式设置 MinIO 对象存储服务器

前些天发现了一个人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;最重要的屌图甚多&#xff0c;忍不住分享一下给大家。点击跳转到网站。 如何在 Ubuntu 20.04 上以独立模式设置 MinIO 对象存储服务器 介绍 存储非结构化对象数据 blob 并使其可通过 …