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; } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?