1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > springboot 大文件分片上传 断点续传和秒传

springboot 大文件分片上传 断点续传和秒传

时间:2024-04-23 16:27:01

相关推荐

springboot 大文件分片上传 断点续传和秒传

目录

前置说明

获取文件分片

项目流程简述

关键代码解读

表设计SQL

接口测试

测试项目获取地址

前置说明

目前没弄前端,搁置后续再说。前端若打算使用element-ui的el-upload改造分片上传组件的,推荐这篇文章。

获取文件分片

后端自测使用的分片可以通过ChunkFile来获取。

public class ChunkFile {private static final String PATH = "D:/file/test/";private static final String FILE_NAME = "稻香-周杰伦";private static final String FILE_EXTENSION = ".mp4";private static final Integer CHUNK_SIZE = 10485760; // 10MBpublic static void main(String[] args) throws Exception {File file = new File(PATH, FILE_NAME + FILE_EXTENSION);RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");long size = FileUtil.size(file);// 分片数int chunkNum = (int) Math.ceil((double) size / CHUNK_SIZE);byte[] bytes;// 循环进行文件读取并写入数据至分片文件for (int i = 0; i < chunkNum; i++) {// 文件当前偏移量long pointer = randomAccessFile.getFilePointer();int len = i == chunkNum - 1 ? (int) (size - pointer) : CHUNK_SIZE;bytes = new byte[len];randomAccessFile.read(bytes, 0, len);FileUtil.writeBytes(bytes, new File(PATH, i + FILE_EXTENSION));}}}

测试文件大小14.5MB,CHUNK_SIZE分片大小设置为10MB。通过RandomAccessFile进行数据读取,通过hutool的FileUtil工具类进行数据写入,得到下面的0.mp4和1.mp4两个分片文件。

项目流程简述

该测试项目实现的功能:文件普通上传、文件分片上传、断点续传、秒传。文件普通上传不谈,这里展开分片逻辑进行讨论。

第一步:前端在进行大文件上传之前,先通过MD5算法(MD5不是加密算法)获取大文件的MD5值(每个文件的MD5都不一样),将MD5值作为参数调用后端秒传检测接口——目的就是在文件上传前判断文件是否已经上传。此步好处->

好处一:如果文件已经上传过,则不用再上传,文件地址复用即可;

好处二:如果大文件是分片上传的,并且因为某些原因,只上传了部分分片,则接口返回已上传的分片信息,前端可以根据信息判断哪些分片还没有上传,只上传还没有上传的分片即可(断点续传)。

第二步:根据第一步的接口响应信息,判断是否需要进行文件上传。若要上传,调用文件上传接口进行文件或分片文件的上传。(每个分片文件也都需要其MD5值进行校验,分片文件合并在后端自动进行)

关键代码解读

有文件上传记录sys_file和分片记录sys_chunk_record两张表,在下面的文字中分表叫做A表和B表。

一、秒传检测代码

public UploadResultVo fastUploadCheck(Boolean isChunk, String md5) {UploadResultVo vo = new UploadResultVo();// 文件表查找QueryWrapper<SysFile> wrapper = new QueryWrapper<SysFile>().eq("file_md5", md5);List<SysFile> sysFileList = sysFileMapper.selectList(wrapper);if (sysFileList.size() > 0) {String kid = sysFileList.get(0).getKid();vo.setUploaded(true).setFileKid(kid);}if (!vo.getUploaded() && isChunk) {// 分片记录表查询QueryWrapper<SysChunkRecord> chunkWrapper = new QueryWrapper<SysChunkRecord>().eq("file_md5", md5);List<SysChunkRecord> sysChunkRecordList = sysChunkRecordMapper.selectList(chunkWrapper);List<Integer> chunkNum = new ArrayList<>();if (CollectionUtil.isNotEmpty(sysChunkRecordList)) {chunkNum = sysChunkRecordList.stream().map(SysChunkRecord::getChunk).collect(Collectors.toList());}vo.setChunkNum(chunkNum);}return vo;}

isChunk和md5都是秒传检测接口请求传递的参数,含义分别是——是否分片和文件md5值。

isChunk为false:只查询A表,selectList()方法如果有数据返回,则说明之前有上传过相同的文件。设置返回参数——uploaded为true,fileKid为文件记录主键。(这里其实使用mp的selectOne()方法即可,不用使用selectList(),因为表已经做了md5字段的唯一约束)

isChunk为true:查询A表的逻辑不变,另还查询了B表。此处的代码对应上面描述的好处二——告诉前端已经上传成功了哪些分片。

二、分片文件上传代码

public UploadResultVo chunkUpload(MultipartFileParam param) {UploadResultVo vo = new UploadResultVo();// 因为表设置了唯一约束,在插入数据前先判断数据是否已经存在SysFile sf = queryByMd5(param.getMd5());if (ObjectUtil.isNotEmpty(sf)) {return vo.setUploaded(true).setFileKid(sf.getKid());}SysChunkRecord scr = queryByChunkMd5(param.getChunkMd5());if (ObjectUtil.isNotEmpty(scr)) {return vo.setUploaded(false);}File file = buildUploadFile(param);MultipartFile multipartFile = param.getFile();try {// 分片文件md5校验checkMd5(multipartFile.getInputStream(), param.getChunkMd5());// 分片数据写入文件RandomAccessFile accessFile = new RandomAccessFile(file, "rw");if (accessFile.length() == 0) {accessFile.setLength(param.getTotalSize());}FileChannel channel = accessFile.getChannel();int position = (param.getChunk() - 1) * fileConfig.getChunkSize();MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_WRITE, position, multipartFile.getSize());map.put(multipartFile.getBytes());cleanBuffer(map);channel.close();accessFile.close();} catch (IOException e) {e.printStackTrace();}// 分片记录入库saveSysChunkRecord(file, param);// 检测是否为最后一块分片QueryWrapper<SysChunkRecord> wrapper1 = new QueryWrapper<SysChunkRecord>().eq("file_md5", param.getMd5());Integer count = sysChunkRecordMapper.selectCount(wrapper1);if (count.equals(param.getTotalChunk())) {try {// 文件md5检验checkMd5(new FileInputStream(file), param.getMd5());// 文件上传记录入库String kid = saveSysFile(file, param);return vo.setUploaded(true).setFileKid(kid);} catch (FileNotFoundException e) {e.printStackTrace();} finally {// 清除分片记录cleanChunkData(param.getMd5());}}return vo.setUploaded(false);}

MultipartFileParam是对文件上传参数的封装,有是否为分片上传、文件、文件名、md5值、分片数等参数。

先使用md5值查询A表和B表,检查是否已经上传过;然后对当前分片的chunkMd5进行校验,校验通过则通过RandomAccessFile进行数据的写入;写入完成后当前分片记录入库;最后判断当前分片是否是最后一块分片,如果是,则校验完整文件的md5,并完成文件记录的入库和分片记录的删除。

注:buildUploadFile()方法——创建文件上传目录和上传文件。

表设计SQL

CREATE TABLE `sys_file` (`kid` varchar(36) NOT NULL COMMENT 'kid',`file_name` varchar(255) NOT NULL COMMENT '文件名称',`extension` varchar(255) DEFAULT NULL COMMENT '文件扩展名',`file_size` bigint DEFAULT NULL COMMENT '文件大小',`file_path` varchar(255) NOT NULL COMMENT '文件存放路径',`file_md5` varchar(255) NOT NULL COMMENT '文件md5',`remark` varchar(500) DEFAULT NULL COMMENT '备注',`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`kid`),UNIQUE KEY `idx_file_md5` (`file_md5`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC COMMENT='附件表';CREATE TABLE `sys_chunk_record` (`kid` varchar(36) NOT NULL COMMENT 'kid',`chunk_file_name` varchar(255) NOT NULL COMMENT '文件名称',`chunk_file_path` varchar(255) NOT NULL COMMENT '文件存放路径',`file_md5` varchar(255) NOT NULL COMMENT '文件md5',`chunk_file_md5` varchar(255) NOT NULL COMMENT '文件md5',`chunk` int NOT NULL COMMENT '第几块分片',`total_chunk` int NOT NULL COMMENT '总分片数量',`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',PRIMARY KEY (`kid`),UNIQUE KEY `idx_chunk_file_md5` (`chunk_file_md5`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC COMMENT='附件表';

接口测试

测试工具:Apifox,测试时间:/10/21

先上传测试文件的第二块分片,sys_chunk_record表中保存了该分片上传记录;

此时调用秒传校验接口,可看见返回的分片数据;

然后上传测试文件的第一块分片,sys_chunk_record表中保存了该分片上传记录。

因为该分片为最后一块分片,sys_file表中会保存文件上传记录并清除sys_chunk_record表中的分片记录。

上传目录使用年月日格式,文件名是传递的参数fileName。

:测试工具自测时,md5值的获取方式——打开cmd,使用certutil -hashfile命令。如:

测试项目获取地址

——代码结构

/dlqx/springboot-code-book中的bigfile-up-down文件夹。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。