113 lines
3.8 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.knowledge.base.infrastructure.cache;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* 使用 Redis 实现的文件缓存服务
*
* @author Luke.ye
* @date 2025/6/7
*/
@Slf4j
@Service
@RequiredArgsConstructor
@ConditionalOnProperty(prefix = "knowledge.base.redis", name = "enable", havingValue = "true", matchIfMissing = true)
public class RedisFileCacheService implements FileCacheService {
private final StringRedisTemplate redisTemplate;
private static final String FILE_CACHE_PREFIX = "kb-file";
private String key(String prefix, String key) {
return String.format(FILE_CACHE_PREFIX + ":%s:%s", prefix, key);
}
@Override
public Optional<String> getPath(String filename) {
String value = redisTemplate.opsForValue().get(key("path", filename));
return Optional.ofNullable(value);
}
@Override
public void cachePath(String filename, String relativePath, long expireMinutes) {
redisTemplate.opsForValue().set(key("path", filename), relativePath, expireMinutes, TimeUnit.MINUTES);
}
@Override
public void cacheMeta(String filename, String jsonMeta, long expireMinutes) {
redisTemplate.opsForValue().set(key("meta", filename), jsonMeta, expireMinutes, TimeUnit.MINUTES);
}
@Override
public Optional<String> getMeta(String filename) {
String value = redisTemplate.opsForValue().get(key("meta", filename));
return Optional.ofNullable(value);
}
@Override
public Optional<Long> getMTime(String relativePath) {
String val = redisTemplate.opsForValue().get(key("mtime", relativePath));
try {
return val != null ? Optional.of(Long.parseLong(val)) : Optional.empty();
} catch (NumberFormatException e) {
log.warn("mtime 解析失败: {}", val, e);
return Optional.empty();
}
}
@Override
public void cacheMTime(String relativePath, Long mtime, long expireMinutes) {
redisTemplate.opsForValue().set(key("mtime", relativePath), String.valueOf(mtime), expireMinutes, TimeUnit.MINUTES);
}
@Override
public void clearPath(String filename) {
redisTemplate.delete(key("path", filename));
}
@Override
public void clearMTime(String relativePath) {
redisTemplate.delete(key("mtime", relativePath));
}
@Override
public void clearAll() {
log.warn("正在清空 Redis 文件缓存,前缀: {}", FILE_CACHE_PREFIX);
Set<String> keysToDelete = scanKeys(FILE_CACHE_PREFIX + "*");
if (keysToDelete.isEmpty()) {
log.info("没有需要清理的文件缓存 Key。");
return;
}
redisTemplate.delete(keysToDelete);
log.info("已清空 Redis 文件缓存 Key 数量: {}", keysToDelete.size());
}
/**
* 用 scan 命令遍历所有带 prefix 的 key推荐不会阻塞大 Redis
*/
private Set<String> scanKeys(String pattern) {
Set<String> keySet = new HashSet<>();
// 采用 scan分批遍历安全高效
redisTemplate.execute((connection) -> {
try (var cursor = connection.scan(org.springframework.data.redis.core.ScanOptions.scanOptions()
.match(pattern)
.count(1000)
.build())) {
cursor.forEachRemaining(item -> keySet.add(new String(item)));
}
return null;
}, false, false);
return keySet;
}
}