参考了下面两位博主发布的文章,整合后更新更完整的流程
博主1:
Xxl-Job实现动态创建、查询、删除定时任务等_xxljob实现动态定时-CSDN博客
博主2:这个是直接修改xxljob源码
SpringBoot项目中通过代码方式手动增删改Xxl-Job定时任务以及定时任务的执行_xxjob 添加更新删除定时任务-CSDN博客
首先是常规的xxljob配置 需要注意的是@Bean 那边有些是直接@Bean,有些后面添加了初始化方法和销毁方法,我是一开始添加了方法会报错,后面只单一用bean注入,这个应该是版本问题,直接都试一下就行。还有在配置文件中配置accesstoken 的时候不能为空有些版本需要你写default_token 你连接才不会报access token is wrong 的错误。这是我自己踩的坑。注意避免。
6.20更新 下方配置文件配置ip的时候,如果你的xxljob是部署在服务器上不是本机上,需要你用内网穿透工具去把自己主机地址:9999端口号映射一下,然后在xxljob里面创建执行器的手动注册地址为内网穿透的映射的地址。
xxl:
job:
userName: //你的xxljob服务器登录的用户名
password: //密码
accessToken: default_token #调度中心通讯TOKEN [选填]:非空时启用
admin:
addresses: url/xxl-job-admin #xxljob调度中心部署 例如:http://127.0.0.1:8080/xxl-job-admin
executor:
appname: testJob #xxljob配置的执行器名称,
ip: #执行器IP,默认为空表示自动获取IP
port: 9999 #xxljob配置的端口号,默认为9999
logpath: /data/xxl-job/jobhandler #执行器运行日志文件存储磁盘路径
logretentiondays: -1 #调度中心日志表数据保存天数,过期日志自动清理;限制大于等于7时生效,否则, 如-1,关闭自动清理功能
@Bean(initMethod = "start", destroyMethod = "destroy")
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import groovy.util.logging.Slf4j;
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;
/**
* @ClassName: XxlJobConfig
* @Description: xxl_job配置类
* @Author wjw
* @Date 2024/6/18
*/
@Configuration
@Slf4j
public class XxlJobConfig {
private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);
@Value("${xxl.job.admin.addresses}")
private String adminAddresses;
@Value("${xxl.job.executor.appname}")
private String appName;
@Value("${xxl.job.executor.ip}")
private String ip;
@Value("${xxl.job.executor.port}")
private int port;
@Value("${xxl.job.accessToken}")
private String accessToken;
@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.setIp(ip);
xxlJobSpringExecutor.setPort(port);
xxlJobSpringExecutor.setAccessToken(accessToken);
xxlJobSpringExecutor.setLogPath(logPath);
xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
return xxlJobSpringExecutor;
}
}
动态创建定时任务的关键是模拟用户登录xxljob调度中心的过程,然后创建定时任务的过程,所以需要保证一个执行器的存在,执行器应该也可以通过发送HTTP请求来创建,目前先完成自动创建定时任务,创建定时任务需要地址和提交表单数据,这边参考博主1的文章,是直接用formData来保存所需变量。下面所需参数可以自己登录xxljob调度中心,F12后查看相关参数。
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName: DynamicJobService
* @Description:
* @Author wangjunwei
* @Date 2024/6/18
*/
@Component
@Slf4j
@Configuration
public class DynamicJobService {
public Map<String, Object> insertXxlJobClient(XxlJob xxlJob) {
Map<String, Object> formData = new HashMap<>();
formData.put("jobGroup", 1);
formData.put("jobDesc", xxlJob.getJobDesc()); //任务名称
formData.put("author", xxlJob.getAuthor()); //用户姓名
formData.put("alarmEmail", "");
formData.put("scheduleType", "CRON");
formData.put("scheduleConf", xxlJob.getCron()); //cron表达式
formData.put("cronGen_display", xxlJob.getCron());//cron表达式
formData.put("schedule_conf_CRON", "");
formData.put("schedule_conf_FIX_RATE", "");
formData.put("schedule_conf_FIX_DELAY", "");
formData.put("glueType", "BEAN");
formData.put("executorHandler", "DataXJobHandler");
formData.put("executorParam", xxlJob.getFilePath()); //传json文件的保存路径
formData.put("executorRouteStrategy", "FIRST");
formData.put("childJobId", "");
formData.put("misfireStrategy", "DO_NOTHING");
formData.put("executorBlockStrategy", "SERIAL_EXECUTION");
formData.put("executorTimeout", "0");
formData.put("executorFailRetryCount", "0");
formData.put("glueRemark", "GLUE代码初始化");
formData.put("glueSource", "");
//构建好以后调用XxlJobClient的addJob方法并传递formData参数,这个类及方法的实现在下面
return formData;
}
}
有了以上准备后就可以准备xxljobutil工具来完成登录,添加定时任务和启动定时任务等一些功能,以下代码是看博主1的,稍微改了一下getcron方法,已经把addjob和startjob方法拆分开,完善了一下HTTP请求地址。
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import parquet.org.codehaus.jackson.JsonNode;
import parquet.org.codehaus.jackson.map.ObjectMapper;
import java.io.IOException;
import java.net.HttpCookie;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @ClassName: XxlJobUtil
* @Description: xxljob工具类
* @Author wangjunwei
* @Date 2024/6/18
*/
@Slf4j
public class XxlJobUtil {
//该MAP主要用于缓存Xxl-Job的Cookie
private static Map<String, String> loginCookie = new HashMap<>();
@Value("${xxl.job.admin.addresses}")
private static String XxlJobUrl;
@Value("${xxl.job.userName}")
private static String userName;
@Value("${xxl.job.password}")
private static String password;
//添加定时任务
public static Integer addJob(Map<String, Object> formData) {
//这里的url接口路径一定要是自己F12抓取到的
HttpRequest request = HttpRequest.post(XxlJobUrl + "/jobinfo/add")
.header("Content-Type", "multipart/form-data")
//每次请求都需要带上Cookie,getCookie方法在后面
.header("Cookie", getCookie())
.form(formData);
try {
// 执行 HTTP POST 请求创建定时任务
HttpResponse response = request.execute();
String result = response.body();
if (StrUtil.isNotBlank(result) && 200 == response.getStatus()) {
//定时任务创建成功后拿到任务id
JSONObject jsonResponse = JSON.parseObject(result);
// content = jsonResponse.getString("content");
Integer content = Integer.parseInt(jsonResponse.getString("content")); // 将字符串转换为Integer
log.info("定时任务创建成功,任务ID为:" + content);
return content;
} else {
log.error("定时任务创建失败");
}
} catch (Exception e) {
log.info("定时任务创建失败,发生异常:" + e.getMessage());
}
return null;
}
//启动定时任务
public static void startJob(Integer jobId) {
//创建后的定时任务默认是STOP状态,所以我们还要通过定时任务id调度任务启动接口
HttpRequest requests = HttpRequest.post(XxlJobUrl + "/jobinfo/start")
.header("Content-Type", "multipart/form-data")
.header("Cookie", getCookie())
.form("id", jobId);
//通过HTTP请求启动定时任务
HttpResponse responses = requests.execute();
String results = responses.body();
if (StrUtil.isNotBlank(results) && 200 == responses.getStatus()) {
log.info("定时任务{}启动成功。", jobId);
} else {
log.error("定时任务{}启动失败。", jobId);
}
}
//停止定时任务
public static void stopJob(Integer jobId) {
//通过定时任务id调度任务停止接口
HttpRequest requests = HttpRequest.post(XxlJobUrl + "/jobinfo/stop")
.header("Content-Type", "multipart/form-data")
.header("Cookie", getCookie())
.form("id", jobId);
//通过HTTP请求停止定时任务
HttpResponse responses = requests.execute();
String results = responses.body();
if (StrUtil.isNotBlank(results) && 200 == responses.getStatus()) {
log.info("定时任务{}停止成功。", jobId);
} else {
log.error("定时任务{}停止失败。", jobId);
}
}
// 因为是动态实时创建定时任务,所以建议单独创建一个执行器去执行这些定时任务,方便后续批量进行查询出来进行删除。
//
// 2.查询定时任务:
//
// 这里入参执行器ID,通过执行器ID来查询该执行器下所有Stop状态的定时任务,因为Xxl-job定时任务默认*在执行完最后一次任务后就会自动进入STOP状态,这样查询出来所有STOP状态任务后方便我们后续进行删除清理定时任务。
public List<Long> SelectJob(Integer jobGroup) {
HttpRequest request = HttpRequest.post(XxlJobUrl + "/jobinfo/pageList")
.header("Content-Type", "multipart/form-data")
.header("Cookie", getCookie())
.form("jobGroup", jobGroup)
.form("triggerStatus", 0)
.form("start", 0);
//执行 HTTP POST 请求启动定时任务
HttpResponse response = request.execute();
// 解析响应体
ObjectMapper mapper = new ObjectMapper();
List<Long> idList = new LinkedList<>();
try {
JsonNode responseNode = mapper.readTree(response.body());
JsonNode dataNode = responseNode.get("data");
//遍历删除id对应的定时任务
if (dataNode.isArray()) {
for (JsonNode node : dataNode) {
Long id = node.get("id").asLong();
idList.add(id);
}
}
} catch (IOException e) {
System.out.println("解析响应体时发生异常:" + e.getMessage());
}
return idList;
}
//3.删除定时任务:
//
// Xxl-job目前没有没有直接批量进行删除定时任务的,所以我们使用遍历去挨个删除,如果考虑到性能问题,单独创建一个定时来调用该删除方法即可,每天凌晨去执行该删除清理的定时任务。
public void removalJob(List<Long> idList) {
for (Long id : idList) {
HttpRequest requests = HttpRequest.post(XxlJobUrl + "/jobinfo/remove")
.header("Content-Type", "multipart/form-data")
.header("Cookie", getCookie())
.form("id", id);
//执行HTTP请求删除定时任务
HttpResponse response = requests.execute();
if (StrUtil.isNotBlank(response.body()) && 200 == response.getStatus()) {
log.info("定时任务{}删除成功。", id);
} else {
log.error("定时任务{}删除失败。", id);
}
}
}
//下面是获取Cookie的方法
public static String getCookie() {
for (int i = 0; i < 3; i++) {
String cookieStr = loginCookie.get("XXL_JOB_LOGIN_IDENTITY");
if (cookieStr != null) {
return "XXL_JOB_LOGIN_IDENTITY=" + cookieStr;
}
login();
}
throw new RuntimeException("获取 xxl-job cookie 失败!");
}
//优先到MAP缓存中获取,如果没有获取到则会请求xxljob的登录来获取Cookie,这里提供三次失败可重试。
public static void login() {
String url = XxlJobUrl+"/login";
HttpResponse response = HttpRequest.post(url)
.form("userName", userName)
.form("password", password)
.execute();
List<HttpCookie> cookies = response.getCookies();
Optional<HttpCookie> cookieOpt = cookies.stream()
.filter(cookie -> cookie.getName().equals("XXL_JOB_LOGIN_IDENTITY")).findFirst();
if (!cookieOpt.isPresent())
throw new RuntimeException("获取 xxl-job cookie 失败!");
String value = cookieOpt.get().getValue();
loginCookie.put("XXL_JOB_LOGIN_IDENTITY", value);
}
/***
* 生成 日期对应的 cron表达式
* @param
* @return String
*/
public static String getCron(String string) throws Exception {
String input1 = "每天+10:00:00"; // 每天10:00:00
String input2 = "每周2+10:00:00"; // 每周一10:00:00
String input3 = "每月15+10:00:00"; // 每月15号10:00:00
try {
System.out.println(convertToCronExpression(input1));
System.out.println(convertToCronExpression(input2));
System.out.println(convertToCronExpression(input3));
} catch (Exception e) {
e.printStackTrace();
}
return convertToCronExpression(string);
}
private static String convertToCronExpression(String input) throws Exception {
// 解析输入字符串
Pattern pattern = Pattern.compile("^(每天|每周\\d|每月\\d+)+\\+(\\d{2}:\\d{2}:\\d{2})$");
Matcher matcher = pattern.matcher(input);
if (!matcher.matches()) {
throw new Exception("Invalid input format");
}
String dateType = matcher.group(1);
String time = matcher.group(2);
// 解析时间字符串
String[] timeParts = time.split(":");
int hours = Integer.parseInt(timeParts[0]);
int minutes = Integer.parseInt(timeParts[1]);
int seconds = Integer.parseInt(timeParts[2]);
// 生成 cron 表达式
String cronExpression;
if (dateType.equals("每天")) {
cronExpression = String.format("0 %d %d * * ?", minutes, hours);
} else if (dateType.startsWith("每周")) {
int dayOfWeek = Integer.parseInt(dateType.substring(2)); // 获取周几
cronExpression = String.format("0 %d %d ? * %d", minutes, hours, dayOfWeek);
} else if (dateType.startsWith("每月")) {
int dayOfMonth = Integer.parseInt(dateType.substring(2)); // 获取几号
cronExpression = String.format("0 %d %d %d * ?", minutes, hours, dayOfMonth);
} else {
throw new Exception("Invalid date type");
}
return cronExpression;
}
}
后面在需要调用的地方传入所需参数就可以返回定时任务id,至此就完成了自动创建定时任务。
Integer jobId =XxlJobUtil.addJob(dynamicJobService.insertXxlJobClient(xxlJob));