Explorar o código

refactor(ipad): 优化文件上传缓存机制并添加分布式锁防止并发重复上传

- 添加 UPLOAD_LOCK_KEY、UPLOAD_LOCK_MINUTES、UPLOAD_WAIT_MS 和 UPLOAD_POLL_MS 常量配置
- 重构 getFile 方法,支持按 iPad 服务器 + 资源 URL 缓存 CDN 上传结果
- 实现分布式锁机制,避免并发场景下的重复下载/上传操作
- 添加文件上传超时处理和异常捕获机制
- 更新 sendImg、sendFile、sendVoice、sendVideo 和 sendMiniProgram 方法参数
- 修复 sendVideo 方法中错误的类型标识符(原为 voice 现改为 video)
- 修复 sendMiniProgram 方法中错误的类型标识符(原为 voice 现改为 img)
xw hai 2 días
pai
achega
77892c8ce6
Modificáronse 1 ficheiros con 62 adicións e 15 borrados
  1. 62 15
      fs-service/src/main/java/com/fs/ipad/IpadSendUtils.java

+ 62 - 15
fs-service/src/main/java/com/fs/ipad/IpadSendUtils.java

@@ -58,7 +58,11 @@ public class IpadSendUtils {
     private final QwUserMapper qwUserMapper;
     private final RedisCache redisCacheUrl;
     private final String FILE_KEY = "ipad:upload:";
+    private final String UPLOAD_LOCK_KEY = "ipad:upload:lock:";
     private final String USER_KEY = "ipad:user:";
+    private static final long UPLOAD_LOCK_MINUTES = 5;
+    private static final long UPLOAD_WAIT_MS = 120_000;
+    private static final long UPLOAD_POLL_MS = 500;
 
     /**
      * 发送卡片消息
@@ -87,24 +91,67 @@ public class IpadSendUtils {
         return wxWorkService.SendTextMsg(dto, vo.getServerId());
     }
 
-    // 公共文件获取
-    private <T> T getFile(String key, Class<T> clazz, Supplier<T> supplier) {
-        String obj = redisCache.getCacheObject(key);
-        T t;
-        if(StringUtils.isEmpty(obj)){
-            t = supplier.get();
-            redisCache.setCacheObject(key, JSON.toJSONString(t), 3, TimeUnit.DAYS);
-        }else{
-            t = JSON.parseObject(obj, clazz);
+    private String buildUploadCacheKey(String type, Long serverId, String url) {
+        return FILE_KEY + type + ":" + serverId + ":" + url;
+    }
+
+    /**
+     * 按 iPad 服务器 + 资源 URL 缓存 CDN 上传结果,并用分布式锁避免并发重复下载/上传。
+     */
+    private <T> T getFile(Long serverId, String type, String url, Class<T> clazz, Supplier<T> supplier) {
+        String cacheKey = buildUploadCacheKey(type, serverId, url);
+        String cached = redisCache.getCacheObject(cacheKey);
+        if (StringUtils.isNotEmpty(cached)) {
+            return JSON.parseObject(cached, clazz);
+        }
+
+        String lockKey = UPLOAD_LOCK_KEY + type + ":" + serverId + ":" + url;
+        boolean locked = false;
+        try {
+            long deadline = System.currentTimeMillis() + UPLOAD_WAIT_MS;
+            while (System.currentTimeMillis() < deadline) {
+                cached = redisCache.getCacheObject(cacheKey);
+                if (StringUtils.isNotEmpty(cached)) {
+                    return JSON.parseObject(cached, clazz);
+                }
+                locked = redisCacheUrl.setIfAbsent(lockKey, "1", UPLOAD_LOCK_MINUTES, TimeUnit.MINUTES);
+                if (locked) {
+                    break;
+                }
+                Thread.sleep(UPLOAD_POLL_MS);
+            }
+
+            if (!locked) {
+                cached = redisCache.getCacheObject(cacheKey);
+                if (StringUtils.isNotEmpty(cached)) {
+                    return JSON.parseObject(cached, clazz);
+                }
+                throw new BaseException("等待文件上传超时,请稍后重试");
+            }
+
+            cached = redisCache.getCacheObject(cacheKey);
+            if (StringUtils.isNotEmpty(cached)) {
+                return JSON.parseObject(cached, clazz);
+            }
+
+            T result = supplier.get();
+            redisCache.setCacheObject(cacheKey, JSON.toJSONString(result), 3, TimeUnit.DAYS);
+            return result;
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new BaseException("等待文件上传被中断");
+        } finally {
+            if (locked) {
+                redisCacheUrl.deleteObject(lockKey);
+            }
         }
-        return t;
     }
 
     /**
      * 发送图片
      */
     public WxWorkResponseDTO<WxwSendCDNImgMsgRespDTO> sendImg(FileVo vo){
-        WxCdnUploadImgLinkResp resp = getFile(FILE_KEY + "img:" + vo.getUrl(), WxCdnUploadImgLinkResp.class, () -> uploadImage(vo.getUuid(), vo.getServerId(), vo.getUrl()));
+        WxCdnUploadImgLinkResp resp = getFile(vo.getServerId(), "img", vo.getUrl(), WxCdnUploadImgLinkResp.class, () -> uploadImage(vo.getUuid(), vo.getServerId(), vo.getUrl()));
         WxwSendCDNImgMsgDTO dto = new WxwSendCDNImgMsgDTO();
         dto.setCdnkey(resp.getCdn_key());
         dto.setAeskey(resp.getAes_key());
@@ -128,7 +175,7 @@ public class IpadSendUtils {
      * 发送文件
      */
     public WxWorkResponseDTO<WxwSendCDNFileMsgRespDTO> sendFile(FileVo vo){
-        WxwUploadCdnLinkFileRespDTO resp = getFile(FILE_KEY + "file:" + vo.getUrl(), WxwUploadCdnLinkFileRespDTO.class, () -> uploadFile(vo.getUuid(), vo.getServerId(), vo.getUrl()));
+        WxwUploadCdnLinkFileRespDTO resp = getFile(vo.getServerId(), "file", vo.getUrl(), WxwUploadCdnLinkFileRespDTO.class, () -> uploadFile(vo.getUuid(), vo.getServerId(), vo.getUrl()));
         WxwSendCDNFileMsgDTO dto = new WxwSendCDNFileMsgDTO();
         dto.setCdnkey(resp.getCdn_key());
         dto.setAeskey(resp.getAes_key());
@@ -148,7 +195,7 @@ public class IpadSendUtils {
      * 发送文件
      */
     public WxWorkResponseDTO<WxwSendCDNVoiceMsgRespDTO> sendVoice(FileVo vo){
-        WxwUploadCdnLinkFileRespDTO resp = getFile(FILE_KEY + "voice:" + vo.getUrl(), WxwUploadCdnLinkFileRespDTO.class, () -> uploadFile(vo.getUuid(), vo.getServerId(), vo.getUrl()));
+        WxwUploadCdnLinkFileRespDTO resp = getFile(vo.getServerId(), "voice", vo.getUrl(), WxwUploadCdnLinkFileRespDTO.class, () -> uploadFile(vo.getUuid(), vo.getServerId(), vo.getUrl()));
         WxwSendCDNVoiceMsgDTO dto = new WxwSendCDNVoiceMsgDTO();
         dto.setCdnkey(resp.getCdn_key());
         dto.setVoice_time(vo.getVoiceTime());
@@ -164,7 +211,7 @@ public class IpadSendUtils {
      * 发送文件
      */
     public WxWorkResponseDTO<WxwSendCDNVideoMsgRespDTO> sendVideo(FileVo vo){
-        WxCdnUploadVideoResp resp = getFile(FILE_KEY + "voice:" + vo.getUrl(), WxCdnUploadVideoResp.class, () -> uploadVideo(vo.getUuid(), vo.getServerId(), vo.getUrl()));
+        WxCdnUploadVideoResp resp = getFile(vo.getServerId(), "video", vo.getUrl(), WxCdnUploadVideoResp.class, () -> uploadVideo(vo.getUuid(), vo.getServerId(), vo.getUrl()));
         WxwSendCDNVideoMsgDTO dto = new WxwSendCDNVideoMsgDTO();
         dto.setCdnkey(resp.getCdn_key());
         dto.setAeskey(resp.getAes_key());
@@ -198,7 +245,7 @@ public class IpadSendUtils {
      * 发送小程序
      */
     public WxWorkResponseDTO<WxWorkSendAppMsgRespDTO> sendMiniProgram(MiniProgramVo vo){
-        WxCdnUploadImgLinkResp resp = getFile(FILE_KEY + "voice:" + vo.getImgUrl(), WxCdnUploadImgLinkResp.class, () -> uploadImage(vo.getUuid(), vo.getServerId(), vo.getImgUrl()));
+        WxCdnUploadImgLinkResp resp = getFile(vo.getServerId(), "img", vo.getImgUrl(), WxCdnUploadImgLinkResp.class, () -> uploadImage(vo.getUuid(), vo.getServerId(), vo.getImgUrl()));
         WxWorkSendAppMsgDTO dto = new WxWorkSendAppMsgDTO();
         dto.setCdnkey(resp.getCdn_key());
         dto.setAeskey(resp.getAes_key());