1.表
job任務表
CREATE TABLE `sys_job` ( `job_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任務ID', `job_name` varchar(64) NOT NULL DEFAULT '' COMMENT '任務名稱', `job_group` varchar(64) NOT NULL 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 DEFAULT NULL COMMENT '創建時間', `update_by` varchar(64) DEFAULT '' COMMENT '更新者', `update_time` datetime DEFAULT NULL COMMENT '更新時間', `remark` varchar(500) DEFAULT '' COMMENT '備注信息', PRIMARY KEY (`job_id`,`job_name`,`job_group`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COMMENT='定時任務調度表';
任務日志表
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) DEFAULT NULL COMMENT '日志信息', `status` char(1) DEFAULT '0' COMMENT '執行狀態(0正常 1失敗)', `exception_info` varchar(2000) DEFAULT '' COMMENT '異常信息', `create_time` datetime DEFAULT NULL COMMENT '創建時間', PRIMARY KEY (`job_log_id`) ) ENGINE=InnoDB AUTO_INCREMENT=173 DEFAULT CHARSET=utf8 COMMENT='定時任務調度日志表';
2.實體類
基礎實體類
package com.ruoyi.common.core.domain; import java.io.Serializable; import java.util.Date; import java.util.HashMap; import java.util.Map; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnore; /** * Entity基類 * * @author ruoyi */ public class BaseEntity implements Serializable { private static final long serialVersionUID = 1L; /** 搜索值 */ private String searchValue; /** 創建者 */ private String createBy; /** 創建時間 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date createTime; /** 更新者 */ private String updateBy; /** 更新時間 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date updateTime; /** 備注 */ private String remark; /** 開始時間 */ @JsonIgnore private String beginTime; /** 結束時間 */ @JsonIgnore private String endTime; /** 請求參數 */ private Map<String, Object> params; public String getSearchValue() { return searchValue; } public void setSearchValue(String searchValue) { this.searchValue = searchValue; } public String getCreateBy() { return createBy; } public void setCreateBy(String createBy) { this.createBy = createBy; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } public String getUpdateBy() { return updateBy; } public void setUpdateBy(String updateBy) { this.updateBy = updateBy; } public Date getUpdateTime() { return updateTime; } public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; } public String getRemark() { return remark; } public void setRemark(String remark) { this.remark = remark; } public String getBeginTime() { return beginTime; } public void setBeginTime(String beginTime) { this.beginTime = beginTime; } public String getEndTime() { return endTime; } public void setEndTime(String endTime) { this.endTime = endTime; } public Map<String, Object> getParams() { if (params == null) { params = new HashMap<>(); } return params; } public void setParams(Map<String, Object> params) { this.params = params; } }
JOB實體類
package com.ruoyi.quartz.domain; import java.io.Serializable; import java.util.Date; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import com.fasterxml.jackson.annotation.JsonFormat; import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.annotation.Excel.ColumnType; import com.ruoyi.common.constant.ScheduleConstants; import com.ruoyi.common.core.domain.BaseEntity; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.quartz.util.CronUtils; /** * 定時任務調度表 sys_job * * @author ruoyi */ public class SysJob extends BaseEntity implements Serializable { private static final long serialVersionUID = 1L; /** 任務ID */ @Excel(name = "任務序號", cellType = ColumnType.NUMERIC) private Long jobId; /** 任務名稱 */ @Excel(name = "任務名稱") private String jobName; /** 任務組名 */ @Excel(name = "任務組名") private String jobGroup; /** 調用目標字符串 */ @Excel(name = "調用目標字符串") private String invokeTarget; /** cron執行表達式 */ @Excel(name = "執行表達式 ") private String cronExpression; /** cron計划策略 */ @Excel(name = "計划策略 ", readConverterExp = "0=默認,1=立即觸發執行,2=觸發一次執行,3=不觸發立即執行") private String misfirePolicy = ScheduleConstants.MISFIRE_DEFAULT; /** 是否並發執行(0允許 1禁止) */ @Excel(name = "並發執行", readConverterExp = "0=允許,1=禁止") private String concurrent; /** 任務狀態(0正常 1暫停) */ @Excel(name = "任務狀態", readConverterExp = "0=正常,1=暫停") private String status; public Long getJobId() { return jobId; } public void setJobId(Long jobId) { this.jobId = jobId; } @NotBlank(message = "任務名稱不能為空") @Size(min = 0, max = 64, message = "任務名稱不能超過64個字符") 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 = 500, 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; } @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") public Date getNextValidTime() { if (StringUtils.isNotEmpty(cronExpression)) { return CronUtils.getNextExecution(cronExpression); } 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; } @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("jobId", getJobId()) .append("jobName", getJobName()) .append("jobGroup", getJobGroup()) .append("cronExpression", getCronExpression()) .append("nextValidTime", getNextValidTime()) .append("misfirePolicy", getMisfirePolicy()) .append("concurrent", getConcurrent()) .append("status", getStatus()) .append("createBy", getCreateBy()) .append("createTime", getCreateTime()) .append("updateBy", getUpdateBy()) .append("updateTime", getUpdateTime()) .append("remark", getRemark()) .toString(); } }
任務日志實體類
package com.ruoyi.quartz.domain; import java.util.Date; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.core.domain.BaseEntity; /** * 定時任務調度日志表 sys_job_log * * @author ruoyi */ public class SysJobLog extends BaseEntity { private static final long serialVersionUID = 1L; /** ID */ @Excel(name = "日志序號") private Long jobLogId; /** 任務名稱 */ @Excel(name = "任務名稱") private String jobName; /** 任務組名 */ @Excel(name = "任務組名") private String jobGroup; /** 調用目標字符串 */ @Excel(name = "調用目標字符串") private String invokeTarget; /** 日志信息 */ @Excel(name = "日志信息") private String jobMessage; /** 執行狀態(0正常 1失敗) */ @Excel(name = "執行狀態", readConverterExp = "0=正常,1=失敗") private String status; /** 異常信息 */ @Excel(name = "異常信息") private String exceptionInfo; /** 開始時間 */ private Date startTime; /** 停止時間 */ private Date stopTime; 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 getStopTime() { return stopTime; } public void setStopTime(Date stopTime) { this.stopTime = stopTime; } @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("jobLogId", getJobLogId()) .append("jobName", getJobName()) .append("jobGroup", getJobGroup()) .append("jobMessage", getJobMessage()) .append("status", getStatus()) .append("exceptionInfo", getExceptionInfo()) .append("startTime", getStartTime()) .append("stopTime", getStopTime()) .toString(); } }
3.定時任務配置類
配置實例化了SchedulerFactoryBean的工廠實例和名為RuoyiScheduler的Scheduler實例,且自動啟動
package com.ruoyi.quartz.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.quartz.SchedulerFactoryBean; import javax.sql.DataSource; import java.util.Properties; /** * 定時任務配置 * * @author ruoyi */ @Configuration public class ScheduleConfig { @Bean public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) { SchedulerFactoryBean factory = new SchedulerFactoryBean(); factory.setDataSource(dataSource); // quartz參數 Properties prop = new Properties(); prop.put("org.quartz.scheduler.instanceName", "RuoyiScheduler"); prop.put("org.quartz.scheduler.instanceId", "AUTO"); // 線程池配置 prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool"); prop.put("org.quartz.threadPool.threadCount", "20"); prop.put("org.quartz.threadPool.threadPriority", "5"); // JobStore配置 prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX"); // 集群配置 prop.put("org.quartz.jobStore.isClustered", "true"); prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000"); prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1"); prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true"); // sqlserver 啟用 // prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?"); prop.put("org.quartz.jobStore.misfireThreshold", "12000"); prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_"); factory.setQuartzProperties(prop); factory.setSchedulerName("RuoyiScheduler"); // 延時啟動 factory.setStartupDelay(1); factory.setApplicationContextSchedulerContextKey("applicationContextKey"); // 可選,QuartzScheduler // 啟動時更新己存在的Job,這樣就不用每次修改targetObject后刪除qrtz_job_details表對應記錄了 factory.setOverwriteExistingJobs(true); // 設置自動啟動,默認為true factory.setAutoStartup(true); return factory; } }
4.自定義JOB任務、定時任務處理
JOB
package com.ruoyi.quartz.util; import java.util.Date; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.constant.ScheduleConstants; import com.ruoyi.common.utils.ExceptionUtil; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.bean.BeanUtils; import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.quartz.domain.SysJob; import com.ruoyi.quartz.domain.SysJobLog; import com.ruoyi.quartz.service.ISysJobLogService; /** * 抽象quartz調用 * * @author ruoyi */ public abstract class AbstractQuartzJob implements Job { private static final Logger log = LoggerFactory.getLogger(AbstractQuartzJob.class); /** * 線程本地變量 */ private static ThreadLocal<Date> threadLocal = new ThreadLocal<>(); @Override public void execute(JobExecutionContext context) throws JobExecutionException { SysJob sysJob = new SysJob(); BeanUtils.copyBeanProp(sysJob, context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES)); 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.setStopTime(new Date()); long runMs = sysJobLog.getStopTime().getTime() - sysJobLog.getStartTime().getTime(); sysJobLog.setJobMessage(sysJobLog.getJobName() + " 總共耗時:" + runMs + "毫秒"); if (e != null) { sysJobLog.setStatus(Constants.FAIL); String errorMsg = StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, 2000); sysJobLog.setExceptionInfo(errorMsg); } else { sysJobLog.setStatus(Constants.SUCCESS); } // 寫入數據庫當中 //SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog); } /** * 執行方法,由子類重載 * * @param context 工作執行上下文對象 * @param sysJob 系統計划任務 * @throws Exception 執行過程中的異常 */ protected abstract void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception; }
定時任務處理-可並發
package com.ruoyi.quartz.util; import org.quartz.JobExecutionContext; import com.ruoyi.quartz.domain.SysJob; /** * 定時任務處理(允許並發執行) * * @author ruoyi * */ public class QuartzJobExecution extends AbstractQuartzJob { @Override protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception { JobInvokeUtil.invokeMethod(sysJob); //自己重寫了執行的方法 因為要傳遞參數 } }
定時任務處理-不可並發
package com.ruoyi.quartz.util; import org.quartz.DisallowConcurrentExecution; import org.quartz.JobExecutionContext; import com.ruoyi.quartz.domain.SysJob; /** * 定時任務處理(禁止並發執行) * * @author ruoyi * */ @DisallowConcurrentExecution public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob { @Override protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception { JobInvokeUtil.invokeMethod(sysJob); } }
任務執行工具
package com.ruoyi.quartz.util; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.LinkedList; import java.util.List; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.quartz.domain.SysJob; /** * 任務執行工具 -- 自己通過反射區獲取定義的任務的方法去執行 --- 因為需要傳遞參數 * * @author ruoyi */ public class JobInvokeUtil { /** * 執行方法 * * @param sysJob 系統任務 */ public static void invokeMethod(SysJob sysJob) throws Exception { String invokeTarget = sysJob.getInvokeTarget(); //如:ryTask.ryMultipleParams('ry', true, 2000L, 316.50D, 100) String beanName = getBeanName(invokeTarget); //如:ryTask String methodName = getMethodName(invokeTarget); //如ryMultipleParams List<Object[]> methodParams = getMethodParams(invokeTarget); //數組里面存的是參數值和參數類型 if (!isValidClassName(beanName)) //ryTask 判斷是否是全類名,如果是,去掉包名 { 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<Object[]> 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<Object[]> getMethodParams(String invokeTarget)//ryTask.ryMultipleParams('ry', true, 2000L, 316.50D, 100) { String methodStr = StringUtils.substringBetween(invokeTarget, "(", ")");//'ry', true, 2000L, 316.50D, 100 if (StringUtils.isEmpty(methodStr)) { return null; } String[] methodParams = methodStr.split(",");//['ry', true, 2000L, 316.50D, 100] List<Object[]> classs = new LinkedList<>();//存儲參數值和參數類型 for (int i = 0; i < methodParams.length; i++) { String str = StringUtils.trimToEmpty(methodParams[i]);//'ry' // 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<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] = (Object) os[0]; index++; } return classs; } }
定時任務工具類
package com.ruoyi.quartz.util; import org.quartz.CronScheduleBuilder; import org.quartz.CronTrigger; import org.quartz.Job; import org.quartz.JobBuilder; import org.quartz.JobDetail; import org.quartz.JobKey; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.TriggerBuilder; import org.quartz.TriggerKey; import com.ruoyi.common.constant.ScheduleConstants; import com.ruoyi.common.exception.job.TaskException; import com.ruoyi.common.exception.job.TaskException.Code; import com.ruoyi.quartz.domain.SysJob; /** * 定時任務工具類 * * @author ruoyi * */ 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(); //創建了一個 JobDetail,類型為jobClass,並設置了Jobkey-就是這個Job的標識 // 表達式調度構建器 CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression()); //job.getCronExpression()任務調度表達式如0/30 * * * * ? cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder); //設置定時任務策略 // 按新的cronExpression表達式構建一個新的trigger CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup)) //創建了一個觸發器-並設置了TriggerKey-就是Trigger的標識 .withSchedule(cronScheduleBuilder).build(); // 放入參數,運行時的方法可以獲取 jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job); //在任務對象中插入我們自定義的(數據庫里的)Job對象-把消息儲存起來 // 判斷是否存在 if (scheduler.checkExists(getJobKey(jobId, jobGroup))) { // 防止創建時存在數據問題 先移除,然后在執行創建操作 scheduler.deleteJob(getJobKey(jobId, jobGroup)); } 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", Code.CONFIG_ERROR); } } }
5.Mapper接口
package com.ruoyi.quartz.mapper; import java.util.List; import com.ruoyi.quartz.domain.SysJob; /** * 調度任務信息 數據層 * * @author ruoyi */ public interface SysJobMapper { /** * 查詢調度任務日志集合 * * @param job 調度信息 * @return 操作日志集合 */ public List<SysJob> selectJobList(SysJob job); /** * 查詢所有調度任務 * * @return 調度任務列表 */ public List<SysJob> selectJobAll(); /** * 通過調度ID查詢調度任務信息 * * @param jobId 調度ID * @return 角色對象信息 */ public SysJob selectJobById(Long jobId); /** * 通過調度ID刪除調度任務信息 * * @param jobId 調度ID * @return 結果 */ public int deleteJobById(Long jobId); /** * 批量刪除調度任務信息 * * @param ids 需要刪除的數據ID * @return 結果 */ public int deleteJobByIds(Long[] ids); /** * 修改調度任務信息 * * @param job 調度任務信息 * @return 結果 */ public int updateJob(SysJob job); /** * 新增調度任務信息 * * @param job 調度任務信息 * @return 結果 */ public int insertJob(SysJob job); }
<?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.ruoyi.quartz.mapper.SysJobMapper"> <resultMap type="SysJob" id="SysJobResult"> <id property="jobId" column="job_id" /> <result property="jobName" column="job_name" /> <result property="jobGroup" column="job_group" /> <result property="invokeTarget" column="invoke_target" /> <result property="cronExpression" column="cron_expression" /> <result property="misfirePolicy" column="misfire_policy" /> <result property="concurrent" column="concurrent" /> <result property="status" column="status" /> <result property="createBy" column="create_by" /> <result property="createTime" column="create_time" /> <result property="updateBy" column="update_by" /> <result property="updateTime" column="update_time" /> <result property="remark" column="remark" /> </resultMap> <sql id="selectJobVo"> select job_id, job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark from sys_job </sql> <select id="selectJobList" parameterType="SysJob" resultMap="SysJobResult"> <include refid="selectJobVo"/> <where> <if test="jobName != null and jobName != ''"> AND job_name like concat('%', #{jobName}, '%') </if> <if test="jobGroup != null and jobGroup != ''"> AND job_group = #{jobGroup} </if> <if test="status != null and status != ''"> AND status = #{status} </if> <if test="invokeTarget != null and invokeTarget != ''"> AND invoke_target like concat('%', #{invokeTarget}, '%') </if> </where> </select> <select id="selectJobAll" resultMap="SysJobResult"> <include refid="selectJobVo"/> </select> <select id="selectJobById" parameterType="Long" resultMap="SysJobResult"> <include refid="selectJobVo"/> where job_id = #{jobId} </select> <delete id="deleteJobById" parameterType="Long"> delete from sys_job where job_id = #{jobId} </delete> <delete id="deleteJobByIds" parameterType="Long"> delete from sys_job where job_id in <foreach collection="array" item="jobId" open="(" separator="," close=")"> #{jobId} </foreach> </delete> <update id="updateJob" parameterType="SysJob"> update sys_job <set> <if test="jobName != null and jobName != ''">job_name = #{jobName},</if> <if test="jobGroup != null and jobGroup != ''">job_group = #{jobGroup},</if> <if test="invokeTarget != null and invokeTarget != ''">invoke_target = #{invokeTarget},</if> <if test="cronExpression != null and cronExpression != ''">cron_expression = #{cronExpression},</if> <if test="misfirePolicy != null and misfirePolicy != ''">misfire_policy = #{misfirePolicy},</if> <if test="concurrent != null and concurrent != ''">concurrent = #{concurrent},</if> <if test="status !=null">status = #{status},</if> <if test="remark != null and remark != ''">remark = #{remark},</if> <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if> update_time = sysdate() </set> where job_id = #{jobId} </update> <insert id="insertJob" parameterType="SysJob" useGeneratedKeys="true" keyProperty="jobId"> insert into sys_job( <if test="jobId != null and jobId != 0">job_id,</if> <if test="jobName != null and jobName != ''">job_name,</if> <if test="jobGroup != null and jobGroup != ''">job_group,</if> <if test="invokeTarget != null and invokeTarget != ''">invoke_target,</if> <if test="cronExpression != null and cronExpression != ''">cron_expression,</if> <if test="misfirePolicy != null and misfirePolicy != ''">misfire_policy,</if> <if test="concurrent != null and concurrent != ''">concurrent,</if> <if test="status != null and status != ''">status,</if> <if test="remark != null and remark != ''">remark,</if> <if test="createBy != null and createBy != ''">create_by,</if> create_time )values( <if test="jobId != null and jobId != 0">#{jobId},</if> <if test="jobName != null and jobName != ''">#{jobName},</if> <if test="jobGroup != null and jobGroup != ''">#{jobGroup},</if> <if test="invokeTarget != null and invokeTarget != ''">#{invokeTarget},</if> <if test="cronExpression != null and cronExpression != ''">#{cronExpression},</if> <if test="misfirePolicy != null and misfirePolicy != ''">#{misfirePolicy},</if> <if test="concurrent != null and concurrent != ''">#{concurrent},</if> <if test="status != null and status != ''">#{status},</if> <if test="remark != null and remark != ''">#{remark},</if> <if test="createBy != null and createBy != ''">#{createBy},</if> sysdate() ) </insert> </mapper>
6.任務調度常量
package com.ruoyi.common.constant; /** * 任務調度通用常量 * * @author ruoyi */ 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 String value; private Status(String value) { this.value = value; } public String getValue() { return value; } } }
7.定時任務測試
package com.ruoyi.quartz.task; import org.springframework.stereotype.Component; import com.ruoyi.common.utils.StringUtils; /** * 定時任務調度測試 * * @author ruoyi */ @Component("ryTask") public class RyTask { public void ryMultipleParams(String s, Boolean b, Long l, Double d, Integer i) { System.out.println(StringUtils.format("執行多參方法: 字符串類型{},布爾類型{},長整型{},浮點型{},整形{}", s, b, l, d, i)); } public void ryParams(String params) { System.out.println("執行有參方法:" + params); } public void ryNoParams() { System.out.println("執行無參方法"); } }
package com.ruoyi.quartz.controller; import java.util.List; import org.quartz.SchedulerException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.exception.job.TaskException; import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.quartz.domain.SysJob; import com.ruoyi.quartz.service.ISysJobService; import com.ruoyi.quartz.util.CronUtils; /** * 調度任務信息操作處理 * * @author ruoyi */ @RestController @RequestMapping("/monitor/job") public class SysJobController extends BaseController { @Autowired private ISysJobService jobService; /** * 查詢定時任務列表 */ @PreAuthorize("@ss.hasPermi('monitor:job:list')") @GetMapping("/list") public TableDataInfo list(SysJob sysJob) { startPage(); List<SysJob> list = jobService.selectJobList(sysJob); return getDataTable(list); } /** * 獲取定時任務詳細信息 */ @PreAuthorize("@ss.hasPermi('monitor:job:query')") @GetMapping(value = "/{jobId}") public AjaxResult getInfo(@PathVariable("jobId") Long jobId) { return AjaxResult.success(jobService.selectJobById(jobId)); } /** * 新增定時任務 */ @PreAuthorize("@ss.hasPermi('monitor:job:add')") @PostMapping public AjaxResult add(@RequestBody SysJob sysJob) throws SchedulerException, TaskException { if (!CronUtils.isValid(sysJob.getCronExpression())) { return AjaxResult.error("cron表達式不正確"); } return toAjax(jobService.insertJob(sysJob)); } /** * 修改定時任務 */ @PreAuthorize("@ss.hasPermi('monitor:job:edit')") @PutMapping public AjaxResult edit(@RequestBody SysJob sysJob) throws SchedulerException, TaskException { if (!CronUtils.isValid(sysJob.getCronExpression())) { return AjaxResult.error("cron表達式不正確"); } return toAjax(jobService.updateJob(sysJob)); } /** * 定時任務狀態修改 */ @PreAuthorize("@ss.hasPermi('monitor:job:changeStatus')") @PutMapping("/changeStatus") public AjaxResult changeStatus(@RequestBody SysJob job) throws SchedulerException { SysJob newJob = jobService.selectJobById(job.getJobId()); newJob.setStatus(job.getStatus()); return toAjax(jobService.changeStatus(newJob)); } /** * 定時任務立即執行一次 */ @PreAuthorize("@ss.hasPermi('monitor:job:changeStatus')") @PutMapping("/run") public AjaxResult run(@RequestBody SysJob job) throws SchedulerException { jobService.run(job); return AjaxResult.success(); } /** * 刪除定時任務 */ @PreAuthorize("@ss.hasPermi('monitor:job:remove')") @DeleteMapping("/{jobIds}") public AjaxResult remove(@PathVariable Long[] jobIds) throws SchedulerException, TaskException { jobService.deleteJobByIds(jobIds); return AjaxResult.success(); } }
package com.ruoyi.quartz.service; import java.util.List; import org.quartz.SchedulerException; import com.ruoyi.common.exception.job.TaskException; import com.ruoyi.quartz.domain.SysJob; /** * 定時任務調度信息信息 服務層 * * @author ruoyi */ public interface ISysJobService { /** * 獲取quartz調度器的計划任務 * * @param job 調度信息 * @return 調度任務集合 */ public List<SysJob> selectJobList(SysJob job); /** * 通過調度任務ID查詢調度信息 * * @param jobId 調度任務ID * @return 調度任務對象信息 */ public SysJob selectJobById(Long jobId); /** * 暫停任務 * * @param job 調度信息 * @return 結果 */ public int pauseJob(SysJob job) throws SchedulerException; /** * 恢復任務 * * @param job 調度信息 * @return 結果 */ public int resumeJob(SysJob job) throws SchedulerException; /** * 刪除任務后,所對應的trigger也將被刪除 * * @param job 調度信息 * @return 結果 */ public int deleteJob(SysJob job) throws SchedulerException; /** * 批量刪除調度信息 * * @param jobIds 需要刪除的任務ID * @return 結果 */ public void deleteJobByIds(Long[] jobIds) throws SchedulerException; /** * 任務調度狀態修改 * * @param job 調度信息 * @return 結果 */ public int changeStatus(SysJob job) throws SchedulerException; /** * 立即運行任務 * * @param job 調度信息 * @return 結果 */ public void run(SysJob job) throws SchedulerException; /** * 新增任務 * * @param job 調度信息 * @return 結果 */ public int insertJob(SysJob job) throws SchedulerException, TaskException; /** * 更新任務 * * @param job 調度信息 * @return 結果 */ public int updateJob(SysJob job) throws SchedulerException, TaskException; /** * 校驗cron表達式是否有效 * * @param cronExpression 表達式 * @return 結果 */ public boolean checkCronExpressionIsValid(String cronExpression); }
package com.ruoyi.quartz.service.impl; import java.util.List; import javax.annotation.PostConstruct; import org.quartz.JobDataMap; import org.quartz.JobKey; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.ruoyi.common.constant.ScheduleConstants; import com.ruoyi.common.exception.job.TaskException; import com.ruoyi.quartz.domain.SysJob; import com.ruoyi.quartz.mapper.SysJobMapper; import com.ruoyi.quartz.service.ISysJobService; import com.ruoyi.quartz.util.CronUtils; import com.ruoyi.quartz.util.ScheduleUtils; /** * 定時任務調度信息 服務層 * * @author ruoyi */ @Service public class SysJobServiceImpl implements ISysJobService { @Autowired private Scheduler scheduler; @Autowired private SysJobMapper jobMapper; /** * 項目啟動時,初始化定時器 主要是防止手動修改數據庫導致未同步到定時任務處理(注:不能手動修改數據庫ID和任務組名,否則會導致臟數據) */ @PostConstruct public void init() throws SchedulerException, TaskException { scheduler.clear(); List<SysJob> jobList = jobMapper.selectJobAll(); for (SysJob job : jobList) { ScheduleUtils.createScheduleJob(scheduler, job); } } /** * 獲取quartz調度器的計划任務列表 * * @param job 調度信息 * @return */ @Override public List<SysJob> selectJobList(SysJob job) { return jobMapper.selectJobList(job); } /** * 通過調度任務ID查詢調度信息 * * @param jobId 調度任務ID * @return 調度任務對象信息 */ @Override public SysJob selectJobById(Long jobId) { return jobMapper.selectJobById(jobId); } /** * 暫停任務 * * @param job 調度信息 */ @Override @Transactional public int pauseJob(SysJob job) throws SchedulerException { Long jobId = job.getJobId(); String jobGroup = job.getJobGroup(); job.setStatus(ScheduleConstants.Status.PAUSE.getValue()); int rows = jobMapper.updateJob(job); if (rows > 0) { scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup)); } return rows; } /** * 恢復任務 * * @param job 調度信息 */ @Override @Transactional public int resumeJob(SysJob job) throws SchedulerException { Long jobId = job.getJobId(); String jobGroup = job.getJobGroup(); job.setStatus(ScheduleConstants.Status.NORMAL.getValue()); int rows = jobMapper.updateJob(job); if (rows > 0) { scheduler.resumeJob(ScheduleUtils.getJobKey(jobId, jobGroup)); } return rows; } /** * 刪除任務后,所對應的trigger也將被刪除 * * @param job 調度信息 */ @Override @Transactional public int deleteJob(SysJob job) throws SchedulerException { Long jobId = job.getJobId(); String jobGroup = job.getJobGroup(); int rows = jobMapper.deleteJobById(jobId); if (rows > 0) { scheduler.deleteJob(ScheduleUtils.getJobKey(jobId, jobGroup)); } return rows; } /** * 批量刪除調度信息 * * @param jobIds 需要刪除的任務ID * @return 結果 */ @Override @Transactional public void deleteJobByIds(Long[] jobIds) throws SchedulerException { for (Long jobId : jobIds) { SysJob job = jobMapper.selectJobById(jobId); deleteJob(job); } } /** * 任務調度狀態修改 * * @param job 調度信息 */ @Override @Transactional public int changeStatus(SysJob job) throws SchedulerException { int rows = 0; String status = job.getStatus(); if (ScheduleConstants.Status.NORMAL.getValue().equals(status)) { rows = resumeJob(job); } else if (ScheduleConstants.Status.PAUSE.getValue().equals(status)) { rows = pauseJob(job); } return rows; } /** * 立即運行任務 * * @param job 調度信息 */ @Override @Transactional public void run(SysJob job) throws SchedulerException { Long jobId = job.getJobId(); String jobGroup = job.getJobGroup(); SysJob properties = selectJobById(job.getJobId()); // 參數 JobDataMap dataMap = new JobDataMap(); dataMap.put(ScheduleConstants.TASK_PROPERTIES, properties); System.out.println(scheduler.getSchedulerName()); scheduler.triggerJob(ScheduleUtils.getJobKey(jobId, jobGroup), dataMap); //執行一次 } /** * 新增任務 * * @param job 調度信息 調度信息 */ @Override @Transactional public int insertJob(SysJob job) throws SchedulerException, TaskException { job.setStatus(ScheduleConstants.Status.PAUSE.getValue()); int rows = jobMapper.insertJob(job); if (rows > 0) { ScheduleUtils.createScheduleJob(scheduler, job); } return rows; } /** * 更新任務的時間表達式 * * @param job 調度信息 */ @Override @Transactional public int updateJob(SysJob job) throws SchedulerException, TaskException { SysJob properties = selectJobById(job.getJobId()); int rows = jobMapper.updateJob(job); if (rows > 0) { updateSchedulerJob(job, properties.getJobGroup()); } return rows; } /** * 更新任務 * * @param job 任務對象 * @param jobGroup 任務組名 */ public void updateSchedulerJob(SysJob job, String jobGroup) throws SchedulerException, TaskException { Long jobId = job.getJobId(); // 判斷是否存在 JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup); if (scheduler.checkExists(jobKey)) { // 防止創建時存在數據問題 先移除,然后在執行創建操作 scheduler.deleteJob(jobKey); } ScheduleUtils.createScheduleJob(scheduler, job); } /** * 校驗cron表達式是否有效 * * @param cronExpression 表達式 * @return 結果 */ @Override public boolean checkCronExpressionIsValid(String cronExpression) { return CronUtils.isValid(cronExpression); } }
8.工具類
package com.ruoyi.quartz.util; import java.text.ParseException; import java.util.Date; import org.quartz.CronExpression; /** * cron表達式工具類 * * @author ruoyi * */ 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()); } } }
package com.ruoyi.common.constant; /** * 通用常量信息 * * @author ruoyi */ public class Constants { /** * UTF-8 字符集 */ public static final String UTF8 = "UTF-8"; /** * GBK 字符集 */ public static final String GBK = "GBK"; /** * 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"; /** * 登錄成功 */ public static final String LOGIN_SUCCESS = "Success"; /** * 注銷 */ public static final String LOGOUT = "Logout"; /** * 登錄失敗 */ public static final String LOGIN_FAIL = "Error"; /** * 驗證碼 redis key */ public static final String CAPTCHA_CODE_KEY = "captcha_codes:"; /** * 登錄用戶 redis key */ public static final String LOGIN_TOKEN_KEY = "login_tokens:"; /** * 防重提交 redis key */ public static final String REPEAT_SUBMIT_KEY = "repeat_submit:"; /** * 驗證碼有效期(分鍾) */ public static final Integer CAPTCHA_EXPIRATION = 2; /** * 令牌 */ public static final String TOKEN = "token"; /** * 令牌前綴 */ public static final String TOKEN_PREFIX = "Bearer "; /** * 令牌前綴 */ public static final String LOGIN_USER_KEY = "login_user_key"; /** * 用戶ID */ public static final String JWT_USERID = "userid"; /** * 用戶名稱 */ public static final String JWT_USERNAME = "sub"; /** * 用戶頭像 */ public static final String JWT_AVATAR = "avatar"; /** * 創建時間 */ public static final String JWT_CREATED = "created"; /** * 用戶權限 */ public static final String JWT_AUTHORITIES = "authorities"; /** * 參數管理 cache key */ public static final String SYS_CONFIG_KEY = "sys_config:"; /** * 字典管理 cache key */ public static final String SYS_DICT_KEY = "sys_dict:"; /** * 資源映射路徑 前綴 */ public static final String RESOURCE_PREFIX = "/profile"; }
package com.ruoyi.common.core.controller; import java.beans.PropertyEditorSupport; import java.util.Date; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.InitBinder; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.ruoyi.common.constant.HttpStatus; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.page.PageDomain; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableSupport; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.sql.SqlUtil; /** * web層通用數據處理 * * @author ruoyi */ public class BaseController { protected final Logger logger = LoggerFactory.getLogger(BaseController.class); /** * 將前台傳遞過來的日期格式的字符串,自動轉化為Date類型 */ @InitBinder public void initBinder(WebDataBinder binder) { // Date 類型轉換 binder.registerCustomEditor(Date.class, new PropertyEditorSupport() { @Override public void setAsText(String text) { setValue(DateUtils.parseDate(text)); } }); } /** * 設置請求分頁數據 */ protected void startPage() { PageDomain pageDomain = TableSupport.buildPageRequest(); Integer pageNum = pageDomain.getPageNum(); Integer pageSize = pageDomain.getPageSize(); if (StringUtils.isNotNull(pageNum) && StringUtils.isNotNull(pageSize)) { String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy()); PageHelper.startPage(pageNum, pageSize, orderBy); } } /** * 響應請求分頁數據 */ @SuppressWarnings({ "rawtypes", "unchecked" }) protected TableDataInfo getDataTable(List<?> list) { TableDataInfo rspData = new TableDataInfo(); rspData.setCode(HttpStatus.SUCCESS); rspData.setMsg("查詢成功"); rspData.setRows(list); rspData.setTotal(new PageInfo(list).getTotal()); return rspData; } /** * 響應返回結果 * * @param rows 影響行數 * @return 操作結果 */ protected AjaxResult toAjax(int rows) { return rows > 0 ? AjaxResult.success() : AjaxResult.error(); } /** * 頁面跳轉 */ public String redirect(String url) { return StringUtils.format("redirect:{}", url); } }
package com.ruoyi.common.utils; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.ruoyi.common.core.text.StrFormatter; /** * 字符串工具類 * * @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> 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); } /** * 格式化文本, {} 表示占位符<br> * 此方法只是簡單將占位符 {} 按照順序替換為參數<br> * 如果想輸出 {} 使用 \\轉義 { 即可,如果想輸出 {} 之前的 \ 使用雙轉義符 \\\\ 即可<br> * 例:<br> * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b<br> * 轉義{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a<br> * 轉義\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b<br> * * @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); } /** * 字符串轉set * * @param str 字符串 * @param sep 分隔符 * @return set集合 */ public static final Set<String> str2Set(String str, String sep) { return new HashSet<String>(str2List(str, sep, true, false)); } /** * 字符串轉list * * @param str 字符串 * @param sep 分隔符 * @param filterBlank 過濾純空白 * @param trim 去掉首尾空白 * @return list集合 */ public static final List<String> str2List(String str, String sep, boolean filterBlank, boolean trim) { List<String> list = new ArrayList<String>(); if (StringUtils.isEmpty(str)) { return list; } // 過濾空白字符串 if (filterBlank && StringUtils.isBlank(str)) { return list; } String[] split = str.split(sep); for (String string : split) { if (filterBlank && StringUtils.isBlank(string)) { continue; } if (trim) { string = string.trim(); } list.add(string); } return list; } /** * 下划線轉駝峰命名 */ 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(); } @SuppressWarnings("unchecked") public static <T> T cast(Object obj) { return (T) obj; } }
package com.ruoyi.common.utils.sql; import com.ruoyi.common.exception.BaseException; import com.ruoyi.common.utils.StringUtils; /** * sql操作工具類 * * @author ruoyi */ public class SqlUtil { /** * 僅支持字母、數字、下划線、空格、逗號、小數點(支持多個字段排序) */ public static String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+"; /** * 檢查字符,防止注入繞過 */ public static String escapeOrderBySql(String value) { if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) { throw new BaseException("參數不符合規范,不能進行查詢"); } return value; } /** * 驗證 order by 語法是否符合規范 */ public static boolean isValidOrderBySql(String value) { return value.matches(SQL_PATTERN); } }
package com.ruoyi.common.core.domain; import java.util.HashMap; import com.ruoyi.common.constant.HttpStatus; import com.ruoyi.common.utils.StringUtils; /** * 操作消息提醒 * * @author ruoyi */ public class AjaxResult extends HashMap<String, Object> { private static final long serialVersionUID = 1L; /** 狀態碼 */ public static final String CODE_TAG = "code"; /** 返回內容 */ public static final String MSG_TAG = "msg"; /** 數據對象 */ public static final String DATA_TAG = "data"; /** * 初始化一個新創建的 AjaxResult 對象,使其表示一個空消息。 */ public AjaxResult() { } /** * 初始化一個新創建的 AjaxResult 對象 * * @param code 狀態碼 * @param msg 返回內容 */ public AjaxResult(int code, String msg) { super.put(CODE_TAG, code); super.put(MSG_TAG, msg); } /** * 初始化一個新創建的 AjaxResult 對象 * * @param code 狀態碼 * @param msg 返回內容 * @param data 數據對象 */ public AjaxResult(int code, String msg, Object data) { super.put(CODE_TAG, code); super.put(MSG_TAG, msg); if (StringUtils.isNotNull(data)) { super.put(DATA_TAG, data); } } /** * 返回成功消息 * * @return 成功消息 */ public static AjaxResult success() { return AjaxResult.success("操作成功"); } /** * 返回成功數據 * * @return 成功消息 */ public static AjaxResult success(Object data) { return AjaxResult.success("操作成功", data); } /** * 返回成功消息 * * @param msg 返回內容 * @return 成功消息 */ public static AjaxResult success(String msg) { return AjaxResult.success(msg, null); } /** * 返回成功消息 * * @param msg 返回內容 * @param data 數據對象 * @return 成功消息 */ public static AjaxResult success(String msg, Object data) { return new AjaxResult(HttpStatus.SUCCESS, msg, data); } /** * 返回錯誤消息 * * @return */ public static AjaxResult error() { return AjaxResult.error("操作失敗"); } /** * 返回錯誤消息 * * @param msg 返回內容 * @return 警告消息 */ public static AjaxResult error(String msg) { return AjaxResult.error(msg, null); } /** * 返回錯誤消息 * * @param msg 返回內容 * @param data 數據對象 * @return 警告消息 */ public static AjaxResult error(String msg, Object data) { return new AjaxResult(HttpStatus.ERROR, msg, data); } /** * 返回錯誤消息 * * @param code 狀態碼 * @param msg 返回內容 * @return 警告消息 */ public static AjaxResult error(int code, String msg) { return new AjaxResult(code, msg, null); } }
package com.ruoyi.common.core.page; import java.io.Serializable; import java.util.List; /** * 表格分頁數據對象 * * @author ruoyi */ public class TableDataInfo implements Serializable { private static final long serialVersionUID = 1L; /** 總記錄數 */ private long total; /** 列表數據 */ private List<?> rows; /** 消息狀態碼 */ private int code; /** 消息內容 */ private String msg; /** * 表格數據對象 */ public TableDataInfo() { } /** * 分頁 * * @param list 列表數據 * @param total 總記錄數 */ public TableDataInfo(List<?> list, int total) { this.rows = list; this.total = total; } public long getTotal() { return total; } public void setTotal(long total) { this.total = total; } public List<?> getRows() { return rows; } public void setRows(List<?> rows) { this.rows = rows; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } }
package com.ruoyi.common.core.page; import java.io.Serializable; import java.util.List; /** * 表格分頁數據對象 * * @author ruoyi */ public class TableDataInfo implements Serializable { private static final long serialVersionUID = 1L; /** 總記錄數 */ private long total; /** 列表數據 */ private List<?> rows; /** 消息狀態碼 */ private int code; /** 消息內容 */ private String msg; /** * 表格數據對象 */ public TableDataInfo() { } /** * 分頁 * * @param list 列表數據 * @param total 總記錄數 */ public TableDataInfo(List<?> list, int total) { this.rows = list; this.total = total; } public long getTotal() { return total; } public void setTotal(long total) { this.total = total; } public List<?> getRows() { return rows; } public void setRows(List<?> rows) { this.rows = rows; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } }
package com.ruoyi.common.exception.job; /** * 計划策略異常 * * @author ruoyi */ public class TaskException extends Exception { private static final long serialVersionUID = 1L; private 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 } }
package com.ruoyi.common.utils.bean; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Bean 工具類 * * @author ruoyi */ public class BeanUtils extends org.springframework.beans.BeanUtils { /** Bean方法名中屬性名開始的下標 */ private static final int BEAN_METHOD_PROP_INDEX = 3; /** * 匹配getter方法的正則表達式 */ private static final Pattern GET_PATTERN = Pattern.compile("get(\\p{javaUpperCase}\\w*)"); /** * 匹配setter方法的正則表達式 */ private static final Pattern SET_PATTERN = Pattern.compile("set(\\p{javaUpperCase}\\w*)"); /** * Bean屬性復制工具方法。 * * @param dest 目標對象 * @param src 源對象 */ public static void copyBeanProp(Object dest, Object src) { try { copyProperties(src, dest); } catch (Exception e) { e.printStackTrace(); } } /** * 獲取對象的setter方法。 * * @param obj 對象 * @return 對象的setter方法列表 */ public static List<Method> getSetterMethods(Object obj) { // setter方法列表 List<Method> setterMethods = new ArrayList<Method>(); // 獲取所有方法 Method[] methods = obj.getClass().getMethods(); // 查找setter方法 for (Method method : methods) { Matcher m = SET_PATTERN.matcher(method.getName()); if (m.matches() && (method.getParameterTypes().length == 1)) { setterMethods.add(method); } } // 返回setter方法列表 return setterMethods; } /** * 獲取對象的getter方法。 * * @param obj 對象 * @return 對象的getter方法列表 */ public static List<Method> getGetterMethods(Object obj) { // getter方法列表 List<Method> getterMethods = new ArrayList<Method>(); // 獲取所有方法 Method[] methods = obj.getClass().getMethods(); // 查找getter方法 for (Method method : methods) { Matcher m = GET_PATTERN.matcher(method.getName()); if (m.matches() && (method.getParameterTypes().length == 0)) { getterMethods.add(method); } } // 返回getter方法列表 return getterMethods; } /** * 檢查Bean方法名中的屬性名是否相等。<br> * 如getName()和setName()屬性名一樣,getName()和setAge()屬性名不一樣。 * * @param m1 方法名1 * @param m2 方法名2 * @return 屬性名一樣返回true,否則返回false */ public static boolean isMethodPropEquals(String m1, String m2) { return m1.substring(BEAN_METHOD_PROP_INDEX).equals(m2.substring(BEAN_METHOD_PROP_INDEX)); } }
package com.ruoyi.common.utils.spring; 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.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; import com.ruoyi.common.utils.StringUtils; /** * spring工具類 方便在非spring管理環境中獲取bean * * @author ruoyi */ @Component public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware { /** Spring應用上下文環境 */ private static ConfigurableListableBeanFactory beanFactory; private static ApplicationContext applicationContext; @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { SpringUtils.beanFactory = beanFactory; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringUtils.applicationContext = applicationContext; } /** * 獲取對象 * * @param name * @return Object 一個以所給名字注冊的bean的實例 * @throws org.springframework.beans.BeansException * */ @SuppressWarnings("unchecked") public static <T> T getBean(String name) throws BeansException { return (T) beanFactory.getBean(name); } /** * 獲取類型為requiredType的對象 * * @param clz * @return * @throws org.springframework.beans.BeansException * */ public static <T> T getBean(Class<T> 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> T getAopProxy(T invoker) { return (T) AopContext.currentProxy(); } /** * 獲取當前的環境配置,無配置返回null * * @return 當前的環境配置 */ public static String[] getActiveProfiles() { return applicationContext.getEnvironment().getActiveProfiles(); } /** * 獲取當前的環境配置,當有多個環境配置時,只獲取第一個 * * @return 當前的環境配置 */ public static String getActiveProfile() { final String[] activeProfiles = getActiveProfiles(); return StringUtils.isNotEmpty(activeProfiles) ? activeProfiles[0] : null; } }