Java定时任务可视化管理
+ 2023.09.09 + + + 阅读量次 + + +代码实现
+代码结构
+ +pom
+<dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-security</artifactId>
+</dependency>
+
+<dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+</dependency>
+
+<dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-quartz</artifactId>
+</dependency>
+
+<dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+</dependency>
+
+<dependency>
+ <groupId>cn.hutool</groupId>
+ <artifactId>hutool-all</artifactId>
+</dependency>
+
库表结构
+-- ----------------------------
+-- 定时任务调度表
+-- ----------------------------
+drop table if exists sys_job;
+create table sys_job (
+ job_id bigint(20) not null auto_increment comment '任务ID',
+ job_name varchar(64) default '' comment '任务名称',
+ job_group varchar(64) default 'DEFAULT' comment '任务组名',
+ invoke_target varchar(500) not null comment '调用目标字符串',
+ cron_expression varchar(255) default '' comment 'cron执行表达式',
+ misfire_policy varchar(20) default '3' comment '计划执行错误策略(1立即执行 2执行一次 3放弃执行)',
+ concurrent char(1) default '1' comment '是否并发执行(0允许 1禁止)',
+ status char(1) default '0' comment '状态(0正常 1暂停)',
+ create_by varchar(64) default '' comment '创建者',
+ create_time datetime comment '创建时间',
+ update_by varchar(64) default '' comment '更新者',
+ update_time datetime comment '更新时间',
+ remark varchar(500) default '' comment '备注信息',
+ primary key (job_id, job_name, job_group)
+) engine=innodb auto_increment=100 comment = '定时任务调度表';
+
+
+-- ----------------------------
+-- 定时任务调度日志表
+-- ----------------------------
+drop table if exists sys_job_log;
+create table sys_job_log (
+ job_log_id bigint(20) not null auto_increment comment '任务日志ID',
+ job_name varchar(64) not null comment '任务名称',
+ job_group varchar(64) not null comment '任务组名',
+ invoke_target varchar(500) not null comment '调用目标字符串',
+ job_message varchar(500) comment '日志信息',
+ status char(1) default '0' comment '执行状态(0正常 1失败)',
+ exception_info varchar(2000) default '' comment '异常信息',
+ create_time datetime comment '创建时间',
+ primary key (job_log_id)
+) engine=innodb comment = '定时任务调度日志表';
+
JobManagerController
+/**
+ * @author: whitepure
+ * @date: 2023/7/20 13:35
+ * @description: JobIndexController
+ */
+@Controller
+public class JobManagerController {
+
+ @RequestMapping({"/","/index"})
+ public String index(){
+ return "index.html";
+ }
+
+}
+
SysJobController
+@RestController
+@RequestMapping("/main")
+public class SysJobController {
+ @Autowired
+ private ISysJobService jobService;
+
+ /**
+ * 查询定时任务列表
+ */
+ @GetMapping("/list")
+ public R<List<SysJob>> list(SysJob sysJob) {
+ return R.ok(jobService.selectJobList(sysJob));
+ }
+
+ /**
+ * 获取定时任务详细信息
+ */
+ @GetMapping(value = "/getInfo")
+ public R<SysJob> getInfo(Long jobId) {
+ return R.ok(jobService.selectJobById(jobId));
+ }
+
+ /**
+ * 新增定时任务
+ */
+ @PostMapping("/add")
+ public R<Boolean> add(@RequestBody SysJob job) throws SchedulerException, TaskException {
+ if (!CronUtils.isValid(job.getCronExpression())) {
+ return R.failed("新增任务'" + job.getJobName() + "'失败,Cron表达式不正确");
+ } else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), ScheduleConstants.LOOKUP_RMI)) {
+ return R.failed("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用");
+ } else if (StrUtil.containsAnyIgnoreCase(job.getInvokeTarget(), new String[]{ScheduleConstants.LOOKUP_LDAP, ScheduleConstants.LOOKUP_LDAPS})) {
+ return R.failed("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用");
+ } else if (StrUtil.containsAnyIgnoreCase(job.getInvokeTarget(), new String[]{ScheduleConstants.HTTP, ScheduleConstants.HTTPS})) {
+ return R.failed("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用");
+ } else if (StrUtil.containsAnyIgnoreCase(job.getInvokeTarget(), ScheduleConstants.JOB_ERROR_STR)) {
+ return R.failed("新增任务'" + job.getJobName() + "'失败,目标字符串存在违规");
+ } else if (!ScheduleUtils.whiteList(job.getInvokeTarget())) {
+ return R.failed("新增任务'" + job.getJobName() + "'失败,目标字符串不在白名单内");
+ }
+ return R.ok(jobService.insertJob(job) > 0);
+ }
+
+ /**
+ * 修改定时任务
+ */
+ @PostMapping("/edit")
+ public R<Boolean> edit(@RequestBody SysJob job) throws SchedulerException, TaskException {
+ if (!CronUtils.isValid(job.getCronExpression())) {
+ return R.failed("修改任务'" + job.getJobName() + "'失败,Cron表达式不正确");
+ } else if (StrUtil.containsIgnoreCase(job.getInvokeTarget(), ScheduleConstants.LOOKUP_RMI)) {
+ return R.failed("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用");
+ } else if (StrUtil.containsAnyIgnoreCase(job.getInvokeTarget(), new String[]{ScheduleConstants.LOOKUP_LDAP, ScheduleConstants.LOOKUP_LDAPS})) {
+ return R.failed("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用");
+ } else if (StrUtil.containsAnyIgnoreCase(job.getInvokeTarget(), new String[]{ScheduleConstants.HTTP, ScheduleConstants.HTTPS})) {
+ return R.failed("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用");
+ } else if (StrUtil.containsAnyIgnoreCase(job.getInvokeTarget(), ScheduleConstants.JOB_ERROR_STR)) {
+ return R.failed("修改任务'" + job.getJobName() + "'失败,目标字符串存在违规");
+ } else if (!ScheduleUtils.whiteList(job.getInvokeTarget())) {
+ return R.failed("修改任务'" + job.getJobName() + "'失败,目标字符串不在白名单内");
+ }
+ return R.ok(jobService.updateJob(job) > 0);
+ }
+
+ /**
+ * 定时任务状态修改
+ */
+ @PostMapping("/changeStatus")
+ public R<Boolean> changeStatus(@RequestBody SysJob job) throws SchedulerException {
+ SysJob newJob = jobService.selectJobById(job.getJobId());
+ newJob.setStatus(job.getStatus());
+ return R.ok(jobService.changeStatus(newJob) > 0);
+ }
+
+ /**
+ * 定时任务立即执行一次
+ */
+ @GetMapping("/run")
+ public R<Boolean> run(Long jobId) throws SchedulerException {
+ boolean result = jobService.run(jobId);
+ return result ? R.ok(true): R.failed("任务不存在或已过期!");
+ }
+
+ /**
+ * 删除定时任务
+ */
+ @GetMapping("/remove")
+ public R<Boolean> remove(Long jobId) throws SchedulerException {
+ jobService.deleteJobByIds(new Long[]{jobId});
+ return R.ok(true);
+ }
+}
+
SysJobLogController
+@RestController
+@RequestMapping("/jobLog")
+public class SysJobLogController {
+ @Autowired
+ private ISysJobLogService jobLogService;
+
+ /**
+ * 查询定时任务调度日志列表
+ */
+ @GetMapping("/list")
+ public R<List<SysJobLog>> list(SysJobLog sysJobLog) {
+ return R.ok(jobLogService.selectJobLogList(sysJobLog));
+ }
+
+
+ /**
+ * 根据调度编号获取详细信息
+ */
+ @GetMapping(value = "/getInfo")
+ public R<SysJobLog> getInfo(Long logId) {
+ return R.ok(jobLogService.selectJobLogById(logId));
+ }
+
+
+ /**
+ * 删除定时任务调度日志
+ */
+ @PostMapping("/{jobLogIds}")
+ public R<Boolean> remove(@PathVariable Long[] jobLogIds) {
+ return R.ok(jobLogService.deleteJobLogByIds(jobLogIds) > 0);
+ }
+
+ /**
+ * 清空定时任务调度日志
+ */
+ @GetMapping("/clean")
+ public R<Boolean> clean() {
+ jobLogService.cleanJobLog();
+ return R.ok(true);
+ }
+
+}
+
JobExceptionHandler
+@Slf4j
+@RestControllerAdvice("com.chenglian.scheduled")
+public class JobExceptionHandler {
+
+
+ @ExceptionHandler(Exception.class)
+ public R<Boolean> globalHandler(Exception exception) {
+ log.error("定时任务程序发生异常.", exception);
+ return R.failed(exception.getMessage());
+ }
+
+
+}
+
TaskException
+public class TaskException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ private final Code code;
+
+ public TaskException(String msg, Code code) {
+ this(msg, code, null);
+ }
+
+ public TaskException(String msg, Code code, Exception nestedEx) {
+ super(msg, nestedEx);
+ this.code = code;
+ }
+
+ public Code getCode() {
+ return code;
+ }
+
+ public enum Code {
+ TASK_EXISTS, NO_TASK_EXISTS, TASK_ALREADY_STARTED, UNKNOWN, CONFIG_ERROR, TASK_NODE_NOT_AVAILABLE
+ }
+}
+
IErrorCode
+public interface IErrorCode {
+ long getCode();
+
+ String getMsg();
+}
+
ApiErrorCode
+@Getter
+@ToString
+public enum ApiErrorCode implements IErrorCode {
+ FAILED(-1L, "操作失败"),
+ SUCCESS(0L, "执行成功");
+
+ private final long code;
+ private final String msg;
+
+ ApiErrorCode(final long code, final String msg) {
+ this.code = code;
+ this.msg = msg;
+ }
+
+ public static ApiErrorCode fromCode(long code) {
+ ApiErrorCode[] ecs = values();
+ ApiErrorCode[] var3 = ecs;
+ int var4 = ecs.length;
+
+ for(int var5 = 0; var5 < var4; ++var5) {
+ ApiErrorCode ec = var3[var5];
+ if (ec.getCode() == code) {
+ return ec;
+ }
+ }
+
+ return SUCCESS;
+ }
+}
+
R
+@Data
+@EqualsAndHashCode
+public class R<T> implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+ private long code;
+ private T data;
+ private String msg;
+
+ public R() {
+ }
+
+ public R(IErrorCode errorCode) {
+ errorCode = (IErrorCode) Optional.ofNullable(errorCode).orElse(ApiErrorCode.FAILED);
+ this.code = errorCode.getCode();
+ this.msg = errorCode.getMsg();
+ }
+
+ public static <T> R<T> ok(T data) {
+ ApiErrorCode aec = ApiErrorCode.SUCCESS;
+ if (data instanceof Boolean && Boolean.FALSE.equals(data)) {
+ aec = ApiErrorCode.FAILED;
+ }
+
+ return restResult(data, aec);
+ }
+
+ public static <T> R<T> failed(String msg) {
+ return restResult(null, ApiErrorCode.FAILED.getCode(), msg);
+ }
+
+ public static <T> R<T> failed(IErrorCode errorCode) {
+ return restResult(null, errorCode);
+ }
+
+ public static <T> R<T> restResult(T data, IErrorCode errorCode) {
+ return restResult(data, errorCode.getCode(), errorCode.getMsg());
+ }
+
+ private static <T> R<T> restResult(T data, long code, String msg) {
+ R<T> apiResult = new R();
+ apiResult.setCode(code);
+ apiResult.setData(data);
+ apiResult.setMsg(msg);
+ return apiResult;
+ }
+
+ public boolean ok() {
+ return ApiErrorCode.SUCCESS.getCode() == this.code;
+ }
+}
+
ISysJobLogService
+public interface ISysJobLogService {
+ /**
+ * 获取quartz调度器日志的计划任务
+ *
+ * @param jobLog 调度日志信息
+ * @return 调度任务日志集合
+ */
+ List<SysJobLog> selectJobLogList(SysJobLog jobLog);
+
+ /**
+ * 通过调度任务日志ID查询调度信息
+ *
+ * @param jobLogId 调度任务日志ID
+ * @return 调度任务日志对象信息
+ */
+ SysJobLog selectJobLogById(Long jobLogId);
+
+ /**
+ * 新增任务日志
+ *
+ * @param jobLog 调度日志信息
+ */
+ void addJobLog(SysJobLog jobLog);
+
+ /**
+ * 批量删除调度日志信息
+ *
+ * @param logIds 需要删除的日志ID
+ * @return 结果
+ */
+ int deleteJobLogByIds(Long[] logIds);
+
+ /**
+ * 删除任务日志
+ *
+ * @param jobId 调度日志ID
+ * @return 结果
+ */
+ int deleteJobLogById(Long jobId);
+
+ /**
+ * 清空任务日志
+ */
+ void cleanJobLog();
+}
+
ISysJobService
+public interface ISysJobService {
+ /**
+ * 获取quartz调度器的计划任务
+ *
+ * @param job 调度信息
+ * @return 调度任务集合
+ */
+ List<SysJob> selectJobList(SysJob job);
+
+ /**
+ * 通过调度任务ID查询调度信息
+ *
+ * @param jobId 调度任务ID
+ * @return 调度任务对象信息
+ */
+ SysJob selectJobById(Long jobId);
+
+ /**
+ * 暂停任务
+ *
+ * @param job 调度信息
+ * @return 结果
+ */
+ int pauseJob(SysJob job) throws SchedulerException;
+
+ /**
+ * 恢复任务
+ *
+ * @param job 调度信息
+ * @return 结果
+ */
+ int resumeJob(SysJob job) throws SchedulerException;
+
+ /**
+ * 删除任务后,所对应的trigger也将被删除
+ *
+ * @param job 调度信息
+ * @return 结果
+ */
+ int deleteJob(SysJob job) throws SchedulerException;
+
+ /**
+ * 批量删除调度信息
+ *
+ * @param jobIds 需要删除的任务ID
+ * @return 结果
+ */
+ void deleteJobByIds(Long[] jobIds) throws SchedulerException;
+
+ /**
+ * 任务调度状态修改
+ *
+ * @param job 调度信息
+ * @return 结果
+ */
+ int changeStatus(SysJob job) throws SchedulerException;
+
+ /**
+ * 立即运行任务
+ *
+ * @param jobId 调度任务ID
+ * @return 结果
+ */
+ boolean run(Long jobId) throws SchedulerException;
+
+ /**
+ * 新增任务
+ *
+ * @param job 调度信息
+ * @return 结果
+ */
+ int insertJob(SysJob job) throws SchedulerException, TaskException;
+
+ /**
+ * 更新任务
+ *
+ * @param job 调度信息
+ * @return 结果
+ */
+ int updateJob(SysJob job) throws SchedulerException, TaskException, TaskException;
+
+ /**
+ * 校验cron表达式是否有效
+ *
+ * @param cronExpression 表达式
+ * @return 结果
+ */
+ boolean checkCronExpressionIsValid(String cronExpression);
+}
+
AbstractQuartzJob
+public abstract class AbstractQuartzJob implements Job {
+ private static final Logger log = LoggerFactory.getLogger(AbstractQuartzJob.class);
+
+ /**
+ * 线程本地变量
+ */
+ private static final ThreadLocal<Date> threadLocal = new ThreadLocal<>();
+
+ @Override
+ public void execute(JobExecutionContext context) {
+ SysJob sysJob = new SysJob();
+ BeanUtil.copyProperties(context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES),sysJob);
+ try {
+ before(context, sysJob);
+ if (sysJob.getJobId() != null) {
+ doExecute(context, sysJob);
+ }
+ after(context, sysJob, null);
+ } catch (Exception e) {
+ log.error("任务执行异常 - :", e);
+ after(context, sysJob, e);
+ }
+ }
+
+ /**
+ * 执行前
+ *
+ * @param context 工作执行上下文对象
+ * @param sysJob 系统计划任务
+ */
+ protected void before(JobExecutionContext context, SysJob sysJob) {
+ threadLocal.set(new Date());
+ }
+
+ /**
+ * 执行后
+ *
+ * @param context 工作执行上下文对象
+ * @param sysJob 系统计划任务
+ */
+ protected void after(JobExecutionContext context, SysJob sysJob, Exception e) {
+ Date startTime = threadLocal.get();
+ threadLocal.remove();
+
+ final SysJobLog sysJobLog = new SysJobLog();
+ sysJobLog.setJobName(sysJob.getJobName());
+ sysJobLog.setJobGroup(sysJob.getJobGroup());
+ sysJobLog.setInvokeTarget(sysJob.getInvokeTarget());
+ sysJobLog.setStartTime(startTime);
+ sysJobLog.setStopTime(new Date());
+ long runMs = sysJobLog.getStopTime().getTime() - sysJobLog.getStartTime().getTime();
+ sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时:" + runMs + "毫秒");
+ if (e != null) {
+ sysJobLog.setStatus(ScheduleConstants.FAIL);
+ String errorMsg = StringUtils.substring(getExceptionMessage(e), 0, 2000);
+ sysJobLog.setExceptionInfo(errorMsg);
+ } else {
+ sysJobLog.setStatus(ScheduleConstants.SUCCESS);
+ }
+
+ // 写入数据库当中
+ SpringUtil.getBean(ISysJobLogService.class).addJobLog(sysJobLog);
+ }
+
+
+ public static String getExceptionMessage(Throwable e) {
+ StringWriter sw = new StringWriter();
+ e.printStackTrace(new PrintWriter(sw, true));
+ return sw.toString();
+ }
+
+ /**
+ * 执行方法,由子类重载
+ *
+ * @param context 工作执行上下文对象
+ * @param sysJob 系统计划任务
+ * @throws Exception 执行过程中的异常
+ */
+ protected abstract void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception;
+}
+
CronUtils
+public class CronUtils {
+ /**
+ * 返回一个布尔值代表一个给定的Cron表达式的有效性
+ *
+ * @param cronExpression Cron表达式
+ * @return boolean 表达式是否有效
+ */
+ public static boolean isValid(String cronExpression) {
+ return CronExpression.isValidExpression(cronExpression);
+ }
+
+ /**
+ * 返回一个字符串值,表示该消息无效Cron表达式给出有效性
+ *
+ * @param cronExpression Cron表达式
+ * @return String 无效时返回表达式错误描述,如果有效返回null
+ */
+ public static String getInvalidMessage(String cronExpression) {
+ try {
+ new CronExpression(cronExpression);
+ return null;
+ } catch (ParseException pe) {
+ return pe.getMessage();
+ }
+ }
+
+ /**
+ * 返回下一个执行时间根据给定的Cron表达式
+ *
+ * @param cronExpression Cron表达式
+ * @return Date 下次Cron表达式执行时间
+ */
+ public static Date getNextExecution(String cronExpression) {
+ try {
+ CronExpression cron = new CronExpression(cronExpression);
+ return cron.getNextValidTimeAfter(new Date(System.currentTimeMillis()));
+ } catch (ParseException e) {
+ throw new IllegalArgumentException(e.getMessage());
+ }
+ }
+}
+
JobInvokeUtil
+public class JobInvokeUtil {
+ /**
+ * 执行方法
+ *
+ * @param sysJob 系统任务
+ */
+ public static void invokeMethod(SysJob sysJob) throws Exception {
+ String invokeTarget = sysJob.getInvokeTarget();
+ String beanName = getBeanName(invokeTarget);
+ String methodName = getMethodName(invokeTarget);
+ List<Object[]> methodParams = getMethodParams(invokeTarget);
+
+ if (!isValidClassName(beanName)) {
+ Object bean = SpringUtil.getBean(beanName);
+ invokeMethod(bean, methodName, methodParams);
+ } else {
+ Object bean = Class.forName(beanName).getDeclaredConstructor().newInstance();
+ invokeMethod(bean, methodName, methodParams);
+ }
+ }
+
+ /**
+ * 调用任务方法
+ *
+ * @param bean 目标对象
+ * @param methodName 方法名称
+ * @param methodParams 方法参数
+ */
+ private static void invokeMethod(Object bean, String methodName, List<Object[]> methodParams)
+ throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
+ InvocationTargetException {
+ if (ObjectUtil.isNotEmpty(methodParams) && !methodParams.isEmpty()) {
+ Method method = bean.getClass().getMethod(methodName, getMethodParamsType(methodParams));
+ method.invoke(bean, getMethodParamsValue(methodParams));
+ } else {
+ Method method = bean.getClass().getMethod(methodName);
+ method.invoke(bean);
+ }
+ }
+
+ /**
+ * 校验是否为为class包名
+ *
+ * @param invokeTarget 名称
+ * @return true是 false否
+ */
+ public static boolean isValidClassName(String invokeTarget) {
+ return StringUtils.countMatches(invokeTarget, ".") > 1;
+ }
+
+ /**
+ * 获取bean名称
+ *
+ * @param invokeTarget 目标字符串
+ * @return bean名称
+ */
+ public static String getBeanName(String invokeTarget) {
+ String beanName = StringUtils.substringBefore(invokeTarget, "(");
+ return StringUtils.substringBeforeLast(beanName, ".");
+ }
+
+ /**
+ * 获取bean方法
+ *
+ * @param invokeTarget 目标字符串
+ * @return method方法
+ */
+ public static String getMethodName(String invokeTarget) {
+ String methodName = StringUtils.substringBefore(invokeTarget, "(");
+ return StringUtils.substringAfterLast(methodName, ".");
+ }
+
+ /**
+ * 获取method方法参数相关列表
+ *
+ * @param invokeTarget 目标字符串
+ * @return method方法相关参数列表
+ */
+ public static List<Object[]> getMethodParams(String invokeTarget) {
+ String methodStr = StringUtils.substringBetween(invokeTarget, "(", ")");
+ if (StringUtils.isEmpty(methodStr)) {
+ return null;
+ }
+ String[] methodParams = methodStr.split(",(?=([^\"']*[\"'][^\"']*[\"'])*[^\"']*$)");
+ List<Object[]> classs = new LinkedList<>();
+ for (int i = 0; i < methodParams.length; i++) {
+ String str = StringUtils.trimToEmpty(methodParams[i]);
+ // String字符串类型,以'或"开头
+ if (StringUtils.startsWithAny(str, "'", "\"")) {
+ classs.add(new Object[]{StringUtils.substring(str, 1, str.length() - 1), String.class});
+ }
+ // boolean布尔类型,等于true或者false
+ else if ("true".equalsIgnoreCase(str) || "false".equalsIgnoreCase(str)) {
+ classs.add(new Object[]{Boolean.valueOf(str), Boolean.class});
+ }
+ // long长整形,以L结尾
+ else if (StringUtils.endsWith(str, "L")) {
+ classs.add(new Object[]{Long.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Long.class});
+ }
+ // double浮点类型,以D结尾
+ else if (StringUtils.endsWith(str, "D")) {
+ classs.add(new Object[]{Double.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Double.class});
+ }
+ // 其他类型归类为整形
+ else {
+ classs.add(new Object[]{Integer.valueOf(str), Integer.class});
+ }
+ }
+ return classs;
+ }
+
+ /**
+ * 获取参数类型
+ *
+ * @param methodParams 参数相关列表
+ * @return 参数类型列表
+ */
+ public static Class<?>[] getMethodParamsType(List<Object[]> methodParams) {
+ Class<?>[] classs = new Class<?>[methodParams.size()];
+ int index = 0;
+ for (Object[] os : methodParams) {
+ classs[index] = (Class<?>) os[1];
+ index++;
+ }
+ return classs;
+ }
+
+ /**
+ * 获取参数值
+ *
+ * @param methodParams 参数相关列表
+ * @return 参数值列表
+ */
+ public static Object[] getMethodParamsValue(List<Object[]> methodParams) {
+ Object[] classs = new Object[methodParams.size()];
+ int index = 0;
+ for (Object[] os : methodParams) {
+ classs[index] = os[0];
+ index++;
+ }
+ return classs;
+ }
+}
+
QuartzDisallowConcurrentExecution
+@DisallowConcurrentExecution
+public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob {
+ @Override
+ protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception {
+ JobInvokeUtil.invokeMethod(sysJob);
+ }
+}
+
QuartzJobExecution
+public class QuartzJobExecution extends AbstractQuartzJob {
+ @Override
+ protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception {
+ JobInvokeUtil.invokeMethod(sysJob);
+ }
+}
+
ScheduleConstants
+public class ScheduleConstants {
+ public static final String TASK_CLASS_NAME = "TASK_CLASS_NAME";
+
+ /**
+ * 执行目标key
+ */
+ public static final String TASK_PROPERTIES = "TASK_PROPERTIES";
+
+ /**
+ * 默认
+ */
+ public static final String MISFIRE_DEFAULT = "0";
+
+ /**
+ * 立即触发执行
+ */
+ public static final String MISFIRE_IGNORE_MISFIRES = "1";
+
+ /**
+ * 触发一次执行
+ */
+ public static final String MISFIRE_FIRE_AND_PROCEED = "2";
+
+ /**
+ * 不触发立即执行
+ */
+ public static final String MISFIRE_DO_NOTHING = "3";
+
+ public enum Status {
+ /**
+ * 正常
+ */
+ NORMAL("0"),
+ /**
+ * 暂停
+ */
+ PAUSE("1");
+
+ private final String value;
+
+ Status(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+ }
+
+
+
+
+
+ /**
+ * http请求
+ */
+ public static final String HTTP = "http://";
+
+ /**
+ * https请求
+ */
+ public static final String HTTPS = "https://";
+
+ /**
+ * 通用成功标识
+ */
+ public static final String SUCCESS = "0";
+
+ /**
+ * 通用失败标识
+ */
+ public static final String FAIL = "1";
+
+
+ /**
+ * RMI 远程方法调用
+ */
+ public static final String LOOKUP_RMI = "rmi:";
+
+ /**
+ * LDAP 远程方法调用
+ */
+ public static final String LOOKUP_LDAP = "ldap:";
+
+ /**
+ * LDAPS 远程方法调用
+ */
+ public static final String LOOKUP_LDAPS = "ldaps:";
+
+ /**
+ * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加)
+ */
+ public static final String[] JOB_WHITELIST_STR = {"com.chenglian"};
+
+ /**
+ * 定时任务违规的字符
+ */
+ public static final String[] JOB_ERROR_STR = {"java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
+ "org.springframework", "org.apache", "com.chenglian.common.utils.file", "com.chenglian.common.config"};
+
+
+}
+
ScheduleUtils
+public class ScheduleUtils {
+ /**
+ * 得到quartz任务类
+ *
+ * @param sysJob 执行计划
+ * @return 具体执行任务类
+ */
+ private static Class<? extends Job> getQuartzJobClass(SysJob sysJob) {
+ boolean isConcurrent = "0".equals(sysJob.getConcurrent());
+ return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class;
+ }
+
+ /**
+ * 构建任务触发对象
+ */
+ public static TriggerKey getTriggerKey(Long jobId, String jobGroup) {
+ return TriggerKey.triggerKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
+ }
+
+ /**
+ * 构建任务键对象
+ */
+ public static JobKey getJobKey(Long jobId, String jobGroup) {
+ return JobKey.jobKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
+ }
+
+ /**
+ * 创建定时任务
+ */
+ public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, TaskException {
+ Class<? extends Job> jobClass = getQuartzJobClass(job);
+ // 构建job信息
+ Long jobId = job.getJobId();
+ String jobGroup = job.getJobGroup();
+ JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build();
+
+ // 表达式调度构建器
+ CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
+ cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder);
+
+ // 按新的cronExpression表达式构建一个新的trigger
+ CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup))
+ .withSchedule(cronScheduleBuilder).build();
+
+ // 放入参数,运行时的方法可以获取
+ jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job);
+
+ // 判断是否存在
+ if (scheduler.checkExists(getJobKey(jobId, jobGroup))) {
+ // 防止创建时存在数据问题 先移除,然后在执行创建操作
+ scheduler.deleteJob(getJobKey(jobId, jobGroup));
+ }
+
+ // 判断任务是否过期
+ if (Objects.nonNull(CronUtils.getNextExecution(job.getCronExpression()))) {
+ // 执行调度任务
+ scheduler.scheduleJob(jobDetail, trigger);
+ }
+
+ // 暂停任务
+ if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue())) {
+ scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
+ }
+ }
+
+ /**
+ * 设置定时任务策略
+ */
+ public static CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJob job, CronScheduleBuilder cb)
+ throws TaskException {
+ switch (job.getMisfirePolicy()) {
+ case ScheduleConstants.MISFIRE_DEFAULT:
+ return cb;
+ case ScheduleConstants.MISFIRE_IGNORE_MISFIRES:
+ return cb.withMisfireHandlingInstructionIgnoreMisfires();
+ case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED:
+ return cb.withMisfireHandlingInstructionFireAndProceed();
+ case ScheduleConstants.MISFIRE_DO_NOTHING:
+ return cb.withMisfireHandlingInstructionDoNothing();
+ default:
+ throw new TaskException("The task misfire policy '" + job.getMisfirePolicy()
+ + "' cannot be used in cron schedule tasks", TaskException.Code.CONFIG_ERROR);
+ }
+ }
+
+ /**
+ * 检查包名是否为白名单配置
+ *
+ * @param invokeTarget 目标字符串
+ * @return 结果
+ */
+ public static boolean whiteList(String invokeTarget) {
+ String packageName = StringUtils.substringBefore(invokeTarget, "(");
+ int count = StringUtils.countMatches(packageName, ".");
+ if (count > 1) {
+ return CharSequenceUtil.containsAnyIgnoreCase(invokeTarget, ScheduleConstants.JOB_WHITELIST_STR);
+ }
+ Object obj = SpringUtil.getBean(StringUtils.split(invokeTarget, ".")[0]);
+ String beanPackageName = obj.getClass().getPackage().getName();
+ return CharSequenceUtil.containsAnyIgnoreCase(beanPackageName, ScheduleConstants.JOB_WHITELIST_STR)
+ && !CharSequenceUtil.containsAnyIgnoreCase(beanPackageName, ScheduleConstants.JOB_ERROR_STR);
+ }
+}
+