|
@@ -156,80 +156,87 @@ public class SysFileMultipartServiceImpl implements SysFileMultipartService {
|
|
|
String filename = multipartFile.getOriginalFilename();
|
|
|
if (filename.length() > 50) throw new CustException("文件名长度不能超过 50 字符");
|
|
|
|
|
|
- // 获得公共配置
|
|
|
- List<SysCommon> sysCommonList = sysCommonService.getCommonByCategory("UPLOAD");
|
|
|
- AtomicReference<Integer> UPLOAD_TARGET = new AtomicReference<>();
|
|
|
- AtomicReference<Integer> UPLOAD_THUMB_SIZE = new AtomicReference<>();
|
|
|
- AtomicReference<Boolean> UPLOAD_MD5_DUPLICATE = new AtomicReference<>();
|
|
|
- sysCommonList.stream().forEach(sysCommon -> {
|
|
|
- if (sysCommon.getTag().equals("UPLOAD_TARGET")) UPLOAD_TARGET.set(Convert.toInt(sysCommon.getValue()));
|
|
|
- if (sysCommon.getTag().equals("UPLOAD_THUMB_SIZE")) UPLOAD_THUMB_SIZE.set(Convert.toInt(sysCommon.getValue()));
|
|
|
- if (sysCommon.getTag().equals("UPLOAD_MD5_DUPLICATE")) UPLOAD_MD5_DUPLICATE.set(Convert.toBool(sysCommon.getValue()));
|
|
|
- });
|
|
|
-
|
|
|
- try {
|
|
|
-
|
|
|
- SysFile sysFileEntity = null;
|
|
|
-
|
|
|
- String md5 = DigestUtil.md5Hex(multipartFile.getInputStream());
|
|
|
-
|
|
|
- // 如果开启了 MD5秒传,则判断文件是否上传过,是的话就直接返回
|
|
|
- // [系统配置] 是否启用文件MD5查重 (仅判断用户自己上传过的文件)
|
|
|
- // - 存在,则根据文件MD5 判断是否上传过文件 (仅更新时间,不上传已存在的文件,允许更新文件分类)
|
|
|
- // - 存在,如果存在两个相同 md5 以上的文件,则需要排重后再上传 (如果是不同用户
|
|
|
- // - 不存在,则走上传流程
|
|
|
- Boolean is_exist = false;
|
|
|
- if (UPLOAD_MD5_DUPLICATE.get()) {
|
|
|
-
|
|
|
- // 排除文件异常的情况(出现两个以上相同MD5的文件)
|
|
|
- LambdaQueryWrapper<SysFile> wrapperFile = new LambdaQueryWrapper<>();
|
|
|
- wrapperFile.eq(SysFile::getMd5, md5);
|
|
|
- wrapperFile.eq(SysFile::getUser_id, SecurityUtil.getUserId());
|
|
|
-
|
|
|
- // [DB] 查询已存在的文件分块记录 (只有完全上传成功,才会有 MD5)
|
|
|
- // - 异常情况:如果有两个相同的文件,一个上传50%,一个上传成功,就会出现以下这种情况,需要手动解决
|
|
|
- List<SysFile> sysFileEntityList = sysFileDao.selectList(wrapperFile);
|
|
|
- if (sysFileEntityList != null && sysFileEntityList.size() > 1) {
|
|
|
- throw new CustException("存在 " + sysFileEntityList.size() + " 个相同MD5 (" + md5 + ") 的文件,请删除后再重新上传");
|
|
|
- }
|
|
|
+ // 按用户加锁
|
|
|
+ RLock lock = redissonClient.getLock("lock::multipart-upload::user-id::" + SecurityUtil.getUserId());
|
|
|
+ try { lock.tryLock(3, TimeUnit.SECONDS);
|
|
|
+
|
|
|
+ // 获得公共配置
|
|
|
+ List<SysCommon> sysCommonList = sysCommonService.getCommonByCategory("UPLOAD");
|
|
|
+ AtomicReference<Integer> UPLOAD_TARGET = new AtomicReference<>();
|
|
|
+ AtomicReference<Integer> UPLOAD_THUMB_SIZE = new AtomicReference<>();
|
|
|
+ AtomicReference<Boolean> UPLOAD_MD5_DUPLICATE = new AtomicReference<>();
|
|
|
+ sysCommonList.stream().forEach(sysCommon -> {
|
|
|
+ if (sysCommon.getTag().equals("UPLOAD_TARGET")) UPLOAD_TARGET.set(Convert.toInt(sysCommon.getValue()));
|
|
|
+ if (sysCommon.getTag().equals("UPLOAD_THUMB_SIZE")) UPLOAD_THUMB_SIZE.set(Convert.toInt(sysCommon.getValue()));
|
|
|
+ if (sysCommon.getTag().equals("UPLOAD_MD5_DUPLICATE")) UPLOAD_MD5_DUPLICATE.set(Convert.toBool(sysCommon.getValue()));
|
|
|
+ });
|
|
|
+
|
|
|
+ try {
|
|
|
+
|
|
|
+ SysFile sysFileEntity = null;
|
|
|
+
|
|
|
+ String md5 = DigestUtil.md5Hex(multipartFile.getInputStream());
|
|
|
+
|
|
|
+ // 如果开启了 MD5秒传,则判断文件是否上传过,是的话就直接返回
|
|
|
+ // [系统配置] 是否启用文件MD5查重 (仅判断用户自己上传过的文件)
|
|
|
+ // - 存在,则根据文件MD5 判断是否上传过文件 (仅更新时间,不上传已存在的文件,允许更新文件分类)
|
|
|
+ // - 存在,如果存在两个相同 md5 以上的文件,则需要排重后再上传 (如果是不同用户
|
|
|
+ // - 不存在,则走上传流程
|
|
|
+ Boolean is_exist = false;
|
|
|
+ if (UPLOAD_MD5_DUPLICATE.get()) {
|
|
|
+
|
|
|
+ // 排除文件异常的情况(出现两个以上相同MD5的文件)
|
|
|
+ LambdaQueryWrapper<SysFile> wrapperFile = new LambdaQueryWrapper<>();
|
|
|
+ wrapperFile.eq(SysFile::getMd5, md5);
|
|
|
+ wrapperFile.eq(SysFile::getUser_id, SecurityUtil.getUserId());
|
|
|
+
|
|
|
+ // [DB] 查询已存在的文件分块记录 (只有完全上传成功,才会有 MD5)
|
|
|
+ // - 异常情况:如果有两个相同的文件,一个上传50%,一个上传成功,就会出现以下这种情况,需要手动解决
|
|
|
+ List<SysFile> sysFileEntityList = sysFileDao.selectList(wrapperFile);
|
|
|
+ if (sysFileEntityList != null && sysFileEntityList.size() > 1) {
|
|
|
+ throw new CustException("存在 " + sysFileEntityList.size() + " 个相同MD5 (" + md5 + ") 的文件,请删除后再重新上传");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (sysFileEntityList != null && sysFileEntityList.size() > 0) {
|
|
|
+ is_exist = true;
|
|
|
+ // 将已存在的文件,赋值
|
|
|
+ sysFileEntity = sysFileEntityList.get(0);
|
|
|
+ // [DB] 更新文件 (文件分类、上传时间)
|
|
|
+ sysFileEntity.setCategory_id(category_id);
|
|
|
+ sysFileEntity.setUpload_time(DateUtil.now());
|
|
|
+ sysFileDao.updateById(sysFileEntity);
|
|
|
+ } else {
|
|
|
+ // [DB] 创建新的文件
|
|
|
+ sysFileEntity = uploadInitEvent(multipartFile, category_id, UPLOAD_TARGET.get(), upload_chunk_count, md5);
|
|
|
+ // [格式化] 封面 (图片类型)
|
|
|
+ sysFileEntity = setThumbUrl(sysFileEntity, UPLOAD_THUMB_SIZE.get(), UPLOAD_THUMB_SIZE.get(), StyleEnums.THUMB_BACKGROUND.getValue());
|
|
|
+ }
|
|
|
|
|
|
- if (sysFileEntityList != null && sysFileEntityList.size() > 0) {
|
|
|
- is_exist = true;
|
|
|
- // 将已存在的文件,赋值
|
|
|
- sysFileEntity = sysFileEntityList.get(0);
|
|
|
- // [DB] 更新文件 (文件分类、上传时间)
|
|
|
- sysFileEntity.setCategory_id(category_id);
|
|
|
- sysFileEntity.setUpload_time(DateUtil.now());
|
|
|
- sysFileDao.updateById(sysFileEntity);
|
|
|
} else {
|
|
|
+ // 不开启 MD5秒 传,则直接初始化分块
|
|
|
// [DB] 创建新的文件
|
|
|
sysFileEntity = uploadInitEvent(multipartFile, category_id, UPLOAD_TARGET.get(), upload_chunk_count, md5);
|
|
|
// [格式化] 封面 (图片类型)
|
|
|
sysFileEntity = setThumbUrl(sysFileEntity, UPLOAD_THUMB_SIZE.get(), UPLOAD_THUMB_SIZE.get(), StyleEnums.THUMB_BACKGROUND.getValue());
|
|
|
}
|
|
|
|
|
|
- } else {
|
|
|
- // 不开启 MD5秒 传,则直接初始化分块
|
|
|
- // [DB] 创建新的文件
|
|
|
- sysFileEntity = uploadInitEvent(multipartFile, category_id, UPLOAD_TARGET.get(), upload_chunk_count, md5);
|
|
|
- // [格式化] 封面 (图片类型)
|
|
|
- sysFileEntity = setThumbUrl(sysFileEntity, UPLOAD_THUMB_SIZE.get(), UPLOAD_THUMB_SIZE.get(), StyleEnums.THUMB_BACKGROUND.getValue());
|
|
|
+ Map<String, Object> resp = new LinkedHashMap<>();
|
|
|
+ resp.put("target", UPLOAD_TARGET.get());
|
|
|
+ resp.put("target_label", StyleEnums.targetToLabel(UPLOAD_TARGET.get()));
|
|
|
+ resp.put("upload_chunk_count", upload_chunk_count);
|
|
|
+ resp.put("upload_id", sysFileEntity.getUpload_id());
|
|
|
+ resp.put("object_key", sysFileEntity.getObject_key());
|
|
|
+ resp.put("md5", md5);
|
|
|
+ resp.put("is_exist", is_exist);
|
|
|
+ resp.put("file_info", is_exist ? sysFileEntity : null);
|
|
|
+ return resp;
|
|
|
+
|
|
|
+ } catch (IOException e) {
|
|
|
+ throw new RuntimeException(e);
|
|
|
}
|
|
|
|
|
|
- Map<String, Object> resp = new LinkedHashMap<>();
|
|
|
- resp.put("target", UPLOAD_TARGET.get());
|
|
|
- resp.put("target_label", StyleEnums.targetToLabel(UPLOAD_TARGET.get()));
|
|
|
- resp.put("upload_chunk_count", upload_chunk_count);
|
|
|
- resp.put("upload_id", sysFileEntity.getUpload_id());
|
|
|
- resp.put("object_key", sysFileEntity.getObject_key());
|
|
|
- resp.put("md5", md5);
|
|
|
- resp.put("is_exist", is_exist);
|
|
|
- resp.put("file_info", is_exist ? sysFileEntity : null);
|
|
|
- return resp;
|
|
|
-
|
|
|
- } catch (IOException e) {
|
|
|
- throw new RuntimeException(e);
|
|
|
- }
|
|
|
+ } catch (InterruptedException e) { throw new RuntimeException(e);
|
|
|
+ } finally { lock.unlock(); }
|
|
|
}
|
|
|
|
|
|
// 2.上传分块
|
|
@@ -238,14 +245,14 @@ public class SysFileMultipartServiceImpl implements SysFileMultipartService {
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
public Map<String, Object> multipartUpload(MultipartFile multipartFile, String upload_id, Integer upload_chunk_index) {
|
|
|
|
|
|
+ if (multipartFile.isEmpty()) throw new CustException("file 上传文件不能为空");
|
|
|
+ if (StrUtil.isEmpty(upload_id)) throw new CustException("upload_id 不能为空");
|
|
|
+ if (upload_chunk_index == null) throw new CustException("upload_chunk_index 不能为空");
|
|
|
+
|
|
|
// 按用户加锁
|
|
|
RLock lock = redissonClient.getLock("lock::multipart-upload::user-id::" + SecurityUtil.getUserId());
|
|
|
try { lock.tryLock(3, TimeUnit.SECONDS);
|
|
|
|
|
|
- if (multipartFile.isEmpty()) throw new CustException("file 上传文件不能为空");
|
|
|
- if (StrUtil.isEmpty(upload_id)) throw new CustException("upload_id 不能为空");
|
|
|
- if (upload_chunk_index == null) throw new CustException("upload_chunk_index 不能为空");
|
|
|
-
|
|
|
// [Get] 获得初始化分块信息
|
|
|
SysFile sysFileEntity = sysFileDao.selectOne(new LambdaQueryWrapper<SysFile>().eq(SysFile::getUpload_id, upload_id));
|
|
|
if (sysFileEntity == null) throw new CustException("分块记录不存在");
|
|
@@ -302,89 +309,95 @@ public class SysFileMultipartServiceImpl implements SysFileMultipartService {
|
|
|
|
|
|
if (StrUtil.isEmpty(upload_id)) throw new CustException("upload_id 不能为空");
|
|
|
|
|
|
- // [Get] 查询文件分块记录
|
|
|
- SysFile sysFileEntity = sysFileDao.selectOne(new LambdaQueryWrapper<SysFile>().eq(SysFile::getUpload_id, upload_id));
|
|
|
- if (sysFileEntity == null) throw new CustException("upload_id 不存在");
|
|
|
+ // 按用户加锁
|
|
|
+ RLock lock = redissonClient.getLock("lock::multipart-upload::user-id::" + SecurityUtil.getUserId());
|
|
|
+ try { lock.tryLock(3, TimeUnit.SECONDS);
|
|
|
|
|
|
- // 判断分块索引和分块数量是否一致
|
|
|
- if (sysFileEntity.getUpload_chunk_index() != sysFileEntity.getUpload_chunk_count()) {
|
|
|
- throw new CustException("分块索引和分块数量不一致");
|
|
|
- }
|
|
|
+ // [Get] 查询文件分块记录
|
|
|
+ SysFile sysFileEntity = sysFileDao.selectOne(new LambdaQueryWrapper<SysFile>().eq(SysFile::getUpload_id, upload_id));
|
|
|
+ if (sysFileEntity == null) throw new CustException("upload_id 不存在");
|
|
|
|
|
|
+ // 判断分块索引和分块数量是否一致
|
|
|
+ if (sysFileEntity.getUpload_chunk_index() != sysFileEntity.getUpload_chunk_count()) {
|
|
|
+ throw new CustException("分块索引和分块数量不一致");
|
|
|
+ }
|
|
|
|
|
|
- // 获得公共配置
|
|
|
- List<SysCommon> sysCommonList = sysCommonService.getCommonByCategory("UPLOAD");
|
|
|
- AtomicReference<Integer> UPLOAD_TARGET = new AtomicReference<>();
|
|
|
- AtomicReference<Integer> UPLOAD_THUMB_SIZE = new AtomicReference<>();
|
|
|
- sysCommonList.stream().forEach(sysCommon -> {
|
|
|
- if (sysCommon.getTag().equals("UPLOAD_TARGET")) UPLOAD_TARGET.set(Convert.toInt(sysCommon.getValue()));
|
|
|
- if (sysCommon.getTag().equals("UPLOAD_THUMB_SIZE")) UPLOAD_THUMB_SIZE.set(Convert.toInt(sysCommon.getValue()));
|
|
|
- });
|
|
|
- System.out.println("[系统配置] 上传目标(-1:本地, 1:腾讯云, 2:阿里云, 3.抖音云): " + UPLOAD_TARGET);
|
|
|
- System.out.println("[系统配置] 图片的缩略图宽高尺寸 (px): " + UPLOAD_THUMB_SIZE);
|
|
|
|
|
|
+ // 获得公共配置
|
|
|
+ List<SysCommon> sysCommonList = sysCommonService.getCommonByCategory("UPLOAD");
|
|
|
+ AtomicReference<Integer> UPLOAD_TARGET = new AtomicReference<>();
|
|
|
+ AtomicReference<Integer> UPLOAD_THUMB_SIZE = new AtomicReference<>();
|
|
|
+ sysCommonList.stream().forEach(sysCommon -> {
|
|
|
+ if (sysCommon.getTag().equals("UPLOAD_TARGET")) UPLOAD_TARGET.set(Convert.toInt(sysCommon.getValue()));
|
|
|
+ if (sysCommon.getTag().equals("UPLOAD_THUMB_SIZE")) UPLOAD_THUMB_SIZE.set(Convert.toInt(sysCommon.getValue()));
|
|
|
+ });
|
|
|
+ System.out.println("[系统配置] 上传目标(-1:本地, 1:腾讯云, 2:阿里云, 3.抖音云): " + UPLOAD_TARGET);
|
|
|
+ System.out.println("[系统配置] 图片的缩略图宽高尺寸 (px): " + UPLOAD_THUMB_SIZE);
|
|
|
|
|
|
- // target: 上传目标 (-1:本地, 1:腾讯云, 2:阿里云, 3.抖音云)
|
|
|
- if (UPLOAD_TARGET.get() == 1) {
|
|
|
-
|
|
|
- // 查询分块集合 (etag)
|
|
|
- Map<String, Object> partList = listParts(upload_id, sysFileEntity.getObject_key());
|
|
|
- PartListing partListing = (PartListing) partList.get("listParts");
|
|
|
- List<PartSummary> partSummaryList = partListing.getParts();
|
|
|
- List<PartETag> etags = partSummaryList.stream()
|
|
|
- .map(partSummary -> new PartETag(partSummary.getPartNumber(), partSummary.getETag()))
|
|
|
- .collect(Collectors.toList());
|
|
|
-
|
|
|
- // 如果分块数量对不上,最好还是重新上传
|
|
|
- if (sysFileEntity.getUpload_chunk_count() != etags.size()) throw new CustException("分块索引异常,请重新上传");
|
|
|
-
|
|
|
- // [腾讯云] 合并分块
|
|
|
- CompleteMultipartUploadResult completeResult = tencentCosService.completeMultipartUpload(upload_id, sysFileEntity.getObject_key(), etags);
|
|
|
- if (completeResult == null) throw new CustException("分块合并失败");
|
|
|
-
|
|
|
- // 拼接图片路径
|
|
|
- sysFileEntity.setUrl(TENCENT_ACCESSIBLE_DOMAIN + "/" + completeResult.getKey());
|
|
|
- sysFileEntity.setRequest_id(completeResult.getRequestId());
|
|
|
- }
|
|
|
|
|
|
- // 3: 抖音云
|
|
|
- if (UPLOAD_TARGET.get() == 3) {
|
|
|
+ // target: 上传目标 (-1:本地, 1:腾讯云, 2:阿里云, 3.抖音云)
|
|
|
+ if (UPLOAD_TARGET.get() == 1) {
|
|
|
|
|
|
- CompleteMultipartUploadV2Output completeResult = douyinTosService.completeMultipartUpload(upload_id, sysFileEntity.getObject_key());
|
|
|
- if (completeResult == null) throw new CustException("分块合并失败");
|
|
|
+ // 查询分块集合 (etag)
|
|
|
+ Map<String, Object> partList = listParts(upload_id, sysFileEntity.getObject_key());
|
|
|
+ PartListing partListing = (PartListing) partList.get("listParts");
|
|
|
+ List<PartSummary> partSummaryList = partListing.getParts();
|
|
|
+ List<PartETag> etags = partSummaryList.stream()
|
|
|
+ .map(partSummary -> new PartETag(partSummary.getPartNumber(), partSummary.getETag()))
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
|
- // 拼接图片路径
|
|
|
- sysFileEntity.setUrl(DOUYIN_ACCESSIBLE_DOMAIN + "/" + completeResult.getKey());
|
|
|
- sysFileEntity.setRequest_id(completeResult.getRequestInfo().getRequestId());
|
|
|
+ // 如果分块数量对不上,最好还是重新上传
|
|
|
+ if (sysFileEntity.getUpload_chunk_count() != etags.size()) throw new CustException("分块索引异常,请重新上传");
|
|
|
|
|
|
- }
|
|
|
+ // [腾讯云] 合并分块
|
|
|
+ CompleteMultipartUploadResult completeResult = tencentCosService.completeMultipartUpload(upload_id, sysFileEntity.getObject_key(), etags);
|
|
|
+ if (completeResult == null) throw new CustException("分块合并失败");
|
|
|
|
|
|
- /*
|
|
|
- 如果是图片类型,就设置封面:
|
|
|
- 图片Icon (png / jpg / jpeg / gif / bmp / webp)
|
|
|
- 视频 Icon (mp4 / avi / mov / flv / 3gp / mpeg / wmv)
|
|
|
- Word Icon (doc / docx)
|
|
|
- Excel Icon (xls / xlsx)
|
|
|
- PDF Icon (pdf)
|
|
|
- 其他 Icon
|
|
|
- */
|
|
|
- setThumbUrl(sysFileEntity, UPLOAD_THUMB_SIZE.get(), UPLOAD_THUMB_SIZE.get(), StyleEnums.THUMB_BACKGROUND.getValue());
|
|
|
-
|
|
|
- // [DB] 更新分块记录
|
|
|
- sysFileDao.updateCompleteFile(sysFileEntity);
|
|
|
-
|
|
|
- // -- 合并分块成功时,返回值 -------------------------------------
|
|
|
- // 移除 upload_id
|
|
|
- sysFileEntity.setUpload_id(null);
|
|
|
- // 翻译 储存介质
|
|
|
- sysFileEntity.setTarget_label(StyleEnums.targetToLabel(sysFileEntity.getTarget()));
|
|
|
- // 翻译 用户名
|
|
|
- SysUser userEntity = sysUserDao.selectById(sysFileEntity.getUser_id());
|
|
|
- sysFileEntity.setUsername(userEntity.getUsername());
|
|
|
- // ----------------------------------------------------
|
|
|
+ // 拼接图片路径
|
|
|
+ sysFileEntity.setUrl(TENCENT_ACCESSIBLE_DOMAIN + "/" + completeResult.getKey());
|
|
|
+ sysFileEntity.setRequest_id(completeResult.getRequestId());
|
|
|
+ }
|
|
|
|
|
|
- return sysFileEntity;
|
|
|
+ // 3: 抖音云
|
|
|
+ if (UPLOAD_TARGET.get() == 3) {
|
|
|
+
|
|
|
+ CompleteMultipartUploadV2Output completeResult = douyinTosService.completeMultipartUpload(upload_id, sysFileEntity.getObject_key());
|
|
|
+ if (completeResult == null) throw new CustException("分块合并失败");
|
|
|
|
|
|
+ // 拼接图片路径
|
|
|
+ sysFileEntity.setUrl(DOUYIN_ACCESSIBLE_DOMAIN + "/" + completeResult.getKey());
|
|
|
+ sysFileEntity.setRequest_id(completeResult.getRequestInfo().getRequestId());
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ 如果是图片类型,就设置封面:
|
|
|
+ 图片Icon (png / jpg / jpeg / gif / bmp / webp)
|
|
|
+ 视频 Icon (mp4 / avi / mov / flv / 3gp / mpeg / wmv)
|
|
|
+ Word Icon (doc / docx)
|
|
|
+ Excel Icon (xls / xlsx)
|
|
|
+ PDF Icon (pdf)
|
|
|
+ 其他 Icon
|
|
|
+ */
|
|
|
+ setThumbUrl(sysFileEntity, UPLOAD_THUMB_SIZE.get(), UPLOAD_THUMB_SIZE.get(), StyleEnums.THUMB_BACKGROUND.getValue());
|
|
|
+
|
|
|
+ // [DB] 更新分块记录
|
|
|
+ sysFileDao.updateCompleteFile(sysFileEntity);
|
|
|
+
|
|
|
+ // -- 合并分块成功时,返回值 -------------------------------------
|
|
|
+ // 移除 upload_id
|
|
|
+ sysFileEntity.setUpload_id(null);
|
|
|
+ // 翻译 储存介质
|
|
|
+ sysFileEntity.setTarget_label(StyleEnums.targetToLabel(sysFileEntity.getTarget()));
|
|
|
+ // 翻译 用户名
|
|
|
+ SysUser userEntity = sysUserDao.selectById(sysFileEntity.getUser_id());
|
|
|
+ sysFileEntity.setUsername(userEntity.getUsername());
|
|
|
+ // ----------------------------------------------------
|
|
|
+
|
|
|
+ return sysFileEntity;
|
|
|
+
|
|
|
+ } catch (InterruptedException e) { throw new RuntimeException(e);
|
|
|
+ } finally { lock.unlock(); }
|
|
|
}
|
|
|
|
|
|
// 查询分块上传情况
|