You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
114 lines
4.0 KiB
114 lines
4.0 KiB
package com.wok.supportbot.cache;
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import org.springframework.stereotype.Component;
|
|
|
|
import java.util.List;
|
|
import java.util.Optional;
|
|
import java.util.concurrent.CompletableFuture;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
/**
|
|
* AI 推荐问题(suggest-message-list)的内存缓存。
|
|
* <p>
|
|
* key 为 chatId,value 为最近一次生成的 3 条建议问题。
|
|
* 不持久化到数据库——suggestions 是实时生成、即时消费的短生命周期数据。
|
|
* 每次新的生成会覆盖前一次的建议。
|
|
* <p>
|
|
* 增加 {@code pending} 映射,避免同一 chatId 的并发请求重复触发 LLM 生成。
|
|
*/
|
|
@Component
|
|
@Slf4j
|
|
public class SuggestionCache {
|
|
|
|
/** 最大缓存条目数,防止无限增长 */
|
|
private static final int MAX_ENTRIES = 1000;
|
|
|
|
/** 生成中任务的并发控制:key=chatId,value=正在执行的生成任务 */
|
|
private final ConcurrentHashMap<String, CompletableFuture<List<String>>> pending = new ConcurrentHashMap<>();
|
|
|
|
private final ConcurrentHashMap<String, List<String>> cache = new ConcurrentHashMap<>();
|
|
|
|
/**
|
|
* 存入建议问题列表。
|
|
*
|
|
* @param chatId 会话 ID
|
|
* @param suggestions 建议问题列表
|
|
*/
|
|
public void put(String chatId, List<String> suggestions) {
|
|
if (chatId == null || suggestions == null || suggestions.isEmpty()) {
|
|
return;
|
|
}
|
|
// 超过容量上限时清理一半旧条目(简单 LRU 近似)
|
|
if (cache.size() >= MAX_ENTRIES) {
|
|
int toRemove = MAX_ENTRIES / 2;
|
|
var it = cache.keySet().iterator();
|
|
while (it.hasNext() && toRemove > 0) {
|
|
it.next();
|
|
it.remove();
|
|
toRemove--;
|
|
}
|
|
log.info("SuggestionCache 达到上限 {},已清理 {} 条旧记录", MAX_ENTRIES, MAX_ENTRIES / 2);
|
|
}
|
|
cache.put(chatId, List.copyOf(suggestions));
|
|
log.debug("SuggestionCache 写入: chatId={}, count={}", chatId, suggestions.size());
|
|
}
|
|
|
|
/**
|
|
* 获取建议问题列表。
|
|
*
|
|
* @param chatId 会话 ID
|
|
* @return 建议问题列表,不存在则返回 Optional.empty()
|
|
*/
|
|
public Optional<List<String>> get(String chatId) {
|
|
if (chatId == null) {
|
|
return Optional.empty();
|
|
}
|
|
List<String> suggestions = cache.get(chatId);
|
|
return Optional.ofNullable(suggestions);
|
|
}
|
|
|
|
/**
|
|
* 注册一个正在生成中的任务。
|
|
*
|
|
* @param chatId 会话 ID
|
|
* @param future 生成任务
|
|
* @return 若已存在相同 chatId 的任务,返回已存在的任务;否则返回传入的任务
|
|
*/
|
|
public CompletableFuture<List<String>> putIfAbsent(String chatId, CompletableFuture<List<String>> future) {
|
|
if (chatId == null || future == null) {
|
|
return future;
|
|
}
|
|
CompletableFuture<List<String>> existing = pending.putIfAbsent(chatId, future);
|
|
if (existing != null) {
|
|
return existing;
|
|
}
|
|
// future 完成后写入缓存并从 pending 移除
|
|
future.whenComplete((result, ex) -> {
|
|
pending.remove(chatId, future);
|
|
if (ex != null) {
|
|
log.warn("SuggestionCache 生成任务异常: chatId={}", chatId, ex);
|
|
} else if (result != null && !result.isEmpty()) {
|
|
put(chatId, result);
|
|
}
|
|
});
|
|
return future;
|
|
}
|
|
|
|
/**
|
|
* 清除指定会话的建议缓存(会话删除时调用)。
|
|
*
|
|
* @param chatId 会话 ID
|
|
*/
|
|
public void evict(String chatId) {
|
|
if (chatId != null) {
|
|
cache.remove(chatId);
|
|
// 同时取消并清理可能正在进行的生成任务
|
|
CompletableFuture<List<String>> future = pending.remove(chatId);
|
|
if (future != null && !future.isDone()) {
|
|
future.cancel(true);
|
|
}
|
|
log.debug("SuggestionCache 清除: chatId={}", chatId);
|
|
}
|
|
}
|
|
}
|