能搜到这里的最起码一定知道 xxl-job 是用来干什么的,我就不多啰嗦怎么下载以及它的历史了
首先我们要知道 xxl-job 这个框架的结构,如下图:
xxl-job-master:
xxl-job-admin:调度中心
xxl-job-core:公共依赖
xxl-job-executor-samples:执行器Sample示例(选择合适的版本执行器,可直接使用,也可以参考其并将现有项目改造成执行器)
xxl-job-executor-sample-springboot:Springboot版本,通过Springboot管理执行器,推荐这种方式;
xxl-job-executor-sample-frameless:无框架版本;
将项目拉到本地后打开目录结构就是张这个样子的
我们首先要打开 /xxl-job-master/doc/db/tables_xxl_job.sql 这个目录,然后执行调度数据库的初始化脚本,这时我们就可以去部署调度中心模块了(本地用户直接启动 SpringBoot 启动类就好了),记得启动之前先修改一下配置文件。。。
项目启动好之后就可以通过 http://your_project_ip:8080/xxl-job-admin 去访问调度中心
默认登录账号 “admin/123456”, 登录后运行界面如下图所示:
这样调度中心就启动好啦!!!
然后我们要在我们的项目中启动执行器项目
首先确保引入了依赖(使用 Maven ,不是用 Maven 的友友自行百度)
<!-- xxl-job-core -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>2.4.1-SNAPSHOT</version>
</dependency>
然后就是编辑配置文件,具体的其实就是抄 xxl-job 示例模块的配置文件,我就贴到这了
### 调度中心部署根地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册;
xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin
### 执行器通讯TOKEN [选填]:非空时启用;
xxl.job.accessToken=
### 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
xxl.job.executor.appname=xxl-job-executor-sample
### 执行器注册 [选填]:优先使用该配置作为注册地址,为空时使用内嵌服务 ”IP:PORT“ 作为注册地址。从而更灵活的支持容器类型执行器动态IP和动态映射端口问题。
xxl.job.executor.address=
### 执行器IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯实用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务";
xxl.job.executor.ip=
### 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口;
xxl.job.executor.port=9999
### 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;
xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler
### 执行器日志文件保存天数 [选填] : 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能;
xxl.job.executor.logretentiondays=30
配置文件编辑完毕然后就该进行组件配置了,具体的就是将xxl-job-executor-sample-springboot 执行器项目的XxlJobConfig类复制过来
package com.atguigu.daijia.dispatch.xxl.config;
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class XxlJobConfig {
private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);
@Value("${xxl.job.admin.addresses}")
private String adminAddresses;
@Value("${xxl.job.accessToken}")
private String accessToken;
@Value("${xxl.job.executor.appname}")
private String appname;
@Value("${xxl.job.executor.address}")
private String address;
@Value("${xxl.job.executor.ip}")
private String ip;
@Value("${xxl.job.executor.port}")
private int port;
@Value("${xxl.job.executor.logpath}")
private String logPath;
@Value("${xxl.job.executor.logretentiondays}")
private int logRetentionDays;
@Bean
public XxlJobSpringExecutor xxlJobExecutor() {
logger.info(">>>>>>>>>>> xxl-job config init.");
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
xxlJobSpringExecutor.setAppname(appname);
xxlJobSpringExecutor.setAddress(address);
xxlJobSpringExecutor.setIp(ip);
xxlJobSpringExecutor.setPort(port);
xxlJobSpringExecutor.setAccessToken(accessToken);
xxlJobSpringExecutor.setLogPath(logPath);
xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
return xxlJobSpringExecutor;
}
/**
* 针对多网卡、容器内部署等情况,可借助 "spring-cloud-commons" 提供的 "InetUtils" 组件灵活定制注册IP;
*
* 1、引入依赖:
* <dependency>
* <groupId>org.springframework.cloud</groupId>
* <artifactId>spring-cloud-commons</artifactId>
* <version>${version}</version>
* </dependency>
*
* 2、配置文件,或者容器启动变量
* spring.cloud.inetutils.preferred-networks: 'xxx.xxx.xxx.'
*
* 3、获取IP
* String ip_ = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
*/
}```
到这里其实我们就已经将 xxl-job 集成到我们的项目中了
然后我们编写一个测试方法
```java
package com.atguigu.daijia.dispatch.xxl.job;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class DispatchJobHandler {
@XxlJob("firstJobHandler")
public void firstJobHandler() {
log.info("xxl-job项目集成测试");
}
}
在调度中心配置,图片中 JobHandler 的名字要和注解中 @XxlJob(“firstJobHandler”) 的名字一致
这样我们就可以在控制台看见啦!!
代码方式实现动态增删改查
但是这样我们只能通过图形化界面的方式操作,及其不方便,所以我们通过代码的方式进行动态创建,下面就以代驾项目中的订单模块举例
首先我们要在xxl-job-admin模块,添加改造后的api接口(我们自己的),如下:
@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> updateJob(@RequestBody XxlJobInfo jobInfo) {
return xxlJobService.update(jobInfo);
}
@RequestMapping("/removeJob")
@ResponseBody
@PermissionLimit(limit = false)
public ReturnT<String> removeJob(@RequestBody XxlJobInfo jobInfo) {
return xxlJobService.remove(jobInfo.getId());
}
@RequestMapping("/stopJob")
@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("/addAndStartJob")
@ResponseBody
@PermissionLimit(limit = false)
public ReturnT<String> addAndStartJob(@RequestBody XxlJobInfo jobInfo) {
ReturnT<String> result = xxlJobService.add(jobInfo);
int id = Integer.valueOf(result.getContent());
xxlJobService.start(id);
//立即执行一次
JobTriggerPoolHelper.trigger(id, TriggerTypeEnum.MANUAL, -1, null, jobInfo.getExecutorParam(), "");
return result;
}
说明:排除登录校验的注解 @PermissionLimit(limit = false)
完整配置:
xxl:
job:
admin:
# 调度中心部署跟地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册
addresses: http://139.198.30.131:8080/xxl-job-admin
# addresses: http://localhost:8080/xxl-job-admin
# 执行器通讯TOKEN [选填]:非空时启用
accessToken:
executor:
# 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
appname: xxl-job-executor-sample
# 执行器注册 [选填]:优先使用该配置作为注册地址,为空时使用内嵌服务 ”IP:PORT“ 作为注册地址。从而更灵活的支持容器类型执行器动态IP和动态映射端口问题。
address:
# 执行器IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯实用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务";
ip:
# 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口;
port: 9999
# 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;
logpath: /data/applogs/xxl-job/jobhandler
# 执行器日志文件保存天数 [选填] : 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能;
logretentiondays: 30
client:
jobGroupId: 1
addUrl: ${xxl.job.admin.addresses}/jobinfo/addJob
removeUrl: ${xxl.job.admin.addresses}/jobinfo/removeJob
startJobUrl: ${xxl.job.admin.addresses}/jobinfo/startJob
stopJobUrl: ${xxl.job.admin.addresses}/jobinfo/stopJob
addAndStartUrl: ${xxl.job.admin.addresses}/jobinfo/addAndStartJob
重点就是下面的 client 里面的内容(${xxl.job.admin.addresses} 这部分内容自己在配置文件中配置好自己的 http 接口地址就好)
然后在订单模块创建配置类:
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "xxl.job.client")
public class XxlJobClientConfig {
private Integer jobGroupId;
private String addUrl;
private String removeUrl;
private String startJobUrl;
private String stopJobUrl;
private String addAndStartUrl;
}
配置好自己的客户端
package com.atguigu.daijia.dispatch.xxl.client;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.daijia.common.execption.GuiguException;
import com.atguigu.daijia.common.result.ResultCodeEnum;
import com.atguigu.daijia.dispatch.xxl.config.XxlJobClientConfig;
import com.atguigu.daijia.model.entity.dispatch.XxlJobInfo;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
/**
* https://dandelioncloud.cn/article/details/1598865461087518722
*/
@Slf4j
@Component
public class XxlJobClient {
@Autowired
private XxlJobClientConfig xxlJobClientConfig;
@Autowired
private RestTemplate restTemplate;
@SneakyThrows
public Long addJob(String executorHandler, String param, String corn, String desc){
XxlJobInfo xxlJobInfo = new XxlJobInfo();
xxlJobInfo.setJobGroup(xxlJobClientConfig.getJobGroupId());
xxlJobInfo.setJobDesc(desc);
xxlJobInfo.setAuthor("qy");
xxlJobInfo.setScheduleType("CRON");
xxlJobInfo.setScheduleConf(corn);
xxlJobInfo.setGlueType("BEAN");
xxlJobInfo.setExecutorHandler(executorHandler);
xxlJobInfo.setExecutorParam(param);
xxlJobInfo.setExecutorRouteStrategy("FIRST");
xxlJobInfo.setExecutorBlockStrategy("SERIAL_EXECUTION");
xxlJobInfo.setMisfireStrategy("FIRE_ONCE_NOW");
xxlJobInfo.setExecutorTimeout(0);
xxlJobInfo.setExecutorFailRetryCount(0);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<XxlJobInfo> request = new HttpEntity<>(xxlJobInfo, headers);
String url = xxlJobClientConfig.getAddUrl();
ResponseEntity<JSONObject> response = restTemplate.postForEntity(url, request, JSONObject.class);
if(response.getStatusCode().value() == 200 && response.getBody().getIntValue("code") == 200) {
log.info("增加xxl执行任务成功,返回信息:{}", response.getBody().toJSONString());
//content为任务id
return response.getBody().getLong("content");
}
log.info("调用xxl增加执行任务失败:{}", response.getBody().toJSONString());
throw new GuiguException(ResultCodeEnum.XXL_JOB_ERROR);
}
public Boolean startJob(Long jobId) {
XxlJobInfo xxlJobInfo = new XxlJobInfo();
xxlJobInfo.setId(jobId.intValue());
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<XxlJobInfo> request = new HttpEntity<>(xxlJobInfo, headers);
String url = xxlJobClientConfig.getStartJobUrl();
ResponseEntity<JSONObject> response = restTemplate.postForEntity(url, request, JSONObject.class);
if(response.getStatusCode().value() == 200 && response.getBody().getIntValue("code") == 200) {
log.info("启动xxl执行任务成功:{},返回信息:{}", jobId, response.getBody().toJSONString());
return true;
}
log.info("启动xxl执行任务失败:{},返回信息:{}", jobId, response.getBody().toJSONString());
throw new GuiguException(ResultCodeEnum.XXL_JOB_ERROR);
}
public Boolean stopJob(Long jobId) {
XxlJobInfo xxlJobInfo = new XxlJobInfo();
xxlJobInfo.setId(jobId.intValue());
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<XxlJobInfo> request = new HttpEntity<>(xxlJobInfo, headers);
String url = xxlJobClientConfig.getStopJobUrl();
ResponseEntity<JSONObject> response = restTemplate.postForEntity(url, request, JSONObject.class);
if(response.getStatusCode().value() == 200 && response.getBody().getIntValue("code") == 200) {
log.info("停止xxl执行任务成功:{},返回信息:{}", jobId, response.getBody().toJSONString());
return true;
}
log.info("停止xxl执行任务失败:{},返回信息:{}", jobId, response.getBody().toJSONString());
throw new GuiguException(ResultCodeEnum.XXL_JOB_ERROR);
}
public Boolean removeJob(Long jobId) {
XxlJobInfo xxlJobInfo = new XxlJobInfo();
xxlJobInfo.setId(jobId.intValue());
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<XxlJobInfo> request = new HttpEntity<>(xxlJobInfo, headers);
String url = xxlJobClientConfig.getRemoveUrl();
ResponseEntity<JSONObject> response = restTemplate.postForEntity(url, request, JSONObject.class);
if(response.getStatusCode().value() == 200 && response.getBody().getIntValue("code") == 200) {
log.info("删除xxl执行任务成功:{},返回信息:{}", jobId, response.getBody().toJSONString());
return true;
}
log.info("删除xxl执行任务失败:{},返回信息:{}", jobId, response.getBody().toJSONString());
throw new GuiguException(ResultCodeEnum.XXL_JOB_ERROR);
}
public Long addAndStart(String executorHandler, String param, String corn, String desc) {
XxlJobInfo xxlJobInfo = new XxlJobInfo();
xxlJobInfo.setJobGroup(xxlJobClientConfig.getJobGroupId());
xxlJobInfo.setJobDesc(desc);
xxlJobInfo.setAuthor("qy");
xxlJobInfo.setScheduleType("CRON");
xxlJobInfo.setScheduleConf(corn);
xxlJobInfo.setGlueType("BEAN");
xxlJobInfo.setExecutorHandler(executorHandler);
xxlJobInfo.setExecutorParam(param);
xxlJobInfo.setExecutorRouteStrategy("FIRST");
xxlJobInfo.setExecutorBlockStrategy("SERIAL_EXECUTION");
xxlJobInfo.setMisfireStrategy("FIRE_ONCE_NOW");
xxlJobInfo.setExecutorTimeout(0);
xxlJobInfo.setExecutorFailRetryCount(0);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<XxlJobInfo> request = new HttpEntity<>(xxlJobInfo, headers);
String url = xxlJobClientConfig.getAddAndStartUrl();
ResponseEntity<JSONObject> response = restTemplate.postForEntity(url, request, JSONObject.class);
if(response.getStatusCode().value() == 200 && response.getBody().getIntValue("code") == 200) {
log.info("增加并开始执行xxl任务成功,返回信息:{}", response.getBody().toJSONString());
//content为任务id
return response.getBody().getLong("content");
}
log.info("增加并开始执行xxl任务失败:{}", response.getBody().toJSONString());
throw new GuiguException(ResultCodeEnum.XXL_JOB_ERROR);
}
}
这样操作之后就可以在接口中调用自己的 xxl-job 客户端进行定时任务调用啦