断点续传一般在header中需要携带Content-Range
后端接收该header,正则校验如下:
public static final String RANGE_PATTERN = "bytes \\d+-\\d+/\\d+";
上传的时候,加一张临时表,文件信息保存到临时表,并生成一个UUID,上传完成了再插入到主表,并返回前端一个文件的fileId
代码如下:
@Override
@Transactional(rollbackFor = Exception.class)
public UploadResumeDTO uploadFileResume(MultipartFile file,
UploadFileResumeRequest uploadFileResumeRequest,
String range) {
long start = System.currentTimeMillis();
Long fileId = null;
InputStream inputStream = null;
FileInputStream fileInputStream;
FileChannel inChannel = null;
FileOutputStream outStream = null;
FileChannel outChannel = null;
long uploadSize = 0;
long length = 0;
// 解析Content-Range
int startByte = 0;
int endByte = 0;
try {
String bytes = range.replaceAll("bytes", "").trim();
String[] split = bytes.split("-");
startByte = Integer.parseInt(split[0]);
String[] split1 = split[1].split("/");
endByte = Integer.parseInt(split1[0]);
} catch (Exception e) {
log.error("header=====>Content-Range转换失败:{}", range);
}
// 文件上传
FileTemp fileTemp = new FileTemp();
BeanUtils.copyProperties(uploadFileResumeRequest, fileTemp);
File destFile = new File(config.getRootPath() + file.getOriginalFilename());
try {
if (!destFile.exists()) {
destFile.createNewFile();
}
length = destFile.length();
// 避免重复上传文件
if (uploadSize + length >= uploadFileResumeRequest.getFileSize()) {
return UploadResumeDTO.builder()
.uuid(uploadFileResumeRequest.getUuid())
.fileSize(uploadSize + length)
.build();
}
inputStream = file.getInputStream();
fileInputStream = (FileInputStream) inputStream;
inChannel = fileInputStream.getChannel();
outStream = new FileOutputStream(destFile, true);
outChannel = outStream.getChannel();
// 第二个参数为传多少字节
uploadSize = inChannel.transferTo(startByte, endByte - startByte, outChannel);
log.debug("总上传:{}", uploadSize);
log.debug("文件目前大小:{}", uploadSize + length);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
if (outStream != null) {
outStream.close();
}
if (inChannel != null) {
inChannel.close();
}
if (outChannel != null) {
outChannel.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
List<FileTemp> tempList = getTempFile(uploadFileResumeRequest.getUuid());
// 首次上传
if (CollectionUtils.isEmpty(tempList)) {
log.info("不存在临时文件:{}", uploadFileResumeRequest.getUuid());
// 上传完成
if (uploadFileResumeRequest.getFileSize() == uploadSize + length) {
// 首次就上传完,直接插入到主表
DeviceFileInfoVO deviceFileInfoVO = new DeviceFileInfoVO();
BeanUtils.copyProperties(uploadFileResumeRequest, deviceFileInfoVO);
deviceFileInfoVO.setSourceType(uploadFileResumeRequest.getSourceType().getCode());
deviceFileInfoVO.setFileName(file.getOriginalFilename());
deviceFileInfoVO.setFilePath(config.getRootPath());
devMapper.insert(deviceFileInfoVO);
fileId = deviceFileInfoVO.getFileId();
} else {
// 没有上传完成,则插入临时表
fileTemp.setFilePath(config.getRootPath());
fileTemp.setFileName(file.getOriginalFilename());
fileTemp.setFileSize(uploadSize + length);
fileTempMapper.insert(fileTemp);
}
}
// 非首次上传
if (CollectionUtils.isNotEmpty(tempList)) {
// 非首次,临时表已经存在记录
if (uploadFileResumeRequest.getFileSize() == uploadSize + length) {
// 上传完,直接插入到主表
DeviceFileInfoVO deviceFileInfoVO = new DeviceFileInfoVO();
BeanUtils.copyProperties(uploadFileResumeRequest, deviceFileInfoVO);
deviceFileInfoVO.setSourceType(uploadFileResumeRequest.getSourceType().getCode());
deviceFileInfoVO.setFileName(file.getOriginalFilename());
deviceFileInfoVO.setFilePath(config.getRootPath());
devMapper.insert(deviceFileInfoVO);
fileId = deviceFileInfoVO.getFileId();
fileTempMapper.deleteById(fileTemp);
} else {
fileTemp = tempList.get(0);
fileTemp.setFileSize(uploadSize + length);
fileTempMapper.updateById(fileTemp);
}
}
log.info("耗时:{} ms", System.currentTimeMillis() - start);
return UploadResumeDTO.builder()
.uuid(uploadFileResumeRequest.getUuid())
.fileSize(uploadSize + length)
.fileId(fileId == null ? null : fileId.toString())
.build();
}