当前位置:   article > 正文

一个程序员的重启-解决大文件上传采用fastDFS分布式文件存储,实现文件分片上传、断点续传,超详细的编码_fastdfs上传大文件

fastdfs上传大文件

《重启7》

技术分享-解决大文件上传采用fastDFS分布式文件存储,实现文件分片上传、断点续传,超详细的编码

引言

最近实习,碰到一个大文件上传的难题,然后公司用的也是使用fastDFS进行存储文件,所以博主这两天一直在学习这方面的知识现在跟大家分享一波。

ps:本文章只介绍后端的实现

实现思路

1.前端首先要将文件分片,并获取关键数据,例如

 /**     
 * 当前要上传第几分片     
 */
 private long chunkNumber;    
 /**     
 * 每个分片的大小     
 */    
 private long chunkSize;    
 /**     
 * 分片总数     
 */    
 private long totalChunks;   
 /**     
 * 文件唯一标识     
 */    
 private String identifier;    
 /**     
 * 分块文件传输对象     
 */   
  private MultipartFile file;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

进行轮巡访问后端提供的接口

2.后端通过相关获取相关值进行业务处理(例如:判断断点,是否重复传),并把file存进fdfs的文件存储位置

环境准备

liunx的centos7系统(用于安装FastDFS,因为FastDFS没有window的安装程序)redis
fdfs的安装步骤:https://blog.csdn.net/qq_29761395/article/details/107577250
步骤很详细觉得不错,希望帮这位大佬点个赞!

后端具体实现流程

在这里插入图片描述

关键代码

普通添加文件

storePath = appendFileStorageClient.uploadAppenderFile(CommonConstant.DEFAULT_GROUP, file.getInputStream(),file.getSize(), fileName);
  • 1

追加文件(拼接)

appendFileStorageClient.modifyFile(CommonConstant.DEFAULT_GROUP, groundPath, file.getInputStream(),file.getSize(), historyUploadSize);                    
  • 1

具体代码

pom

<!-- FastDFS -->
<dependency>
    <groupId>com.github.tobato</groupId>
    <artifactId>fastdfs-client</artifactId>
    <verison>1.26.5</verison>
</dependency>
<!-- SpringBoot Boot Redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <verison>5.0.4</verison>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

controller

/**
	 * 大文件文件分片,断点续传
	 */
	@PostMapping("/uploadBigFile")
	public String uploadBigFile(MultipartFileParam param) throws Exception {
		//当前第几分片        
		long chunkNumber = param.getChunkNumber();        
		//每个分片的大小        
		long chunkSize = param.getChunkSize();        
		//分片总数        
		long totalChunks = param.getTotalChunks();        
		//文件唯一标识        
		String identifier = param.getIdentifier();        
		//分块文件传输对象        
		MultipartFile file = param.getFile();
		//文件后缀名        
		String fileName = FileUtil.extName(file.getOriginalFilename());
		//历史上传文件大小        
		long historyUploadSize = (chunkNumber-1)*chunkSize;        
		//包含组和服务器文件路径,例如:StorePath [group=group1, path=M00/00/00/xxx.xxx]        
		StorePath storePath = null;
		//服务器文件路径,例如:M00/00/00/xxx.xxx        
		String groundPath;
		try{            
			//先检查已经上传到第几片了            
			String checkUploadChunkNum = (String)redisService.getCacheObject(CommonConstant.uploadChunkNum+identifier);
			if (checkUploadChunkNum==null && chunkNumber!=1){               
			//如果当前即将上传的分片不是第一片,而且又查询不到之前的之前已经存在的分片记录,则上传失败     
	    		throw new Exception("上传失败");
			}else if (checkUploadChunkNum!=null && chunkNumber-1<Long.valueOf(checkUploadChunkNum)){
				//当前即将上传的分片序号-1小于已经上传的分片序号,说明已经重复上传了                
				throw new Exception("重复上传");
			}else if (checkUploadChunkNum!=null && chunkNumber-1>Long.valueOf(checkUploadChunkNum)){ 
				//如果当前即将上传的分片序号-1大于已经上传的分片序号,说明提前上传了,需要等待前面序号的分片上传完才能开始上传
				//循环最多执行100次,最多执行100*100/1000=10(秒)
				int time = 100;
				while (time>0){
					//每次循环休眠100毫秒
					Thread.sleep(100);
					time--;
					//每次循环检查已经上传分片的最新序号
					checkUploadChunkNum = (String) redisService.getCacheObject(CommonConstant.uploadChunkNum+identifier);
					 if (chunkNumber-1==Long.valueOf(checkUploadChunkNum)){ 
				 		//如果当前准备上传的分片序号-1等于已经上传分片的最新序号,则跳出循环
				 		break;                  
			    	 }
				}
			}
			//若上传的是第一片,且只有一片,上传完就结束
			if (chunkNumber == 1 ){                
				if (checkUploadChunkNum !=null){                    
					throw new Exception("第一片已经上传");
				}                
				storePath = appendFileStorageClient.uploadAppenderFile(CommonConstant.DEFAULT_GROUP, file.getInputStream(),file.getSize(), fileName);
				//记录当前传入第一片
			    redisService.setCacheObject(CommonConstant.uploadChunkNum+identifier,String.valueOf(chunkNumber),cacheTime, TimeUnit.SECONDS);
				if (storePath == null){                    
					throw new Exception("第一片上传失败");
				}                               
				//总共只有一片就返回                
				if (totalChunks == 1){                    
					//如果只有一片,直接返回结果
					redisService.deleteObject(CommonConstant.uploadChunkNum+identifier);
					return storePath.getPath();                
				}else {                    
					//记录已存存文件路径
					redisService.setCacheObject(CommonConstant.fastDfsPath+identifier,storePath.getPath(),cacheTime,TimeUnit.SECONDS);
					return "第:"+chunkNumber+"上传成功";                
				}
			} else {
				if (chunkNumber-1 == Long.valueOf(checkUploadChunkNum)){                    
					//获取已存文件路径                    
					groundPath = (String) redisService.getCacheObject(CommonConstant.fastDfsPath+identifier);
					if (groundPath == null){                        
						throw new Exception("获取文件路径失败");
					}                    
					//追加文件
					appendFileStorageClient.modifyFile(CommonConstant.DEFAULT_GROUP, groundPath, file.getInputStream(),file.getSize(), historyUploadSize);                    
					//修改已存文件片数
					redisService.setCacheObject(CommonConstant.uploadChunkNum+identifier,String.valueOf(chunkNumber),cacheTime,TimeUnit.SECONDS);
					if (chunkNumber == totalChunks){                        
						//最后一片,返回结果
						redisService.deleteObject(CommonConstant.uploadChunkNum+identifier);
						return groundPath;                    
					}                    
					return "第:"+chunkNumber+"上传成功";                
				}else {                    
//					logger.error("第:"+chunkNumber+"上传失败,原因:当前上传");
					throw new Exception("第:"+chunkNumber+"上传失败");
				}
			}
		}catch (Exception e){            
			e.printStackTrace();            
//			logger.error("第:"+chunkNumber+"上传失败,原因:{}",e);
			throw new Exception("第:"+chunkNumber+"上传失败");
		}
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97

parm(工具类)

public class CommonConstant {
		private  final static  String uploading="Uploading:";    
   		 private  final  static String file=uploading+"file:";    
	    /**     
	    * 记录当前文件上传了多少片     
	    */    
	    public   final static  String uploadChunkNum=file+"chunkNum:";    
	    /**     
	    * 当前文件上传到fastdfs路径     
	    * */    
	    public final static String fastDfsPath=file+"fastDfsPath:";    
	    /**     
	    * 默认分组     
	    */    
	    public final static  String DEFAULT_GROUP = "group1";
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class MultipartFileParam {
	 /**     
	 * 当前要上传第几分片     
	 */
	 private long chunkNumber;    
	 /**     
	 * 每个分片的大小     
	 */    
	 private long chunkSize;    
	 /**     
	 * 分片总数     
	 */    
	 private long totalChunks;   
	 /**     
	 * 文件唯一标识     
	 */    
	 private String identifier;    
	 /**     
	 * 分块文件传输对象     
	 */   
	  private MultipartFile file;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

整个实现不易,求求点赞支持下!!!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/170083
推荐阅读
相关标签
  

闽ICP备14008679号