斷點續傳一般在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();
}