文章目录
前言
项目中遇到一个延迟闹钟功能,谨以此篇博客和大家分享下。
需求
有个日程功能需要添加个闹钟提醒功能,可以设置一次提醒和多次提醒,并且可以设置提醒时间范围。
总体流程
- 通过接口新增一个闹铃(选择提醒时间,设置范围)
- 解析参数生成corn表达式,并生成一条任务数据存入数据库
- 判断闹铃的下一次时间是否有今天,有的话需要马上新增一条任务
- 每天晚上定时去获取数据库的数据通过判断时间范围来区分,找到有效的闹铃加到任务中去,因为有些任务并不是当天执行的,可能设在几个月后
- 任务失效后去数据库把失效字段设为失效,扫描的时候不扫描失效数据
- 服务重启也要把所有有效任务加上
实现
新增任务关键逻辑
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;
}
任务的管理通过CronTaskRegistrar类实现:
任务开启:
任务关闭:
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卷