赞
踩
引言
最近实习,碰到一个大文件上传的难题,然后公司用的也是使用fastDFS进行存储文件,所以博主这两天一直在学习这方面的知识现在跟大家分享一波。
ps:本文章只介绍后端的实现
1.前端首先要将文件分片,并获取关键数据,例如
/** * 当前要上传第几分片 */ private long chunkNumber; /** * 每个分片的大小 */ private long chunkSize; /** * 分片总数 */ private long totalChunks; /** * 文件唯一标识 */ private String identifier; /** * 分块文件传输对象 */ private MultipartFile file;
进行轮巡访问后端提供的接口
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);
追加文件(拼接)
appendFileStorageClient.modifyFile(CommonConstant.DEFAULT_GROUP, groundPath, file.getInputStream(),file.getSize(), historyUploadSize);
具体代码
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>
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+"上传失败"); } }
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"; }
@Data @AllArgsConstructor @NoArgsConstructor @ToString public class MultipartFileParam { /** * 当前要上传第几分片 */ private long chunkNumber; /** * 每个分片的大小 */ private long chunkSize; /** * 分片总数 */ private long totalChunks; /** * 文件唯一标识 */ private String identifier; /** * 分块文件传输对象 */ private MultipartFile file; }
整个实现不易,求求点赞支持下!!!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。