113 lines
3.8 KiB
Java
113 lines
3.8 KiB
Java
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;
|
||
}
|
||
|
||
}
|