增加定时任务删除不存在的图标

This commit is contained in:
2025-09-13 19:36:07 +08:00
parent d9071fdef5
commit b3352a2864
8 changed files with 189 additions and 8 deletions

View File

@@ -3,9 +3,11 @@ package com.onekeycall.videotablet;
import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.EnableScheduling;
@EnableScheduling @EnableScheduling
@EnableAsync // 启用异步支持
@MapperScan({"com.onekeycall.videotablet.mapper",}) @MapperScan({"com.onekeycall.videotablet.mapper",})
@SpringBootApplication @SpringBootApplication
public class VideoTabletApplication { public class VideoTabletApplication {

View File

@@ -0,0 +1,23 @@
package com.onekeycall.videotablet.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10); // 核心线程数
executor.setMaxPoolSize(20); // 最大线程数
executor.setQueueCapacity(100); // 队列容量
executor.setThreadNamePrefix("async-task-");
executor.initialize();
return executor;
}
}

View File

@@ -0,0 +1,31 @@
package com.onekeycall.videotablet.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import java.util.concurrent.Executors;
import java.util.concurrent.Executor;
@Configuration
public class SchedulerConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
// taskRegistrar.setScheduler(taskExecutor());
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10); // 核心线程数
scheduler.setThreadNamePrefix("scheduled-task-"); // 线程名前缀
scheduler.setAwaitTerminationSeconds(60); // 等待终止时间
scheduler.setWaitForTasksToCompleteOnShutdown(true); // 关闭时等待任务完成
scheduler.initialize(); // 初始化调度器
taskRegistrar.setTaskScheduler(scheduler);
}
public Executor taskExecutor() {
// 创建包含10个线程的线程池
return Executors.newScheduledThreadPool(10);
}
}

View File

@@ -3,16 +3,10 @@ package com.onekeycall.videotablet.controller.user;
import com.onekeycall.videotablet.config.PushIdConfig; import com.onekeycall.videotablet.config.PushIdConfig;
import com.onekeycall.videotablet.controller.pub.LoginController; import com.onekeycall.videotablet.controller.pub.LoginController;
import com.onekeycall.videotablet.dto.TokenPair; import com.onekeycall.videotablet.dto.TokenPair;
import com.onekeycall.videotablet.entity.DeviceApkInfo; import com.onekeycall.videotablet.entity.*;
import com.onekeycall.videotablet.entity.DeviceInfo;
import com.onekeycall.videotablet.entity.DeviceLocation;
import com.onekeycall.videotablet.entity.User;
import com.onekeycall.videotablet.gson.GsonUtils; import com.onekeycall.videotablet.gson.GsonUtils;
import com.onekeycall.videotablet.result.Result; import com.onekeycall.videotablet.result.Result;
import com.onekeycall.videotablet.service.DeviceApkInfoService; import com.onekeycall.videotablet.service.*;
import com.onekeycall.videotablet.service.DeviceLocationService;
import com.onekeycall.videotablet.service.DeviceSnService;
import com.onekeycall.videotablet.service.UserService;
import com.onekeycall.videotablet.utils.DevicePushUtils; import com.onekeycall.videotablet.utils.DevicePushUtils;
import com.onekeycall.videotablet.utils.JwtUtil; import com.onekeycall.videotablet.utils.JwtUtil;
import com.onekeycall.videotablet.utils.TextUtils; import com.onekeycall.videotablet.utils.TextUtils;
@@ -26,7 +20,9 @@ import org.springframework.web.bind.annotation.*;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
@RestController @RestController
@RequestMapping("/user") @RequestMapping("/user")
@@ -45,6 +41,8 @@ public class UserController {
private DeviceLocationService deviceLocationService; private DeviceLocationService deviceLocationService;
@Autowired @Autowired
private DeviceApkInfoService deviceApkInfoService; private DeviceApkInfoService deviceApkInfoService;
@Autowired
private ApkIconService apkIconService;
Logger logger = LoggerFactory.getLogger(LoginController.class); Logger logger = LoggerFactory.getLogger(LoginController.class);
@@ -190,6 +188,14 @@ public class UserController {
if (deviceApkInfo == null || deviceApkInfo.getApkList() == null) { if (deviceApkInfo == null || deviceApkInfo.getApkList() == null) {
return Result.notFound().message("未找到设备APK信息"); return Result.notFound().message("未找到设备APK信息");
} }
List<ApkInfo> apkList = deviceApkInfo.getApkList();
apkList.stream().forEach(new Consumer<ApkInfo>() {
@Override
public void accept(ApkInfo apkInfo) {
Optional<ApkIconFileInfo> apkIconFileInfo = apkIconService.findMaxVersionCodeByPackageName(apkInfo.getPackageName());
apkIconFileInfo.ifPresent(iconFileInfo -> apkInfo.setIconUrl(iconFileInfo.getFileName()));
}
});
return Result.ok().data(deviceApkInfo.getApkList()); return Result.ok().data(deviceApkInfo.getApkList());
} }

View File

@@ -1,9 +1,15 @@
package com.onekeycall.videotablet.repository; package com.onekeycall.videotablet.repository;
import com.onekeycall.videotablet.entity.ApkIconFileInfo; import com.onekeycall.videotablet.entity.ApkIconFileInfo;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List; import java.util.List;
import java.util.Optional;
public interface ApkIconRepository extends JpaRepository<ApkIconFileInfo, Long> { public interface ApkIconRepository extends JpaRepository<ApkIconFileInfo, Long> {
@@ -15,4 +21,23 @@ public interface ApkIconRepository extends JpaRepository<ApkIconFileInfo, Long>
List<ApkIconFileInfo> findByPackageName(String packageName); List<ApkIconFileInfo> findByPackageName(String packageName);
List<ApkIconFileInfo> findAll();
Integer deleteApkIconFileInfosById(Long id);
/**
* 通过packageName查找version_code最大的一条记录
* 使用 JPQL 并限制结果数量但JPQL不支持LIMIT故用Pageable
*/
@Query("SELECT e FROM ApkIconFileInfo e WHERE e.packageName = :pkgName ORDER BY e.versionCode DESC")
Page<ApkIconFileInfo> findTopByPackageNameOrderByVersionCodeDesc(@Param("pkgName") String packageName, Pageable pageable);
/**
* 在Service中调用该方法获取第一条记录
*/
default Optional<ApkIconFileInfo> findMaxVersionCodeByPackageName(String packageName) {
PageRequest pageRequest = PageRequest.of(0, 1); // 获取第一页的第一条记录
Page<ApkIconFileInfo> page = findTopByPackageNameOrderByVersionCodeDesc(packageName, pageRequest);
return page.getContent().stream().findFirst();
}
} }

View File

@@ -4,8 +4,10 @@ import com.onekeycall.videotablet.entity.ApkIconFileInfo;
import com.onekeycall.videotablet.repository.ApkIconRepository; import com.onekeycall.videotablet.repository.ApkIconRepository;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List; import java.util.List;
import java.util.Optional;
@Service @Service
public class ApkIconService { public class ApkIconService {
@@ -27,4 +29,17 @@ public class ApkIconService {
public List<ApkIconFileInfo> findByPackageName(String packageName) { public List<ApkIconFileInfo> findByPackageName(String packageName) {
return apkIconRepository.findByPackageName(packageName); return apkIconRepository.findByPackageName(packageName);
} }
public Optional<ApkIconFileInfo> findMaxVersionCodeByPackageName(String packageName) {
return apkIconRepository.findMaxVersionCodeByPackageName(packageName);
}
public List<ApkIconFileInfo> findAll() {
return apkIconRepository.findAll();
}
@Transactional
public Integer deleteApkIconFileInfosById(Long id) {
return apkIconRepository.deleteApkIconFileInfosById(id);
}
} }

View File

@@ -0,0 +1,20 @@
package com.onekeycall.videotablet.task;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class AsyncScheduledTasks {
// @Async // 异步执行
// @Scheduled(fixedRate = 5000)
// public void asyncTask() {
// try {
// Thread.sleep(3000); // 模拟耗时操作
// System.out.println("异步任务执行: " + Thread.currentThread().getName());
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
}

View File

@@ -0,0 +1,59 @@
package com.onekeycall.videotablet.task;
import com.onekeycall.videotablet.config.FilePath;
import com.onekeycall.videotablet.entity.ApkIconFileInfo;
import com.onekeycall.videotablet.repository.ApkIconRepository;
import com.onekeycall.videotablet.service.ApkIconService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.io.File;
import java.util.Date;
import java.util.List;
@Component
public class ScheduledTasks {
@Autowired
private ApkIconService apkIconService;
Logger logger = LoggerFactory.getLogger(ScheduledTasks.class);
// // 固定速率每隔5秒执行一次从上一次任务开始时间计算
// @Scheduled(fixedRate = 5000)
// public void taskWithFixedRate() {
// System.out.println("固定速率任务执行: " + new Date());
// }
//
// // 固定延迟上一次任务结束后延迟3秒执行
// @Scheduled(fixedDelay = 3000)
// public void taskWithFixedDelay() {
// System.out.println("固定延迟任务执行: " + new Date());
// }
//
// // 初始延迟应用启动后延迟10秒开始然后每隔5秒执行一次
// @Scheduled(initialDelay = 10000, fixedRate = 5000)
// public void taskWithInitialDelay() {
// System.out.println("带初始延迟的任务执行: " + new Date());
// }
// 使用Cron表达式每天中午12点执行
@Scheduled(cron = "0 0 0 * * ?")
// @Scheduled(initialDelay = 10000)
@Async
public void taskWithCron() {
logger.info("Cron表达式任务执行: " + new Date());
List<ApkIconFileInfo> apkIconFileInfoList = apkIconService.findAll();
for (ApkIconFileInfo apkIconFileInfo : apkIconFileInfoList) {
logger.info("检查图标文件是否存在: " + apkIconFileInfo);
File file = new File(FilePath.getApkIconPath() + apkIconFileInfo.getFileName());
if (!file.exists()) {
Integer i = apkIconService.deleteApkIconFileInfosById(apkIconFileInfo.getId());
logger.info("删除图标数据: id = " + apkIconFileInfo.getId() + " Successful =" + i);
}
}
}
}