|
|
@@ -13,6 +13,7 @@ import java.security.NoSuchAlgorithmException;
|
|
|
import java.time.Duration;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
+import java.util.Optional;
|
|
|
|
|
|
/**
|
|
|
* 全局 QA 缓存(不区分用户,24 小时过期)。
|
|
|
@@ -26,7 +27,7 @@ public class QACacheService {
|
|
|
|
|
|
private static final String CACHE_PREFIX = "aiyaodian:qa:";
|
|
|
private static final String PENDING_PREFIX = "aiyaodian:qa:pending:";
|
|
|
- private static final Duration TTL = Duration.ofHours(24);
|
|
|
+ private static final Duration TTL = Duration.ofHours(168); // 7 天
|
|
|
/** 处理中标记超时:防止死锁,30 秒后自动释放 */
|
|
|
private static final Duration PENDING_TTL = Duration.ofSeconds(30);
|
|
|
/** 等待缓存写入的最大重试次数 */
|
|
|
@@ -55,7 +56,9 @@ public class QACacheService {
|
|
|
|
|
|
/** 规范化问题文本,用于生成缓存 key */
|
|
|
public String normalize(String query) {
|
|
|
- if (query == null) return "";
|
|
|
+ if (query == null) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
return query.trim().replaceAll("\\s+", " ");
|
|
|
}
|
|
|
|
|
|
@@ -77,8 +80,11 @@ public class QACacheService {
|
|
|
}
|
|
|
try {
|
|
|
String json = redis.opsForValue().get(cacheKey(normalizedQuery));
|
|
|
- if (json == null || json.isEmpty()) return null;
|
|
|
- return objectMapper.readValue(json, new TypeReference<Map<String, Object>>() {});
|
|
|
+ if (json == null || json.isEmpty()) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return objectMapper.readValue(json, new TypeReference<>() {
|
|
|
+ });
|
|
|
} catch (Exception e) {
|
|
|
return null;
|
|
|
}
|
|
|
@@ -114,14 +120,14 @@ public class QACacheService {
|
|
|
*/
|
|
|
public boolean tryMarkPending(String normalizedQuery) {
|
|
|
if (!available || normalizedQuery == null || normalizedQuery.isBlank()) {
|
|
|
- return true; // Redis 不可用时放行,避免阻塞
|
|
|
+ return false; // Redis 不可用时放行,避免阻塞
|
|
|
}
|
|
|
try {
|
|
|
Boolean ok = redis.opsForValue()
|
|
|
.setIfAbsent(pendingKey(normalizedQuery), "1", PENDING_TTL);
|
|
|
- return Boolean.TRUE.equals(ok);
|
|
|
+ return !Boolean.TRUE.equals(ok);
|
|
|
} catch (Exception e) {
|
|
|
- return true; // 异常时放行
|
|
|
+ return false; // 异常时放行
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -159,6 +165,44 @@ public class QACacheService {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 清除 QA 缓存。不传 keyword 则全清;传 keyword 则只清除 answer/content
|
|
|
+ * 中包含该关键字的条目。
|
|
|
+ * @param keyword 可选,不为空时按关键字过滤
|
|
|
+ * @return 删除的 key 数量
|
|
|
+ */
|
|
|
+ public int clearAll(String keyword) {
|
|
|
+ if (!available) {
|
|
|
+ log.warn("Redis 不可用,无法清除缓存");
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ boolean filterByKeyword = keyword != null && !keyword.isBlank();
|
|
|
+ try {
|
|
|
+ var keys = redis.keys(CACHE_PREFIX + "*");
|
|
|
+ int count = 0;
|
|
|
+ if (!keys.isEmpty()) {
|
|
|
+ for (String key : keys) {
|
|
|
+ if (filterByKeyword) {
|
|
|
+ String json = redis.opsForValue().get(key);
|
|
|
+ if (json == null || !json.contains(keyword)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ count += Optional.of(redis.delete(key)).orElse(false) ? 1 : 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ var pendingKeys = redis.keys(PENDING_PREFIX + "*");
|
|
|
+ if (!pendingKeys.isEmpty()) {
|
|
|
+ count += redis.delete(pendingKeys).intValue();
|
|
|
+ }
|
|
|
+ log.info("QA 缓存已清除: keyword={}, deleted={}", keyword, count);
|
|
|
+ return count;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("清除 QA 缓存失败: {}", e.getMessage());
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private static String sha256(String input) {
|
|
|
try {
|
|
|
MessageDigest md = MessageDigest.getInstance("SHA-256");
|