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…...

使用Python计算平面多边形间最短距离,数据需要从excel表格中导入
使用Python计算平面多边形间最短距离,数据需要从excel表格中导入, * 多边形种类包括(圆形、矩形、六边形、五边形、跑道形/胶囊形), * Python代码需要使用gjk算法进行判断两个多边形间是否重叠, * 如果未重…...

华为数通方向HCIP-DataCom H12-831题库(多选题:1-20)
第01题 如图所示,路由器所有的接口开启OSPF,图中标识的ip地址为设备的Loopback0接口的IP地址,R1、R2,R3的Loopback0通告在区域1,R4的Loopback0通告在区域0、R5的Lopback0通告在区域2,下列哪些IP地址之间可以相互Ping通? A、10.0.3.3和10.0.5.5 B、10.0.4.4和10.0.2.2 …...

CCC数字钥匙设计【NFC】--通过NFC进行车主配对Phase3
1、车主配对流程介绍 车主配对可以通过车内NFC进行,若支持UWB测距,也可以通过蓝牙/UWB进行。通过NFC进行车主配对总共有5个Phase。本文档主要对Phase3进行介绍。 1) Phase0:准备阶段; 2) Phase1:启动流程࿱…...

开源OA协同办公系统,集成Flowable流程引擎 可拖拽创建个性表单
源码下载:https://download.csdn.net/download/m0_66047725/88403340 源码下载2: 关注我留言 开源OA协同办公系统,集成Flowable流程引擎 可拖拽创建个性表单。基于RuoYi-VUE版本开发。 1、使用RuoYi-Vue的基础上开发。 2、集成flowable&a…...

为什么嵌入通常优于TF-IDF:探索NLP的力量
塔曼纳 一、说明 自然语言处理(NLP)是计算机科学的一个领域,涉及人类语言的处理和分析。它用于各种应用程序,例如聊天机器人、情绪分析、语音识别等。NLP 中的重要任务之一是文本分类,我们根据文本的内容将文本分类为不…...

oracle-AWR报告生成方法
AWR报告生成方法 1. 以oracle用户登陆服务器 2. 进入到要保存awr报告的目录 3. 以sysdba身份连接数据库 sqlplus / as sysdba4. 执行生成AWR报告命令 ?/rdbms/admin/awrrpt.sql5. 选择AWR报告的文件格式 6. 选择生成多少天的AWR报告 7. 选择报告的快照起始和结束ID 8. 输入生…...

笙默考试管理系统-MyExamTest----codemirror(37)
笙默考试管理系统-MyExamTest----codemirror(36) 目录 一、 笙默考试管理系统-MyExamTest 二、 笙默考试管理系统-MyExamTest 三、 笙默考试管理系统-MyExamTest 四、 笙默考试管理系统-MyExamTest 五、 笙默考试管理系统-MyExamTest 笙默考试…...

【Unity3D编辑器拓展】Unity3D的IMGUI、GUI、GUILayout、EditorGUI、EditorGUILayout、OnGUI【全面总结】
推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址我的个人博客 大家好,我是佛系工程师☆恬静的小魔龙☆,不定时更新Unity开发技巧,觉得有用记得一键三连哦。 一、前言 在开发中,常常会遇到要使用OnGUI的地方。 也会遇到…...

11. 机器学习 - 评价指标2
文章目录 混淆矩阵F-scoreAUC-ROC 更多内容: 茶桁的AI秘籍 Hi, 你好。我是茶桁。 上一节课,咱们讲到了评测指标,并且在文章的最后提到了一个矩阵,我们就从这里开始。 混淆矩阵 在我们实际的工作中,会有一个矩阵&am…...

Nginx的代理和负载均衡
一、nginx的代理方式 1.1 七层代理 七层代理:基于http协议,对请求的内容进行处理,然后转发到后端服务器 七层代理是客户端请求代理服务器,由代理服务器转发客户端的http请求,转发到内部的服务器进行处理(服务器可以是…...