springboot实现闹钟功能(动态定时器)

文章目录

前言

项目中遇到一个延迟闹钟功能,谨以此篇博客和大家分享下。

需求

有个日程功能需要添加个闹钟提醒功能,可以设置一次提醒和多次提醒,并且可以设置提醒时间范围。

总体流程

  1. 通过接口新增一个闹铃(选择提醒时间,设置范围)
  2. 解析参数生成corn表达式,并生成一条任务数据存入数据库
  3. 判断闹铃的下一次时间是否有今天,有的话需要马上新增一条任务
  4. 每天晚上定时去获取数据库的数据通过判断时间范围来区分,找到有效的闹铃加到任务中去,因为有些任务并不是当天执行的,可能设在几个月后
  5. 任务失效后去数据库把失效字段设为失效,扫描的时候不扫描失效数据
  6. 服务重启也要把所有有效任务加上

实现

新增任务关键逻辑

String corn = "";
        if (appSchedule.getPushType() == DataConstant.ONE) {
            int day = localDateTime.getDayOfMonth();
            int monthValue = localDateTime.getMonthValue();
            int year = localDateTime.getYear();
            // 一次
            corn = second + " " + minute + " " + hour + " " + day + " " + monthValue + " ?";
        } else if (appSchedule.getPushType() == DataConstant.TWO) {
            // 每天
            corn = second + " " + minute + " " + hour + " * * ?";
        } else if (appSchedule.getPushType() == DataConstant.ZERO) {
            // 每月
            int day = localDateTime.getDayOfMonth();
            corn = second + " " + minute + " " + hour + " " + day + " * ?";
        } else  {
            // 每周几 pushtype-2就是周几
            int week = appSchedule.getPushType() - DataConstant.TWO;
            corn = second + " " + minute + " " + hour + " ? * " + week;
        }
        appSchedule.setCorn(corn);
        // 新增
        appScheduleService.save(appSchedule);
        // 如果是今天有执行的任务就注册定时器,不然就是晚上凌晨自动注册
        List<String> recentDataByCorn = getRecentDataByCorn(corn, 1, new Date());
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDateTime nextDay = LocalDateTime.parse(recentDataByCorn.get(DataConstant.ZERO), dateTimeFormatter);
        if (nextDay.toLocalDate().isEqual(LocalDate.now())) {
            // 注册定时器,定时器执行的时候会调用appScheduleService的pushOne方法
            SchedulingRunnable task = new SchedulingRunnable("appScheduleService", "pushOne", appSchedule);
            cronTaskRegistrar.addCronTask(appSchedule.getId(), task, corn);
        }

注意: appScheduleService是注册到spring里的beanName

解析corn

 /**
     * 解析corn获取最近数据
     * @param corn
     * @param size 获取条数
     * @param startDate 开始时间
     * @return
     */
    private List<String> getRecentDataByCorn(String corn, Integer size, Date startDate) {
        CronSequenceGenerator cronSequenceGenerator = new CronSequenceGenerator(corn);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        List<String> list = new ArrayList<>(size);
        for (int i = 0; i < size; i++) {
            // 计算下次时间点的开始时间
            startDate = cronSequenceGenerator.next(startDate);
            list.add(sdf.format(startDate));
        }
        return list;
    }

springboot实现闹钟功能(动态定时器)
任务的管理通过CronTaskRegistrar类实现:
任务开启:
springboot实现闹钟功能(动态定时器)
任务关闭:
springboot实现闹钟功能(动态定时器)

ScheduledTask.java

public final class ScheduledTask {

    public volatile ScheduledFuture<?> future;
    /**
     * 取消定时任务
     */
    public void cancel() {
        ScheduledFuture<?> future = this.future;
        if (future != null) {
            future.cancel(true);
        }
    }
}

线程池配置类:SchedulingConfig.class

@Configuration
public class SchedulingConfig {
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        // 定时任务执行线程池核心线程数
        taskScheduler.setPoolSize(4);
        taskScheduler.setRemoveOnCancelPolicy(true);
        taskScheduler.setThreadNamePrefix("TaskSchedulerThreadPool-");
        return taskScheduler;
    }
}

程序启动时加载数据库中的定时器

@Component
@Transactional(rollbackFor = Exception.class)
public class SchedulingInitConfig {

    @Resource
    AppScheduleMapper appScheduleMapper;

    @Resource
    CronTaskRegistrar cronTaskRegistrar;

    /**
     * 在服务启动时查询数据库数据启动相关任务
     */
    @PostConstruct
    public void initFileSuffix() {
        //在服务启动时查询数据库数据启动相关任务
        LocalDate now = LocalDate.now();
        List<AppSchedule> appSchedules = appScheduleMapper.selectInitList(now);
        if (DataUtil.isEmpty(appSchedules)) {
            return;
        }
        appSchedules.forEach(v -> {
            SchedulingRunnable task = new SchedulingRunnable("appScheduleService", "pushOne", v);
            cronTaskRegistrar.addCronTask(v.getId(), task, v.getCorn());
        });
    }

    /**
     * 每天凌晨启动有效的定时任务,同时去掉过时的定时任务
     */
    @Async("DefaultExecutor")
    @Scheduled(cron = "0 0 0 * * ?")
    public void updateRechargeRecord() {
        // 找过时且有效的数据, 改成失效
        LocalDate now = LocalDate.now();
        List<AppSchedule> appSchedules = appScheduleMapper.selectNotValidList(now);
        if (DataUtil.isNotEmpty(appSchedules)) {
            // 批量失效
            appScheduleMapper.batchUpdateIfValid(appSchedules.stream().map(v -> {return v.getId().toString();}).collect(Collectors.joining(",")));
        }
        // 启动有效的定时任务
        List<AppSchedule> validList = appScheduleMapper.selectInitList(now);
        if (DataUtil.isNotEmpty(validList)) {
            validList.forEach(v -> {
                SchedulingRunnable task = new SchedulingRunnable("appScheduleService", "pushOne", v);
                cronTaskRegistrar.addCronTask(v.getId(), task, v.getCorn());
            });
        }
    }
}

定时器执行类:

public class SchedulingRunnable implements Runnable {

    private static final Logger logger = LoggerFactory.getLogger(SchedulingRunnable.class);

    private String beanName;

    private String methodName;

    private Object[] params;

    public SchedulingRunnable(String beanName, String methodName) {
        this(beanName, methodName, null);
    }

    public SchedulingRunnable(String beanName, String methodName, Object...params ) {
        this.beanName = beanName;
        this.methodName = methodName;
        this.params = params;
    }

    @Override
    public void run() {
        logger.info("定时任务开始执行 - bean:{},方法:{},参数:{}", beanName, methodName, params);
        long startTime = System.currentTimeMillis();

        try {
            Object target = SpringContextTaskUtils.getBean(beanName);

            Method method = null;
            if (null != params && params.length > 0) {
                Class<?>[] paramCls = new Class[params.length];
                for (int i = 0; i < params.length; i++) {
                    paramCls[i] = params[i].getClass();
                }
                method = target.getClass().getDeclaredMethod(methodName, paramCls);
            } else {
                method = target.getClass().getDeclaredMethod(methodName);
            }

            ReflectionUtils.makeAccessible(method);
            if (null != params && params.length > 0) {
                method.invoke(target, params);
            } else {
                method.invoke(target);
            }
        } catch (Exception ex) {
            logger.error(String.format("定时任务执行异常 - bean:%s,方法:%s,参数:%s ", beanName, methodName, params), ex);
        }

        long times = System.currentTimeMillis() - startTime;
        logger.info("定时任务执行结束 - bean:{},方法:{},参数:{},耗时:{} 毫秒", beanName, methodName, params, times);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        SchedulingRunnable that = (SchedulingRunnable) o;
        if (params == null) {
            return beanName.equals(that.beanName) &&
                    methodName.equals(that.methodName) &&
                    that.params == null;
        }

        return beanName.equals(that.beanName) &&
                methodName.equals(that.methodName) &&
                params.equals(that.params);
    }

    @Override
    public int hashCode() {
        if (params == null) {
            return Objects.hash(beanName, methodName);
        }

        return Objects.hash(beanName, methodName, params);
    }
}

获取上下文类:

@Component
public class SpringContextTaskUtils implements ApplicationContextAware {
    private static ApplicationContext applicationContext = null;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (SpringContextTaskUtils.applicationContext == null) {
            SpringContextTaskUtils.applicationContext = applicationContext;
        }
    }

    //获取applicationContext
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    //通过name获取 Bean.
    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

    //通过class获取Bean.
    public static <T> T getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }

    //通过name,以及Clazz返回指定的Bean
    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }
}

这样就实现了灵活配置定时器了

写在最后

非常感谢大家的认真阅读,如果有其他好用的技巧或者其他代码技巧都可以和我交流哦,如有不足,还望各位看官多批评指正=_=
技术交流秋秋群:719023986

微x关注:干饭必备外卖神券,每天领大额卷
微x关注:正好想买,自助查桃宝京d卷

上一篇:MybatisPlus条件构造器Wrapper


下一篇:Android与Flutter混合开发,超详细讲解