+52
@@ -5,11 +5,17 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -29,6 +35,12 @@ public class ZiniaoTransientCacheService {
|
||||
|
||||
private final ConcurrentHashMap<String, Holder> map = new ConcurrentHashMap<>();
|
||||
|
||||
@Value("${aiimage.ziniao.transient-cache-max-entries:10000}")
|
||||
private int maxEntries;
|
||||
|
||||
@Value("${aiimage.ziniao.transient-cache-max-payload-bytes:5242880}")
|
||||
private int maxPayloadBytes;
|
||||
|
||||
public <T> Optional<T> get(String cacheType, String cacheKey, Class<T> valueType) {
|
||||
Holder holder = getHolder(cacheType, cacheKey);
|
||||
if (holder == null) {
|
||||
@@ -68,8 +80,18 @@ public class ZiniaoTransientCacheService {
|
||||
}
|
||||
try {
|
||||
String json = objectMapper.writeValueAsString(payload);
|
||||
int payloadBytes = json.getBytes(StandardCharsets.UTF_8).length;
|
||||
int payloadLimit = maxPayloadBytes > 0 ? maxPayloadBytes : 5 * 1024 * 1024;
|
||||
if (payloadBytes > payloadLimit) {
|
||||
log.warn("[ziniao-transient] skip oversized cache type={} keyLen={} bytes={} maxBytes={}",
|
||||
normalizedType, normalizedKey.length(),
|
||||
payloadBytes, payloadLimit);
|
||||
return;
|
||||
}
|
||||
LocalDateTime expiresAt = LocalDateTime.now().plusSeconds(ttl.getSeconds());
|
||||
cleanupExpiredEntries(LocalDateTime.now());
|
||||
map.put(compoundKey(normalizedType, normalizedKey), new Holder(json, expiresAt));
|
||||
evictToCapacity();
|
||||
log.trace("[ziniao-transient] put type={} keyLen={} ttlSec={}", normalizedType, normalizedKey.length(), ttl.getSeconds());
|
||||
} catch (Exception ex) {
|
||||
throw new BusinessException("写入紫鸟进程缓存失败");
|
||||
@@ -83,6 +105,36 @@ public class ZiniaoTransientCacheService {
|
||||
log.trace("[ziniao-transient] delete type={}", normalizedType);
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelayString = "${aiimage.ziniao.transient-cache-cleanup-delay-ms:60000}")
|
||||
void cleanupExpiredEntriesScheduled() {
|
||||
cleanupExpiredEntries(LocalDateTime.now());
|
||||
evictToCapacity();
|
||||
}
|
||||
|
||||
private void cleanupExpiredEntries(LocalDateTime now) {
|
||||
map.entrySet().removeIf(entry -> {
|
||||
Holder holder = entry.getValue();
|
||||
return holder == null || holder.expiresAt == null || !holder.expiresAt.isAfter(now);
|
||||
});
|
||||
}
|
||||
|
||||
private void evictToCapacity() {
|
||||
int limit = maxEntries > 0 ? maxEntries : 10000;
|
||||
int overflow = map.size() - limit;
|
||||
if (overflow <= 0) {
|
||||
return;
|
||||
}
|
||||
List<Map.Entry<String, Holder>> candidates = new ArrayList<>(map.entrySet());
|
||||
candidates.sort(Comparator.comparing(entry -> entry.getValue().expiresAt,
|
||||
Comparator.nullsFirst(Comparator.naturalOrder())));
|
||||
candidates.stream().limit(overflow).forEach(entry ->
|
||||
map.remove(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
|
||||
int size() {
|
||||
return map.size();
|
||||
}
|
||||
|
||||
private Holder getHolder(String cacheType, String cacheKey) {
|
||||
String normalizedType = normalizeRequired(cacheType, "cacheType 不能为空");
|
||||
String normalizedKey = normalizeRequired(cacheKey, "cacheKey 不能为空");
|
||||
|
||||
Reference in New Issue
Block a user