手撸大文件上传:实现切片上传,断点上传和文件秒传的功能。

news2024/11/17 14:24:38

一、前提说明

此文章主要讲述后端服务代码和前后端实现思路部分,不涉及前端代码。

二、应用场景

上传视频等大文件的时候,调用服务器的上传接口,可能出现因为文件过大,连接时间超时导致的上传失败,如果文件太大了,可能出现上传一半网络异常,从而再次上传需要重新开始上传。为了解决这种场景问题,手写一个大文件上传实现切片上传,断点上传和文件秒传的功能。

三、概念

切片:切片上传是一种将大文件分割成多个小文件的方式,此时小文件就是切片。
断点上传:在文件分块的基础上,将每个小文件采用单独的线程进行上传\下载,如果碰到网络故障,可以从已经上传\下载的部分开始继续上传\下载未完成的部分,而没有必要从头开始上传\下载。
秒传:当文件上传时,文件资源标识存在时,文件不再重新上传,而直接返回文件URL。

四、思路

4.1 前端思路

  1. 获取大文件信息包含(文件名称,文件大小,格式类型
  2. 大文件分块可以利用强大的​​js​​库或者现成的组件进行分块处理。需要确定分块【chunk】的大小和分块的总数量【chunkChecksum】,然后为每一个分块指定一个索引值【chunkIndex】
  3. 为了实现秒传的功能,需要将文件的文件名称,文件大小,格式类型拼接然后进行MD5加密作为文件的唯一标识【fileId】
  4. 循环切块,将上面标红的信息作为参数传给接口,此时将接口返回的date,作为下一个需要传的切片索引,再次走接口,这是为了实现断点上传。
  5. 直到接口返回date是url地址为止,此时上传完成。
  6. 文件的暂停和继续上传由前端自行研究。

4.2 后端思路

redis使用redisTemplate.opsForList()的方式存贮切片,然后最后循环缓存合并,最好使用redisTemplate.opsForList().rightPush(key,value)存,并且设置过期时间防止上传一部分的放弃上传造成内存占用。redis使用自行百度。

  1. 获取到前端传的信息,利用redis暂存切片。
  2. 根据当前大文件的fileId,然后去数据库查询,如果存在,则直接返回当前大文件的地址,实现秒传功能
  3. 判断当前切片索引是否上传过,上传过则在判断如果已存在切片数量=总数量则直接合成切片,否则直接返回下一个需要上传的切片索引值,
  4. 当前切片没有上传过,则需要把当前文件暂时保存到redis中,并且返回下一个需要上传的切片索引值
  5. 判断是否切片上传完成,上传完成则合并切片形成大文件,否则直接进行下一个切片上传

五、接口实现过程

	/**
	 * 上传大文件
	 *
	 * @param chunk         切片文件
	 * @param chunkIndex    切片索引
	 * @param chunkChecksum 切片总数
	 * @param fileFormat    文件格式
	 * @param fileId        大文件标志(最好是让前端把文件的(名称+大小+类型)进行MD5加密)
	 * @return
	 * @throws Exception
	 */
	@Inside(value = false)
	@PostMapping("/uploadBigFile")
	public R uploadFile(@RequestParam("chunk") MultipartFile chunk,
						@RequestParam("chunkIndex") Integer chunkIndex,
						@RequestParam("chunkChecksum") Integer chunkChecksum,
						@RequestParam("fileFormat") String fileFormat,
						@RequestParam("fileId") String fileId) throws Exception {
		log.info("切片索引" + chunkIndex);
		log.info("切片总数" + chunkChecksum);
		String key = CommonConstants.FILE_KEY + fileId;
		//STEP 获取当前大文件的id,然后去数据库查询,如果存在,则直接返回当前大文件的地址,实现秒传功能
		Material material = materialService.getByFileId(fileId);
		if (material != null) {
			return R.ok(material.getUrl(), "上传成功,实现秒传");
		}
		//STEP 判断当前切片索引是否上传过,上传过则在判断如果已存在切片数量=总数量则直接合成切片,否则直接返回下一个需要上传的切片索引值,
		Object chunkIndexRedis = redisTemplate.opsForList().index(key, chunkIndex);
		if (chunkIndexRedis != null) {
			Long chunkIndexRedisMax = redisTemplate.opsForList().size(key);
			log.info("已经上传的切片数量" + chunkIndexRedisMax);
			int chunkIndexRedisIntMax = chunkIndexRedisMax.intValue();
			if (chunkIndexRedisIntMax == chunkChecksum) {
				String merge = merge(key, fileFormat);
				redisTemplate.delete(key);
				return R.ok(merge, "上传完成,实现切片合并异常");
			}
			return R.ok(chunkIndexRedisMax + 1, "上传成功,实现断点功能");
		}
		//STEP 当前切片没有上传过,则需要把当前文件暂时保存到redis中,并且返回下一个需要上传的切片索引值
		//分片文件大小
		byte[] chunkBytes = chunk.getBytes();
		redisTemplate.opsForList().rightPush(key, chunkBytes);
		redisTemplate.expire(key, CommonConstants.FILE_KEY_OUT_TIME, TimeUnit.MINUTES);
		//STEP 判断是否切片上传完成,上传完成则合并切片形成大文件,否则直接进行下一个切片上传
		if (chunkIndex == chunkChecksum) {
			String merge = merge(key, fileFormat);
			redisTemplate.delete(key);
			return R.ok(merge, "上传完成,实现全部切片上传");
		}
		return R.ok(chunkIndex + 1, "上传成功,实现当前切片上传");
	}

	/**
	 * 合并切片,把切片写入到文件夹中
	 *
	 * @param key        redis中的key
	 * @param fileFormat 文件格式
	 * @return
	 * @throws FileNotFoundException
	 */
	public String merge(String key, String fileFormat) throws FileNotFoundException {
		log.info("合并切片");
		//文件名称随机生成(文件名+.+后缀)
		String fileName = IdUtil.getSnowflake(0, 0).nextId() + "." + fileFormat;
		//文件地址
		String path = UpmsConstants.TOMCAT_PATH + "/video/";
		//判断文件是否存在,不存在创建文件
		File newFile = new File(path);
		//如果文件夹不存在
		if (!newFile.exists()) {
			//创建文件夹
			newFile.mkdir();
		}
		FileOutputStream outputStream = new FileOutputStream(path + fileName, true);//文件追加写入
		FileInputStream fileInputStream = null;//分片文件
		try {
			List<byte[]> chunkList = redisTemplate.opsForList().range(key, 0, -1);
			for (byte[] bytes : chunkList) {
				outputStream.write(bytes);
			}
		} catch (IOException e) {
			log.error("分片合并异常", e);
		} finally {
			try {
				if (fileInputStream != null) {
					fileInputStream.close();
				}
				outputStream.close();
				log.info("IO流关闭");
				System.gc();
			} catch (Exception e) {
				log.error("IO流关闭", e);
			}
		}
		log.info("合并分片结束");
		return UpmsConstants.TOMCAT_Url + "video/" + fileName;
	}

六、日志结果

此时日志打印如下:
在这里插入图片描述
文件存储成功并且可以成功播放。

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

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

相关文章

如何修复Python中的缩进错误?

目录 缩进的概念和规则 缩进错误的类型 &#xff08;1&#xff09;缩进量错误 &#xff08;2&#xff09;缩进范围错误 修复缩进错误的方法 &#xff08;1&#xff09;检查代码块的层次结构 &#xff08;2&#xff09;统一使用空格或制表符 &#xff08;3&#xff09;使…

java三层架构/表现层-业务层-持久层

三层架构 什么是 Java 三层架构 三层架构是指&#xff1a;视图层view&#xff08;表现层&#xff09;&#xff0c;服务层service&#xff08;业务逻辑层&#xff09;&#xff0c;持久层Dao&#xff08;数据访问层&#xff09;&#xff0c; Java的三层架构是指将Java程序分为三…

公众号留言功能卖多少钱?报价多少?

为什么公众号没有留言功能&#xff1f;2018年2月12日之后直到现在&#xff0c;新注册公众号的运营者会发现一个问题&#xff1a;无论是个人还是企业的公众号&#xff0c;在后台都找不到留言功能了。这对公众号来说绝对是一个极差的体验&#xff0c;少了一个这么重要的功能&…

Vue2基础知识(二) 计算属性/侦听器/生命周期

&#x1f48c; 所属专栏&#xff1a;【Vue2】&#x1f600; 作 者&#xff1a;长安不及十里&#x1f4bb;工作&#xff1a;目前从事电力行业开发&#x1f308;目标&#xff1a;全栈开发&#x1f680; 个人简介&#xff1a;一个正在努力学技术的Java工程师&#xff0c;专注基础和…

vue记住密码

<div class"checkbox-login"><input type"checkbox" id"defaults" v-model"loginForm.rememberMe" /><label class"label" for"defaults">记住密码</label></div> .checkbox-logi…

RPA的尽头是超自动化?

超自动化在经过数年的发酵期后&#xff0c;已从一个科技概念崛起为市值近千亿元的新赛道&#xff0c;包括各大互联网巨头、科技公司都纷纷围绕超自动化进行战略布局。 一方面&#xff0c;是行业巨头选择纷纷跻身超自动化新赛道&#xff0c;另一方面&#xff0c;RPA行业的领军企…

邯郸学院软件学院软件工程专业教师参加“火焰杯”软件测试颁奖典礼

近日&#xff0c;全国第三届“火焰杯”软件测试河北赛区颁奖典礼在河北工程技术学院举行。我院软件工程教研室主任张颖、教师王金如受邀参与此次颁奖典礼。王金如老师获得大赛优秀指导教师二等奖&#xff0c;软件学院荣获优秀组织单位奖。 赛事开始之际&#xff0c;学院就积极…

如何安装nvm管理node版本

如果已经有node可以先卸载&#xff08;也可以不卸载&#xff0c;安装nvm的时候会有提示&#xff0c;可以管理现有的node&#xff09; 一、在控制面板卸载程序中卸载现有的node 二、下载nvm并进行安装 nvm官网地址&#xff1a;nvm文档手册 - nvm是一个nodejs版本管理工具 - nvm…

JMH:让你的Java程序性能翻倍的神器

大家好&#xff01;今天我要向大家详细介绍JMH&#xff08;Java Microbenchmark Harness&#xff09;&#xff0c;这个被誉为Java性能测试的利器。无论你是想优化现有的Java代码还是开发新的项目&#xff0c;JMH都能够帮助你准确、可靠地测量和分析代码的性能&#xff0c;让我们…

复制交易为什么用经纪商信号?anzo capital昂首资本3点理由心服口服

为什么那么多成功的交易者喜欢复制经纪商的信号进行交易呢&#xff1f;anzo capital昂首资本认为这种模式具有以下优势&#xff1a; 首先&#xff0c;复制信号是免费的&#xff0c;投资者无需支付任何费用即可享受到信号提供商的交易策略。 其次&#xff0c;交易员的排名是透…

双指针——移动零

一&#xff0c;题目要求&#xff1a; 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 示例 1: 输入: nums [0,1,0,3,12] 输出: [1,3,12,0,0…

零代码编程:用ChatGPT下载lexfridman的所有播客音频和文本

莱克斯弗里德曼&#xff08;Lex Fridman&#xff09;&#xff0c;男&#xff0c;麻省理工学院&#xff08;MIT&#xff09;研究科学家兼播客节目主持人&#xff0c;是一位俄罗斯裔美国计算机科学家。2014年&#xff0c;弗里德曼加入谷歌&#xff0c;但在6个月后离开了公司。201…

苹果电脑如何修改文件创建时间

如果您想修改 Mac 上的文件创建时间&#xff0c;可以采用几种不同的方法。您可以使用 Finder 修改文件创建时间&#xff0c;也可以使用终端修改文件创建时间。当然&#xff0c;您还可以使用第三方应用软件进行修改文件创建时间。 小编比较懒&#xff0c;不喜欢太麻烦的操作&am…

苹果手机怎么隐藏照片?(详细图文教程)

我们的手机相册中可能会保存一些与个人隐私相关的照片&#xff0c;比如&#xff1a;银行卡、身份证、护照等私人信息。这些照片如果不进行加密处理的话&#xff0c;会很容易泄露出去。 在别人使用您的手机时&#xff0c;如果您不想这些隐私照片被人看到该怎么办呢&#xff1f;…

Java基础练习(矩阵的加减乘除运算)

简介 对于有了解&#xff0c;但是了解不深的同学&#xff0c;学习Java总是感觉一看就会&#xff0c;一些就废。往往需要一些实操练习&#xff0c;来夯实我们的学习结果。九九乘法表和计算器都是在编程学习领域比较经典的案例。本文为大家讲解一下两个基础实操&#xff0c;熟悉一…

在线JSON转EXCEL工具

全天下的柔情共十分&#xff0c;你占八分。你喊我名字那晚的凉风秋月算一分&#xff0c;其余所有占一分。 推荐 在线JSON转Excel工具 - WeJSON 工具简介 在线JSON转Excel工具&#xff0c;可以快速将JSON数组数据一键转换为Excel格式&#xff0c;方便数据的可视化和交流。 所…

基于Qt设计的邮件收发管理系统(垃圾邮件识别)

基于Qt设计的邮件收发管理系统(垃圾邮件识别) 一、项目背景 随着互联网的发展,邮件成为人们沟通交流不可或缺的一部分。然而,随之而来的是大量的垃圾邮件和欺诈邮件,给人们的生活造成了很大的困扰和威胁。为了解决这个问题,本文提出了一种基于Qt设计的朴素贝叶斯算法邮件收…

SSL证书品牌 Positive

Positive品牌的SSL证书具有以下优势&#xff1a; 1. 安全性&#xff1a;Positive SSL证书提供强大的加密算法&#xff0c;确保通过网站传输的数据得到保护&#xff0c;防止被未经授权的第三方窃取或篡改。 2. 可信度&#xff1a;Positive SSL证书由全球知名的认证机构颁发&am…

AIGC|一文揭秘如何利用MYSCALE实现高效图像搜索?

图像搜索已成为一种流行且功能强大的能力&#xff0c;使用户能够通过匹配功能或视觉内容来查找相似的图像。随着计算机视觉和深度学习的快速发展&#xff0c;这种能力得到了极大的增强。 本文主要介绍如何基于矢量数据库MYSCALE来实现图像搜索功能。 一、MySCALE简介 MyScale 是…

win10下yolov6 tensorrt模型部署

TensorRT系列之 Win10下yolov8 tensorrt模型加速部署 TensorRT系列之 Linux下 yolov8 tensorrt模型加速部署 TensorRT系列之 Linux下 yolov7 tensorrt模型加速部署 TensorRT系列之 Linux下 yolov6 tensorrt模型加速部署 TensorRT系列之 Linux下 yolov5 tensorrt模型加速部署…