Przeglądaj źródła

分块上传,尝试加锁

tsurumure 3 miesięcy temu
rodzic
commit
58be556049

+ 4 - 0
src/main/java/com/backendsys/modules/sdk/tencentcloud/cos/service/TencentCosService.java

@@ -9,6 +9,10 @@ import java.net.URL;
 import java.util.List;
 import java.util.Map;
 
+/**
+ * 文档中心 > 对象存储 > SDK文档 > Java SDK > 对象操作 > 上传对象
+ * https://cloud.tencent.com/document/product/436/65935
+ */
 public interface TencentCosService {
 
     // [腾讯云COS] 上传对象

+ 59 - 44
src/main/java/com/backendsys/modules/upload/service/impl/SysFileMultipartServiceImpl.java

@@ -7,6 +7,7 @@ import cn.hutool.core.util.StrUtil;
 import cn.hutool.crypto.digest.DigestUtil;
 import com.backendsys.exception.CustException;
 import com.backendsys.modules.common.config.security.utils.HttpRequestUtil;
+import com.backendsys.modules.common.config.security.utils.SecurityUtil;
 import com.backendsys.modules.sdk.douyincloud.tos.service.DouyinTosService;
 import com.backendsys.modules.sdk.tencentcloud.cos.service.TencentCosService;
 import com.backendsys.modules.system.entity.SysCommon;
@@ -20,8 +21,11 @@ import com.qcloud.cos.model.*;
 import com.volcengine.tos.model.object.CompleteMultipartUploadV2Output;
 import com.volcengine.tos.model.object.CreateMultipartUploadOutput;
 import com.volcengine.tos.model.object.UploadPartV2Output;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.multipart.MultipartFile;
@@ -31,6 +35,7 @@ import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Collectors;
 
@@ -43,6 +48,9 @@ public class SysFileMultipartServiceImpl implements SysFileMultipartService {
     @Value("${douyin.tos.domain}")
     private String DOUYIN_ACCESSIBLE_DOMAIN;
 
+    @Lazy
+    @Autowired
+    RedissonClient redissonClient;
     @Autowired
     private HttpRequestUtil httpRequestUtil;
     @Autowired
@@ -150,61 +158,68 @@ public class SysFileMultipartServiceImpl implements SysFileMultipartService {
     }
 
     // 2.上传分块
-    // - 抖音云单个分块大小不能小于4MB
+    // - 单个分块大小不能小于4MB
     @Override
     @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 不能为空");
-
-        // [Get] 获得初始化分块信息
-        SysFile sysFileEntity = sysFileDao.selectOne(new LambdaQueryWrapper<SysFile>().eq(SysFile::getUpload_id, upload_id));
-        if (sysFileEntity == null) throw new CustException("分块记录不存在");
-        String object_key = sysFileEntity.getObject_key();
-        Integer upload_chunk_size = sysFileEntity.getUpload_chunk_count();
-        if (upload_chunk_index > upload_chunk_size) throw new CustException("分块索引(index)不能大于分块数量(count)");
-
-        if (upload_chunk_index < upload_chunk_size) {
-            // 分片编号从 1 开始,最大为 10000。除最后一个分片以外,其他分片大小最小为 4MiB
-            if (multipartFile.getSize() < 4 * 1024 * 1024) {
-                throw new CustException("分块文件大小不能小于 4MB");
+        // 按用户加锁
+        RLock lock = redissonClient.getLock("multipartUpload:" + 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("分块记录不存在");
+            String object_key = sysFileEntity.getObject_key();
+            Integer upload_chunk_size = sysFileEntity.getUpload_chunk_count();
+            if (upload_chunk_index > upload_chunk_size) throw new CustException("分块索引(index)不能大于分块数量(count)");
+
+            if (upload_chunk_index < upload_chunk_size) {
+                // 分片编号从 1 开始,最大为 10000。除最后一个分片以外,其他分片大小最小为 4MiB
+                if (multipartFile.getSize() < 4 * 1024 * 1024) {
+                    throw new CustException("分块文件大小不能小于 4MB");
+                }
             }
-        }
 
-        // 获得公共配置
-        List<SysCommon> sysCommonList = sysCommonService.getCommonByCategory("UPLOAD");
-        AtomicReference<Integer> UPLOAD_TARGET = new AtomicReference<>();
-        sysCommonList.stream().forEach(sysCommon -> {
-            if (sysCommon.getTag().equals("UPLOAD_TARGET")) UPLOAD_TARGET.set(Convert.toInt(sysCommon.getValue()));
-        });
-        System.out.println("[系统配置] 上传目标(-1:本地, 1:腾讯云, 2:阿里云, 3.抖音云): " + UPLOAD_TARGET);
+            // 获得公共配置
+            List<SysCommon> sysCommonList = sysCommonService.getCommonByCategory("UPLOAD");
+            AtomicReference<Integer> UPLOAD_TARGET = new AtomicReference<>();
+            sysCommonList.stream().forEach(sysCommon -> {
+                if (sysCommon.getTag().equals("UPLOAD_TARGET")) UPLOAD_TARGET.set(Convert.toInt(sysCommon.getValue()));
+            });
+            System.out.println("[系统配置] 上传目标(-1:本地, 1:腾讯云, 2:阿里云, 3.抖音云): " + UPLOAD_TARGET);
 
 
-        String part_etag = null;
+            String part_etag = null;
 
-        // target: 上传目标 (-1:本地, 1:腾讯云, 2:阿里云, 3.抖音云)
-        if (UPLOAD_TARGET.get() == 1) {
-            UploadPartResult partResult = tencentCosService.uploadPart(multipartFile, upload_id, object_key, upload_chunk_index);
-            part_etag = partResult.getPartETag().getETag();
-        }
-        // 3: 抖音云
-        if (UPLOAD_TARGET.get() == 3) {
-            UploadPartV2Output partResult = douyinTosService.uploadPart(multipartFile, upload_id, object_key, upload_chunk_index);
-            part_etag = partResult.getEtag().replace("\"", "");
-        }
+            // target: 上传目标 (-1:本地, 1:腾讯云, 2:阿里云, 3.抖音云)
+            if (UPLOAD_TARGET.get() == 1) {
+                UploadPartResult partResult = tencentCosService.uploadPart(multipartFile, upload_id, object_key, upload_chunk_index);
+                part_etag = partResult.getPartETag().getETag();
+            }
+            // 3: 抖音云
+            if (UPLOAD_TARGET.get() == 3) {
+                UploadPartV2Output partResult = douyinTosService.uploadPart(multipartFile, upload_id, object_key, upload_chunk_index);
+                part_etag = partResult.getEtag().replace("\"", "");
+            }
 
-        // [Update] 更新分块文件信息
-        sysFileEntity.setUpload_chunk_index(upload_chunk_index);
-        sysFileDao.updateById(sysFileEntity);
+            // [Update] 更新分块文件信息
+            sysFileEntity.setUpload_chunk_index(upload_chunk_index);
+            sysFileDao.updateById(sysFileEntity);
 
-        Map<String, Object> resp = new LinkedHashMap<>();
-        resp.put("upload_chunk_index", upload_chunk_index);
-        resp.put("upload_id", upload_id);
-        resp.put("object_key", object_key);
-        resp.put("part_etag", part_etag);
-        return resp;
+            Map<String, Object> resp = new LinkedHashMap<>();
+            resp.put("upload_chunk_index", upload_chunk_index);
+            resp.put("upload_id", upload_id);
+            resp.put("object_key", object_key);
+            resp.put("part_etag", part_etag);
+            return resp;
+
+        } catch (InterruptedException e) { throw new RuntimeException(e);
+        } finally { lock.unlock(); }
     }
 
     // 完成分块上传