From 2972ebab7d2ca8ad66a6df41aaad13639141d109 Mon Sep 17 00:00:00 2001 From: mediabot-pt <295750538+mediabot-pt@users.noreply.github.com> Date: Wed, 1 Jul 2026 17:09:38 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20access-result=E6=8F=90=E4=BA=A45?= =?UTF-8?q?=E5=88=86=E9=92=9F=E5=86=85=E5=AE=B9=E5=8E=BB=E9=87=8D=EF=BC=8C?= =?UTF-8?q?=E9=98=B2=E6=AD=A2mediabot=E9=87=8D=E5=A4=8D=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AccessResultController.java | 13 ++++++----- .../DuplicateSubmissionException.java | 7 ++++++ .../service/impl/AccessResultServiceImpl.java | 22 +++++++++++++++++++ 3 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 par-core/src/main/java/com/par/core/exception/DuplicateSubmissionException.java diff --git a/par-api/src/main/java/com/par/api/controller/AccessResultController.java b/par-api/src/main/java/com/par/api/controller/AccessResultController.java index b3946bf..72adbbf 100644 --- a/par-api/src/main/java/com/par/api/controller/AccessResultController.java +++ b/par-api/src/main/java/com/par/api/controller/AccessResultController.java @@ -54,11 +54,14 @@ public class AccessResultController { String clientId = (String) request.getAttribute("clientId"); Long submitterId = (Long) request.getAttribute("accountId"); - AccessResult result = accessResultService.submit( - siteId, configVersion, clientId, contentType, content, - url, statusCode, responseTimeMs, submitterId); - - return ApiResponse.success(accessResultService.toDTO(result)); + try { + AccessResult result = accessResultService.submit( + siteId, configVersion, clientId, contentType, content, + url, statusCode, responseTimeMs, submitterId); + return ApiResponse.success(accessResultService.toDTO(result)); + } catch (com.par.core.exception.DuplicateSubmissionException e) { + return ApiResponse.success(Map.of("skipped", true, "reason", "duplicate")); + } } // ==================== 管理员查看接口(JWT 鉴权) ==================== diff --git a/par-core/src/main/java/com/par/core/exception/DuplicateSubmissionException.java b/par-core/src/main/java/com/par/core/exception/DuplicateSubmissionException.java new file mode 100644 index 0000000..e9d0568 --- /dev/null +++ b/par-core/src/main/java/com/par/core/exception/DuplicateSubmissionException.java @@ -0,0 +1,7 @@ +package com.par.core.exception; + +/** + * 重复提交异常:相同内容的访问结果在去重窗口内再次提交 + */ +public class DuplicateSubmissionException extends RuntimeException { +} diff --git a/par-core/src/main/java/com/par/core/service/impl/AccessResultServiceImpl.java b/par-core/src/main/java/com/par/core/service/impl/AccessResultServiceImpl.java index cab4873..fb2b43c 100644 --- a/par-core/src/main/java/com/par/core/service/impl/AccessResultServiceImpl.java +++ b/par-core/src/main/java/com/par/core/service/impl/AccessResultServiceImpl.java @@ -11,6 +11,8 @@ import com.par.core.mapper.AccessResultMapper; import com.par.core.mapper.SiteConfigMapper; import com.par.core.mapper.SiteMapper; import com.par.core.service.AccessResultService; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; import com.par.core.storage.CachedStorageService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -19,6 +21,7 @@ import org.springframework.stereotype.Service; import java.sql.Timestamp; import java.time.LocalDateTime; import java.util.*; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @Slf4j @@ -31,12 +34,31 @@ public class AccessResultServiceImpl implements AccessResultService { private final SiteMapper siteMapper; private final CachedStorageService storageService; + /** 去重缓存:siteId|url → contentHash,5分钟内相同内容跳过 */ + private final Cache dedupCache = Caffeine.newBuilder() + .maximumSize(10000) + .expireAfterWrite(5, TimeUnit.MINUTES) + .build(); + @Override public AccessResult submit(String siteId, String configVersion, String clientId, String contentType, String content, String url, Integer statusCode, Integer responseTimeMs, Long submitterId) { String type = (contentType != null && !contentType.isBlank()) ? contentType : "html"; String ext = "json".equalsIgnoreCase(type) ? "json" : "html"; + + // 去重:5分钟内相同site+url+contentHash跳过 + String dedupKey = siteId + "|" + (url != null ? url : ""); + String contentHash = content.length() > 1000 + ? Integer.toHexString(content.substring(0, 1000).hashCode()) + : Integer.toHexString(content.hashCode()); + String existing = dedupCache.getIfPresent(dedupKey); + if (contentHash.equals(existing)) { + log.debug("跳过重复提交: siteId={}, url={}", siteId, url); + throw new DuplicateSubmissionException(); + } + dedupCache.put(dedupKey, contentHash); + String storagePath = String.format("access-results/%s/%d_%s.%s", siteId, System.currentTimeMillis(), UUID.randomUUID().toString().substring(0, 8), ext);