diff --git a/src/main/java/com/fc/test/common/listener/ConfigListener.java b/src/main/java/com/fc/test/common/listener/ConfigListener.java index 427277231905e3860a140ea4f4afdaf9c55249c2..6b3761dde524c0114b1db677cfc88b17abdfa414 100644 --- a/src/main/java/com/fc/test/common/listener/ConfigListener.java +++ b/src/main/java/com/fc/test/common/listener/ConfigListener.java @@ -1,8 +1,7 @@ package com.fc.test.common.listener; -import org.springframework.beans.factory.annotation.Autowired; - import com.fc.test.common.quartz.QuartzScheduler; +import org.springframework.beans.factory.annotation.Autowired; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; @@ -20,8 +19,6 @@ import java.util.Map; @WebListener public class ConfigListener implements ServletContextListener { - @Autowired - private QuartzScheduler scheduler; private static Map conf = new HashMap<>(); public static Map getConf() { @@ -43,8 +40,6 @@ public class ConfigListener implements ServletContextListener { //servletContextPath,默认"" conf.put("contextPath", sc.getContextPath()); - //开启定时调度 - // scheduler.startJob(); } } \ No newline at end of file diff --git a/src/main/java/com/fc/test/common/quartz/AbstractQuartzJob.java b/src/main/java/com/fc/test/common/quartz/AbstractQuartzJob.java new file mode 100644 index 0000000000000000000000000000000000000000..701cbc25a8563f0fde12b91d6c52966cd847fdff --- /dev/null +++ b/src/main/java/com/fc/test/common/quartz/AbstractQuartzJob.java @@ -0,0 +1,105 @@ +package com.fc.test.common.quartz; + +import cn.hutool.core.exceptions.ExceptionUtil; +import com.fc.test.common.quartz.entity.SysJob; +import com.fc.test.common.quartz.entity.SysJobLog; +import com.fc.test.common.quartz.utils.StringUtils; +import org.quartz.Job; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeanUtils; + +import java.util.Date; + +/** + * @CLASSNAME AbstractQuartzJob + * @Description + * @Auther Jan 橙寂 + * @DATE 2019/9/5 0005 15:05 + */ + +public abstract class AbstractQuartzJob implements Job { + + private static final Logger log = LoggerFactory.getLogger(AbstractQuartzJob.class); + + /** + * 线程本地变量 + */ + private static ThreadLocal threadLocal = new ThreadLocal<>(); + + @Override + public void execute(JobExecutionContext context) throws JobExecutionException + { + SysJob sysJob = new SysJob(); + BeanUtils.copyProperties(context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES),sysJob); + try + { + before(context, sysJob); + if (sysJob != 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.setEndTime(new Date()); + long runMs = sysJobLog.getEndTime().getTime() - sysJobLog.getStartTime().getTime(); + sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时:" + runMs + "毫秒"); + if (e != null) + { + sysJobLog.setStatus(ScheduleConstants.FAIL_STATUS); + String errorMsg = StringUtils.substring(ExceptionUtil.getMessage(e), 0, 2000); + sysJobLog.setExceptionInfo(errorMsg); + } + else + { + sysJobLog.setStatus(ScheduleConstants.SUCCESS_STATUS); + } + + // 这里获取service然后插入库中 + System.out.println(sysJob.toString()); + // SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog); + } + + /** + * 子类去实现 + * @param jobExecutionContext + * @param sysJob + */ + protected abstract void doExecute(JobExecutionContext jobExecutionContext, SysJob sysJob) throws Exception; +} diff --git a/src/main/java/com/fc/test/common/quartz/QuartzDefault.java b/src/main/java/com/fc/test/common/quartz/QuartzDefault.java deleted file mode 100644 index 4dab396f53137cc6a238c572dddfb205b77c746e..0000000000000000000000000000000000000000 --- a/src/main/java/com/fc/test/common/quartz/QuartzDefault.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.fc.test.common.quartz; - -import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.annotation.Scheduled; - -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; - -/** - * @CLASSNAME QuartzDefault - * @Description springboot 默认的定时调度配置(不需要任何包)但是不能动态的开启(关闭)或者添加 任务 - * @Auther Jan 橙寂 - * @DATE 2019/9/2 0002 14:46 - */ -@Configuration -//@EnableScheduling //开启定时调度的开关 -public class QuartzDefault { - - - //配置表达式 现在这个是每一分钟执行一次 - @Scheduled(cron = "0 0/1 * * * *") - public void timer(){ - //获取当前时间 - LocalDateTime localDateTime = LocalDateTime.now(); - System.out.println("当前时间为:" + localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); - } - - - /*按顺序依次为 - 1 秒(0~59) - 2 分钟(0~59) - 3 小时(0~23) - 4 天(0~31) - 5 月(0~11) - 6 星期(1~7 1=SUN 或 SUN,MON,TUE,WED,THU,FRI,SAT) - 7.年份(1970-2099) - 其中每个元素可以是一个值(如6),一个连续区间(9-12),一个间隔时间(8-18/4)(/表示每隔4小时),一个列表(1,3,5),通配符。由于"月份中的日期"和"星期中的日期"这两个元素互斥的,必须要对其中一个设置?. - 0 0 10,14,16 * * ? 每天上午10点,下午2点,4点 - 0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时 - 0 0 12 ? * WED 表示每个星期三中午12点 - "0 0 12 * * ?" 每天中午12点触发 - "0 15 10 ? * *" 每天上午10:15触发 - "0 15 10 * * ?" 每天上午10:15触发 - "0 15 10 * * ? *" 每天上午10:15触发 - "0 15 10 * * ? 2005" 2005年的每天上午10:15触发 - "0 * 14 * * ?" 在每天下午2点到下午2:59期间的每1分钟触发 - "0 0/5 14 * * ?" 在每天下午2点到下午2:55期间的每5分钟触发 - "0 0/5 14,18 * * ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发 - "0 0-5 14 * * ?" 在每天下午2点到下午2:05期间的每1分钟触发 - "0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发 - "0 15 10 ? * MON-FRI" 周一至周五的上午10:15触发 - "0 15 10 15 * ?" 每月15日上午10:15触发 - "0 15 10 L * ?" 每月最后一日的上午10:15触发 - "0 15 10 ? * 6L" 每月的最后一个星期五上午10:15触发 - "0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发 - "0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发 - 有些子表达式能包含一些范围或列表 - 例如:子表达式(天(星期))可以为 “MON-FRI”,“MON,WED,FRI”,“MON-WED,SAT” - “*”字符代表所有可能的值 - “/”字符用来指定数值的增量 - 例如:在子表达式(分钟)里的“0/15”表示从第0分钟开始,每15分钟 - 在子表达式(分钟)里的“3/20”表示从第3分钟开始,每20分钟(它和“3,23,43”)的含义一样 - “?”字符仅被用于天(月)和天(星期)两个子表达式,表示不指定值 - 当2个子表达式其中之一被指定了值以后,为了避免冲突,需要将另一个子表达式的值设为“?” - “L” 字符仅被用于天(月)和天(星期)两个子表达式,它是单词“last”的缩写 - 如果在“L”前有具体的内容,它就具有其他的含义了。例如:“6L”表示这个月的倒数第6天 - 注意:在使用“L”参数时,不要指定列表或范围,因为这会导致问题 - W 字符代表着平日(Mon-Fri),并且仅能用于日域中。它用来指定离指定日的最近的一个平日。大部分的商业处理都是基于工作周的,所以 W 字符可能是非常重要的。 - 例如,日域中的 15W 意味着 "离该月15号的最近一个平日。" 假如15号是星期六,那么 trigger 会在14号(星期五)触发,因为星期四比星期一离15号更近。 - C:代表“Calendar”的意思。它的意思是计划所关联的日期,如果日期没有被关联,则相当于日历中所有日期。例如5C在日期字段中就相当于日历5日以后的第一天。1C在星期字段中相当于星期日后的第一天。 - 字段 允许值 允许的特殊字符 - 秒 0-59 , - * / - 分 0-59 , - * / - 小时 0-23 , - * / - 日期 1-31 , - * ? / L W C - 月份 1-12 或者 JAN-DEC , - * / - 星期 1-7 或者 SUN-SAT , - * ? / L C # - 年(可选) 留空, 1970-2099 , - * /*/ - - -} diff --git a/src/main/java/com/fc/test/common/quartz/QuartzDisallowConcurrentExecution.java b/src/main/java/com/fc/test/common/quartz/QuartzDisallowConcurrentExecution.java new file mode 100644 index 0000000000000000000000000000000000000000..2de836b35ba9f8dea375f3b6b0df854bfafa70ad --- /dev/null +++ b/src/main/java/com/fc/test/common/quartz/QuartzDisallowConcurrentExecution.java @@ -0,0 +1,22 @@ +package com.fc.test.common.quartz; + +import com.fc.test.common.quartz.entity.SysJob; +import com.fc.test.common.quartz.utils.JobInvokeUtil; +import org.quartz.DisallowConcurrentExecution; +import org.quartz.JobExecutionContext; + +/** + * 定时任务处理(禁止并发执行) + * + * @author jan 橙寂 + * + */ +@DisallowConcurrentExecution +public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob +{ + @Override + protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception + { + JobInvokeUtil.invokeMethod(sysJob); + } +} diff --git a/src/main/java/com/fc/test/common/quartz/QuartzJobExecution.java b/src/main/java/com/fc/test/common/quartz/QuartzJobExecution.java new file mode 100644 index 0000000000000000000000000000000000000000..5038d34062e7e7bbbf602a00ae02f9d071d4b729 --- /dev/null +++ b/src/main/java/com/fc/test/common/quartz/QuartzJobExecution.java @@ -0,0 +1,21 @@ +package com.fc.test.common.quartz; + +import com.fc.test.common.quartz.entity.SysJob; +import com.fc.test.common.quartz.utils.JobInvokeUtil; +import org.quartz.JobExecutionContext; + +/** + * 定时任务处理(允许并发执行) + * + * @author jan 橙寂 + * + */ +public class QuartzJobExecution extends AbstractQuartzJob +{ + @Override + protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception { + JobInvokeUtil.invokeMethod(sysJob); + } + + +} diff --git a/src/main/java/com/fc/test/common/quartz/QuartzScheduler.java b/src/main/java/com/fc/test/common/quartz/QuartzScheduler.java index d0748b9a3004c9ab4db6a628284ece8a606884c2..aeae34d726ee53526b24d51261b36c512ef47752 100644 --- a/src/main/java/com/fc/test/common/quartz/QuartzScheduler.java +++ b/src/main/java/com/fc/test/common/quartz/QuartzScheduler.java @@ -1,11 +1,11 @@ package com.fc.test.common.quartz; +import com.fc.test.common.quartz.entity.SysJob; import org.quartz.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; -import com.fc.test.common.quartz.job.TestQuartzJob; -import java.util.Date; +import javax.annotation.PostConstruct; /** * @CLASSNAME QuartzConfig @@ -24,81 +24,123 @@ public class QuartzScheduler { private final String TEST_CRON="0 0/1 * * * ?"; /** - * 开始工作 + * 容器初始化时执行此方法 + * 也就是类初始化的时候 */ - public void startJob() - { - try { - initTestJob("test","test"); - scheduler.start(); - + @PostConstruct + public void init() throws SchedulerException { + //这一块可以从数据库中查 + for (int i=1;i<=5;i++) + { + SysJob job=new SysJob(); + job.setJobId(new Long(i)); + job.setJobName("测试工作"); + job.setJobGroup("quartz"); + job.setCronExpression("0 0/1 * * * ?"); + //并发执行 + job.setConcurrent("0"); + //0启用 + job.setStatus("0"); + //执行的job类 + job.setInvokeTarget("ryTask.runTask2(1,2l,'asa',true,2D)"); + try { + //防止因为数据问题重复创建 + if(checkJobExists(job)) + { + deleteJob(job); + } + createSchedule(job); } catch (SchedulerException e) { e.printStackTrace(); - System.out.println("任务掉度开启失败"); } + } + + start(); } /** - * 初始化一个任务 - * @param taskName - * @param groupName + * 启动定时器 + */ + public void start() +{ + try { + scheduler.start(); + } catch (SchedulerException e) { + e.printStackTrace(); + System.out.println("定时任务执行失败"); + } +} + + + /** + * 创建一个定时任务 + * @param job * @throws SchedulerException */ - private void initTestJob(String taskName,String groupName) throws SchedulerException { - if (!checkJobExists(taskName,groupName)) { + private void createSchedule(SysJob job) throws SchedulerException { + if (!checkJobExists(job)) { + //获取指定的job工作类 + Class jobClass = getQuartzJobClass(job); // 通过JobBuilder构建JobDetail实例,JobDetail规定只能是实现Job接口的实例 // JobDetail 是具体Job实例 - JobDetail jobDetail = JobBuilder.newJob(TestQuartzJob.class).withIdentity(taskName, groupName).build(); + JobDetail jobDetail = JobBuilder.newJob((Class) jobClass).withIdentity(ScheduleConstants.TASK_PRE+job.getJobId(),job.getJobName()).build(); // 基于表达式构建触发器 CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(TEST_CRON); // CronTrigger表达式触发器 继承于Trigger // TriggerBuilder 用于构建触发器实例 - CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(taskName, groupName) + CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(ScheduleConstants.TASK_PRE+job.getJobId(),job.getJobName()) .withSchedule(cronScheduleBuilder.withMisfireHandlingInstructionDoNothing()).build(); + + //放入参数,运行时的方法可以获取 + jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job); + scheduler.scheduleJob(jobDetail, cronTrigger); + + //如果这个工作的状态为1 + if (job.getStatus().equals("1")) + { + pauseJob(job); + } } } + /** - * 修改某个任务的执行时间 - * - * @param name - * @param group - * @param cron 表达式 + * 修改定时任务 + * @param job * @return * @throws SchedulerException */ - public boolean modifyJob(String name, String group, String cron) throws SchedulerException { - Date date = null; - TriggerKey triggerKey = new TriggerKey(name, group); - CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey); - if (cronTrigger != null) { - String oldTime = cronTrigger.getCronExpression(); - if (!oldTime.equalsIgnoreCase(cron)) { - CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cron); - CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(name, group) - .withSchedule(cronScheduleBuilder).build(); - date = scheduler.rescheduleJob(triggerKey, trigger); + public boolean modifyJob(SysJob job) { + + try { + //先删除 + if(checkJobExists(job)) + { + deleteJob(job); } + createSchedule(job); + } catch (SchedulerException e) { + e.printStackTrace(); + return false; } - return date==null?false:true; + return true; } /** * 继续执行定时任务 - * @param taskName - * @param groupName + * @param job * @return */ - public boolean resumeJob(String taskName, String groupName) { + public boolean resumeJob(SysJob job) { boolean bl = false; try { //JobKey定义了job的名称和组别 - JobKey jobKey = JobKey.jobKey(taskName, groupName); + JobKey jobKey = JobKey.jobKey(ScheduleConstants.TASK_PRE+job.getJobId(), job.getJobGroup()); if (jobKey != null) { //继续任务 scheduler.resumeJob(jobKey); @@ -112,17 +154,39 @@ public class QuartzScheduler { return bl; } + /** + * 删除定时任务 + * @param job + * @return + */ + public boolean deleteJob(SysJob job) { + boolean bl = false; + try { + //JobKey定义了job的名称和组别 + JobKey jobKey = JobKey.jobKey(ScheduleConstants.TASK_PRE+job.getJobId(), job.getJobGroup()); + if (jobKey != null) { + //删除定时任务 + scheduler.deleteJob(jobKey); + bl = true; + } + } catch (SchedulerException e) { + System.out.println("删除调度任务异常:" + e); + } catch (Exception e) { + System.out.println("删除调度任务异常:" + e); + } + return bl; + } + /** * 暂停任务 - * @param taskName - * @param groupName + * @param job * @return */ - public boolean pauseJob(String taskName, String groupName) { + public boolean pauseJob(SysJob job) { boolean bl = false; try { //JobKey定义了job的名称和组别 - JobKey jobKey = JobKey.jobKey(taskName, groupName); + JobKey jobKey = JobKey.jobKey(ScheduleConstants.TASK_PRE+job.getJobId(), job.getJobGroup()); //暂停任务 if (jobKey != null) { scheduler.pauseJob(jobKey); @@ -138,13 +202,24 @@ public class QuartzScheduler { /** * 判断定时任务是否已经存在 - * @param taskName - * @param groupName + * @param job * @return * @throws SchedulerException */ - public boolean checkJobExists(String taskName, String groupName) throws SchedulerException { - TriggerKey triggerKey = TriggerKey.triggerKey(taskName, groupName); + public boolean checkJobExists(SysJob job) throws SchedulerException { + TriggerKey triggerKey = TriggerKey.triggerKey(ScheduleConstants.TASK_PRE+job.getJobId(), job.getJobGroup()); return scheduler.checkExists(triggerKey); } + + + /** + * 获取定时任务的具体执行类 + * @param sysJob + * @return + */ + private static Class getQuartzJobClass(SysJob sysJob) + { + boolean isConcurrent = "0".equals(sysJob.getConcurrent()); + return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class; + } } diff --git a/src/main/java/com/fc/test/common/quartz/ScheduleConstants.java b/src/main/java/com/fc/test/common/quartz/ScheduleConstants.java new file mode 100644 index 0000000000000000000000000000000000000000..0344c98664a380c463d727895c959cbeb35a00eb --- /dev/null +++ b/src/main/java/com/fc/test/common/quartz/ScheduleConstants.java @@ -0,0 +1,66 @@ +package com.fc.test.common.quartz; + +/** + * 任务调度通用常量 + * + * @author jan 橙寂 + */ +public interface ScheduleConstants +{ + public static final String TASK_PRE = "TASK"; + + /** 执行目标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 static final String FAIL_STATUS = "1"; + + /** + * 成功状态 + */ + public static final String SUCCESS_STATUS = "2"; + + + /** + * 任务的关闭的状态 + */ + public static final String STOP_STATUS = "1"; + + public enum Status + { + /** + * 正常 + */ + NORMAL("0"), + /** + * 暂停 + */ + PAUSE("1"); + + private String value; + + private Status(String value) + { + this.value = value; + } + + public String getValue() + { + return value; + } + } +} diff --git a/src/main/java/com/fc/test/common/quartz/entity/SysJob.java b/src/main/java/com/fc/test/common/quartz/entity/SysJob.java new file mode 100644 index 0000000000000000000000000000000000000000..0f336e3f75a259d79b666a65097c2e45f5829dbd --- /dev/null +++ b/src/main/java/com/fc/test/common/quartz/entity/SysJob.java @@ -0,0 +1,132 @@ +package com.fc.test.common.quartz.entity; + + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.io.Serializable; +import java.util.Date; + +/** + * 定时任务调度表 sys_job + * + * @author Jan 橙寂 + */ +public class SysJob implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 任务ID */ + private Long jobId; + + /** 任务名称 */ + private String jobName; + + /** 任务组名 */ + private String jobGroup; + + /** 调用目标字符串 */ + private String invokeTarget; + + /** cron执行表达式 */ + private String cronExpression; + + /** cron计划策略 */ + private String misfirePolicy; + /** 是否并发执行(0允许 1禁止) */ + private String concurrent; + + /** 任务状态(0正常 1暂停) */ + private String status; + + public Long getJobId() + { + return jobId; + } + + public void setJobId(Long jobId) + { + this.jobId = jobId; + } + + public String getJobName() + { + return jobName; + } + + public void setJobName(String jobName) + { + this.jobName = jobName; + } + + public String getJobGroup() + { + return jobGroup; + } + + public void setJobGroup(String jobGroup) + { + this.jobGroup = jobGroup; + } + + @NotBlank(message = "调用目标字符串不能为空") + @Size(min = 0, max = 1000, message = "调用目标字符串长度不能超过500个字符") + public String getInvokeTarget() + { + return invokeTarget; + } + + public void setInvokeTarget(String invokeTarget) + { + this.invokeTarget = invokeTarget; + } + + @NotBlank(message = "Cron执行表达式不能为空") + @Size(min = 0, max = 255, message = "Cron执行表达式不能超过255个字符") + public String getCronExpression() + { + return cronExpression; + } + + public void setCronExpression(String cronExpression) + { + this.cronExpression = cronExpression; + } + + public Date getNextValidTime() + { + + return null; + } + + public String getMisfirePolicy() + { + return misfirePolicy; + } + + public void setMisfirePolicy(String misfirePolicy) + { + this.misfirePolicy = misfirePolicy; + } + + public String getConcurrent() + { + return concurrent; + } + + public void setConcurrent(String concurrent) + { + this.concurrent = concurrent; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + +} \ No newline at end of file diff --git a/src/main/java/com/fc/test/common/quartz/entity/SysJobLog.java b/src/main/java/com/fc/test/common/quartz/entity/SysJobLog.java new file mode 100644 index 0000000000000000000000000000000000000000..ee9dbf0bddd8e9c68455aeddce1e84bf849bd5ac --- /dev/null +++ b/src/main/java/com/fc/test/common/quartz/entity/SysJobLog.java @@ -0,0 +1,147 @@ +package com.fc.test.common.quartz.entity; + + +import java.io.Serializable; +import java.util.Date; + +/** + * 定时任务调度日志表 sys_job_log + * + * @author jan 橙寂 + */ +public class SysJobLog implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** ID */ + private Long jobLogId; + + /** 任务名称 */ + private String jobName; + + /** 任务组名 */ + private String jobGroup; + + /** 调用目标字符串 */ + private String invokeTarget; + + /** 日志信息 */ + private String jobMessage; + + /** 执行状态(0正常 1失败) */ + private String status; + + /** 异常信息 */ + private String exceptionInfo; + + /** 开始时间 */ + private Date startTime; + + /** 结束时间 */ + private Date endTime; + + public Long getJobLogId() + { + return jobLogId; + } + + public void setJobLogId(Long jobLogId) + { + this.jobLogId = jobLogId; + } + + public String getJobName() + { + return jobName; + } + + public void setJobName(String jobName) + { + this.jobName = jobName; + } + + public String getJobGroup() + { + return jobGroup; + } + + public void setJobGroup(String jobGroup) + { + this.jobGroup = jobGroup; + } + + public String getInvokeTarget() + { + return invokeTarget; + } + + public void setInvokeTarget(String invokeTarget) + { + this.invokeTarget = invokeTarget; + } + + public String getJobMessage() + { + return jobMessage; + } + + public void setJobMessage(String jobMessage) + { + this.jobMessage = jobMessage; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getExceptionInfo() + { + return exceptionInfo; + } + + public void setExceptionInfo(String exceptionInfo) + { + this.exceptionInfo = exceptionInfo; + } + + public Date getStartTime() + { + return startTime; + } + + public void setStartTime(Date startTime) + { + this.startTime = startTime; + } + + public Date getEndTime() + { + return endTime; + } + + public void setEndTime(Date endTime) + { + this.endTime = endTime; + } + + @Override + public String toString() { + return "SysJobLog{" + + "jobLogId=" + jobLogId + + ", jobName='" + jobName + '\'' + + ", jobGroup='" + jobGroup + '\'' + + ", invokeTarget='" + invokeTarget + '\'' + + ", jobMessage='" + jobMessage + '\'' + + ", status='" + status + '\'' + + ", exceptionInfo='" + exceptionInfo + '\'' + + ", startTime=" + startTime + + ", endTime=" + endTime + + '}'; + } +} diff --git a/src/main/java/com/fc/test/common/quartz/job/TestQuartzJob.java b/src/main/java/com/fc/test/common/quartz/job/TestQuartzJob.java deleted file mode 100644 index f39dde1264a0327dbf1cb32889e5786cab85e430..0000000000000000000000000000000000000000 --- a/src/main/java/com/fc/test/common/quartz/job/TestQuartzJob.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.fc.test.common.quartz.job; - -import org.quartz.Job; -import org.quartz.JobExecutionContext; -import org.quartz.JobExecutionException; -import org.springframework.stereotype.Component; -import java.util.Date; - -/** - * @CLASSNAME TestQuartzJob - * @Description 定时调度具体工作类 - * @Auther Jan 橙寂 - * @DATE 2019/9/2 0002 15:33 - */ -@Component -public class TestQuartzJob implements Job { - @Override - public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { - System.out.println(new Date()+"测试定时调度正在执行"); - } -} diff --git a/src/main/java/com/fc/test/common/quartz/task/RyTask.java b/src/main/java/com/fc/test/common/quartz/task/RyTask.java new file mode 100644 index 0000000000000000000000000000000000000000..aebce01d4881b884e01a68830e1255f3629cbfda --- /dev/null +++ b/src/main/java/com/fc/test/common/quartz/task/RyTask.java @@ -0,0 +1,33 @@ +package com.fc.test.common.quartz.task; + +import org.springframework.stereotype.Component; + +/** + * @CLASSNAME RyTask + * @Description 定时调度具体工作类 + * @Auther Jan 橙寂 + * @DATE 2019/9/2 0002 15:33 + */ +@Component("ryTask") +public class RyTask { + + /** + * 无参的任务 + */ + public void runTask1() + { + System.out.println("正在执行定时任务,无参方法"); + } + + /** + * 有参任务 + * 目前仅执行常见的数据类型 Integer Long 带L string 带 '' bool Double 带 d + * @param a + * @param b + */ + public void runTask2(Integer a,Long b,String c,Boolean d,Double e) + { + System.out.println("正在执行定时任务,带多个参数的方法"+a+" "+b+" "+c+" "+d+" "+e); + + } +} diff --git a/src/main/java/com/fc/test/common/quartz/utils/JobInvokeUtil.java b/src/main/java/com/fc/test/common/quartz/utils/JobInvokeUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..658605d6181b49606f99e9f1c2adf7dd602fd367 --- /dev/null +++ b/src/main/java/com/fc/test/common/quartz/utils/JobInvokeUtil.java @@ -0,0 +1,183 @@ +package com.fc.test.common.quartz.utils; + + + +import com.fc.test.common.quartz.entity.SysJob; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.LinkedList; +import java.util.List; + +/** + * 任务执行工具 + * + * @author ruoyi + */ +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 methodParams = getMethodParams(invokeTarget); + + if (!isValidClassName(beanName)) + { + Object bean = SpringUtils.getBean(beanName); + invokeMethod(bean, methodName, methodParams); + } + else + { + Object bean = Class.forName(beanName).newInstance(); + invokeMethod(bean, methodName, methodParams); + } + } + + /** + * 调用任务方法 + * + * @param bean 目标对象 + * @param methodName 方法名称 + * @param methodParams 方法参数 + */ + private static void invokeMethod(Object bean, String methodName, List methodParams) + throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, + InvocationTargetException + { + if (StringUtils.isNotNull(methodParams) && methodParams.size() > 0) + { + Method method = bean.getClass().getDeclaredMethod(methodName, getMethodParamsType(methodParams)); + method.invoke(bean, getMethodParamsValue(methodParams)); + } + else + { + Method method = bean.getClass().getDeclaredMethod(methodName); + method.invoke(bean); + } + } + + /** + * 校验是否为为class包名 + * + * @param str 名称 + * @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 getMethodParams(String invokeTarget) + { + String methodStr = StringUtils.substringBetween(invokeTarget, "(", ")"); + if (StringUtils.isEmpty(methodStr)) + { + return null; + } + String[] methodParams = methodStr.split(","); + List classs = new LinkedList<>(); + for (int i = 0; i < methodParams.length; i++) + { + String str = StringUtils.trimToEmpty(methodParams[i]); + // String字符串类型,包含' + if (StringUtils.contains(str, "'")) + { + classs.add(new Object[] { StringUtils.replace(str, "'", ""), String.class }); + } + // boolean布尔类型,等于true或者false + else if (StringUtils.equals(str, "true") || StringUtils.equalsIgnoreCase(str, "false")) + { + classs.add(new Object[] { Boolean.valueOf(str), Boolean.class }); + } + // long长整形,包含L + else if (StringUtils.containsIgnoreCase(str, "L")) + { + classs.add(new Object[] { Long.valueOf(StringUtils.replaceIgnoreCase(str, "L", "")), Long.class }); + } + // double浮点类型,包含D + else if (StringUtils.containsIgnoreCase(str, "D")) + { + classs.add(new Object[] { Double.valueOf(StringUtils.replaceIgnoreCase(str, "D", "")), Double.class }); + } + // 其他类型归类为整形 + else + { + classs.add(new Object[] { Integer.valueOf(str), Integer.class }); + } + } + return classs; + } + + /** + * 获取参数类型 + * + * @param methodParams 参数相关列表 + * @return 参数类型列表 + */ + public static Class[] getMethodParamsType(List 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 methodParams) + { + Object[] classs = new Object[methodParams.size()]; + int index = 0; + for (Object[] os : methodParams) + { + classs[index] = (Object) os[0]; + index++; + } + return classs; + } +} diff --git a/src/main/java/com/fc/test/common/quartz/utils/SpringUtils.java b/src/main/java/com/fc/test/common/quartz/utils/SpringUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..cf83f259007f7d45a8ae1ef4067e79e995e6fe99 --- /dev/null +++ b/src/main/java/com/fc/test/common/quartz/utils/SpringUtils.java @@ -0,0 +1,114 @@ +package com.fc.test.common.quartz.utils; + +import org.springframework.aop.framework.AopContext; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.stereotype.Component; + +/** + * spring工具类 方便在非spring管理环境中获取bean + * + * @author ruoyi + */ +@Component +public final class SpringUtils implements BeanFactoryPostProcessor +{ + /** Spring应用上下文环境 */ + private static ConfigurableListableBeanFactory beanFactory; + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException + { + SpringUtils.beanFactory = beanFactory; + } + + /** + * 获取对象 + * + * @param name + * @return Object 一个以所给名字注册的bean的实例 + * @throws org.springframework.beans.BeansException + * + */ + @SuppressWarnings("unchecked") + public static T getBean(String name) throws BeansException + { + return (T) beanFactory.getBean(name); + } + + /** + * 获取类型为requiredType的对象 + * + * @param clz + * @return + * @throws org.springframework.beans.BeansException + * + */ + public static T getBean(Class clz) throws BeansException + { + T result = (T) beanFactory.getBean(clz); + return result; + } + + /** + * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true + * + * @param name + * @return boolean + */ + public static boolean containsBean(String name) + { + return beanFactory.containsBean(name); + } + + /** + * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) + * + * @param name + * @return boolean + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * + */ + public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.isSingleton(name); + } + + /** + * @param name + * @return Class 注册对象的类型 + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * + */ + public static Class getType(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.getType(name); + } + + /** + * 如果给定的bean名字在bean定义中有别名,则返回这些别名 + * + * @param name + * @return + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * + */ + public static String[] getAliases(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.getAliases(name); + } + + /** + * 获取aop代理对象 + * + * @param invoker + * @return + */ + @SuppressWarnings("unchecked") + public static T getAopProxy(T invoker) + { + return (T) AopContext.currentProxy(); + } +} diff --git a/src/main/java/com/fc/test/common/quartz/utils/StringUtils.java b/src/main/java/com/fc/test/common/quartz/utils/StringUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..2902cfbcc35c39ecf741b6d28c4bfbbf82f6e94d --- /dev/null +++ b/src/main/java/com/fc/test/common/quartz/utils/StringUtils.java @@ -0,0 +1,400 @@ +package com.fc.test.common.quartz.utils; + + +import cn.hutool.core.text.StrFormatter; + +import java.util.Collection; +import java.util.Map; + +/** + * 字符串工具类 + * + * @author ruoyi + */ +public class StringUtils extends org.apache.commons.lang3.StringUtils +{ + /** 空字符串 */ + private static final String NULLSTR = ""; + + /** 下划线 */ + private static final char SEPARATOR = '_'; + + /** + * 获取参数不为空值 + * + * @param value defaultValue 要判断的value + * @return value 返回值 + */ + public static T nvl(T value, T defaultValue) + { + return value != null ? value : defaultValue; + } + + /** + * * 判断一个Collection是否为空, 包含List,Set,Queue + * + * @param coll 要判断的Collection + * @return true:为空 false:非空 + */ + public static boolean isEmpty(Collection coll) + { + return isNull(coll) || coll.isEmpty(); + } + + /** + * * 判断一个Collection是否非空,包含List,Set,Queue + * + * @param coll 要判断的Collection + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Collection coll) + { + return !isEmpty(coll); + } + + /** + * * 判断一个对象数组是否为空 + * + * @param objects 要判断的对象数组 + ** @return true:为空 false:非空 + */ + public static boolean isEmpty(Object[] objects) + { + return isNull(objects) || (objects.length == 0); + } + + /** + * * 判断一个对象数组是否非空 + * + * @param objects 要判断的对象数组 + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Object[] objects) + { + return !isEmpty(objects); + } + + /** + * * 判断一个Map是否为空 + * + * @param map 要判断的Map + * @return true:为空 false:非空 + */ + public static boolean isEmpty(Map map) + { + return isNull(map) || map.isEmpty(); + } + + /** + * * 判断一个Map是否为空 + * + * @param map 要判断的Map + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Map map) + { + return !isEmpty(map); + } + + /** + * * 判断一个字符串是否为空串 + * + * @param str String + * @return true:为空 false:非空 + */ + public static boolean isEmpty(String str) + { + return isNull(str) || NULLSTR.equals(str.trim()); + } + + /** + * * 判断一个字符串是否为非空串 + * + * @param str String + * @return true:非空串 false:空串 + */ + public static boolean isNotEmpty(String str) + { + return !isEmpty(str); + } + + /** + * * 判断一个对象是否为空 + * + * @param object Object + * @return true:为空 false:非空 + */ + public static boolean isNull(Object object) + { + return object == null; + } + + /** + * * 判断一个对象是否非空 + * + * @param object Object + * @return true:非空 false:空 + */ + public static boolean isNotNull(Object object) + { + return !isNull(object); + } + + /** + * * 判断一个对象是否是数组类型(Java基本型别的数组) + * + * @param object 对象 + * @return true:是数组 false:不是数组 + */ + public static boolean isArray(Object object) + { + return isNotNull(object) && object.getClass().isArray(); + } + + /** + * 去空格 + */ + public static String trim(String str) + { + return (str == null ? "" : str.trim()); + } + + /** + * 截取字符串 + * + * @param str 字符串 + * @param start 开始 + * @return 结果 + */ + public static String substring(final String str, int start) + { + if (str == null) + { + return NULLSTR; + } + + if (start < 0) + { + start = str.length() + start; + } + + if (start < 0) + { + start = 0; + } + if (start > str.length()) + { + return NULLSTR; + } + + return str.substring(start); + } + + /** + * 截取字符串 + * + * @param str 字符串 + * @param start 开始 + * @param end 结束 + * @return 结果 + */ + public static String substring(final String str, int start, int end) + { + if (str == null) + { + return NULLSTR; + } + + if (end < 0) + { + end = str.length() + end; + } + if (start < 0) + { + start = str.length() + start; + } + + if (end > str.length()) + { + end = str.length(); + } + + if (start > end) + { + return NULLSTR; + } + + if (start < 0) + { + start = 0; + } + if (end < 0) + { + end = 0; + } + + return str.substring(start, end); + } + + /** + * 格式化文本, {} 表示占位符
+ * 此方法只是简单将占位符 {} 按照顺序替换为参数
+ * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
+ * 例:
+ * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b
+ * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a
+ * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b
+ * + * @param template 文本模板,被替换的部分用 {} 表示 + * @param params 参数值 + * @return 格式化后的文本 + */ + public static String format(String template, Object... params) + { + if (isEmpty(params) || isEmpty(template)) + { + return template; + } + return StrFormatter.format(template, params); + } + + /** + * 下划线转驼峰命名 + */ + public static String toUnderScoreCase(String str) + { + if (str == null) + { + return null; + } + StringBuilder sb = new StringBuilder(); + // 前置字符是否大写 + boolean preCharIsUpperCase = true; + // 当前字符是否大写 + boolean curreCharIsUpperCase = true; + // 下一字符是否大写 + boolean nexteCharIsUpperCase = true; + for (int i = 0; i < str.length(); i++) + { + char c = str.charAt(i); + if (i > 0) + { + preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1)); + } + else + { + preCharIsUpperCase = false; + } + + curreCharIsUpperCase = Character.isUpperCase(c); + + if (i < (str.length() - 1)) + { + nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1)); + } + + if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase) + { + sb.append(SEPARATOR); + } + else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase) + { + sb.append(SEPARATOR); + } + sb.append(Character.toLowerCase(c)); + } + + return sb.toString(); + } + + /** + * 是否包含字符串 + * + * @param str 验证字符串 + * @param strs 字符串组 + * @return 包含返回true + */ + public static boolean inStringIgnoreCase(String str, String... strs) + { + if (str != null && strs != null) + { + for (String s : strs) + { + if (str.equalsIgnoreCase(trim(s))) + { + return true; + } + } + } + return false; + } + + /** + * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如:HELLO_WORLD->HelloWorld + * + * @param name 转换前的下划线大写方式命名的字符串 + * @return 转换后的驼峰式命名的字符串 + */ + public static String convertToCamelCase(String name) + { + StringBuilder result = new StringBuilder(); + // 快速检查 + if (name == null || name.isEmpty()) + { + // 没必要转换 + return ""; + } + else if (!name.contains("_")) + { + // 不含下划线,仅将首字母大写 + return name.substring(0, 1).toUpperCase() + name.substring(1); + } + // 用下划线将原始字符串分割 + String[] camels = name.split("_"); + for (String camel : camels) + { + // 跳过原始字符串中开头、结尾的下换线或双重下划线 + if (camel.isEmpty()) + { + continue; + } + // 首字母大写 + result.append(camel.substring(0, 1).toUpperCase()); + result.append(camel.substring(1).toLowerCase()); + } + return result.toString(); + } + + /** + * 驼峰式命名法 例如:user_name->userName + */ + public static String toCamelCase(String s) + { + if (s == null) + { + return null; + } + s = s.toLowerCase(); + StringBuilder sb = new StringBuilder(s.length()); + boolean upperCase = false; + for (int i = 0; i < s.length(); i++) + { + char c = s.charAt(i); + + if (c == SEPARATOR) + { + upperCase = true; + } + else if (upperCase) + { + sb.append(Character.toUpperCase(c)); + upperCase = false; + } + else + { + sb.append(c); + } + } + return sb.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/com/fc/test/controller/admin/QuartzController.java b/src/main/java/com/fc/test/controller/admin/QuartzController.java index d8f80f3026b55030d3e5ddc4a5ae54d8491be37a..6cc11d365cac29a0cb463d829a644835d3fb7616 100644 --- a/src/main/java/com/fc/test/controller/admin/QuartzController.java +++ b/src/main/java/com/fc/test/controller/admin/QuartzController.java @@ -2,8 +2,8 @@ package com.fc.test.controller.admin; import com.fc.test.common.base.BaseController; import com.fc.test.common.quartz.QuartzScheduler; +import com.fc.test.common.quartz.entity.SysJob; import com.fc.test.model.custom.TitleVo; - import io.swagger.annotations.Api; import org.quartz.SchedulerException; import org.springframework.beans.factory.annotation.Autowired; @@ -29,18 +29,6 @@ public class QuartzController extends BaseController{ private QuartzScheduler scheduler; - /** - * 调用这个方法开启任务调度 - * @param model - * @return - */ - @GetMapping("start") - @ResponseBody - public String start(Model model) - { - scheduler.startJob(); - return "success"; - } /** @@ -50,9 +38,9 @@ public class QuartzController extends BaseController{ */ @GetMapping("stop") @ResponseBody - public Object stop(Model model,String taskName,String groupName) + public Object stop(Model model, SysJob job) { - return scheduler.pauseJob(taskName,groupName); + return scheduler.pauseJob(job); } @@ -64,9 +52,9 @@ public class QuartzController extends BaseController{ */ @GetMapping("resume") @ResponseBody - public Object resume(Model model,String taskName,String groupName) + public Object resume(Model model,SysJob job) { - return scheduler.resumeJob(taskName,groupName); + return scheduler.resumeJob(job); } @@ -78,15 +66,27 @@ public class QuartzController extends BaseController{ */ @GetMapping("update") @ResponseBody - public String update(Model model,String taskName,String groupName,String cron) + public String update(Model model,SysJob job) { - try { - scheduler.modifyJob(taskName,groupName,cron); - } catch (SchedulerException e) { - e.printStackTrace(); - return "fail"; - } + + scheduler.modifyJob(job); + return "success"; } + + /** + * 删除定时器 + * @param model + * @return + */ + @GetMapping("delete") + @ResponseBody + public String delete(Model model,SysJob job) + { + + scheduler.deleteJob(job); + + return "success"; + } }