Xxl-job 是目前比较主流的轻量级定时任务框架,该框架以相对简单的使用方式,灵活的配置,和可读性强的源码 等优势,深得广大开发者的喜欢。让我们的定时任务实现起来变得简单。具体的使用方法这里就不介绍了,想了解的可以直接访问官网:分布式任务调度平台XXL-JOB
今天我们主要解决一下使用xxl-job中的一个痛点,就是动态创建任务。使用过xxl-job的朋友们都知道,xxl-job给我们提供一个管理后台,我们可以在管理后台上,配置执行器,配置任务,管理任务。如下图。
这种方式使用起来虽然方便,可以有时候,我们就是需要在代码中动态创建一个定时任务,而不是到页面上进行配置,这个时候该怎么办呢?方式就是动态的扩展源码中提供的api。 这里着重强调一下,截止至当前,xxl-job的最新版本是2.2.0,这个版本中已经提供了一些开放的Rest Api供我们调用,不过只有心跳检测,忙碌检测,触发任务,终止任务,查看执行日志,等接口,如果你的需求是上述的这几个功能,完全可以使用开放的api进行扩展。但是这几个接口是不包含动态创建修改的。
好了,接下来回归正题,如果想动态创建任务该怎么办呢?目前的方式是只能通过调用xxl-job中的新增修改等接口完成。我们可以去github上把xxl-job的源码下载下来,其实我们正常部署xxl-job服务也是这个过程,在源码中主要有两个模块,一个是admin,一个是core, admin就是我们部署的服务,是一个springboot项目。
在admin项目中,有一个JobInfoController, 这个类就是处理各种新增任务,修改任务,触发任务的Controller,
但是有个问题就是,这些接口都是后台使用的,要想调用,就必须得登录才行,不登录是没有办法访问的。那怎么办?难道还要模拟登录一次,其实大可不必,因为xxl-job中已经为我们提供了一个口子,就是一个注解和一个拦截器,通过这个注解可以配置接口是否可以跳过登录进行访问。也就是我们可以把接口配置成跳过登录验证进行访问就可以了,这样我们就能不登录而进行请求 。这个注解就是 @PermissionLimit(limit = false) ,默认是true,也就是需要做登录验证。
好了有了这个注解就好办了,但是这里要注意,我们最好不要直接把这个注解配置到刚才截图中的对任务的操作的方法上,因为这些方法都是通过后台调用的,也就是对于操作者这些接口是可见的,如果打开了他们的权限,其实对于接口是有威胁的。所以我们可以自己扩展几个接口,保留原接口不变。那么我们直接在JobInfoController中添加几个方法
代码语言:javascript复制/*------------------自定义方法---------------------- */
@RequestMapping("/addJob")
@ResponseBody
@PermissionLimit(limit = false)
public ReturnT<String> addJobInfo(@RequestBody XxlJobInfo jobInfo) {
return xxlJobService.add(jobInfo);
}
@RequestMapping("/updateJob")
@ResponseBody
@PermissionLimit(limit = false)
public ReturnT<String> updateJobCron(@RequestBody XxlJobInfo jobInfo) {
return xxlJobService.updateCron(jobInfo);
}
@RequestMapping("/removeJob")
@ResponseBody
@PermissionLimit(limit = false)
public ReturnT<String> removeJob(@RequestBody XxlJobInfo jobInfo) {
return xxlJobService.remove(jobInfo.getId());
}
@RequestMapping("/pauseJob")
@ResponseBody
@PermissionLimit(limit = false)
public ReturnT<String> pauseJob(@RequestBody XxlJobInfo jobInfo) {
return xxlJobService.stop(jobInfo.getId());
}
@RequestMapping("/startJob")
@ResponseBody
@PermissionLimit(limit = false)
public ReturnT<String> startJob(@RequestBody XxlJobInfo jobInfo) {
return xxlJobService.start(jobInfo.getId());
}
@RequestMapping("/addAndStart")
@ResponseBody
@PermissionLimit(limit = false)
public ReturnT<String> addAndStart(@RequestBody XxlJobInfo jobInfo) {
ReturnT<String> result = xxlJobService.add(jobInfo);
int id = Integer.valueOf(result.getContent());
xxlJobService.start(id);
return result;
}
【新增】在 JobGroupController 中添加一个方法:获取groupId,
代码语言:javascript复制@RequestMapping("/getGroupId")
@ResponseBody
@PermissionLimit(limit = false)
public ReturnT<String> getGroupId(@RequestBody XxlJobGroup jobGroup) {
XxlJobGroup group = xxlJobGroupDao.findByName(jobGroup.getAppname());
return new ReturnT<String>(String.valueOf(group.getId()));
}
如果上面的findByName找不到,那就自己写一个,就是根据名字查询数据的一个方法:因为我忘了这个方法是自带的还是我自己加的。
代码语言:javascript复制<select id="findByName" parameterType="java.lang.String" resultMap="XxlJobGroup">
SELECT <include refid="Base_Column_List" />
FROM xxl_job_group AS t
WHERE t.app_name = #{appName}
</select>
然后重新打包部署。然后在我们的项目中直接访问刚刚加好的接口就可以了。 但是说实话这种方式确实也不是很安全,毕竟相当于如果被别人发现了这些接口都是可以无限制进行调用的,所以大家还是要谨慎使用,最好用的时候在内网中访问,同时开启xxl-job的accessToken.
还有一个问题,也说明一下,我们在新增任务的时候,其实是要给这个任务选择一个执行器,也就是有一个jobGroup字段需要设置,这个最好不要写死,我们可以按照上面的方式在扩展一个根据appname获取group表中id的接口,这样通过appname去获取到groupid,在设置到任务中,就更加灵活一下。好了这篇文章就先分享到这里了,有问题的话,欢迎大家留言。最后附一个xxl-job的调用工具类:
代码语言:javascript复制@Slf4j
@Component
public class XxlJobUtil {
@Value("${xxl.job.admin.addresses}")
private String adminAddresses;
@Value("${xxl.job.executor.appname}")
private String appname;
private RestTemplate restTemplate = new RestTemplate();
private static final String ADD_URL = "/jobinfo/addJob";
private static final String UPDATE_URL = "/jobinfo/updateJob";
private static final String REMOVE_URL = "/jobinfo/removeJob";
private static final String PAUSE_URL = "/jobinfo/pauseJob";
private static final String START_URL = "/jobinfo/startJob";
private static final String ADD_START_URL = "/jobinfo/addAndStart";
private static final String GET_GROUP_ID = "/jobgroup/getGroupId";
public String add(XxlJobInfo jobInfo){
// 查询对应groupId:
Map<String,Object> param = new HashMap<>();
param.put("appname", appname);
String json = JSON.toJSONString(param);
String result = doPost(adminAddresses GET_GROUP_ID, json);
JSONObject jsonObject = JSON.parseObject(result);
String groupId = jsonObject.getString("content");
jobInfo.setJobGroup(Integer.parseInt(groupId));
String json2 = JSON.toJSONString(jobInfo);
return doPost(adminAddresses ADD_URL, json2);
}
public String update(int id, String cron){
Map<String,Object> param = new HashMap<>();
param.put("id", id);
param.put("jobCron", cron);
String json = JSON.toJSONString(param);
return doPost(adminAddresses UPDATE_URL, json);
}
public String remove(int id){
Map<String,Object> param = new HashMap<>();
param.put("id", id);
String json = JSON.toJSONString(param);
return doPost(adminAddresses REMOVE_URL, json);
}
public String pause(int id){
Map<String,Object> param = new HashMap<>();
param.put("id", id);
String json = JSON.toJSONString(param);
return doPost(adminAddresses PAUSE_URL, json);
}
public String start(int id){
Map<String,Object> param = new HashMap<>();
param.put("id", id);
String json = JSON.toJSONString(param);
return doPost(adminAddresses START_URL, json);
}
public String addAndStart(XxlJobInfo jobInfo){
Map<String,Object> param = new HashMap<>();
param.put("appname", appname);
String json = JSON.toJSONString(param);
String result = doPost(adminAddresses GET_GROUP_ID, json);
JSONObject jsonObject = JSON.parseObject(result);
String groupId = jsonObject.getString("content");
jobInfo.setJobGroup(Integer.parseInt(groupId));
String json2 = JSON.toJSONString(jobInfo);
return doPost(adminAddresses ADD_START_URL, json2);
}
public String doPost(String url, String json){
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(json ,headers);
log.info(entity.toString());
ResponseEntity<String> stringResponseEntity = restTemplate.postForEntity(url, entity, String.class);
return stringResponseEntity.getBody().toString();
}
}