Vue—大文件分片上传

news2025/4/22 9:21:24

背景

如题,最近遇到大文件上传慢的问题,用户需要经常上传一些超过一百多M的文件,系统由于历史原因上传功能并没有做分片上传的功能,是整个文件上传,并且服务器带宽限制和NGINX对文件大小的限制等问题,所以决定将文件上传功能改为分片上传。

决定将上传功能修改为分片上传后遂百度分片上传的相关开源项目,本项目使用的技术是Vue2+antd+SpringBoot,但是找到的开源项目基本不合适。

前端Vue代码

1、引入依赖

// 引入SparkMD5用于计算文件MD5值
npm install --save SparkMD5

2、编写UI及对应函数

在这里插入图片描述

3、设置分片大小

data() {
      return {
     	 CHUNK_SIZE: 20 * 1024 * 1024, // 分片上传大小20MB
      }
}

4、

async customRequest(data) {
   var that = this
   // 1、设置文件状态为上传中
   for (var ff of this.fileList) {
     if (ff.uid === data.file.uid) {
       ff.status = 'done'
       break;
     }
   }

   let file = data.file;
   let time = new Date().getTime();
   // 2、求出分片数量、计算文件MD5
   let chunks = Math.ceil(file.size / that.CHUNK_SIZE);
   let spark = new SparkMD5.ArrayBuffer();
   spark.append(file);
   let md5 = spark.end();
   console.log(`MD5计算完毕:${file.name} \nMD5:${md5} \n分片:${chunks} 大小:${file.size} 用时:${new Date().getTime() - time} ms`);
   spark.destroy(); //释放缓存

   // 3、循环读取分片并上传
   let currentChunk = 0;
   // 3.1、读完第一个分片
   let blob = loadNext(currentChunk, that.CHUNK_SIZE);
   for (currentChunk = 1; currentChunk <= chunks; currentChunk++) {
     // 上传分片
     var params = {
       chunkNumber: currentChunk,
       totalChunks: chunks,
       chunkSize: that.CHUNK_SIZE,
       currentChunkSize: blob.size,
       totalSize: file.size,
       identifier: md5,
       filename: file.name
     }
     // 3.2、上传文件分片,阻塞等待返回再继续执行
     await this.uploadFileChunk(data, blob, params);

     // 3.3、加载下一分片
     if (currentChunk < chunks) {
       blob = loadNext(currentChunk, that.CHUNK_SIZE);
     }
   }

   // 4、发送合并文件请求
   var mergeParams = {
     totalChunks: chunks,
     chunkSize: that.CHUNK_SIZE,
     totalSize: file.size,
     identifier: md5,
     filename: file.name
   }
   await this.mergeFileChunk(data, mergeParams);
   
   // 获取文件分片方法
   function loadNext(currentChunk, CHUNK_SIZE) {
     let start = currentChunk * CHUNK_SIZE;
     let end = start + CHUNK_SIZE >= file.size ? file.size : start + CHUNK_SIZE;
     return file.slice(start, end);
   }
 },

后端Spring Boot代码

后端代码这块是基于开源项目(金鳞岂是池中物灬 / simple-uploader)做了点小改动,具体代码如下:

文件分片上传类FileChunk

@Data
public class FileChunk {
    /**
     * 主键id
     */
    private Long id;
    /**
     * 当前块的次序,第一个块是 1,注意不是从 0 开始的
     */
    private Integer chunkNumber;
    /**
     * 文件被分成块的总数。
     */
    private Integer totalChunks;
    /**
     * 分块大小,根据 totalSize 和这个值你就可以计算出总共的块数。注意最后一块的大小可能会比这个要大。
     */
    private Integer chunkSize;
    /**
     * 当前块的大小,实际大小。
     */
    private Integer currentChunkSize;
    /**
     * 文件总大小。
     */
    private Long totalSize;
    /**
     * 这个就是每个文件的唯一标示。
     */
    private String identifier;
    /**
     * 文件名。
     */
    private String filename;
    /**
     * 文件夹上传的时候文件的相对路径属性。
     */
    private String relativePath;
    /**
     * 创建时间
     */
    private Date createTime;
    /**
     * Spring MultipartFile
     */
    private MultipartFile file;
}

文件分片上传接口


	/**
	 * Post方法:分片上传
	 *
	 * @param fileChunk 分片
	 * @return AjaxResult
	 */
	@PostMapping("/upload")
	public BaseResponse uploadChunk(FileChunk fileChunk) {
		logger.info("上传分片——开始:{}", fileChunk.toString());
		if (fileChunk.getFile().isEmpty()) {
			logger.error("上传文件不存在!");
			throw new RuntimeException("上传文件不存在!");
		}
		File chunkPath  = new File(uploadPath + File.separator + "temp" + File.separator + fileChunk.getIdentifier());
		if (!chunkPath.exists()) {
			final boolean flag = chunkPath.mkdirs();
			if (!flag) {
				logger.error("创建目录失败!");
				return new BaseResponse().fail("上传失败");
			}
		}
		RandomAccessFile raFile = null;
		BufferedInputStream inputStream = null;
		try {
			File chuckFile = new File(chunkPath, String.valueOf(fileChunk.getChunkNumber()));
			raFile = new RandomAccessFile(chuckFile, "rw");
			raFile.seek(raFile.length());
			inputStream = new BufferedInputStream(fileChunk.getFile().getInputStream());
			byte[] buf = new byte[1024];
			int length = 0;
			while ((length = inputStream.read(buf)) != -1) {
				raFile.write(buf, 0, length);
			}
		} catch (IOException e) {
			throw new RuntimeException(e);
		} finally {
			if (inputStream != null) {
				try {
					inputStream.close();
				} catch (IOException e) {
					throw new RuntimeException(e);
				}
			}
			if (raFile != null) {
				try {
					raFile.close();
				} catch (IOException e) {
					throw new RuntimeException(e);
				}
			}
		}
		logger.info("上传分片——结束:{}", fileChunk.toString());
		return new BaseResponse().success();
	}

合并文件分片接口

/**
* 合并文件
*
* @param fileChunk 分片信息
* @return AjaxResult
*/
@PostMapping("/merge")
public BaseResponse merge(FileChunk fileChunk) {
	logger.info("合并文件——开始:{}", fileChunk.toString());
	//分片文件临时目录
	File tempPath = new File(uploadPath + File.separator + "temp" + File.separator + fileChunk.getIdentifier());
	// 上传的文件
	File realFile = new File(uploadPath + File.separator + "temp" + File.separator + fileChunk.getFilename());
	// 文件追加写入
	FileOutputStream os;
	try {
		os = new FileOutputStream(realFile, true);
		if (tempPath.exists()) {
			//获取临时目录下的所有文件
			File[] tempFiles = tempPath.listFiles();
			//按名称排序
			Arrays.sort(tempFiles, (o1, o2) -> {
				if (Integer.parseInt(o1.getName()) < Integer.parseInt(o2.getName())) {
					return -1;
				}
				if (Integer.parseInt(o1.getName()) == Integer.parseInt(o2.getName())) {
					return 0;
				}
				return 1;
			});
			//每次读取10MB大小,字节读取
			byte[] bytes = new byte[10 * 1024 * 1024];
			int len;
	
			for (int i = 0; i < tempFiles.length; i++) {
				FileInputStream fis = new FileInputStream(tempFiles[i]);
				while ((len = fis.read(bytes)) != -1) {
					os.write(bytes, 0, len);
				}
				fis.close();
				//删除分片
				tempFiles[i].delete();
			}
			os.close();
			// TODO:验证合并文件的MD5值是否与传输过来的文件MD5值一致
			//删除临时目录
			if (tempPath.isDirectory() && tempPath.exists()) {
				System.gc(); // 回收资源
				tempPath.delete();
			}
	
		}
	} catch (Exception e) {
		logger.error("文件合并——失败 " + e.getMessage());
		return new BaseResponse().fail("文件合并失败");
	}
	logger.info("合并文件——结束:{}", fileChunk.toString());
	// 文件合并成功,下一步上传至到阿里云
	String ossUrl = uploadFileToOos(realFile, fileChunk.getFilename());
	if(StrUtil.isEmpty(ossUrl)){
		return new BaseResponse().fail("文件上传失败");
	}
	Map<String, Object> returnMap = new HashMap<>();
	returnMap.put("oosUrl", ossUrl);
	returnMap.put("attachmentName", fileChunk.getFilename());
	return new BaseResponse().success(returnMap);
}

上述合并文件代码其实缺少了验证文件MD5值这一步,当时写代码时没发现。哈哈。

总结

至此文件分片上传的功能已经开发完毕,基于上述代码其实还可以实现文件秒传、断点续传和失败重试功能。

参考

CSDN博文 vue—大文件分片上传
金鳞岂是池中物灬 / simple-uploader

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

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

相关文章

亳州市的自然风光与旅游资源:欣赏安徽省中部的壮丽景色

亳州市是中国安徽省的一个地级市&#xff0c;位于该省的中部。 亳州市辖区包括谯城区、涡阳县、蒙城县和利辛县等地。亳州市拥有悠久的历史和丰富的文化遗产&#xff0c;同时也以其独特的自然风光而闻名。 首先&#xff0c;让我们来了解一下亳州的历史和景点。亳州的历史可以…

机器人硬件在环仿真:解决实体开发与测试挑战,提升效率与安全性

工业机器人具备出色的灵活性和运动能力&#xff0c;广泛应用于工业制造领域。它们可以完成装配、焊接、喷涂、搬运、加工、品质检测等任务&#xff0c;提高了生产效率&#xff0c;保证了产品质量。此外&#xff0c;在医疗领域也有辅助手术等特殊应用&#xff0c;展现了其在多个…

pdf处理工具 Enfocus PitStop Pro 2022 中文 for mac

Enfocus PitStop Pro 2022是一款专业的PDF预检和编辑软件&#xff0c;旨在帮助用户提高生产效率、确保印刷品质量并减少错误。以下是该软件的一些特色功能&#xff1a; PDF预检。PitStop Pro可以自动检测和修复常见的PDF文件问题&#xff0c;如缺失字体、图像分辨率低、颜色空…

在 Elasticsearch 中实现自动完成功能 2:n-gram

在第一部分中&#xff0c;我们讨论了使用前缀查询&#xff0c;这是一种自动完成的查询时间方法。 在这篇文章中&#xff0c;我们将讨论 n-gram - 一种索引时间方法&#xff0c;它在基本标记化后生成额外的分词&#xff0c;以便我们稍后在查询时能够获得更快的前缀匹配。 但在此…

高质量!推荐一些免费自学网站

大家好&#xff0c;我是 jonssonyan 说到自学网站&#xff0c;大家第一印象肯定是”菜鸟教程“、”w3school“、B 站大学。这些教程当然非常的好&#xff0c;而且适合入门学习&#xff0c;但是存在一些缺点&#xff0c;第一&#xff0c;知识点比较分散&#xff0c;没有一个整体…

【EI检索征稿】第五届机器学习、大数据与商务智能国际会议(MLBDBI 2023)

第五届机器学习、大数据与商务智能国际会议&#xff08;MLBDBI 2023&#xff09; 2023 5th International Conference on Machine Learning, Big Data and Business Intelligence 由浙江财经大学信息管理与人工智能学院主办&#xff0c;AEIC学术交流中心作支持单位的第五届机器…

Squids DBMotion新增多款同构数据库迁移

秋天的第一次数据传输&#xff01; 又双叒叕&#xff0c;丝滑的零停机数据库在线迁移工具Squids DBMotion再发新版&#xff01; Squids DBMotion继续横向扩展数据库能力&#xff0c;本次不仅增加了PostgreSQL、GaussDB和openGauss的同构数据库能力&#xff0c;也提供了这些数…

自己在家给电脑重装系统Win10教程

自己在家怎么给电脑重装系统Win10&#xff1f;Win10电脑系统如果操作时间特别长了&#xff0c;就可能出现卡顿、蓝屏等系统问题&#xff0c;这时候用户就想给电脑重装系统&#xff0c;却不知道重装具体的操作步骤&#xff0c;下面小编给大家详细介绍自己在家给电脑重装Win10系统…

架构必备能力——kafka的选型对比及应用场景

系列文章目录 上手第一关&#xff0c;手把手教你安装kafka与可视化工具kafka-eagle Kafka是什么&#xff0c;以及如何使用SpringBoot对接Kafka 架构必备能力——kafka的选型对比及应用场景 系列文章目录一、Kafka的模型与优势1. Kafka 模型2. Kafka 优势 二、Kafka与竞争对手的…

【@胡锡进】大模型量化分析- 南京银行 601009.SH

对于股票价格的预测&#xff0c;以下是几种常见的方法&#xff1a; SARIMA模型&#xff1a;SARIMA&#xff08;Seasonal Autoregressive Integrated Moving Average&#xff09;模型适用于具有季节性变动的时间序列数据。它结合了ARIMA模型和季节性差分的方法来预测未来的价格…

axios响应拦截器 路由导航守卫

axios响应拦截器使用场景&#xff1a; 1. 处理全局错误&#xff1a;响应拦截器可以用来统一处理API请求的错误。当后端返回错误状态码&#xff0c;或者响应数据不符合预期时&#xff0c;我们可以在拦截器中进行统一的错误处理逻辑&#xff0c;例如弹窗提示、记录日志等。 2. 统…

倾斜摄影三维模型的顶层构建的问题分析

倾斜摄影三维模型的顶层构建的问题分析 在构建倾斜摄影超大场景的三维模型时&#xff0c;常见的顶层构建问题可能包括以下几个方面&#xff1a; 1、数据质量问题&#xff1a;倾斜摄影所获取的原始数据可能存在噪点、缺失、重叠或者变形等问题&#xff0c;这些问题会直接影响到…

Hadoop问题:start-all.sh显示未找到命令

在sbin文件夹下是start-all.sh可以运行的&#xff0c;但是到了别的文件夹下就不行了&#xff0c;于是想到了是文件路径问题&#xff0c;因为hadoop环境是和java环境一起配置的导致sbin写成了bin 解决办法&#xff1a; 打开.bashrc配置hadoop的环境变量 sudo vim ~/.bashrc …

热电厂蒸汽流量如何无线传输至无纸记录仪上显示?

某数码影像材料制造集团生产目前主要消耗蒸汽和电能源&#xff0c;蒸汽用能情况较为复杂&#xff0c;需要用5公里的蒸汽管线将较远区域某热电厂的蒸汽接入厂内&#xff0c;每周专人巡查一次管线&#xff0c;部分蒸汽管线位置特别偏僻&#xff0c;不易出入。 为了监控蒸汽流量&…

Java NIO到底是个什么东西?

Java NIO到底是个什么东西? 面试官&#xff1a;这次咱们就来聊聊Java 的NIO呗&#xff1f;你对NIO有多少了解&#xff1f; 候选者&#xff1a;嗯&#xff0c;我对Java NIO还是有一定的了解的&#xff0c;NIO是JDK 1.4 开始有的&#xff0c;其目的是为了提高速度。NIO翻译成 n…

GIS地图学知识

一、投影坐标系 "WGS 1984"坐标系墨卡托投影分度带(UTM ZONE)的选择&#xff1a; 北半球区选取最后字母为N的带&#xff0c;带数(经度的整数/6)31&#xff0c;如113.25,34.12中带数(113/6)3149&#xff0c;选取WGS 1984 UTM ZONE 49N 二、投影分带 经度中三度分带投…

Flink测试利器之DataGen初探 | 京东云技术团队

什么是 Flinksql Flink SQL 是基于 Apache Calcite 的 SQL 解析器和优化器构建的&#xff0c;支持ANSI SQL 标准&#xff0c;允许使用标准的 SQL 语句来处理流式和批处理数据。通过 Flink SQL&#xff0c;可以以声明式的方式描述数据处理逻辑&#xff0c;而无需编写显式的代码…

【2023研电赛】商业计划书赛道上海市一等奖:基于双矢量优化谐波预测控制的MMC-PET光伏储能系统

该作品参与极术社区组织的2023研电赛作品征集活动&#xff0c;欢迎同学们投稿&#xff0c;获取作品传播推广&#xff0c;并有丰富礼品哦~ 团队介绍 参赛单位&#xff1a;上海理工大学 参赛队伍&#xff1a;Dream explorers 参赛队员&#xff1a;吕哲 李天皓 赵安杰 项目意义…

基于Springboot实现商务安全邮箱邮件收发系统项目【项目源码+论文说明】计算机毕业设计

基于Springboot实现商务安全邮箱邮件收发系统演示 摘要 随着社会的发展&#xff0c;社会的方方面面都在利用信息化时代的优势。计算机的优势和普及使得商务安全邮箱的开发成为必需。 本文以实际运用为开发背景&#xff0c;运用软件工程原理和开发方法&#xff0c;采用jsp技术…

Allegro芯片引脚如何散出?

在日常进行PCB设计时,遇到有BGA芯片的项目,引脚就需要散出。那么如何散出呢? 需要对引脚进行散出的BGA芯片 下面详细介绍散出方法。 (1)选择菜单Route(布线) (2)选择Create Fanout (3)在选择散出命令后,在Find选项卡,可以选择Symbol(器件)或Pins(引脚)