|
|
@@ -19,6 +19,9 @@ import java.time.Duration;
|
|
|
import java.util.ArrayList;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
+import java.util.UUID;
|
|
|
+import java.util.concurrent.CompletableFuture;
|
|
|
+import java.util.concurrent.ConcurrentHashMap;
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
/**
|
|
|
@@ -35,6 +38,7 @@ public class AdminController {
|
|
|
private final RetryService retryService;
|
|
|
private final ManualTencentCallbackService manualTencentCallbackService;
|
|
|
private final StringRedisTemplate redisTemplate;
|
|
|
+ private final Map<String, RedisMaintenanceJob> redisMaintenanceJobs = new ConcurrentHashMap<>();
|
|
|
|
|
|
public AdminController(ConversionClient conversionClient,
|
|
|
@Nullable ConversionSyncRunner conversionSyncRunner,
|
|
|
@@ -186,6 +190,90 @@ public class AdminController {
|
|
|
patterns = defaultBidMediaPatterns();
|
|
|
}
|
|
|
|
|
|
+ String jobId = UUID.randomUUID().toString();
|
|
|
+ RedisMaintenanceJob job = new RedisMaintenanceJob(jobId, "RUNNING", ttlLessThanSeconds, scanCount, maxKeys,
|
|
|
+ 0, 0, null, List.of());
|
|
|
+ redisMaintenanceJobs.put(jobId, job);
|
|
|
+ List<String> taskPatterns = List.copyOf(patterns);
|
|
|
+ long taskTtlLessThanSeconds = ttlLessThanSeconds;
|
|
|
+ long taskScanCount = scanCount;
|
|
|
+ long taskMaxKeys = maxKeys;
|
|
|
+ CompletableFuture.runAsync(() -> runDeleteExpiringJob(jobId, taskPatterns, taskTtlLessThanSeconds,
|
|
|
+ taskScanCount, taskMaxKeys));
|
|
|
+ return Map.of(
|
|
|
+ "jobId", jobId,
|
|
|
+ "status", "RUNNING",
|
|
|
+ "ttlLessThanSeconds", ttlLessThanSeconds,
|
|
|
+ "ttlLessThanHuman", Duration.ofSeconds(ttlLessThanSeconds).toString(),
|
|
|
+ "scanCount", scanCount,
|
|
|
+ "maxKeys", maxKeys,
|
|
|
+ "patterns", taskPatterns
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ @GetMapping("/redis/delete-bid-media-expiring-before/{jobId}")
|
|
|
+ public Object getDeleteBidMediaExpiringBeforeJob(@PathVariable("jobId") String jobId) {
|
|
|
+ RedisMaintenanceJob job = redisMaintenanceJobs.get(jobId);
|
|
|
+ if (job == null) {
|
|
|
+ throw new ResponseStatusException(HttpStatus.NOT_FOUND, "job not found");
|
|
|
+ }
|
|
|
+ return job;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void runDeleteExpiringJob(String jobId, List<String> patterns, long ttlLessThanSeconds,
|
|
|
+ long scanCount, long maxKeys) {
|
|
|
+ List<Map<String, Object>> results = new ArrayList<>();
|
|
|
+ long totalScanned = 0;
|
|
|
+ long totalDeleted = 0;
|
|
|
+ try {
|
|
|
+ for (String pattern : patterns) {
|
|
|
+ DeleteExpiringResult result = deleteExpiringKeys(pattern, ttlLessThanSeconds, scanCount,
|
|
|
+ Math.max(0, maxKeys - totalScanned));
|
|
|
+ results.add(Map.of(
|
|
|
+ "pattern", pattern,
|
|
|
+ "scanned", result.scanned(),
|
|
|
+ "deleted", result.deleted(),
|
|
|
+ "stoppedByLimit", result.stoppedByLimit()
|
|
|
+ ));
|
|
|
+ totalScanned += result.scanned();
|
|
|
+ totalDeleted += result.deleted();
|
|
|
+ redisMaintenanceJobs.put(jobId, new RedisMaintenanceJob(jobId, "RUNNING", ttlLessThanSeconds,
|
|
|
+ scanCount, maxKeys, totalScanned, totalDeleted, null, List.copyOf(results)));
|
|
|
+ if (totalScanned >= maxKeys) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ redisMaintenanceJobs.put(jobId, new RedisMaintenanceJob(jobId, "COMPLETED", ttlLessThanSeconds,
|
|
|
+ scanCount, maxKeys, totalScanned, totalDeleted, null, List.copyOf(results)));
|
|
|
+ } catch (Exception e) {
|
|
|
+ redisMaintenanceJobs.put(jobId, new RedisMaintenanceJob(jobId, "FAILED", ttlLessThanSeconds,
|
|
|
+ scanCount, maxKeys, totalScanned, totalDeleted, rootMessage(e), List.copyOf(results)));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @PostMapping("/redis/delete-bid-media-expiring-before-sync")
|
|
|
+ public Object deleteBidMediaExpiringBeforeSync(@RequestBody(required = false) Map<String, Object> body) {
|
|
|
+ if (redisTemplate == null) {
|
|
|
+ throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE,
|
|
|
+ "redis template is not configured");
|
|
|
+ }
|
|
|
+ long ttlLessThanSeconds = toLong(body == null ? null : body.get("ttlLessThanSeconds"));
|
|
|
+ if (ttlLessThanSeconds <= 0) {
|
|
|
+ ttlLessThanSeconds = Duration.ofDays(2).getSeconds();
|
|
|
+ }
|
|
|
+ long scanCount = toLong(body == null ? null : body.get("scanCount"));
|
|
|
+ if (scanCount <= 0) {
|
|
|
+ scanCount = 1000;
|
|
|
+ }
|
|
|
+ long maxKeys = toLong(body == null ? null : body.get("maxKeys"));
|
|
|
+ if (maxKeys <= 0) {
|
|
|
+ maxKeys = 50000;
|
|
|
+ }
|
|
|
+ List<String> patterns = parseStringList(body == null ? null : body.get("patterns"));
|
|
|
+ if (patterns.isEmpty()) {
|
|
|
+ patterns = defaultBidMediaPatterns();
|
|
|
+ }
|
|
|
+
|
|
|
List<Map<String, Object>> results = new ArrayList<>();
|
|
|
long totalScanned = 0;
|
|
|
long totalDeleted = 0;
|
|
|
@@ -324,7 +412,19 @@ public class AdminController {
|
|
|
return List.of(-1, -2, -3, -4, -5, -6, -7);
|
|
|
}
|
|
|
|
|
|
+ private static String rootMessage(Throwable e) {
|
|
|
+ Throwable cur = e;
|
|
|
+ while (cur.getCause() != null) {
|
|
|
+ cur = cur.getCause();
|
|
|
+ }
|
|
|
+ return cur.getMessage() == null ? cur.getClass().getName() : cur.getMessage();
|
|
|
+ }
|
|
|
+
|
|
|
private record TtlTightenResult(long scanned, long updated, boolean stoppedByLimit) {}
|
|
|
|
|
|
private record DeleteExpiringResult(long scanned, long deleted, boolean stoppedByLimit) {}
|
|
|
+
|
|
|
+ private record RedisMaintenanceJob(String jobId, String status, long ttlLessThanSeconds, long scanCount,
|
|
|
+ long maxKeys, long scanned, long deleted, String error,
|
|
|
+ List<Map<String, Object>> patterns) {}
|
|
|
}
|