一、开启定时器,需要在启动类和定时任务类上添加注解
–>springboot+springcloud的定时任务
二、设计定时任务的数据库,通过数据库动态配置定时任务。
-- auto-generated definition
create table timer_config
(
creation_date datetime not null,
created_by bigint not null,
last_update_date datetime(3) null,
last_updated_by bigint not null,
enabled_flag varchar(1) not null,
timer_config_id bigint auto_increment comment '主键'
primary key,
timer_code varchar(64) not null comment '定时器编码',
timer_name varchar(1024) not null comment '定时器名称',
timer_ip varchar(64) not null comment '定时器可执行IP',
timer_cron varchar(32) not null comment '定时器执行的表达式(0 10 6 * * ?)(* 0/10 * * * ?)',
timer_para varchar(512) null comment '定时器带入参数(JSON字符)',
timer_app varchar(32) null comment '定时器应用模块(应用名)',
log_flag varchar(1) not null comment '是否记录运行日志(打印在日志控制台)',
constraint timer_config_u1
unique (timer_code)
)
comment '定时器配置';
creation_date --> 创建时间
created_by --> 创建人id
last_update_date–> 最后更新时间
last_updated_by --> 最后更新人id
enabled_flag --> 是否有效
三、基于SchedulingConfigurer接口实现配置化的定时任务
@Schedule 注解的方式,缺点是不能动态配置,SchedulingConfigurer可以做到。
/**
* 如果大时间段改为小时间段或者更改IP,需要重启应用才能立即生效。比如:1天改为1小时
*/
public abstract class AbstractTimer implements SchedulingConfigurer {
protected final Logger log = LoggerFactory.getLogger(getClass());
@Autowired
protected TimerConfigService timerConfigService;
@Autowired
protected MonitorSendByMqService monitorSendByMqService;
@Autowired
private CacheService cache;
private TimerConfig getTimerConfig(ContextInfo context, String timerCode) {
String key = CacheKeyUtil.buildKey(TimerConfig.class, timerCode);
try {
return cache.get(key, () -> {
TimerConfig config = timerConfigService.getTimerConfig(timerCode);
if (null == config) {
log.error("找不到定时器配置。timerCode={},sessionId={}", timerCode, context.getSessionId());
}
return config;
});
} catch (Exception e) {
log.error("查询缓存的定时器配置出错。timerCode={},sessionId={}", timerCode, context.getSessionId(), e);
return null;
}
}
@SuppressWarnings("rawtypes")
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
ContextInfo context = ContextInfo.buildTimer();//构建上下文
Set<String> netIps = BeeGetLocalIpUtil.get();//获取ip地址 https://blog.csdn.net/shihuahao0353/article/details/121167878
String timerCode = this.getTimerCode();
TimerConfig config = this.getTimerConfig(context, timerCode);
if (null == config) {
return;
}
taskRegistrar.addTriggerTask(() -> {
if (YesOrNo.Y.val.equals(config.getLogFlag())) {
log.info("定时任务执行[开始], timerCode={},sessionId={}", timerCode, context.getSessionId());
}
// 判断定时器是否可以在本机运行
if (!this.isRun(config.getTimerIp(), netIps)) {
log.error("定时任务执行失败,IP不合法, timerCode={},执行ip={},合法ip={},sessionId={}", timerCode, netIps.toString(), config.getTimerIp(), context.getSessionId());
return;
}
long start = System.currentTimeMillis();
// 执行业务逻辑
try {
CommonResult cr = this.process(context, BeeStringUtil.isEmpty(config.getTimerPara()) ? null : JSONObject.parseObject(config.getTimerPara()));
if (cr == null) {
log.error("定时任务执行失败,业务处理未返回处理结果, timerCode={},sessionId={}", timerCode, context.getSessionId());
return;
} else if (!cr.isSuccess()) {
log.error("定时任务执行失败,业务处理未返回错误, timerCode={},msg={},sessionId={}", timerCode, cr.getMessage(), context.getSessionId());
return;
}
} catch (BusinessException e) {
// 业务异常不发通知
log.error("执行定时器任务出错!timerCode={},sessionId={}", timerCode, context.getSessionId(), e);
} catch (Exception e) {
log.error("执行定时器任务异常!timerCode={},sessionId={}", timerCode, context.getSessionId(), e);
// 运行时异常发通知
this.sendMonitorNotice("执行定时器任务异常!timerCode=" + timerCode + ",sessionId=" + context.getSessionId());
} finally {
if (YesOrNo.Y.val.equals(config.getLogFlag())) {
long end = System.currentTimeMillis();
log.info("定时任务执行[结束]。timerCode={},耗时={}毫秒,sessionId={}", timerCode, end - start, context.getSessionId());
}
}
}, triggerContext -> {
// log.info("====获取定时器配置,sessionId={},TimerCode={}", context.getSessionId(), timerCode);
return new CronTrigger(config.getTimerCron()).nextExecutionTime(triggerContext);
});
}
@SuppressWarnings("rawtypes")
protected abstract CommonResult process(ContextInfo context, JSONObject para) throws Exception;
/** timer_config表中的timer_code字段 */
protected abstract String getTimerCode();
private boolean isRun(String configIps, Set<String> netIps) {
for (String configIp : configIps.split(",")) {
for (String netIp : netIps) {
if (configIp.equals(netIp)) {
return true;
}
}
}
return false;
}
private void sendMonitorNotice(String msg) {
try {
SysMonitorBean bean = SysMonitorBean.buildMail("定时器运行异常", msg, null);
monitorSendByMqService.send(bean);
} catch (Exception e) {
log.error("发送错误监控消息异常:" + e.getMessage(), e);
}
}
}
定时任务的实现类继承AbstractTimer抽象类,添加@Component注解,实现抽象方法,就可以实现通过数据库来灵活配置定时器了。
附:CommonResult类
public class CommonResult<T> implements java.io.Serializable {
private static final long serialVersionUID = 2683217789586688528L;
private int status;
private String message;
private T data;
public CommonResult() {
}
public CommonResult(int status, String message, T data) {
this.status = status;
this.message = message;
this.data = data;
}
// 成功
public static <T> CommonResult<T> success() {
return new CommonResult<T>(RespStatus.SUCCESS, null, null);
}
public static <T> CommonResult<T> successWithMessage(String msg) {
return new CommonResult<T>(RespStatus.SUCCESS, msg, null);
}
public static <T> CommonResult<T> successWithMessage(String msg, Object... objects) {
return new CommonResult<T>(RespStatus.SUCCESS, String.format(msg, objects), null);
}
public static <T> CommonResult<T> successWithData(T data) {
return new CommonResult<T>(RespStatus.SUCCESS, null, data);
}
// 失败
public static <T> CommonResult<T> fail() {
return new CommonResult<T>(RespStatus.BUSIENSSERROR, null, null);
}
public static <T> CommonResult<T> failWithMessageAndData(String msg, T data) {
return new CommonResult<T>(RespStatus.BUSIENSSERROR, msg, data);
}
public static <T> CommonResult<T> failWithMessage(String msg) {
return new CommonResult<T>(RespStatus.BUSIENSSERROR, msg, null);
}
public static <T> CommonResult<T> failWithMessage(String msg, Object... objects) {
return new CommonResult<T>(RespStatus.BUSIENSSERROR, String.format(msg, objects), null);
}
public static <T> CommonResult<T> build(int status, String message, T data) {
return new CommonResult<T>(status, message, data);
}
//
public boolean isSuccess() {
return RespStatus.SUCCESS == this.getStatus();
}
public boolean isFailure() {
return !this.isSuccess();
}
//
public String getMessage() {
return message;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}