入参:
//任务ID private String identifier; //当前为第几块分片 private int chunkNumber; //总分片数量 private int totalChunks; //当前分片大小 private long currentChunkSize = 0L; //每分片大小 private long chunkSize = 0L; //总大小 private long totalSize = 0L; //文件名 private String filename; //分片对象 private MultipartFile file;
逻辑:
校验资源类型
判断分片文件数量(如果只有一个分片文件就直接上传,多个分片文件才走特殊流程)
if (param.getTotalChunks() == 1) {
return uploadFile(param);
}
多个文件上传到指定唯一存放数据的服务器临时目录(不能放在多实例服务器上面)
每个上传文件存储redis并判断是否传完
redisUtil.set(RedisConstant.UPLOAD_FILE.concat(param.getIdentifier() + (param.getChunkNumber() - 1)), param.getFilename(), 600, TimeUnit.SECONDS);
boolean isSuccess = true;
for (int i = 0; i < param.getTotalChunks(); i++) {
if (!redisUtil.hasKey(RedisConstant.UPLOAD_FILE.concat(param.getIdentifier() + i))) {
isSuccess = false;
}
}
if (isSuccess) {
return mergeMultipleFile(param);
}
上传完成开始合并
创建10个线程数量的线程池下载所有的块文件
private final ExecutorService threadPool = Executors.newFixedThreadPool(10);
private File[] downloadChunkFilesFromMinio(MultipartFileParam param, String temporaryFilePath) throws Exception {
// 得到分块文件所在目录
String chunkFileFolderPath = temporaryFilePath + param.getIdentifier();
// 块总数
int chunkTotal = param.getTotalChunks();
// 分块文件数组
File[] chunkFiles = new File[chunkTotal];
// 设置计数器
CountDownLatch countDownLatch = new CountDownLatch(chunkTotal);
// 开始逐个下载
for (int i = 0; i < chunkTotal; i++) {
int index = i;
threadPool.execute(() -> {
//得到分块文件的路径
String chunkFilePath = chunkFileFolderPath + index;
//下载分块文件
try {
chunkFiles[index] = minioUtils.downloadFile("chunk", null, bucketName, chunkFilePath);
} catch (Exception e) {
// 计数器减1
countDownLatch.countDown();
throw new RuntimeException(e);
}
// 计数器减1
countDownLatch.countDown();
});
}
/*
阻塞到任务执行完成,当countDownLatch计数器归零,这里的阻塞解除等待,
给一个充裕的超时时间,防止无限等待,到达超时时间还没有处理完成则结束任务
*/
countDownLatch.await(30, TimeUnit.MINUTES);
//返回所有块文件
return chunkFiles;
}
将所有块文件合成生成一个新的完整临时源文件
上传该临时源文件到指定目录
删除所有的临时文件
//开始合成
int index = 0;
for (File file : chunkFiles) {
//上传文件到指定目录
File newFile = null;
try {
// 创建临时文件
String tmpFileName = param.getIdentifier() + "_tmp";
File tmpDir = new File(tempPath);
if (!tmpDir.exists()) {
tmpDir.mkdirs();
}
File tmpFile = new File(tempPath, tmpFileName);
RandomAccessFile tempRaf = new RandomAccessFile(tmpFile, "rw");
FileChannel fileChannel = tempRaf.getChannel();
//写入该分片数据
long offset = param.getChunkSize() * index;
FileInputStream fileInputStream = new FileInputStream(file);
MultipartFile multipartFile = new MockMultipartFile(file.getName(), file.getName(), ContentType.OCTET_STREAM.toString(), fileInputStream);
byte[] fileData = multipartFile.getBytes();
MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, offset, fileData.length);
mappedByteBuffer.put(fileData);
// 释放
fileChannel.close();
//检查文件是否已完整
boolean isOk = checkAndSetUploadProgress(param.getTotalChunks(), index, tempPath, param.getIdentifier());
if (isOk) {
String fileName = param.getFilename();
int lastIndex = fileName.lastIndexOf(".");
String type = fileName.substring(lastIndex + 1);
String newFileName = resourceId.concat("_").concat(String.valueOf(System.currentTimeMillis())).concat(".").concat(type);
newFile = new File(tempPath, newFileName);
//修改文件名
tmpFile.renameTo(newFile);
FileInputStream newFileInputStream = new FileInputStream(newFile);
MultipartFile newMultipartFile = new MockMultipartFile(newFile.getName(), newFile.getName(), ContentType.OCTET_STREAM.toString(), newFileInputStream);
//上传文件
//删除临时文件
}
index++;
} catch (Exception e) {
e.printStackTrace();
throw new BaseException(BusinessError.FILE_COMPOSE_ERROR);
} finally {
//删除临时文件
if (file != null && file.exists()) {
file.delete();
}
if (newFile != null && newFile.exists()) {
newFile.delete();
}
}
}
private boolean checkAndSetUploadProgress(long totalChunks, long chunkNumber, String uploadDirPath, String fileName) throws IOException {
File confFile = new File(uploadDirPath, fileName + ".conf");
RandomAccessFile accessConfFile = new RandomAccessFile(confFile, "rw");
//把该分段标记为 true 表示完成
accessConfFile.setLength(totalChunks);
accessConfFile.seek(chunkNumber);
accessConfFile.write(Byte.MAX_VALUE);
//completeList 检查是否全部完成,如果数组里是否全部都是(全部分片都成功上传)
byte[] completeList = FileUtils.readFileToByteArray(confFile);
byte isComplete = Byte.MAX_VALUE;
for (int i = 0; i < completeList.length && isComplete == Byte.MAX_VALUE; i++) {
isComplete = (byte) (isComplete & completeList[i]);
}
accessConfFile.close();
if (isComplete == Byte.MAX_VALUE) {
confFile.delete();
return true;
} else {
return false;
}
}