Bootstrap

大文件上传:秒传、断点续传、分片上传

2.分片上传的场景

1.大文件上传

2.网络环境环境不好,存在需要重传风险的场景

断点续传
1、什么是断点续传

断点续传是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传或者下载未完成的部分,而没有必要从头开始上传或者下载。本文的断点续传主要是针对断点上传场景。

2、应用场景

断点续传可以看成是分片上传的一个衍生,因此可以使用分片上传的场景,都可以使用断点续传。

3、实现断点续传的核心逻辑

在分片上传的过程中,如果因为系统崩溃或者网络中断等异常因素导致上传中断,这时候客户端需要记录上传的进度。在之后支持再次上传时,可以继续从上次上传中断的地方进行继续上传。

为了避免客户端在上传之后的进度数据被删除而导致重新开始从头上传的问题,服务端也可以提供相应的接口便于客户端对已经上传的分片数据进行查询,从而使客户端知道已经上传的分片数据,从而从下一个分片数据开始继续上传。

4、实现流程步骤

a、方案一,常规步骤

  • 将需要上传的文件按照一定的分割规则,分割成相同大小的数据块;

  • 初始化一个分片上传任务,返回本次分片上传唯一标识;

  • 按照一定的策略(串行或并行)发送各个分片数据块;

  • 发送完成后,服务端根据判断数据上传是否完整,如果完整,则进行数据块合成得到原始文件。

b、方案二、本文实现的步骤

  • 前端(客户端)需要根据固定大小对文件进行分片,请求后端(服务端)时要带上分片序号和大小

  • 服务端创建conf文件用来记录分块位置,conf文件长度为总分片数,每上传一个分块即向conf文件中写入一个127,那么没上传的位置就是默认的0,已上传的就是Byte.MAX_VALUE 127(这步是实现断点续传和秒传的核心步骤)

  • 服务器按照请求数据中给的分片序号和每片分块大小(分片大小是固定且一样的)算出开始位置,与读取到的文件片段数据,写入文件。

5、分片上传/断点上传代码实现

a、前端采用百度提供的webuploader的插件,进行分片。因本文主要介绍服务端代码实现,webuploader如何进行分片,具体实现可以查看如下链接:

http://fex.baidu.com/webuploader/getting-started.html

b、后端用两种方式实现文件写入,一种是用RandomAccessFile,如果对RandomAccessFile不熟悉的朋友,可以查看如下链接:

https://blog.csdn.net/dimudan2015/article/details/81910690

另一种是使用MappedByteBuffer,对MappedByteBuffer不熟悉的朋友,可以查看如下链接进行了解:

https://www.jianshu.com/p/f90866dcbffc

后端进行写入操作的核心代码

a、RandomAccessFile实现方式

@UploadMode(mode = UploadModeEnum.RANDOM_ACCESS)

@Slf4j

public class RandomAccessUploadStrategy extends SliceUploadTemplate {

@Autowired

private FilePathUtil filePathUtil;

@Value(“${upload.chunkSize}”)

private long defaultChunkSize;

@Override

public boolean upload(FileUploadRequestDTO param) {

RandomAccessFile accessTmpFile = null;

try {

String uploadDirPath = filePathUtil.getPath(param);

File tmpFile = super.createTmpFile(param);

accessTmpFile = new RandomAccessFile(tmpFile, “rw”);

//这个必须与前端设定的值一致

long chunkSize = Objects.isNull(param.getChunkSize()) ? defaultChunkSize * 1024 * 1024

: param.getChunkSize();

long offset = chunkSize * param.getChunk();

//定位到该分片的偏移量

accessTmpFile.seek(offset);

//写入该分片数据

accessTmpFile.write(param.getFile().getBytes());

boolean isOk = super.checkAndSetUploadProgress(param, uploadDirPath);

return isOk;

} catch (IOException e) {

log.error(e.getMessage(), e);

} finally {

FileUtil.close(accessTmpFile);

}

return false;

}

}

b、MappedByteBuffer实现方式

@UploadMode(mode = UploadModeEnum.MAPPED_BYTEBUFFER)

@Slf4j

public class MappedByteBufferUploadStrategy extends SliceUploadTemplate {

@Autowired

private FilePathUtil filePathUtil;

@Value(“${upload.chunkSize}”)

private long defaultChunkSize;

@Override

public boolean upload(FileUploadRequestDTO param) {

RandomAccessFile tempRaf = null;

FileChannel fileChannel = null;

MappedByteBuffer mappedByteBuffer = null;

try {

String uploadDirPath = filePathUtil.getPath(param);

File tmpFile = super.createTmpFile(param);

tempRaf = new RandomAccessFile(tmpFile, “rw”);

fileChannel = tempRaf.getChannel();

long chunkSize = Objects.isNull(param.getChunkSize()) ? defaultChunkSize * 1024 * 1024

: param.getChunkSize();

//写入该分片数据

long offset = chunkSize * param.getChunk();

byte[] fileData = param.getFile().getBytes();

mappedByteBuffer = fileChannel

.map(FileChannel.MapMode.READ_WRITE, offset, fileData.length);

mappedByteBuffer.put(fileData);

boolean isOk = super.checkAndSetUploadProgress(param, uploadDirPath);

return isOk;

} catch (IOException e) {

log.error(e.getMessage(), e);

} finally {

FileUtil.freedMappedByteBuffer(mappedByteBuffer);

FileUtil.close(fileChannel);

FileUtil.close(tempRaf);

}

return false;

}

}

c、文件操作核心模板类代码

@Slf4j

public abstract class SliceUploadTemplate implements SliceUploadStrategy {

public abstract boolean upload(FileUploadRequestDTO param);

protected File createTmpFile(FileUploadRequestDTO param) {

FilePathUtil filePathUtil = SpringContextHolder.getBean(FilePathUtil.class);

param.setPath(FileUtil.withoutHeadAndTailDiagonal(param.getPath()));

String fileName = param.getFile().getOriginalFilename();

String uploadDirPath = filePathUtil.getPath(param);

String tempFileName = fileName + “_tmp”;

File tmpDir = new File(uploadDirPath);

File tmpFile = new File(uploadDirPath, tempFileName);

if (!tmpDir.exists()) {

tmpDir.mkdirs();

}

return tmpFile;

}

@Override

public FileUploadDTO sliceUpload(FileUploadRequestDTO param) {

boolean isOk = this.upload(param);

if (isOk) {

File tmpFile = this.createTmpFile(param);

FileUploadDTO fileUploadDTO = this.saveAndFileUploadDTO(param.getFile().getOriginalFilename(), tmpFile);

return fileUploadDTO;

}

String md5 = FileMD5Util.getFileMD5(param.getFile());

Map<Integer, String> map = new HashMap<>();

map.put(param.getChunk(), md5);

return FileUploadDTO.builder().chunkMd5Info(map).build();

}

/**

* 检查并修改文件上传进度

*/

public boolean checkAndSetUploadProgress(FileUploadRequestDTO param, String uploadDirPath) {

String fileName = param.getFile().getOriginalFilename();

File confFile = new File(uploadDirPath, fileName + “.conf”);

byte isComplete = 0;

RandomAccessFile accessConfFile = null;

try {

accessConfFile = new RandomAccessFile(confFile, “rw”);

//把该分段标记为 true 表示完成

System.out.println(“set part " + param.getChunk() + " complete”);

//创建conf文件文件长度为总分片数,每上传一个分块即向conf文件中写入一个127,那么没上传的位置就是默认0,已上传的就是Byte.MAX_VALUE 127

Java核心架构进阶知识点

面试成功其实都是必然发生的事情,因为在此之前我做足了充分的准备工作,不单单是纯粹的刷题,更多的还会去刷一些Java核心架构进阶知识点,比如:JVM、高并发、多线程、缓存、Spring相关、分布式、微服务、RPC、网络、设计模式、MQ、Redis、MySQL、设计模式、负载均衡、算法、数据结构、kafka、ZK、集群等。而这些也全被整理浓缩到了一份pdf——《Java核心架构进阶知识点整理》,全部都是精华中的精华,本着共赢的心态,好东西自然也是要分享的

image

image

image

内容颇多,篇幅却有限,这就不在过多的介绍了,大家可根据以上截图自行脑补

7

Java核心架构进阶知识点

面试成功其实都是必然发生的事情,因为在此之前我做足了充分的准备工作,不单单是纯粹的刷题,更多的还会去刷一些Java核心架构进阶知识点,比如:JVM、高并发、多线程、缓存、Spring相关、分布式、微服务、RPC、网络、设计模式、MQ、Redis、MySQL、设计模式、负载均衡、算法、数据结构、kafka、ZK、集群等。而这些也全被整理浓缩到了一份pdf——《Java核心架构进阶知识点整理》,全部都是精华中的精华,本着共赢的心态,好东西自然也是要分享的

[外链图片转存中…(img-ZHeLZHpe-1714194796659)]

[外链图片转存中…(img-1RCjo7Kw-1714194796659)]

[外链图片转存中…(img-DDYJVvX6-1714194796660)]

内容颇多,篇幅却有限,这就不在过多的介绍了,大家可根据以上截图自行脑补

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

;