简介
Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。在java企业级应用中,Quartz是使用最广泛的定时调度框架。
在Quartz中的主要概念:
- Scheduler:调度任务的主要API
- ScheduleBuilder:用于构建Scheduler,例如其简单实现类SimpleScheduleBuilder
- Job:调度任务执行的接口,也即定时任务执行的方法
- JobDetail:定时任务作业的实例
- JobBuilder:关联具体的Job,用于构建JobDetail
- Trigger:定义调度执行计划的组件,即定时执行
- TriggerBuilder:构建Trigger
教程
1.首先在maven项目的pom文件引入spring-boot-starter-quartz依赖
<!--定时任务-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
2. 数据库创建一个sys_job表存储定时任务的调度信息,表结构如下:
创建表sql语句
CREATE TABLE "public"."sys_job" (
"id" int8 NOT NULL,
"job_name" varchar(50) COLLATE "pg_catalog"."default",
"job_group" varchar(50) COLLATE "pg_catalog"."default",
"invoke_target" varchar(255) COLLATE "pg_catalog"."default",
"cron_expression" varchar(50) COLLATE "pg_catalog"."default",
"misfire_policy" varchar(2) COLLATE "pg_catalog"."default",
"concurrent" varchar(1) COLLATE "pg_catalog"."default",
"create_user" int8,
"create_time" timestamp(6),
"update_user" int8,
"update_time" timestamp(6),
"status" int4,
"is_deleted" int4,
CONSTRAINT "blade_datasource_copy1_pkey" PRIMARY KEY ("id")
)
;
ALTER TABLE "public"."sys_job"
OWNER TO "postgres";
COMMENT ON COLUMN "public"."sys_job"."id" IS '主键';
COMMENT ON COLUMN "public"."sys_job"."job_name" IS '任务名称';
COMMENT ON COLUMN "public"."sys_job"."job_group" IS '任务组名';
COMMENT ON COLUMN "public"."sys_job"."invoke_target" IS '调用目标字符串';
COMMENT ON COLUMN "public"."sys_job"."cron_expression" IS 'cron执行表达式';
COMMENT ON COLUMN "public"."sys_job"."misfire_policy" IS 'cron计划策略 ';
COMMENT ON COLUMN "public"."sys_job"."concurrent" IS '是否并发执行(0允许 1禁止)';
COMMENT ON COLUMN "public"."sys_job"."create_user" IS '创建人';
COMMENT ON COLUMN "public"."sys_job"."create_time" IS '创建时间';
COMMENT ON COLUMN "public"."sys_job"."update_user" IS '修改人';
COMMENT ON COLUMN "public"."sys_job"."update_time" IS '修改时间';
COMMENT ON COLUMN "public"."sys_job"."status" IS '状态';
COMMENT ON COLUMN "public"."sys_job"."is_deleted" IS '是否已删除';
COMMENT ON TABLE "public"."sys_job" IS '数据源配置表';
3.创建数据库映射orm实体类和mapper类,我是用mybatis plus工具实现的
实体类SysJob代码:
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import org.springblade.coalface.modules.quartz.constant.ScheduleConstants;
import org.springblade.core.mp.base.BaseEntity;
/**
* 定时任务调度表 sys_job
*
* @author tarzan
*/
@Data
@TableName("sys_job")
public class SysJob extends BaseEntity {
private static final long serialVersionUID = 1L;
/** 任务名称 */
private String jobName;
/** 任务组名 */
private String jobGroup;
/** 调用目标字符串 */
private String invokeTarget;
/** cron执行表达式 */
private String cronExpression;
/** cron计划策略 */
private String misfirePolicy = ScheduleConstants.MISFIRE_DEFAULT;
/** 是否并发执行(0允许 1禁止) */
private String concurrent;
}
SysJobMapper类代码如下:
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springblade.coalface.modules.quartz.domain.SysJob;
/**
* 调度任务信息 数据层
*
* @author tarzan
*/
public interface SysJobMapper extends BaseMapper<SysJob> {
}
4.创建定时任务表的增删改查接口
创建ISysJobService接口代码如下:
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.quartz.SchedulerException;
import org.springblade.coalface.modules.quartz.domain.SysJob;
import org.springblade.coalface.modules.quartz.util.TaskException;
import org.springblade.core.mp.base.BaseService;
import org.springblade.core.mp.support.Query;
import org.springframework.transaction.annotation.Transactional;
/**
* 定时任务调度信息信息 服务层
*
* @author tarzan
*/
public interface ISysJobService extends BaseService<SysJob> {
IPage<SysJob> selectJobList(SysJob job, Query query);
@Transactional(rollbackFor = Exception.class)
boolean pauseJob(SysJob job) throws SchedulerException;
@Transactional(rollbackFor = Exception.class)
boolean resumeJob(SysJob job) throws SchedulerException;
@Transactional(rollbackFor = Exception.class)
boolean deleteJob(SysJob job) throws SchedulerException;
@Transactional(rollbackFor = Exception.class)
boolean changeStatus(SysJob job) throws SchedulerException;
@Transactional(rollbackFor = Exception.class)
boolean run(SysJob job) throws SchedulerException;
@Transactional(rollbackFor = Exception.class)
boolean insertJob(SysJob job) throws SchedulerException, TaskException;
@Transactional(rollbackFor = Exception.class)
boolean updateJob(SysJob job) throws SchedulerException, TaskException;
}
创建SysJobServiceImpl实现类代码如下:
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.AllArgsConstructor;
import org.quartz.JobDataMap;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.springblade.coalface.modules.quartz.constant.ScheduleConstants;
import org.springblade.coalface.modules.quartz.domain.SysJob;
import org.springblade.coalface.modules.quartz.mapper.SysJobMapper;
import org.springblade.coalface.modules.quartz.service.ISysJobService;
import org.springblade.coalface.modules.quartz.util.ScheduleUtils;
import org.springblade.coalface.modules.quartz.util.TaskException;
import org.springblade.core.mp.base.BaseServiceImpl;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.tool.utils.StringUtil;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Objects;
/**
* 定时任务调度信息 服务层
*
* @author tarzan
*/
@Service
@AllArgsConstructor
public class SysJobServiceImpl extends BaseServiceImpl<SysJobMapper, SysJob> implements ISysJobService {
private final Scheduler scheduler;
/**
* 项目启动时,初始化定时器 主要是防止手动修改数据库导致未同步到定时任务处理(注:不能手动修改数据库ID和任务组名,否则会导致脏数据)
*/
@PostConstruct
public void init() throws SchedulerException, TaskException {
scheduler.clear();
List<SysJob> jobList = super.list();
for (SysJob job : jobList) {
ScheduleUtils.createScheduleJob(scheduler, job);
}
}
/**
* 获取quartz调度器的计划任务列表
*
* @param job 调度信息
* @return 调度任务对象集合
*/
@Override
public IPage<SysJob> selectJobList(SysJob job, Query query) {
return super.lambdaQuery().like(StringUtil.isNotBlank(job.getJobName()),SysJob::getJobName,job.getJobName())
.eq(StringUtil.isNotBlank(job.getJobGroup()),SysJob::getJobGroup,job.getJobGroup())
.eq(Objects.nonNull(job.getStatus()),SysJob::getStatus,job.getStatus()).page(Condition.getPage(query));
}
/**
* 暂停任务
*
* @param job 调度信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean pauseJob(SysJob job) throws SchedulerException {
Long jobId = job.getId();
String jobGroup = job.getJobGroup();
job.setStatus(ScheduleConstants.Status.PAUSE.getValue());
boolean flag = super.updateById(job);
if (flag) {
scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
}
return flag;
}
/**
* 恢复任务
*
* @param job 调度信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean resumeJob(SysJob job) throws SchedulerException {
Long jobId = job.getId();
String jobGroup = job.getJobGroup();
job.setStatus(ScheduleConstants.Status.NORMAL.getValue());
boolean flag = super.updateById(job);
if (flag) {
scheduler.resumeJob(ScheduleUtils.getJobKey(jobId, jobGroup));
}
return flag;
}
/**
* 删除任务后,所对应的trigger也将被删除
*
* @param job 调度信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteJob(SysJob job) throws SchedulerException {
Long jobId = job.getId();
String jobGroup = job.getJobGroup();
boolean flag = super.removeById(jobId);
if (flag) {
scheduler.deleteJob(ScheduleUtils.getJobKey(jobId, jobGroup));
}
return flag;
}
/**
* 任务调度状态修改
*
* @param job 调度信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean changeStatus(SysJob job) throws SchedulerException {
boolean flag = false;
Integer status = job.getStatus();
if (ScheduleConstants.Status.NORMAL.getValue().equals(status)) {
flag = resumeJob(job);
} else if (ScheduleConstants.Status.PAUSE.getValue().equals(status)) {
flag = pauseJob(job);
}
return flag;
}
/**
* 立即运行任务
*
* @param job 调度信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean run(SysJob job) throws SchedulerException {
boolean result = false;
Long jobId = job.getId();
String jobGroup = job.getJobGroup();
SysJob properties = super.getById(job.getId());
// 参数
JobDataMap dataMap = new JobDataMap();
dataMap.put(ScheduleConstants.TASK_PROPERTIES, properties);
JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup);
if (scheduler.checkExists(jobKey)) {
result = true;
scheduler.triggerJob(jobKey, dataMap);
}
return result;
}
/**
* 新增任务
*
* @param job 调度信息 调度信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean insertJob(SysJob job) throws SchedulerException, TaskException {
job.setStatus(ScheduleConstants.Status.PAUSE.getValue());
boolean flag = super.save(job);
if (flag) {
ScheduleUtils.createScheduleJob(scheduler, job);
}
return flag;
}
/**
* 更新任务的时间表达式
*
* @param job 调度信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateJob(SysJob job) throws SchedulerException, TaskException {
SysJob properties = super.getById(job.getId());
boolean flag = super.updateById(job);
if (flag) {
updateSchedulerJob(job, properties.getJobGroup());
}
return flag;
}
/**
* 更新任务
*
* @param job 任务对象
* @param jobGroup 任务组名
*/
public void updateSchedulerJob(SysJob job, String jobGroup) throws SchedulerException, TaskException {
Long jobId = job.getId();
// 判断是否存在
JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup);
if (scheduler.checkExists(jobKey)) {
// 防止创建时存在数据问题 先移除,然后在执行创建操作
scheduler.deleteJob(jobKey);
}
ScheduleUtils.createScheduleJob(scheduler, job);
}
}
创建controller层接口代码如下:
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.quartz.SchedulerException;
import org.springblade.coalface.modules.quartz.domain.SysJob;
import org.springblade.coalface.modules.quartz.service.ISysJobService;
import org.springblade.coalface.modules.quartz.util.CronUtils;
import org.springblade.coalface.modules.quartz.util.TaskException;
import org.springblade.core.mp.support.Query;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.web.bind.annotation.*;
/**
* 调度任务信息操作处理
*
* @author tarzan
*/
@RestController
@Api(tags = "定时任务")
@RequestMapping("/schedule/job")
@AllArgsConstructor
public class SysJobController {
private final ISysJobService jobService;
/**
* 查询定时任务列表
*/
@GetMapping("/page")
@ApiOperation(value = "分页")
public R<IPage<SysJob>> list(SysJob sysJob, Query query) {
return R.data(jobService.selectJobList(sysJob,query));
}
/**
* 获取定时任务详细信息
*/
@GetMapping(value = "/{jobId}")
@ApiOperation(value = "详情")
public R<SysJob> getInfo(@PathVariable("jobId") Long jobId) {
return R.data(jobService.getById(jobId));
}
/**
* 新增定时任务
*/
@PostMapping
@ApiOperation(value = "添加")
public R<Boolean> add(@RequestBody SysJob job) throws SchedulerException, TaskException {
R<Boolean> result=isValid(job);
if(!result.isSuccess()){
return result;
}
return R.status(jobService.insertJob(job));
}
/**
* 修改定时任务
*/
@PutMapping
@ApiOperation(value = "修改")
public R<Boolean> edit(@RequestBody SysJob job) throws SchedulerException, TaskException {
R<Boolean> result=isValid(job);
if(!result.isSuccess()){
return result;
}
return R.status(jobService.updateJob(job));
}
/**
* 定时任务状态修改
*/
@PutMapping("/changeStatus")
@ApiOperation(value = "状态修改")
public R<Boolean> changeStatus(@RequestBody SysJob job) throws SchedulerException {
SysJob newJob = jobService.getById(job.getId());
newJob.setStatus(job.getStatus());
return R.status(jobService.changeStatus(newJob));
}
/**
* 定时任务立即执行一次
*/
@PutMapping("/run")
@ApiOperation(value = "立即执行一次")
public R<Boolean> run(@RequestBody SysJob job) throws SchedulerException {
return R.status(jobService.run(job));
}
/**
* 删除定时任务
*/
@DeleteMapping("/{jobId}")
@ApiOperation(value = "删除")
public R<Boolean> remove(@PathVariable Long jobId) throws SchedulerException, TaskException {
SysJob sysJob=jobService.getById(jobId);
return R.status(jobService.deleteJob(sysJob));
}
private R<Boolean> isValid(SysJob job){
if (!CronUtils.isValid(job.getCronExpression())) {
return R.fail("新增任务'" + job.getJobName() + "'失败,Cron表达式不正确");
} else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), "rmi:")) {
return R.fail("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用");
} else if (StringUtil.containsAnyIgnoreCase(job.getInvokeTarget(), new String[]{"ldap:", "ldaps:"})) {
return R.fail("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用");
} else if (StringUtil.containsAnyIgnoreCase(job.getInvokeTarget(), new String[]{"http://", "https://"})) {
return R.fail("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用");
}
return R.success();
}
}
5.代码中的主要的相关工具类
AbstractQuartzJob 抽象quartz调用类,代码如下:
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springblade.coalface.modules.quartz.constant.ScheduleConstants;
import org.springblade.coalface.modules.quartz.domain.SysJob;
import java.util.Date;
/**
* 抽象quartz调用
*
* @author tarzan
*/
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 = (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();
}
/**
* 执行方法,由子类重载
*
* @param context 工作执行上下文对象
* @param sysJob 系统计划任务
* @throws Exception 执行过程中的异常
*/
protected abstract void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception;
}
cron表达式工具类,代码如下:
import org.quartz.CronExpression;
import java.text.ParseException;
import java.util.Date;
/**
* cron表达式工具类
*
* @author tarzan
*
*/
public class CronUtils {
/**
* 返回一个布尔值代表一个给定的Cron表达式的有效性
*
* @param cronExpression Cron表达式
* @return boolean 表达式是否有效
*/
public static boolean isValid(String cronExpression)
{
return CronExpression.isValidExpression(cronExpression);
}
/**
* 返回一个字符串值,表示该消息无效Cron表达式给出有效性
*
* @param cronExpression Cron表达式
* @return String 无效时返回表达式错误描述,如果有效返回null
*/
public static String getInvalidMessage(String cronExpression) {
try
{
new CronExpression(cronExpression);
return null;
}
catch (ParseException pe)
{
return pe.getMessage();
}
}
/**
* 返回下一个执行时间根据给定的Cron表达式
*
* @param cronExpression Cron表达式
* @return Date 下次Cron表达式执行时间
*/
public static Date getNextExecution(String cronExpression) {
try
{
CronExpression cron = new CronExpression(cronExpression);
return cron.getNextValidTimeAfter(new Date(System.currentTimeMillis()));
}
catch (ParseException e)
{
throw new IllegalArgumentException(e.getMessage());
}
}
}
定时任务执行工具JobInvokeUtil类,代码如下:
import org.springblade.coalface.modules.quartz.domain.SysJob;
import org.springblade.core.tool.utils.CollectionUtil;
import org.springblade.core.tool.utils.SpringUtil;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
/**
* 任务执行工具
*
* @author tarzan
*/
public class JobInvokeUtil {
/**
* 执行方法
*
* @param sysJob 系统任务
*/
public static void invokeMethod(SysJob sysJob) throws Exception {
String invokeTarget = sysJob.getInvokeTarget();
String beanName = getBeanName(invokeTarget);
String methodName = getMethodName(invokeTarget);
List<Object[]> methodParams = getMethodParams(invokeTarget);
if (!isValidClassName(beanName)) {
Object bean = SpringUtil.getBean(beanName);
invokeMethod(bean, methodName, methodParams);
} else {
Object bean = Class.forName(beanName).getDeclaredConstructor().newInstance();
invokeMethod(bean, methodName, methodParams);
}
}
/**
* 调用任务方法
*
* @param bean 目标对象
* @param methodName 方法名称
* @param methodParams 方法参数
*/
private static void invokeMethod(Object bean, String methodName, List<Object[]> methodParams) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
if(Objects.nonNull(bean)){
if (CollectionUtil.isNotEmpty(methodParams)) {
Method method = bean.getClass().getMethod(methodName, getMethodParamsType(methodParams));
method.invoke(bean, getMethodParamsValue(methodParams));
} else {
Method method = bean.getClass().getMethod(methodName);
method.invoke(bean);
}
}
}
/**
* 校验是否为为class包名
*
* @param invokeTarget 名称
* @return true是 false否
*/
public static boolean isValidClassName(String invokeTarget)
{
return StringUtils.countMatches(invokeTarget, ".") > 1;
}
/**
* 获取bean名称
*
* @param invokeTarget 目标字符串
* @return bean名称
*/
public static String getBeanName(String invokeTarget)
{
String beanName = StringUtils.substringBefore(invokeTarget, "(");
return StringUtils.substringBeforeLast(beanName, ".");
}
/**
* 获取bean方法
*
* @param invokeTarget 目标字符串
* @return method方法
*/
public static String getMethodName(String invokeTarget)
{
String methodName = StringUtils.substringBefore(invokeTarget, "(");
return StringUtils.substringAfterLast(methodName, ".");
}
/**
* 获取method方法参数相关列表
*
* @param invokeTarget 目标字符串
* @return method方法相关参数列表
*/
public static List<Object[]> getMethodParams(String invokeTarget)
{
String methodStr = StringUtils.substringBetween(invokeTarget, "(", ")");
if (StringUtils.isEmpty(methodStr))
{
return null;
}
String[] methodParams = methodStr.split(",(?=([^\"']*[\"'][^\"']*[\"'])*[^\"']*$)");
List<Object[]> classs = new LinkedList<>();
for (String methodParam : methodParams) {
String str = StringUtils.trimToEmpty(methodParam);
// String字符串类型,以'或"开头
if (StringUtils.startsWithAny(str, "'", "\"")) {
classs.add(new Object[]{StringUtils.substring(str, 1, str.length() - 1), String.class});
}
// boolean布尔类型,等于true或者false
else if ("true".equalsIgnoreCase(str) || "false".equalsIgnoreCase(str)) {
classs.add(new Object[]{Boolean.valueOf(str), Boolean.class});
}
// long长整形,以L结尾
else if (StringUtils.endsWith(str, "L")) {
classs.add(new Object[]{Long.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Long.class});
}
// double浮点类型,以D结尾
else if (StringUtils.endsWith(str, "D")) {
classs.add(new Object[]{Double.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Double.class});
}
// 其他类型归类为整形
else {
classs.add(new Object[]{Integer.valueOf(str), Integer.class});
}
}
return classs;
}
/**
* 获取参数类型
*
* @param methodParams 参数相关列表
* @return 参数类型列表
*/
public static Class<?>[] getMethodParamsType(List<Object[]> methodParams)
{
Class<?>[] classs = new Class<?>[methodParams.size()];
int index = 0;
for (Object[] os : methodParams)
{
classs[index] = (Class<?>) os[1];
index++;
}
return classs;
}
/**
* 获取参数值
*
* @param methodParams 参数相关列表
* @return 参数值列表
*/
public static Object[] getMethodParamsValue(List<Object[]> methodParams)
{
Object[] classs = new Object[methodParams.size()];
int index = 0;
for (Object[] os : methodParams)
{
classs[index] = os[0];
index++;
}
return classs;
}
}
定时任务工具类 ScheduleUtils 工具类,代码如下:
import org.quartz.*;
import org.springblade.coalface.modules.quartz.constant.ScheduleConstants;
import org.springblade.coalface.modules.quartz.domain.SysJob;
import org.springblade.core.tool.utils.SpringUtil;
import java.util.Objects;
/**
* 定时任务工具类
*
* @author tarzan
*/
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.getId();
String jobGroup = job.getJobGroup();
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build();
// 表达式调度构建器
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder);
// 按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup))
.withSchedule(cronScheduleBuilder).build();
// 放入参数,运行时的方法可以获取
jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job);
// 判断是否存在
if (scheduler.checkExists(getJobKey(jobId, jobGroup))) {
// 防止创建时存在数据问题 先移除,然后在执行创建操作
scheduler.deleteJob(getJobKey(jobId, jobGroup));
}
// 判断任务是否过期
if (Objects.nonNull(CronUtils.getNextExecution(job.getCronExpression()))) {
// 执行调度任务
scheduler.scheduleJob(jobDetail, trigger);
}
// 暂停任务
if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue())) {
scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
}
}
/**
* 设置定时任务策略
*/
public static CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJob job, CronScheduleBuilder cb)
throws TaskException {
switch (job.getMisfirePolicy()) {
case ScheduleConstants.MISFIRE_DEFAULT:
return cb;
case ScheduleConstants.MISFIRE_IGNORE_MISFIRES:
return cb.withMisfireHandlingInstructionIgnoreMisfires();
case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED:
return cb.withMisfireHandlingInstructionFireAndProceed();
case ScheduleConstants.MISFIRE_DO_NOTHING:
return cb.withMisfireHandlingInstructionDoNothing();
default:
throw new TaskException("The task misfire policy '" + job.getMisfirePolicy()
+ "' cannot be used in cron schedule tasks", TaskException.Code.CONFIG_ERROR);
}
}
/**
* 检查包名是否为白名单配置
*
* @param invokeTarget 目标字符串
* @return 结果
*/
public static boolean whiteList(String invokeTarget) {
String packageName = StringUtils.substringBefore(invokeTarget, "(");
int count = StringUtils.countMatches(packageName, ".");
if (count > 1) {
return StringUtils.containsAnyIgnoreCase(invokeTarget, JOB_WHITELIST_STR);
}
Object obj = SpringUtil.getBean(StringUtils.split(invokeTarget, ".")[0]);
String beanPackageName = obj.getClass().getPackage().getName();
return StringUtils.containsAnyIgnoreCase(beanPackageName, JOB_WHITELIST_STR)
&& !StringUtils.containsAnyIgnoreCase(beanPackageName, JOB_ERROR_STR);
}
/**
* 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加)
*/
public static final String[] JOB_WHITELIST_STR = { "org.springblade" };
/**
* 定时任务违规的字符
*/
public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml", "org.springframework", "org.apache"};
}
定时任务处理(禁止并发执行)执行类QuartzDisallowConcurrentExecution,代码如下:
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.springblade.coalface.modules.quartz.domain.SysJob;
/**
* 定时任务处理(禁止并发执行)
*
* @author tarzan
*
*/
@DisallowConcurrentExecution
public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob {
@Override
protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception {
JobInvokeUtil.invokeMethod(sysJob);
}
}
定时任务处理(允许并发执行)执行类QuartzJobExecution,代码如下:
import org.quartz.JobExecutionContext;
import org.springblade.coalface.modules.quartz.domain.SysJob;
/**
* 定时任务处理(允许并发执行)
*
* @author tarzan
*
*/
public class QuartzJobExecution extends AbstractQuartzJob {
@Override
protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception {
JobInvokeUtil.invokeMethod(sysJob);
}
}
字符串工具类StringUtils,代码如下:
import org.springframework.util.AntPathMatcher;
import java.util.*;
/**
* 字符串工具类
*
* @author tarzan
*/
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);
}
/**
* 是否为http(s)://开头
*
* @param link 链接
* @return 结果
*/
public static boolean ishttp(String link) {
return StringUtils.startsWithAny(link, "http://", "https://");
}
/**
* 字符串转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;
}
/**
* 判断给定的collection列表中是否包含数组array 判断给定的数组array中是否包含给定的元素value
*
* @param collection 给定的集合
* @param array 给定的数组
* @return boolean 结果
*/
public static boolean containsAny(Collection<String> collection, String... array)
{
if (isEmpty(collection) || isEmpty(array))
{
return false;
}
else
{
for (String str : array)
{
if (collection.contains(str))
{
return true;
}
}
return false;
}
}
/**
* 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写
*
* @param cs 指定字符串
* @param searchCharSequences 需要检查的字符串数组
* @return 是否包含任意一个字符串
*/
public static boolean containsAnyIgnoreCase(CharSequence cs, CharSequence... searchCharSequences)
{
if (isEmpty(cs) || isEmpty(searchCharSequences))
{
return false;
}
for (CharSequence testStr : searchCharSequences)
{
if (containsIgnoreCase(cs, testStr))
{
return true;
}
}
return false;
}
/**
* 驼峰转下划线命名
*/
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;
}
if (s.indexOf(SEPARATOR) == -1)
{
return s;
}
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();
}
/**
* 查找指定字符串是否匹配指定字符串列表中的任意一个字符串
*
* @param str 指定字符串
* @param strs 需要检查的字符串数组
* @return 是否匹配
*/
public static boolean matches(String str, List<String> strs)
{
if (isEmpty(str) || isEmpty(strs))
{
return false;
}
for (String pattern : strs)
{
if (isMatch(pattern, str))
{
return true;
}
}
return false;
}
/**
* 判断url是否与规则配置:
* ? 表示单个字符;
* * 表示一层路径内的任意字符串,不可跨层级;
* ** 表示任意层路径;
*
* @param pattern 匹配规则
* @param url 需要匹配的url
* @return
*/
public static boolean isMatch(String pattern, String url)
{
AntPathMatcher matcher = new AntPathMatcher();
return matcher.match(pattern, url);
}
@SuppressWarnings("unchecked")
public static <T> T cast(Object obj)
{
return (T) obj;
}
/**
* 数字左边补齐0,使之达到指定长度。注意,如果数字转换为字符串后,长度大于size,则只保留 最后size个字符。
*
* @param num 数字对象
* @param size 字符串指定长度
* @return 返回数字的字符串格式,该字符串为指定长度。
*/
public static final String padl(final Number num, final int size)
{
return padl(num.toString(), size, '0');
}
/**
* 字符串左补齐。如果原始字符串s长度大于size,则只保留最后size个字符。
*
* @param s 原始字符串
* @param size 字符串指定长度
* @param c 用于补齐的字符
* @return 返回指定长度的字符串,由原字符串左补齐或截取得到。
*/
public static final String padl(final String s, final int size, final char c)
{
final StringBuilder sb = new StringBuilder(size);
if (s != null)
{
final int len = s.length();
if (s.length() <= size)
{
for (int i = size - len; i > 0; i--)
{
sb.append(c);
}
sb.append(s);
}
else
{
return s.substring(len - size, len);
}
}
else
{
for (int i = size; i > 0; i--)
{
sb.append(c);
}
}
return sb.toString();
}
}
6.定时任务异常类
TaskException计划策略异常类,代码如下:
/**
* 计划策略异常
*
* @author tarzan
*/
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
}
}
7.代码中使用的常量类:
任务调度通用常量ScheduleConstants 代码如下:
/**
* 任务调度通用常量
*
* @author tarzan
*/
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 Integer value;
Status(Integer value)
{
this.value = value;
}
public Integer getValue()
{
return value;
}
}
}
8.自定义定时任务类,示例代码
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.stereotype.Component;
/**
* 定时任务调度测试
*
* @author tarzan
*/
@Component("ryTask")
public class AppTask {
public void ryMultipleParams(String s, Boolean b, Long l, Double d, Integer i) {
System.out.println(StringUtil.format("执行多参方法: 字符串类型{},布尔类型{},长整型{},浮点型{},整形{}", s, b, l, d, i));
}
public void ryParams(String params)
{
System.out.println("执行有参方法:" + params);
}
public void ryNoParams()
{
System.out.println("执行无参方法");
}
}
9.页面配置使用,如图所示
调用字符目标,配置成你自定义的任务类和方法即可!