springboot之quartz动态可控定时任务
Quartz
Quartz是一个开源的任务调度框架,可以用来实现定时任务的调度,如定时发送邮件、定时备份数据等。Quartz具有很高的可靠性和灵活性,支持集群部署和分布式调度,并且提供了丰富的API和插件,可以轻松实现复杂的调度需求。Quartz的工作原理是通过Job和Trigger两个核心概念来实现的,Job是具体需要执行的任务,Trigger用来触发任务的执行时机。在Quartz中,可以通过定义各种Trigger来实现不同的调度策略,如简单调度、Cron调度等。Quartz还提供了很多内置的Job和Trigger实现,如邮件发送、HTTP请求等,可以方便地用来实现常见的任务调度需求。
核心
Quartz的核心组件包含Scheduler、Job和Trigger。这三个核心组件共同组成了Quartz的任务调度机制,使得开发人员可以通过配置简单的定时任务来实现复杂的调度策略。
Scheduler
是Quartz的核心组件,它负责调度和执行任务。Scheduler有一个任务管理器,负责维护任务列表,并根据Triggers的定义来决定何时执行任务。Scheduler还提供了API,通过API可以动态地添加、删除和修改任务。
-
管理作业:Scheduler负责管理Quartz中的所有作业,包括创建、修改和删除作业。
-
触发器管理:Scheduler负责管理Quartz中的所有触发器,包括创建、修改和删除触发器。
-
作业执行:Scheduler负责执行Quartz中的所有作业,并记录作业执行情况。
-
调度管理:Scheduler负责管理Quartz的整个调度过程,包括启动调度器、暂停调度器和恢复调度器。
-
监控和统计:Scheduler提供了各种监控和统计信息,以帮助开发人员了解Quartz的运行状况。
Job
Job是一个接口,它定义了需要执行的逻辑,开发人员需要实现该接口,并在其中编写需要执行的业务逻辑。
Trigger
Trigger是一个定义了任务执行时间的对象,Quartz提供了多种类型的Trigger,例如
-
SimpleTrigger:简单触发器,用于在指定时间执行一次或者按照指定的时间间隔重复执行。
-
CronTrigger:Cron触发器,用于按照类似于Unix/Linux系统中Cron表达式的方式指定复杂的时间计划,例如每周五下午五点执行。
-
CalendarIntervalTrigger:日历间隔触发器,用于按照在指定时间间隔内执行的时间计划,例如每隔一小时执行一次。
-
DailyTimeIntervalTrigger:每日时间间隔触发器,用于按照每日指定时间间隔执行的时间计划,例如每天上午10点和下午3点各执行一次。
实战
我这里采用jdk17 Springboot3.1.2+mybatis-flex+openapi3与mysql来实现一个动态控制任务的demo
<properties><java.version>17</java.version><mybatis-flex.version>1.7.2</mybatis-flex.version><fastjson.version>1.2.47</fastjson.version>
</properties><dependencyManagement><dependencies><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-dependencies</artifactId><version>4.1.0</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-quartz</artifactId></dependency><dependency><groupId>com.mybatis-flex</groupId><artifactId>mybatis-flex-spring-boot-starter</artifactId><version>${mybatis-flex.version}</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>${fastjson.version}</version></dependency></dependencies>
mybatis-flex:datasource:ds1:url: jdbc:mysql://127.0.0.1:3306/testusername: rootpassword: zkb.com
springdoc:swagger-ui:path: /swagger-ui.htmltags-sorter: alphaapi-docs:path: /v3/api-docsgroup-configs:- group: '接口'paths-to-match: '/**'packages-to-scan: com.zxs.springbootmybatisflex.controller.clientdefault-flat-param-object: true
package com.zxs.springbootmybatisflex.quartz;import com.zxs.springbootmybatisflex.entity.SysJob;
import com.zxs.springbootmybatisflex.zenum.JobEnum;
import org.quartz.Job;
import org.quartz.JobExecutionContext;public class QuartzJob implements Job {@Overridepublic void execute(JobExecutionContext context) {SysJob sysJob = (SysJob) context.getJobDetail().getJobDataMap().get(JobEnum.Key.getCode());QuartzUtil.runJob(sysJob.getInvokeTarget());}
}
package com.zxs.springbootmybatisflex.quartz;import com.zxs.springbootmybatisflex.entity.JobVo;
import com.zxs.springbootmybatisflex.entity.SysJob;
import com.zxs.springbootmybatisflex.uitl.SpringContextHolder;
import com.zxs.springbootmybatisflex.zenum.ActionEnum;
import com.zxs.springbootmybatisflex.zenum.JobEnum;
import lombok.SneakyThrows;
import org.quartz.*;
import org.springframework.util.ObjectUtils;import java.lang.reflect.Method;
import java.util.Date;public class QuartzUtil {@SneakyThrowspublic static void startJob(SysJob sysJob) {// 获取调度器 SchedulerScheduler scheduler = SchedulerStatic.getScheduler();Integer jobId = sysJob.getJobId();String jobGroup = sysJob.getJobGroup();// 构造一个jobJobKey jobKey = JobKey.jobKey(jobId.toString(), jobGroup);JobDetail jobDetail = JobBuilder.newJob(QuartzJob.class).withIdentity(jobKey).build();// 构造cron调度器CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(sysJob.getCronExpression());getMisfirePolicy(sysJob, cronScheduleBuilder);// 构造触发器 triggerTriggerKey triggerKey = TriggerKey.triggerKey(jobId.toString(), jobGroup);CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();// 放入job信息,为后续执行改任务的具体方法做铺垫(需要反射调用), 在execute中获取并应用jobDetail.getJobDataMap().put(JobEnum.Key.getCode(), sysJob);// 判断该任务是否存在,修改任务,先删除然后添加if (scheduler.checkExists(jobKey)) {// 防止创建时存在数据问题 先移除,然后在执行创建操作scheduler.deleteJob(jobKey);}// 判断任务是否过期CronExpression cron = new CronExpression(sysJob.getCronExpression());Date nextValidTimeAfter = cron.getNextValidTimeAfter(new Date(System.currentTimeMillis()));if (!ObjectUtils.isEmpty(nextValidTimeAfter)) {// 执行调度任务scheduler.scheduleJob(jobDetail, trigger);}}@SneakyThrowspublic static void doJob(JobVo sysJob,String action) {// 获取调度器 SchedulerScheduler scheduler = SchedulerStatic.getScheduler();// 构造一个jobJobKey jobKey = JobKey.jobKey(sysJob.getJobId().toString(), sysJob.getJobGroup());// 判断该任务是否存在,修改任务,先删除然后添加if (!scheduler.checkExists(jobKey)) {return;}switch (action){case ActionEnum.check: //System.out.println(true);break;case ActionEnum.pause: //暂停scheduler.pauseJob(jobKey);break;case ActionEnum.resume: //继续scheduler.resumeJob(jobKey);break;case ActionEnum.delete: //删除scheduler.deleteJob(jobKey);break;default:scheduler.checkExists(jobKey);break;}}private static void getMisfirePolicy(SysJob sysJob, CronScheduleBuilder cronScheduleBuilder) {String s= sysJob.getMisfirePolicy();if(s.equals(JobEnum.MISFIRE_DEFAULT.getCode())){}else if(s.equals(JobEnum.MISFIRE_IGNORE.getCode())){cronScheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires();} else if(s.equals(JobEnum.MISFIRE_FIRE_AND_PROCEED.getCode())){cronScheduleBuilder.withMisfireHandlingInstructionFireAndProceed();} else if(s.equals(JobEnum.MISFIRE_DO_NOTHING.getCode())){cronScheduleBuilder.withMisfireHandlingInstructionDoNothing();}else{throw new RuntimeException("The task misfire policy '" + sysJob.getMisfirePolicy() + "' cannot be used in cron schedule tasks");}}public static void invokeMethod(Object bean, String methodName) throws Exception {Method method = bean.getClass().getMethod(methodName);method.invoke(bean);}public static void runJob(String invokeTarget){String[] parts = invokeTarget.split("\\.");String beanName = parts[0];String methodName = parts[1];Object bean = SpringContextHolder.getBean(beanName);try {QuartzUtil.invokeMethod(bean, methodName);} catch (Exception e) {throw new RuntimeException("Error running job", e);}}}
package com.zxs.springbootmybatisflex.quartz;import org.quartz.Scheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class SchedulerStatic {private static Scheduler scheduler;@Autowiredpublic SchedulerStatic(Scheduler scheduler) {SchedulerStatic.scheduler = scheduler;}public static Scheduler getScheduler() {return scheduler;}
}
package com.zxs.springbootmybatisflex.config;import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;@EnableCaching
@Configuration
public class CacheConfig {
}
package com.zxs.springbootmybatisflex.config;import com.mybatisflex.core.mybatis.FlexConfiguration;
import com.mybatisflex.spring.boot.ConfigurationCustomizer;
import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.springframework.context.annotation.Configuration;@Configuration
public class MyConfigurationCustomizer implements ConfigurationCustomizer {@Overridepublic void customize(FlexConfiguration configuration) {configuration.setLogImpl(StdOutImpl.class);}
}
package com.zxs.springbootmybatisflex.config;import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
@EnableKnife4j
public class SwaggerConfig {// 设置 openapi 基础参数@Beanpublic OpenAPI customOpenAPI() {return new OpenAPI().info(new Info().title("zxs API 管理").version("1.0").description("探索mybatis-flex与quartz demo").license(new License().name("Apache 2.0")));}
}
package com.zxs.springbootmybatisflex.controller.client;import com.zxs.springbootmybatisflex.entity.JobVo;
import com.zxs.springbootmybatisflex.entity.SysJob;
import com.zxs.springbootmybatisflex.quartz.QuartzUtil;
import com.zxs.springbootmybatisflex.service.SysJobService;
import com.zxs.springbootmybatisflex.uitl.DataResult;
import com.zxs.springbootmybatisflex.zenum.ActionEnum;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*;import java.util.List;/*** 定时任务调度表(SysJob)表控制层** @author makejava* @since 2023-10-19 09:17:58*/
@RestController
@Tag(name = "任务中心")
@RequestMapping("sysJob")
public class SysJobController {/*** 服务对象*/@Resourceprivate SysJobService sysJobService;@Operation(summary = "获取任务列表", description = "获取任务列表")@PostMapping("/getJobList")public DataResult<List<SysJob>> getJobList() {DataResult<List<SysJob>> result = new DataResult<>();List<SysJob> list =sysJobService.list();result.setData(list);return result;}@Operation(summary = "根据主键获取任务信息")@PostMapping("/getJobById/{id}")public DataResult<SysJob> getJobById(@PathVariable(value = "id") Integer id) {DataResult<SysJob> result = new DataResult<>();result.setData(sysJobService.getById(id));return result;}@PostMapping("/saveJob")@Operation(summary="新增/修改任务",description="新增/修改任务,并加入的调度器中执行")public DataResult<Boolean> saveJob(SysJob sysJob) {DataResult<Boolean> result = new DataResult<>();result.setData(sysJobService.saveOrUpdate(sysJob));QuartzUtil.startJob(sysJob);return result;}@PostMapping("/deleteJob/{id}")@Operation(summary="删除任务",description="删除任务,并删除调度器中执行的该任务")public DataResult<Boolean> deleteJob(@PathVariable(value = "id") Integer id) {DataResult<Boolean> result = new DataResult<>();SysJob sysJob = sysJobService.getById(id);boolean data = this.sysJobService.removeById(id);result.setData(data);if(!ObjectUtils.isEmpty(sysJob)) {JobVo jobVo = new JobVo();jobVo.setJobId(sysJob.getJobId());jobVo.setJobGroup(sysJob.getJobGroup());QuartzUtil.doJob(jobVo, ActionEnum.delete);}return result;}@GetMapping("/getJobAndJoin/{id}")@Operation(summary="开启/关闭任务",description="获取一个任务,修改任务,并加入/丢出的调度器中执行")public DataResult<SysJob> getJobAndJoin(@PathVariable(value = "id") Integer id) {DataResult<SysJob> result = new DataResult<>();SysJob one = sysJobService.getById(id);if("0".equals(one.getStatus())){one.setStatus("1");}else{one.setStatus("0");}sysJobService.updateById(one);QuartzUtil.startJob(one);result.setData(one);return result;}@PostMapping("/runJob")@Operation(summary="手动执行任务",description="手动执行任务")public DataResult<Boolean> runJob(String invokeTarget) {DataResult<Boolean> result = new DataResult<>();QuartzUtil.runJob(invokeTarget);return result;}@PostMapping("/checkJob")@Operation(summary="检查任务",description="检测任务")public DataResult<Boolean> checkJob(JobVo jobVo) {DataResult<Boolean> result = new DataResult<>();QuartzUtil.doJob(jobVo,ActionEnum.check);return result;}@PostMapping("/resumeJob")@Operation(summary="恢复任务",description="恢复任务")public DataResult<Boolean> resumeJob(JobVo sysJob) {DataResult<Boolean> result = new DataResult<>();QuartzUtil.doJob(sysJob,ActionEnum.resume);return result;}@PostMapping("/pauseJob")@Operation(summary="暂停任务",description="暂停任务")public DataResult<Boolean> pauseJob(JobVo sysJob) {DataResult<Boolean> result = new DataResult<>();QuartzUtil.doJob(sysJob,ActionEnum.pause);return result;}}
package com.zxs.springbootmybatisflex.dao;import com.mybatisflex.core.BaseMapper;
import com.zxs.springbootmybatisflex.entity.SysJob;
import org.apache.ibatis.annotations.Mapper;/*** 定时任务调度表(SysJob)表数据库访问层** @author makejava* @since 2023-10-19 09:17:58*/
@Mapper
public interface SysJobDao extends BaseMapper<SysJob> {}
package com.zxs.springbootmybatisflex.entity;import lombok.Data;import java.io.Serializable;@Data
public class JobVo implements Serializable {private Integer jobId;private String jobGroup;
}
package com.zxs.springbootmybatisflex.entity;import com.fasterxml.jackson.annotation.JsonFormat;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import com.mybatisflex.annotation.Table;
import com.mybatisflex.core.activerecord.Model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;import java.util.Date;/*** 定时任务调度表(SysJob)表实体类** @author makejava* @since 2023-10-19 09:17:59*/
@Data
@Schema(description="定时任务调度表")
@Table("sys_job")
public class SysJob extends Model<SysJob> {@Schema(description="任务ID")@Id(keyType = KeyType.Auto)private Integer jobId;@Schema(description="任务名称")private String jobName;@Schema(description="任务组名")private String jobGroup;@Schema(description="调用目标字符串")private String invokeTarget;@Schema(description="cron执行表达式")private String cronExpression;@Schema(description="计划执行错误策略(1立即执行 2执行一次 3放弃执行)")private String misfirePolicy;@Schema(description="是否并发执行(0允许 1禁止)")private String concurrent;@Schema(description="状态(0正常 1暂停)")private String status;@Schema(description="创建者")private String createBy;@Schema(description="创建时间")@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private Date createTime;@Schema(description="更新者")private String updateBy;@Schema(description="更新时间")@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private Date updateTime;@Schema(description="备注信息")private String remark;@Schema(description="0-正常,1-删除")private Integer deleteStatus;}
package com.zxs.springbootmybatisflex.exception.code;public enum BaseResponseCode implements ResponseCodeInterface {/*** 这个要和前段约定好* 引导用户去登录界面的* code=401001 引导用户重新登录* code=401002 token 过期刷新token* code=401008 无权限访问*/SUCCESS(200,"操作成功"),SYSTEM_BUSY(500001, "系统繁忙,请稍候再试"),OPERATION_ERRO(500002,"操作失败"),METHODARGUMENTNOTVALIDEXCEPTION(500003, "方法参数校验异常"),;/*** 错误码*/private final int code;/*** 错误消息*/private final String msg;BaseResponseCode(int code, String msg) {this.code = code;this.msg = msg;}@Overridepublic int getCode() {return code;}@Overridepublic String getMsg() {return msg;}
}
package com.zxs.springbootmybatisflex.exception.code;public interface ResponseCodeInterface {int getCode();String getMsg();
}
package com.zxs.springbootmybatisflex.exception.handler;import com.zxs.springbootmybatisflex.exception.BusinessException;
import com.zxs.springbootmybatisflex.exception.code.BaseResponseCode;
import com.zxs.springbootmybatisflex.uitl.DataResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;import java.util.List;@RestControllerAdvice
@Slf4j
public class RestExceptionHandler {@ExceptionHandler(Exception.class)public <T> DataResult<T> handleException(Exception e){log.error("Exception,exception:{}", e);return DataResult.getResult(BaseResponseCode.SYSTEM_BUSY);}@ExceptionHandler(value = BusinessException.class)<T> DataResult<T> businessExceptionHandler(BusinessException e) {log.error("BusinessException,exception:{}", e);return new DataResult<>(e.getMessageCode(),e.getDetailMessage());}@ExceptionHandler(MethodArgumentNotValidException.class)<T> DataResult<T> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {log.error("methodArgumentNotValidExceptionHandler bindingResult.allErrors():{},exception:{}", e.getBindingResult().getAllErrors(), e);List<ObjectError> errors = e.getBindingResult().getAllErrors();return createValidExceptionResp(errors);}private <T> DataResult<T> createValidExceptionResp(List<ObjectError> errors) {String[] msgs = new String[errors.size()];int i = 0;for (ObjectError error : errors) {msgs[i] = error.getDefaultMessage();log.info("msg={}",msgs[i]);i++;}return DataResult.getResult(BaseResponseCode.METHODARGUMENTNOTVALIDEXCEPTION.getCode(), msgs[0]);}
}
package com.zxs.springbootmybatisflex.exception;import com.zxs.springbootmybatisflex.exception.code.ResponseCodeInterface;public class BusinessException extends RuntimeException{/*** 异常编号*/private final int messageCode;/*** 对messageCode 异常信息进行补充说明*/private final String detailMessage;public BusinessException(int messageCode,String message) {super(message);this.messageCode = messageCode;this.detailMessage = message;}/*** 构造函数* @param code 异常码*/public BusinessException(ResponseCodeInterface code) {this(code.getCode(), code.getMsg());}public int getMessageCode() {return messageCode;}public String getDetailMessage() {return detailMessage;}
}
package com.zxs.springbootmybatisflex.exception;import com.zxs.springbootmybatisflex.exception.code.ResponseCodeInterface;public class RoleSaveException extends RuntimeException{/*** 异常编号*/private final int messageCode;/*** 对messageCode 异常信息进行补充说明*/private final String detailMessage;public RoleSaveException(int messageCode, String message) {super(message);this.messageCode = messageCode;this.detailMessage = message;}/*** 构造函数* @param code 异常码*/public RoleSaveException(ResponseCodeInterface code) {this(code.getCode(), code.getMsg());}public int getMessageCode() {return messageCode;}public String getDetailMessage() {return detailMessage;}
}
package com.zxs.springbootmybatisflex.quartz.task;import org.springframework.stereotype.Service;@Service("task")
public class DoTask {public void sout() {System.out.println("我是干输出的");}public void ceshi() {System.out.println("我是干测试的");}public void buzhidao() {System.out.println("我不知道干啥的");}public void dajiangyou() {System.out.println("我是打酱油的");}public void chuiniu() {System.out.println("我是吹牛的");}public void maren() {System.out.println("我是骂人的");}public void duiren() {System.out.println("我是怼人的");}public void yaofan() {System.out.println("我是要饭的");}public void chifan() {System.out.println("我是吃饭的");}
}
package com.zxs.springbootmybatisflex.service.impl;import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.zxs.springbootmybatisflex.dao.SysJobDao;
import com.zxs.springbootmybatisflex.entity.SysJob;
import com.zxs.springbootmybatisflex.service.SysJobService;
import org.springframework.stereotype.Service;/*** 定时任务调度表(SysJob)表服务实现类** @author makejava* @since 2023-10-19 09:17:59*/
@Service
public class SysJobServiceImpl extends ServiceImpl<SysJobDao, SysJob> implements SysJobService {}
package com.zxs.springbootmybatisflex.service;import com.mybatisflex.core.service.IService;
import com.zxs.springbootmybatisflex.entity.SysJob;/*** 定时任务调度表(SysJob)表服务接口** @author makejava* @since 2023-10-19 09:17:59*/
public interface SysJobService extends IService<SysJob> {}
package com.zxs.springbootmybatisflex.uitl;import com.zxs.springbootmybatisflex.exception.code.BaseResponseCode;
import com.zxs.springbootmybatisflex.exception.code.ResponseCodeInterface;
import lombok.Data;@Data
public class DataResult<T>{/*** 请求响应code,0为成功 其他为失败*/private int code;/*** 响应异常码详细信息*/private String msg;/*** 响应内容 , code 0 时为 返回 数据*/private T data;public DataResult(int code, T data) {this.code = code;this.data = data;this.msg=null;}public DataResult(int code, String msg, T data) {this.code = code;this.msg = msg;this.data = data;}public DataResult(int code, String msg) {this.code = code;this.msg = msg;this.data=null;}public DataResult() {this.code= BaseResponseCode.SUCCESS.getCode();this.msg=BaseResponseCode.SUCCESS.getMsg();this.data=null;}public DataResult(T data) {this.data = data;this.code=BaseResponseCode.SUCCESS.getCode();this.msg=BaseResponseCode.SUCCESS.getMsg();}public DataResult(ResponseCodeInterface responseCodeInterface) {this.data = null;this.code = responseCodeInterface.getCode();this.msg = responseCodeInterface.getMsg();}public DataResult(ResponseCodeInterface responseCodeInterface, T data) {this.data = data;this.code = responseCodeInterface.getCode();this.msg = responseCodeInterface.getMsg();}public static <T>DataResult success(){return new <T>DataResult();}public static <T>DataResult success(T data){return new <T>DataResult(data);}public static <T>DataResult getResult(int code,String msg,T data){return new <T>DataResult(code,msg,data);}public static <T>DataResult getResult(int code,String msg){return new <T>DataResult(code,msg);}public static <T>DataResult getResult(BaseResponseCode responseCode){return new <T>DataResult(responseCode);}public static <T>DataResult getResult(BaseResponseCode responseCode,T data){return new <T>DataResult(responseCode,data);}
}
package com.zxs.springbootmybatisflex.uitl;import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;@Component
public class SpringContextHolder implements ApplicationContextAware, DisposableBean {private static ApplicationContext applicationContext = null;private static Logger logger = LoggerFactory.getLogger(SpringContextHolder.class);/*** 取得存储在静态变量中的ApplicationContext.*/public static ApplicationContext getApplicationContext() {assertContextInjected();return applicationContext;}/*** 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.*/@SuppressWarnings("unchecked")public static <T> T getBean(String name) {assertContextInjected();return (T) applicationContext.getBean(name);}/*** 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.*/public static <T> T getBean(Class<T> requiredType) {assertContextInjected();return applicationContext.getBean(requiredType);}/*** 清除SpringContextHolder中的ApplicationContext为Null.*/public static void clearHolder() {logger.debug("清除SpringContextHolder中的ApplicationContext:"+ applicationContext);applicationContext = null;}/*** 实现ApplicationContextAware接口, 注入Context到静态变量中.*/@Overridepublic void setApplicationContext(ApplicationContext applicationContext)throws BeansException {
// logger.debug("注入ApplicationContext到SpringContextHolder:{}", applicationContext);if (SpringContextHolder.applicationContext != null) {logger.warn("SpringContextHolder中的ApplicationContext被覆盖, 原有ApplicationContext为:" + SpringContextHolder.applicationContext);}SpringContextHolder.applicationContext = applicationContext; // NOSONAR}/*** 实现DisposableBean接口, 在Context关闭时清理静态变量.*/@Overridepublic void destroy() throws Exception {SpringContextHolder.clearHolder();}/*** 检查ApplicationContext不为空.*/private static void assertContextInjected() {Validate.validState(applicationContext != null, "applicaitonContext属性未注入, 请在applicationContext.xml中定义SpringContextHolder.");}
}
package com.zxs.springbootmybatisflex.zenum;public interface ActionEnum {String pause="pause";String resume="resume";String check="check";String delete="delete";String start="start";
}
package com.zxs.springbootmybatisflex.zenum;public enum JobEnum implements JobInterface {Key("jobkey"),MISFIRE_DEFAULT("0"),MISFIRE_IGNORE("1"),MISFIRE_FIRE_AND_PROCEED("2"),MISFIRE_DO_NOTHING("3"),PAUSE("1"),;private final String code;JobEnum(String code) {this.code = code;}@Overridepublic String getCode() {return code;}}
package com.zxs.springbootmybatisflex.zenum;public interface JobInterface {String getCode();
}
package com.zxs.springbootmybatisflex;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
@MapperScan("com.zxs.springbootmybatisflex.dao")
public class SpringbootMybatisFlexApplication {public static void main(String[] args) {SpringApplication.run(SpringbootMybatisFlexApplication.class, args);}}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zxs.springbootmybatisflex.dao.SysJobDao"><resultMap type="com.zxs.springbootmybatisflex.entity.SysJob" id="SysJobMap"><result property="jobId" column="job_id" jdbcType="INTEGER"/><result property="jobName" column="job_name" jdbcType="VARCHAR"/><result property="jobGroup" column="job_group" jdbcType="VARCHAR"/><result property="invokeTarget" column="invoke_target" jdbcType="VARCHAR"/><result property="cronExpression" column="cron_expression" jdbcType="VARCHAR"/><result property="misfirePolicy" column="misfire_policy" jdbcType="VARCHAR"/><result property="concurrent" column="concurrent" jdbcType="VARCHAR"/><result property="status" column="status" jdbcType="VARCHAR"/><result property="createBy" column="create_by" jdbcType="VARCHAR"/><result property="createTime" column="create_time" jdbcType="TIMESTAMP"/><result property="updateBy" column="update_by" jdbcType="VARCHAR"/><result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/><result property="remark" column="remark" jdbcType="VARCHAR"/><result property="deleteStatus" column="delete_status" jdbcType="INTEGER"/></resultMap></mapper>
DROP TABLE IF EXISTS `sys_job`;
CREATE TABLE `sys_job` (`job_id` int(6) NOT NULL AUTO_INCREMENT COMMENT '任务ID',`job_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '任务名称',`job_group` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'DEFAULT' COMMENT '任务组名',`invoke_target` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '调用目标字符串',`cron_expression` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT 'cron执行表达式',`misfire_policy` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '3' COMMENT '计划执行错误策略(1立即执行 2执行一次 3放弃执行)',`concurrent` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '1' COMMENT '是否并发执行(0允许 1禁止)',`status` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '状态(0正常 1暂停)',`create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '创建者',`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',`update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '更新者',`update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间',`remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '备注信息',`delete_status` int(1) NOT NULL DEFAULT 0 COMMENT '0-正常,1-删除',PRIMARY KEY (`job_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '定时任务调度表' ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of sys_job
-- ----------------------------
INSERT INTO `sys_job` VALUES (1, '输出任务', 'DEFAULT', 'task.sout', '0/3 * * * * ?', '3', '0', '0', 'admin', '2023-06-09 11:27:28', 'zxs', '2023-10-19 10:51:17', '', 0);
INSERT INTO `sys_job` VALUES (2, '测试任务', 'DEFAULT', 'task.ceshi', '0/4 * * * * ?', '3', '0', '1', '', NULL, 'zxs', '2023-10-18 16:28:02', '', 0);
INSERT INTO `sys_job` VALUES (3, '我不知道干啥的', 'DEFAULT', 'task.buzhidao', '0/5 * * * * ?', '3', '0', '1', 'zxs', '2023-10-18 14:48:40', '', '2023-10-18 16:28:04', '', 0);
INSERT INTO `sys_job` VALUES (4, '打酱油', 'DEFAULT', 'task.dajiangyou', '0/6 * * * * ?', '3', '0', '1', 'zxs', '2023-10-18 14:49:20', 'zxs', '2023-10-18 16:28:06', '', 0);
INSERT INTO `sys_job` VALUES (5, '吹牛', 'DEFAULT', 'task.chuiniu', '0/7 * * * * ?', '3', '0', '1', 'zxs', '2023-10-18 14:50:53', '', '2023-10-18 16:28:07', '', 0);
INSERT INTO `sys_job` VALUES (6, '骂人', 'DEFAULT', 'task.maren', '0/8 * * * * ?', '3', '0', '1', 'zxs', '2023-10-18 14:51:28', '', '2023-10-18 16:28:09', '', 0);
INSERT INTO `sys_job` VALUES (7, '怼人的', 'DEFAULT', 'task.duiren', '0/4 * * * * ?', '3', '0', '1', 'zxs', '2023-10-18 15:22:34', '', '2023-10-18 16:28:10', '', 0);
INSERT INTO `sys_job` VALUES (8, '要饭的', 'DEFAULT', 'task.yaofan', '0/5 * * * * ?', '3', '0', '1', 'zxs', '2023-10-18 15:23:38', '', '2023-10-18 16:28:12', '', 0);
INSERT INTO `sys_job` VALUES (9, '吃饭的', 'DEFAULT', 'task.chifan', '0/6 * * * * ?', '3', '0', '1', 'zxs', '2023-10-18 15:24:08', 'zxs', '2023-10-18 16:28:15', '哈哈哈哈0', 0);SET FOREIGN_KEY_CHECKS = 1;
以上就是一个完整的demo了
运行之后访问http://127.0.0.1:8080/doc.html
可以看到我们开放的api
测试
执行以上的sql,有可以看到我内置的一些拿来测试的任务
然后我随便找一个任务开启
当任务状态为0的时候,该任务就是正常的运行中的状态
当然你也可以控制一下,当服务关闭时,去把所有为0状态重置为1,把任务关闭掉,或者当服务重启时,去初始化把状态为0的任务加入执行器中,不然每次重启,任务都不在执行器中与数据库的状态不一致
例如:
@PreDestroypublic void stop() {stopTask();}public void stopTask() {List<SysJob> jobs = sysJobService.list().stream().map(s -> {s.setStatus("1");return s;}).collect(Collectors.toList());sysJobService.saveOrUpdateBatch(jobs);}
https://download.csdn.net/download/qq_14926283/88445520
相关文章:

springboot之quartz动态可控定时任务
Quartz Quartz是一个开源的任务调度框架,可以用来实现定时任务的调度,如定时发送邮件、定时备份数据等。Quartz具有很高的可靠性和灵活性,支持集群部署和分布式调度,并且提供了丰富的API和插件,可以轻松实现复杂的调度…...
什么是CSS的外边距重叠?
区块的上下外边距有时会合并(折叠)为单个边距,其大小为两个边距中的最大值(或如果它们相等,则仅为其中一个),这种行为称为外边距折叠。注意:有设定浮动和绝对定位的元素不会发生外边…...

设计模式之抽象工厂模式
前言 工厂模式一般指的是简单工厂模式、工厂方法模式、抽象工厂模式,这是三种工厂模式的最后一篇,其他两种的文章链接如下: 设计模式之简单工厂模式-CSDN博客 设计模式之工厂方法模式-CSDN博客 建议三种模式放在一起对比学习,…...
Compose预处理组件大比拼:性能、应用场景和可视化对比总结
在机器学习的世界里,预处理组件就像是厨师的烹饪工具。选择合适的工具不仅可以让整个烹饪过程更加顺畅,还能确保最终的菜肴更加美味。 本文将深入探讨四种“烹饪工具”:TransformedTargetRegressor、make_column_transformer、make_column_selector和ColumnTransformer。通…...
【小米】Linux 实习生
下午不准备去图书馆自习来着,中午就狠狠地多睡了一个小时,三点起床靠在椅子上剥柚子,太爽了,这秋天的下午。“邮件:小米公司邀请你预约面试时间”.......... 我擦,投了一个月了,认真准备的时候…...
python一点通:coroutine (协程)是什么和重要知识点?
协程已经成为Python用于编写并发和异步代码的重要工具之一。在这篇博客文章中,我们将深入探讨协程是什么,它们的优点,以及它们与传统的线程和进程有何不同。 什么是协程? 协程是用于合作式多任务处理的子程序(或函数…...
QCC51XX-QCC30XX系列开发教程(实战篇) 之 12.1-空间音频相关模块的概述
查看全部教程开发请点击:全网最全-QCC51xx-QCC30xx(TWS)系列从入门到精通开发教程汇总(持续更新中) ==================================================================== 版权归作者所有,未经允许,请勿转载。 ==========================================...

Servlet的生命周期
2023.10.18 WEB容器创建的Servlet对象,这些Servlet对象都会被放到一个集合当中(HashMap),这个集合当中存储了Servlet对象和请求路径之间的关系 。只有放到这个HashMap集合中的Servlet才能够被WEB容器管理,自己new的Ser…...

2.4 如何在FlinkSQL使用DataGen(数据生成器)
1、DataGen SQL 连接器 FLinkSQL中可以使用内置的DataGen SQL 连接器来生成测试数据 官网链接:DataGen SQL 连接器 2、随机数数据生成器 随机数数据生成器支持随机生成 char、varchar、binary、varbinary、string 类型的数据 它是一个无界流的数据生成器 -- TO…...
Gin + Ant Design Pro JWT认证
文章目录 一:介绍二:Gin JWT 后台1. Claims 定义2. 创建和解析Token3. Gin中间件编写4. 辅助函数 三:Ant Design Pro JWT认证四:Gin中间件和使用示范 一:介绍 JWT现在比较流行的认证方式,微服务中使用特别…...
canvas实现图片标注,绘制区域
使用canvas绘制通过多边形标注区域 AI视频项目中需要分析图片,需要前台绘制区域,后端获取坐标然后识别图像,通过canvas 获取点然后连线绘图 HEML代码段 <div class"areaDrawing"><img src"/assets/images/snapPhotos…...

SELECT COUNT(*) 会造成全表扫描吗?
前言 SELECT COUNT(*)会不会导致全表扫描引起慢查询呢? SELECT COUNT(*) FROM SomeTable 网上有一种说法,针对无 where_clause 的 COUNT(*),MySQL 是有优化的,优化器会选择成本最小的辅助索引查询计数,其实反而性能…...
python考前复习(90题)
文章目录 1.Python特性的是( )。 A. 面向对象 B. 高可移植性 C. 开源、免费 2.临时改变Python语言安装源应当使用的选项是 –index-url 3.Python脚本文件的扩展名为( ) .py 4.安装Python语言的软件包使用的命令是( ) pip install 5 . (单选题)以下哪项是…...

根据SpringBoot Guides完成进行示例学习(详细步骤)
目录 1.打开Spring | Guides官网,或者直接搜索springboot都可 2.选择要学习的内容 3.根据提示的网址,Git到本地 4.将文件用IDEA打开,根据教程完成示例,这里不做细致讲解 5.运行项目 6.在终端查看运行结果 以Scheduling Task…...

waf、yakit和ssh免密登录
WAF安全狗 脏数据适用于所有漏洞绕过waf,但是前提条件垃圾信息必须放在危险信息前,是不能打断原有数据包的结构,不能影响后端对数据包的解析。 以DVWA靶场文件上传为例 新建php文件 上传文件被安全狗拦截 使用bp抓包查看 在数据包Content-…...

【AIGC核心技术剖析】大型语言和视觉助手——LLaVA(论文+源码)
🔥 [新!LLaVA-1.5 在 11 个基准测试上实现了 SoTA,只需对原始 LLaVA 进行简单的修改,利用所有公共数据,在单个 1-A8 节点上在 ~100 天内完成训练,并超越使用数十亿级数据的方法。 LLaVA代表了一种新颖的端到端训练大型多模态模型,结合了视觉编码器和骆马 对于通用的视…...
IBM的WAS简介与基本使用手册
IBM的WAS简介与基本使用手册 1. 基本介绍 WebSphereApplication Server(简称WAS)是IBM的应用服务器 基本结构:单元(cell) ——> 多个节点(node) ——> 多个服务(server) ——> 多个应用(app) 单元是整个分布式网络中一个或多个节点的逻辑分组单元是一个配置概念, 是…...

Deno 快速入门
目录 1、简介 2、安装Deno MacOS下安装 Windows下安装 Linux 下安装 3、创建并运行TypeScript程序 4、内置Web API和Deno命名空间 5、运行时安全 6、导入JavaScript模块 7、远程模块和Deno标准库 8、使用deno.json配置您的项目 9、Node.js API和npm包 10、配置IDE…...

【计算机网络笔记】OSI参考模型基本概念
系列文章目录 什么是计算机网络? 什么是网络协议? 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 分组交换 vs 电路交换 计算机网络性能(1)——速率、带宽、延迟 计算机网络性能(2)…...
ConnectTimeout和ReadTimeout所代表的意义
ConnectTimeout和ReadTimeout所代表的意义 ConnectTimeout 指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间。在java中,网络状况正常的情况下,例如使用HttpClient或者HttpURLConnetion连接时设置参数c…...

19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...

css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...
Python爬虫(二):爬虫完整流程
爬虫完整流程详解(7大核心步骤实战技巧) 一、爬虫完整工作流程 以下是爬虫开发的完整流程,我将结合具体技术点和实战经验展开说明: 1. 目标分析与前期准备 网站技术分析: 使用浏览器开发者工具(F12&…...
MySQL中【正则表达式】用法
MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现(两者等价),用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例: 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...
【HTTP三个基础问题】
面试官您好!HTTP是超文本传输协议,是互联网上客户端和服务器之间传输超文本数据(比如文字、图片、音频、视频等)的核心协议,当前互联网应用最广泛的版本是HTTP1.1,它基于经典的C/S模型,也就是客…...

什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...

Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...