springboot+idea集成xxl-job定时管理中心
最近公司要求做一个定时任务管理的项目,网上查资料看到了xxl-job,尝试搭建了一个,做一下分享,希望对大家有帮助
功能模块:
通过xxl,我们可以用相对简单的方式,把定时相关的任务交给xxl去管理。比如,你需要在十点执行一个定时任务,如果你已经注册了执行器,那么你只需要通过网页添加一条任务数据即可,管理中心会在执行时间去调用你的项目,执行相应的处理器。
xxl可以添加定时执行的任务,也可以添加只执行一次的任务,用的是Quartz,Cron表达式
你也可以通过源码动态地对任务进行CRUD操作
xxl支持分布式管理,可以多个执行器执行同一条任务
它有可视化页面,页面做的很漂亮也很简洁,瞄一眼大概都会操作,如下:
ok,废话不多说
接下来,本文将主要讲解搭建的流程,及通过调用管理中心接口,使用源码对任务进行CRUD操作。
一、源码部分及使用说明
源码:https://github.com/xuxueli/xxl-job
使用说明:https://www.xuxueli.com/xxl-job/#%E3%80%8A%E5%88%86%E5%B8%83%E5%BC%8F%E4%BB%BB%E5%8A%A1%E8%B0%83%E5%BA%A6%E5%B9%B3%E5%8F%B0XXL-JOB%E3%80%8B
1.1 下载管理项目xxl-job
1.2 切换分支,我这里用的是2.2.0版本,注意:不能用人家正在开发的主分支作为管理中心,一方面,主分支正在开发,很可能有未知的错误。另一方面,正在开发的分支没有配套的maven依赖。没有合适的依赖,管理中心项目和客户端项目连接肯定会有问题的
1.3 目录简介
1.3.1 文件tables_xxl_job.sql必须放到自己的库里运行一下,这是管理中心项目要用到的数据库。把“数据库初始化脚本”运行一下,放到自己的库里,这个是mysql的,我这边xxl_job_registry报了键字符太长,就把键删掉了,因为我这边注册的客户端比较少
1.3.2 模块xxl-job-admin就是项目的启动模块
1.3.3 基本上xxl-job-executor-sample-springboot这个项目包含了你要搭建客户端项目的全部内容,如果没有特殊要求,复制粘贴即可
二、搭建管理中心项目
2.1 修改xxl-job\xxl-job-admin\src\main\resources\application.properties,主要是配置数据库和管理界面的用户和密码
2.2 启动项目,访问http://localhost:8100/xxl-job-admin/,如下就成功了
三、搭建客户端(执行端),打开你需要执行定时任务的项目
3.1 结构
3.2 配置类,这个不用修改,放到你自己的项目里就行
3.3 处理器,我这个版本的使用@XxlJob注解即可配置处理器,就是定时任务要执行的代码,注意这里的public ReturnT<String> demoJobHandler(String param) throws Exception格式不可修改
@Component
public class SampleXxlJob {
private static Logger logger = LoggerFactory.getLogger(SampleXxlJob.class);
/**
* 1、简单任务示例(Bean模式)
*/
@XxlJob("demoJobHandler")
public ReturnT<String> demoJobHandler(String param) throws Exception {
XxlJobLogger.log("XXL-JOB, Hello World.");
for (int i = 0; i < 5; i++) {
XxlJobLogger.log("beat at:" + i);
TimeUnit.SECONDS.sleep(2);
}
return ReturnT.SUCCESS;
}
}
3.4 配置文件,添加这个就行了,注意:http://127.0.0.1:8100/xxl-job-admin这个地址就是管理中心的地址,不要配错。还有这个appname将会在创建执行器的时候用到,可以自己命名。这里面的port和项目的port不一样,是执行器的port,不要和项目的port配成一样,默认搞9999就行了
xxl:
job:
admin:
addresses: http://127.0.0.1:8100/xxl-job-admin
executor:
appname: xxl-job-executor-test
ip:
port: 9999
logpath: /data/applogs/xxl-job/jobhandler
logretentiondays: 30
address:
accessToken:
3.5 依赖,主要是第一条依赖,版本一定要配成管理中心的版本;第二条依赖主要是下面使用源码添加任务的时候用,不需要就不要加了
<!--xxl-->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>2.2.0</version>
</dependency><br> <!--源码CRUD任务-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.5.4</version>
</dependency>
3.6 打开页面,添加执行器,AppName就是上面提到的xxl-job-executor-test,名称自己随便写,选自动注册即可,自动注册会自动监听有哪些执行器正在执行,手动录入需要手动添加
3.7 添加任务,这里的JobHandler就是@XxlJob的value
3.8 启动客户端,就能看到你自己的执行器已经被记录了。
3.9 任务里面执行一下,能跑断点就没问题了
至此搭建环境完成,你可以用页面管理你的定时任务了,如果你还需要用源码管理任务,请继续看下面
四、用源码对任务进行CRUD操作
4.1 查看源码,打开管理中心项目的xxl-job\xxl-job-admin\src\main\java\com\xxl\job\admin\controller\JobInfoController.java,这里就是对任务的管理,调用这些接口就能实现功能
4.2 考虑到比较懒,附上一个工具类。maven依赖上面提过了,XxlJobInfo这个类去管理中心项目找
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSON;
import com.greatchn.ssfwpt.common.bean.XxlJobInfo;
import com.greatchn.ssfwpt.common.tools.map.MapTools;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.net.HttpCookie;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* ------参数格式
* XxlJobInfo xxlJobInfo = new XxlJobInfo();
* xxlJobInfo.setId(3);//插入时不需要
* xxlJobInfo.setJobGroup(1);
* xxlJobInfo.setJobDesc("测试任务");
* xxlJobInfo.setExecutorRouteStrategy("FIRST");
* xxlJobInfo.setJobCron("0/6 * * * * ?");
* xxlJobInfo.setGlueType("BEAN");
* xxlJobInfo.setExecutorHandler("demoJobHandler");
* xxlJobInfo.setExecutorBlockStrategy("SERIAL_EXECUTION");
* xxlJobInfo.setExecutorTimeout(0);
* xxlJobInfo.setExecutorFailRetryCount(0);
* xxlJobInfo.setAuthor("XXL");
* xxlJobInfo.setTriggerStatus(1);
*/
@Component
public class XxlTools {
@Value("${xxl.job.admin.addresses}")
private String adminAddresses;
/**
* 添加任务
*
* @param xxlJobInfo
* @return 任务序号(-1失败,1,2,3... 成功)
*/
public int addXxlJob(XxlJobInfo xxlJobInfo) {
HttpResponse response = getResponse(xxlJobInfo, "add");
return response.getStatus() == 200 ? JSON.parseObject(response.body()).getIntValue("content") : -1;
}
/**
* 更新任务
*
* @param xxlJobInfo
* @return true成功,false失败
*/
public boolean updateXxlJob(XxlJobInfo xxlJobInfo) {
HttpResponse response = getResponse(xxlJobInfo, "update");
return response.getStatus() == 200 ? true : false;
}
/**
* 删除任务
*
* @param xxlJobInfo
* @return
*/
public boolean removeXxlJob(XxlJobInfo xxlJobInfo) {
HttpResponse response = getResponse(xxlJobInfo, "remove");
return response.getStatus() == 200 ? true : false;
}
/**
* 停止任务
*
* @param xxlJobInfo
* @return
*/
public boolean stopXxlJob(XxlJobInfo xxlJobInfo) {
HttpResponse response = getResponse(xxlJobInfo, "stop");
return response.getStatus() == 200 ? true : false;
}
/**
* 启动任务
*
* @param xxlJobInfo
* @return
*/
public boolean startXxlJob(XxlJobInfo xxlJobInfo) {
HttpResponse response = getResponse(xxlJobInfo, "start");
return response.getStatus() == 200 ? true : false;
}
/**
* 获取登录cookie
*
* @return
*/
public String getCookie() {
String path = adminAddresses + "/login";
Map<String, Object> hashMap = new HashMap();
hashMap.put("userName", "admin");
hashMap.put("password", "123456");
HttpResponse response = HttpRequest.post(path).form(hashMap).execute();
List<HttpCookie> cookies = response.getCookies();
StringBuilder sb = new StringBuilder();
for (HttpCookie cookie : cookies) {
sb.append(cookie.toString());
}
String cookie = sb.toString();
return cookie;
}
/**
* 调用xxl-job项目获取返回结果
*
* @param xxlJobInfo
* @param method
* @return
*/
public HttpResponse getResponse(XxlJobInfo xxlJobInfo, String method) {
String path = adminAddresses + "/jobinfo/" + method;
Map<String, Object> paramMap = MapTools.entityToMap(xxlJobInfo);
getCookie();
return HttpRequest.post(path).form(paramMap).execute();
}
/**
* 获取固定时间的Cron表达式
*
* @param time
* @return cron表达式
*/
public String getOnTimeCron(String time) {
try {
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(time);
String[] times = time.split(" ");
String[] yMd = times[0].split("-");
String[] Hms = times[1].split(":");
String year = yMd[0];
String month = StringUtils.startsWith(yMd[1], "0") ? StringUtils.substring(yMd[1], 1) : yMd[1];
String day = StringUtils.startsWith(yMd[2], "0") ? StringUtils.substring(yMd[2], 1) : yMd[2];
String hour = StringUtils.startsWith(Hms[0], "0") ? StringUtils.substring(Hms[0], 1) : Hms[0];
String minute = StringUtils.startsWith(Hms[1], "0") ? StringUtils.substring(Hms[1], 1) : Hms[1];
String second = StringUtils.startsWith(Hms[2], "0") ? StringUtils.substring(Hms[2], 1) : Hms[2];
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(second);
stringBuffer.append(" ");
stringBuffer.append(minute);
stringBuffer.append(" ");
stringBuffer.append(hour);
stringBuffer.append(" ");
stringBuffer.append(day);
stringBuffer.append(" ");
stringBuffer.append(month);
stringBuffer.append(" ? ");
stringBuffer.append(year);
return stringBuffer.toString();
} catch (Exception e) {
return null;
}
}
}
五、附上一些测试结果
5.1 执行器appname一样,就是说一个任务多个执行器的情况,该任务执行时,只会在其中一个执行器执行(类似于MQ的点对点),并且如果此执行器不停止,会一直在此执行器执行。不是随机的选一个执行器执行的
5.2 一个任务多个执行器,如果一直在执行此任务的执行器关闭,会选择其他执行器执行