XXL-Job详解(五):动态添加、启动任务

前言

看该文章之前,最好看一下之前的文章,比较方便我们理解
XXL-Job详解(一):组件架构
XXL-Job详解(二):安装部署
XXL-Job详解(三):任务开发
XXL-Job详解(四):任务注册原理

XXL-Job API接口

我们之前xxl-job添加任务,需要去管理后台进行添加,在一些任务量大或者需要实时添加任务的情况,通过手动添加任务是不现实的,那有什么方法呢?

我们需要知道,我们在页面添加任务,其实调用的就是调度平台的API接口,调度平台的API接口就在xxl-job-admin的com.xxl.job.admin.controller目录下
在这里插入图片描述
那我们怎么知道添加任务是哪个接口呢,很简单,我们在页面添加一个任务,看页面请求了哪个接口就行,我新增了一个任务,可以看到,调度平台是请求了add接口,很明显,add就是我们调度平台添加任务的接口

在这里插入图片描述

添加任务API

我们去调度平台项目查找add接口,add接口就在JobInfoController下,add接口如下

	@RequestMapping("/add")
	@ResponseBody
	public ReturnT<String> add(XxlJobInfo jobInfo) {
		return xxlJobService.add(jobInfo);
	}

下面我们看一下service的add方法

@Override
	public ReturnT<String> add(XxlJobInfo jobInfo) {

		// valid base
		XxlJobGroup group = xxlJobGroupDao.load(jobInfo.getJobGroup());
		if (group == null) {
			return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_choose")+I18nUtil.getString("jobinfo_field_jobgroup")) );
		}
		if (jobInfo.getJobDesc()==null || jobInfo.getJobDesc().trim().length()==0) {
			return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_jobdesc")) );
		}
		if (jobInfo.getAuthor()==null || jobInfo.getAuthor().trim().length()==0) {
			return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_author")) );
		}

		// valid trigger
		ScheduleTypeEnum scheduleTypeEnum = ScheduleTypeEnum.match(jobInfo.getScheduleType(), null);
		if (scheduleTypeEnum == null) {
			return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) );
		}
		if (scheduleTypeEnum == ScheduleTypeEnum.CRON) {
			if (jobInfo.getScheduleConf()==null || !CronExpression.isValidExpression(jobInfo.getScheduleConf())) {
				return new ReturnT<String>(ReturnT.FAIL_CODE, "Cron"+I18nUtil.getString("system_unvalid"));
			}
		} else if (scheduleTypeEnum == ScheduleTypeEnum.FIX_RATE/* || scheduleTypeEnum == ScheduleTypeEnum.FIX_DELAY*/) {
			if (jobInfo.getScheduleConf() == null) {
				return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")) );
			}
			try {
				int fixSecond = Integer.valueOf(jobInfo.getScheduleConf());
				if (fixSecond < 1) {
					return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) );
				}
			} catch (Exception e) {
				return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) );
			}
		}

		// valid job
		if (GlueTypeEnum.match(jobInfo.getGlueType()) == null) {
			return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_gluetype")+I18nUtil.getString("system_unvalid")) );
		}
		if (GlueTypeEnum.BEAN==GlueTypeEnum.match(jobInfo.getGlueType()) && (jobInfo.getExecutorHandler()==null || jobInfo.getExecutorHandler().trim().length()==0) ) {
			return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_input")+"JobHandler") );
		}
		// 》fix "\r" in shell
		if (GlueTypeEnum.GLUE_SHELL==GlueTypeEnum.match(jobInfo.getGlueType()) && jobInfo.getGlueSource()!=null) {
			jobInfo.setGlueSource(jobInfo.getGlueSource().replaceAll("\r", ""));
		}

		// valid advanced
		if (ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null) == null) {
			return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_executorRouteStrategy")+I18nUtil.getString("system_unvalid")) );
		}
		if (MisfireStrategyEnum.match(jobInfo.getMisfireStrategy(), null) == null) {
			return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("misfire_strategy")+I18nUtil.getString("system_unvalid")) );
		}
		if (ExecutorBlockStrategyEnum.match(jobInfo.getExecutorBlockStrategy(), null) == null) {
			return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_executorBlockStrategy")+I18nUtil.getString("system_unvalid")) );
		}

		// 》ChildJobId valid
		if (jobInfo.getChildJobId()!=null && jobInfo.getChildJobId().trim().length()>0) {
			String[] childJobIds = jobInfo.getChildJobId().split(",");
			for (String childJobIdItem: childJobIds) {
				if (childJobIdItem!=null && childJobIdItem.trim().length()>0 && isNumeric(childJobIdItem)) {
					XxlJobInfo childJobInfo = xxlJobInfoDao.loadById(Integer.parseInt(childJobIdItem));
					if (childJobInfo==null) {
						return new ReturnT<String>(ReturnT.FAIL_CODE,
								MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_not_found")), childJobIdItem));
					}
				} else {
					return new ReturnT<String>(ReturnT.FAIL_CODE,
							MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_unvalid")), childJobIdItem));
				}
			}

			// join , avoid "xxx,,"
			String temp = "";
			for (String item:childJobIds) {
				temp += item + ",";
			}
			temp = temp.substring(0, temp.length()-1);

			jobInfo.setChildJobId(temp);
		}

		// add in db
		jobInfo.setAddTime(new Date());
		jobInfo.setUpdateTime(new Date());
		jobInfo.setGlueUpdatetime(new Date());
		xxlJobInfoDao.save(jobInfo);
		if (jobInfo.getId() < 1) {
			return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_add")+I18nUtil.getString("system_fail")) );
		}

		return new ReturnT<String>(String.valueOf(jobInfo.getId()));
	}

这个方法很简单,最重要就是两步

1、校验jobinfo参数

2、插入jobinfo到表

好了,到这里我们就基本了解了xxl-job添加任务的流程了,那么我们想动态添加任务就很简单了,主要流程是以下两步

1、构造jobinfo对象

2、以jobinfo对象为入参请求add接口,请求add接口我们可以使用RestTemplate或者openFeign等,大家按自己项目来决定,下面我使用的是RestTemplate

动态添加任务

1、首先把调度平台的jobinfo对象拷贝到我们的执行器项目,jobinfo对象在com.xxl.job.admin.core.model目录下,我们按需拷贝对象

public class XxlJobInfo {
	
	private int id;				// 主键ID
	
	private int jobGroup;		// 执行器主键ID
	private String jobDesc;
	
	private Date addTime;
	private Date updateTime;
	
	private String author;		// 负责人
	private String alarmEmail;	// 报警邮件

	private String scheduleType;			// 调度类型
	private String scheduleConf;			// 调度配置,值含义取决于调度类型
	private String misfireStrategy;			// 调度过期策略

	private String executorRouteStrategy;	// 执行器路由策略
	private String executorHandler;		    // 执行器,任务Handler名称
	private String executorParam;		    // 执行器,任务参数
	private String executorBlockStrategy;	// 阻塞处理策略
	private int executorTimeout;     		// 任务执行超时时间,单位秒
	private int executorFailRetryCount;		// 失败重试次数
	
	private String glueType;		// GLUE类型	#com.xxl.job.core.glue.GlueTypeEnum
	private String glueSource;		// GLUE源代码
	private String glueRemark;		// GLUE备注
	private Date glueUpdatetime;	// GLUE更新时间

	private String childJobId;		// 子任务ID,多个逗号分隔

	private int triggerStatus;		// 调度状态:0-停止,1-运行
	private long triggerLastTime;	// 上次调度时间
	private long triggerNextTime;	// 下次调度时间

}

2、放开登录校验

因为调度平台的api接口都是在后台,要请求接口,都需要通过登录校验,要解决这个问题,有两个方法

1、模拟登录(请求登录接口),获取登录cookie

2、在我们需要请求的接口上添加@PermissionLimit(limit = false)注解 ,默认是true,也就是需要做登录验证

/**
 * 权限限制
 * @author xuxueli 2015-12-12 18:29:02
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionLimit {
	
	/**
	 * 登录拦截 (默认拦截)
	 */
	boolean limit() default true;

	/**
	 * 要求管理员权限
	 *
	 * @return
	 */
	boolean adminuser() default false;

}

我为了方便,就直接使用PermissionLimit注解放开校验,大家按自己实际情况选择

为了不影响原有的接口,新增一个addJob接口,在这个接口上进行修改

在add接口添加PermissionLimit注解,如下所示

	@RequestMapping("/addJob")
	@ResponseBody
	@PermissionLimit(limit = false)
	public ReturnT<String> addJob(@RequestBody XxlJobInfo jobInfo) {
		return xxlJobService.add(jobInfo);
	}

因为我们在设置XxlJobInfo 信息的时候,还需要获取XxlJobGroup的信息,所以我们还需要在JobGroupController添加以下接口

	@RequestMapping("/loadByAppName")
	@ResponseBody
	@PermissionLimit(limit = false)
	public ReturnT<XxlJobGroup> loadByAppName(@RequestBody XxlJobGroup xxlJobGroup){
		XxlJobGroup jobGroup = xxlJobGroupDao.loadByAppName(xxlJobGroup);
		return jobGroup!=null?new ReturnT<XxlJobGroup>(jobGroup):new ReturnT<XxlJobGroup>(ReturnT.FAIL_CODE, null);
	}

loadByAppName也是我新增的,如下所示,使用appname查找XxlJobGroup信息

	<select id="loadByAppName" resultType="com.xxl.job.admin.core.model.XxlJobGroup">
		SELECT <include refid="Base_Column_List" />
		FROM xxl_job_group AS t
		WHERE t.app_name = #{appname}
	</select>

3、添加请求工具类

@Component
public class XxlJobUtil {

    /**
     * xxl-job地址
     */
    @Value("${xxl.job.admin.addresses}")
    private String adminAddresses;

    /**
     * xxl-job标识
     */
    @Value("${xxl.job.executor.appname}")
    private String appname;

    private RestTemplate restTemplate = new RestTemplate();

    /**
     * 调度平台的api
     */
    private static final String ADD_URL = "/jobinfo/addJob";
    private static final String GET_GROUP_INFO = "/jobgroup/loadByAppName";


    /**
     * 添加任务
     * @param jobInfo
     * @return
     */
    public String add(XxlJobInfo jobInfo){
        // 查询appname对应groupId:
        Map<String,Object> param = new HashMap<>();
        param.put("appname", appname);
        String json = JSON.toJSONString(param);
        String result = doPost(adminAddresses + GET_GROUP_INFO, json);
        JSONObject jsonObject = JSON.parseObject(result);
        String content = jsonObject.getString("content");
        if (content != null){
            XxlJobGroup jobGroup = JSONObject.parseObject(content,XxlJobGroup.class);
            int groupId = jobGroup.getId();
            jobInfo.setJobGroup(groupId);
        }
        //发送请求
        String json2 = JSON.toJSONString(jobInfo);
        return doPost(adminAddresses + ADD_URL, json2);
    }



    /**
     * 发送请求
     * @param url
     * @param json
     * @return
     */
    public String doPost(String url, String json){
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> entity = new HttpEntity<>(json ,headers);
        ResponseEntity<String> stringResponseEntity = restTemplate.postForEntity(url, entity, String.class);
        return stringResponseEntity.getBody().toString();
    }

}

4、测试

我们在执行器项目,添加一个接口,来测试下,是否可以动态添加任务

@Controller
@RequestMapping("/job")
public class JobInfoController {
    
    @Autowired
    private XxlJobUtil xxlJobUtil;

    @RequestMapping("/add")
    @ResponseBody
    public String add(XxlJobInfo jobInfo) {
        return xxlJobUtil.add(jobInfo);
    }
    
}

使用postman请求,可以看到,已经成功返回,content就是我们的任务ID,去调度中心的任务管理也可以看到我们的任务
在这里插入图片描述
在这里插入图片描述

动态启动任务

有了上面动态添加任务的案例,动态启动任务就简单了,下面我示范下,其他像暂停、删除等操作也类似

1、在调度平台项目,新增一个启动任务的接口

	@RequestMapping("/startJob")
	@ResponseBody
	@PermissionLimit(limit = false)
	public ReturnT<String> startJob(@RequestBody XxlJobInfo jobInfo) {
		return xxlJobService.start(jobInfo.getId());
	}

2、在执行器项目,添加动态启动项目的方法

/**
     * 启动任务
     * @param jobId
     * @return
     */
    public String start(int jobId) {
        JSONObject json = new JSONObject();
        json.put("id",jobId);
        String jsonString = JSON.toJSONString(json);
        String result = doPost(adminAddresses + START_JOB_URL, jsonString);
        return result;
    }
  • 32
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
xxl-job-nacos 是一个基于微服务的调度系统。它的设计理念是通过将大型应用拆分成多个微服务,每个微服务都有一个独立的调度器,用于管理该微服务的任务调度和执行。xxl-job-nacos 使用 Nacos 作为注册中心,通过 Nacos 实现微服务的注册和发现。 通过将任务调度设置为微服务的一部分,xxl-job-nacos 可以实现任务的集中管理和分布式部署。它提供了一套友好的调度管理界面,用于配置和监控任务的运行情况。用户可以通过该界面添加、编辑和删除任务,指定任务的执行时间和频率。同时,xxl-job-nacos 支持任务动态调度,可以实时修改任务的执行策略和参数,以适应不同的业务需求。 通过使用 Nacos 作为注册中心,xxl-job-nacos 实现了任务的注册和发现。当一个新任务添加到调度系统中时,它会通过 Nacos 将任务的信息注册到注册中心,并通知相应的微服务。微服务通过订阅注册中心的信息,获知新任务的相关信息,并根据任务的调度策略和配置进行任务的执行。当任务执行完成后,微服务会将执行结果反馈给 xxl-job-nacos,并更新任务状态和日志。 总的来说,xxl-job-nacos 是一个基于微服务的调度系统,它通过将任务调度设置为微服务的一部分,实现了任务的集中管理和分布式部署。同时,xxl-job-nacos 使用 Nacos 作为注册中心,实现了任务的注册和发现,以实时更新任务的信息和执行结果。通过这样的设计,xxl-job-nacos 可以提供强大的任务调度能力,适用于大型应用的任务调度和分布式执行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值