diff --git a/src/main/java/com/gmp/mock/service/FileCacheService.java b/src/main/java/com/gmp/mock/service/FileCacheService.java index be4b07d..8532f99 100644 --- a/src/main/java/com/gmp/mock/service/FileCacheService.java +++ b/src/main/java/com/gmp/mock/service/FileCacheService.java @@ -4,16 +4,26 @@ import java.io.IOException; import java.io.OutputStream; import java.nio.file.Files; 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.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; + /** * 文件缓存服务。 * 生成的文件保存到本地磁盘,通过随机 key 索引,供下载接口使用。 + * 文件生成 30 分钟后自动清理。 */ @Service public class FileCacheService { @@ -21,22 +31,39 @@ public class FileCacheService { private static final Logger log = LoggerFactory.getLogger(FileCacheService.class); 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.expiration = expiration; try { Files.createDirectories(this.cacheDir); } catch (IOException 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,并将数据写入缓存文件。 - * - * @param data 文件字节数据 - * @return 随机 key */ public String store(byte[] data) { String key = UUID.randomUUID().toString().replace("-", ""); @@ -52,9 +79,6 @@ public class FileCacheService { /** * 根据 key 读取缓存文件。 - * - * @param key 随机 key - * @return 文件字节数据,或 null(key 不存在) */ public byte[] retrieve(String 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 { Path file = cacheDir.resolve(sanitize(key)); @@ -85,6 +105,30 @@ public class FileCacheService { 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) { return key.replaceAll("[^a-zA-Z0-9\\-_]", ""); }