diff --git a/xuqm-bugcollect-service/src/main/java/com/xuqm/bugcollect/repository/LogSourcemapRepository.java b/xuqm-bugcollect-service/src/main/java/com/xuqm/bugcollect/repository/LogSourcemapRepository.java index 79e61bb..348d35e 100644 --- a/xuqm-bugcollect-service/src/main/java/com/xuqm/bugcollect/repository/LogSourcemapRepository.java +++ b/xuqm-bugcollect-service/src/main/java/com/xuqm/bugcollect/repository/LogSourcemapRepository.java @@ -20,6 +20,14 @@ public interface LogSourcemapRepository extends JpaRepository findFirstByAppKeyAndPlatformAndAppVersionAndBundleNameOrderByUploadedAtDesc( String appKey, String platform, String appVersion, String bundleName); + /** All records for one version, newest first — used for per-version retention pruning. */ + List findByAppKeyAndPlatformAndAppVersionAndBundleNameOrderByUploadedAtDesc( + String appKey, String platform, String appVersion, String bundleName); + + /** All records for one (appKey, platform, bundleName), newest first — used for version-count pruning. */ + List findByAppKeyAndPlatformAndBundleNameOrderByUploadedAtDesc( + String appKey, String platform, String bundleName); + List findByAppKey(String appKey); void deleteByAppKey(String appKey); diff --git a/xuqm-bugcollect-service/src/main/java/com/xuqm/bugcollect/service/SourcemapService.java b/xuqm-bugcollect-service/src/main/java/com/xuqm/bugcollect/service/SourcemapService.java index 37c5770..9eccfb4 100644 --- a/xuqm-bugcollect-service/src/main/java/com/xuqm/bugcollect/service/SourcemapService.java +++ b/xuqm-bugcollect-service/src/main/java/com/xuqm/bugcollect/service/SourcemapService.java @@ -16,6 +16,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -103,6 +105,8 @@ public class SourcemapService { log.info("Sourcemap uploaded: appKey={} platform={} version={} buildId={} bundle={}", appKey, platform, versionKey, buildId, bundleName); + pruneSourcemaps(appKey, platform, appVersion, bundleName); + return new SourcemapUploadResponse( entity.getId(), entity.getAppKey(), entity.getPlatform(), entity.getAppVersion(), entity.getBundleName(), entity.getBuildId(), entity.getStorageKey() @@ -157,6 +161,53 @@ public class SourcemapService { return findSourceMap(appKey, platform, appVersion, null, bundleName); } + /** + * Enforce retention policy after every upload: + * - per version (appVersion): keep at most 3 records (newest by uploadedAt) + * - per (appKey, platform, bundleName): keep at most 5 distinct appVersions (newest by latest upload) + */ + private void pruneSourcemaps(String appKey, String platform, String appVersion, String bundleName) { + // 1. Per-version limit: keep newest 3 buildIds + List versionRecords = sourcemapRepository + .findByAppKeyAndPlatformAndAppVersionAndBundleNameOrderByUploadedAtDesc( + appKey, platform, appVersion, bundleName); + if (versionRecords.size() > 3) { + deleteEntities(versionRecords.subList(3, versionRecords.size())); + } + + // 2. Version-count limit: keep at most 5 distinct appVersions + List allRecords = sourcemapRepository + .findByAppKeyAndPlatformAndBundleNameOrderByUploadedAtDesc(appKey, platform, bundleName); + + // Preserve insertion order = newest-first version order + LinkedHashSet orderedVersions = new LinkedHashSet<>(); + for (LogSourcemapEntity r : allRecords) { + orderedVersions.add(r.getAppVersion()); + } + + if (orderedVersions.size() > 5) { + List versionList = new ArrayList<>(orderedVersions); + Set versionsToDelete = new LinkedHashSet<>(versionList.subList(5, versionList.size())); + List excess = allRecords.stream() + .filter(r -> versionsToDelete.contains(r.getAppVersion())) + .toList(); + deleteEntities(excess); + log.info("Pruned {} old-version sourcemap(s) for appKey={} platform={} bundle={}", + excess.size(), appKey, platform, bundleName); + } + } + + private void deleteEntities(List entities) { + for (LogSourcemapEntity entity : entities) { + try { + Files.deleteIfExists(Paths.get(entity.getStorageKey())); + } catch (IOException e) { + log.warn("Could not delete sourcemap file {}: {}", entity.getStorageKey(), e.getMessage()); + } + sourcemapRepository.delete(entity); + } + } + public String readSourceMapContent(String storageKey) throws IOException { return Files.readString(Paths.get(storageKey)); }