feat: add 30-minute auto-cleanup for generated cache files

This commit is contained in:
agentuser
2026-05-11 17:07:47 +08:00
parent 9fc145ae61
commit da415aab80
@@ -4,16 +4,26 @@ import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.Duration;
import java.time.Instant;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
/** /**
* 文件缓存服务。 * 文件缓存服务。
* 生成的文件保存到本地磁盘,通过随机 key 索引,供下载接口使用。 * 生成的文件保存到本地磁盘,通过随机 key 索引,供下载接口使用。
* 文件生成 30 分钟后自动清理。
*/ */
@Service @Service
public class FileCacheService { public class FileCacheService {
@@ -21,22 +31,39 @@ public class FileCacheService {
private static final Logger log = LoggerFactory.getLogger(FileCacheService.class); private static final Logger log = LoggerFactory.getLogger(FileCacheService.class);
private final Path cacheDir; private final Path cacheDir;
private final Duration expiration;
public FileCacheService(@Value("${gmp.cache-dir:/tmp/gmp-cache}") String cacheDirPath) { private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(
r -> new Thread(r, "file-cache-cleaner"));
public FileCacheService(@Value("${gmp.cache-dir:/tmp/gmp-cache}") String cacheDirPath,
@Value("${gmp.cache-expiration:30m}") Duration expiration) {
this.cacheDir = Path.of(cacheDirPath); this.cacheDir = Path.of(cacheDirPath);
this.expiration = expiration;
try { try {
Files.createDirectories(this.cacheDir); Files.createDirectories(this.cacheDir);
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException("无法创建缓存目录: " + cacheDirPath, e); throw new RuntimeException("无法创建缓存目录: " + cacheDirPath, e);
} }
log.info("File cache directory: {}", this.cacheDir.toAbsolutePath()); log.info("File cache directory: {}, expiration: {}", this.cacheDir.toAbsolutePath(), this.expiration);
}
@PostConstruct
public void startCleanup() {
// Initial cleanup on startup
cleanExpired();
// Schedule periodic cleanup every 5 minutes
scheduler.scheduleAtFixedRate(this::cleanExpired, 5, 5, TimeUnit.MINUTES);
log.info("Cache cleanup scheduled every 5 minutes");
}
@PreDestroy
public void stopCleanup() {
scheduler.shutdown();
} }
/** /**
* 生成一个随机 key,并将数据写入缓存文件。 * 生成一个随机 key,并将数据写入缓存文件。
*
* @param data 文件字节数据
* @return 随机 key
*/ */
public String store(byte[] data) { public String store(byte[] data) {
String key = UUID.randomUUID().toString().replace("-", ""); String key = UUID.randomUUID().toString().replace("-", "");
@@ -52,9 +79,6 @@ public class FileCacheService {
/** /**
* 根据 key 读取缓存文件。 * 根据 key 读取缓存文件。
*
* @param key 随机 key
* @return 文件字节数据,或 null(key 不存在)
*/ */
public byte[] retrieve(String key) { public byte[] retrieve(String key) {
Path file = cacheDir.resolve(sanitize(key)); Path file = cacheDir.resolve(sanitize(key));
@@ -71,10 +95,6 @@ public class FileCacheService {
/** /**
* 把缓存文件内容写入输出流。 * 把缓存文件内容写入输出流。
*
* @param key 随机 key
* @param out 输出流
* @return 是否写入成功
*/ */
public boolean writeToStream(String key, OutputStream out) throws IOException { public boolean writeToStream(String key, OutputStream out) throws IOException {
Path file = cacheDir.resolve(sanitize(key)); Path file = cacheDir.resolve(sanitize(key));
@@ -85,6 +105,30 @@ public class FileCacheService {
return true; return true;
} }
/**
* 清理过期的缓存文件(超过 expiration 时间的文件)。
*/
private void cleanExpired() {
try {
Instant cutoff = Instant.now().minus(this.expiration);
Files.list(cacheDir).forEach(file -> {
try {
if (Files.isRegularFile(file)) {
BasicFileAttributes attrs = Files.readAttributes(file, BasicFileAttributes.class);
if (attrs.creationTime().toInstant().isBefore(cutoff)) {
Files.delete(file);
log.debug("Deleted expired cache file: {}", file.getFileName());
}
}
} catch (IOException e) {
log.warn("Failed to clean cache file: {}", file.getFileName(), e);
}
});
} catch (IOException e) {
log.warn("Cache cleanup failed", e);
}
}
private String sanitize(String key) { private String sanitize(String key) {
return key.replaceAll("[^a-zA-Z0-9\\-_]", ""); return key.replaceAll("[^a-zA-Z0-9\\-_]", "");
} }