xxljob集成到Java动态创建定时任务

参考了下面两位博主发布的文章,整合后更新更完整的流程

博主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));

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值