疯狂的狮子li
2021-10-20 aaa15b403a89490a33a45d68c8b7cfa34d87b175
add [重大更新]增加 ruoyi-job 任务调度模块(基于xxl-job)
已添加4个文件
已修改5个文件
509 ■■■■■ 文件已修改
pom.xml 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/pom.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application-dev.yml 53 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application-prod.yml 54 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-extend/ruoyi-xxl-job-admin/pom.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-job/pom.xml 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-job/src/main/java/com/ruoyi/job/config/XxlJobConfig.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-job/src/main/java/com/ruoyi/job/config/properties/XxlJobProperties.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-job/src/main/java/com/ruoyi/job/service/SampleService.java 254 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml
@@ -209,6 +209,13 @@
                <version>${lock4j.version}</version>
            </dependency>
            <!-- xxl-job-core -->
            <dependency>
                <groupId>com.xuxueli</groupId>
                <artifactId>xxl-job-core</artifactId>
                <version>${xxl-job-core-version}</version>
            </dependency>
            <dependency>
                <groupId>com.yomahub</groupId>
                <artifactId>tlog-spring-boot-configuration</artifactId>
@@ -237,10 +244,23 @@
                <version>${tlog.version}</version>
            </dependency>
            <dependency>
                <groupId>com.yomahub</groupId>
                <artifactId>tlog-xxl-job</artifactId>
                <version>${tlog.version}</version>
            </dependency>
            <!-- å®šæ—¶ä»»åŠ¡ @deprecated 3.5.0删除 è¿ç§»è‡³xxl-job -->
            <dependency>
                <groupId>com.ruoyi</groupId>
                <artifactId>ruoyi-quartz</artifactId>
                <version>${ruoyi-vue-plus.version}</version>
            </dependency>
            <!-- å®šæ—¶ä»»åŠ¡ -->
            <dependency>
                <groupId>com.ruoyi</groupId>
                <artifactId>ruoyi-job</artifactId>
                <version>${ruoyi-vue-plus.version}</version>
            </dependency>
@@ -294,6 +314,7 @@
        <module>ruoyi-framework</module>
        <module>ruoyi-system</module>
        <module>ruoyi-quartz</module>
        <module>ruoyi-job</module>
        <module>ruoyi-generator</module>
        <module>ruoyi-common</module>
        <module>ruoyi-demo</module>
ruoyi-admin/pom.xml
@@ -49,6 +49,11 @@
        <dependency>
            <groupId>com.ruoyi</groupId>
            <artifactId>ruoyi-job</artifactId>
        </dependency>
        <dependency>
            <groupId>com.ruoyi</groupId>
            <artifactId>ruoyi-oss</artifactId>
        </dependency>
ruoyi-admin/src/main/resources/application-dev.yml
@@ -1,4 +1,41 @@
# æ•°æ®æºé…ç½®
--- # ç›‘控配置
spring:
  boot:
    admin:
      # Spring Boot Admin Client å®¢æˆ·ç«¯çš„相关配置
      client:
        # å¢žåŠ å®¢æˆ·ç«¯å¼€å…³
        enabled: true
        # è®¾ç½® Spring Boot Admin Server åœ°å€
        url: http://localhost:9090/admin
        instance:
          prefer-ip: true # æ³¨å†Œå®žä¾‹æ—¶ï¼Œä¼˜å…ˆä½¿ç”¨ IP
        username: ruoyi
        password: 123456
--- # xxl-job é…ç½®
xxl:
  job:
    # è°ƒåº¦ä¸­å¿ƒåœ°å€ï¼šå¦‚调度中心集群部署存在多个地址则用逗号分隔。
    admin-addresses: http://localhost:9100/xxl-job-admin
    # æ‰§è¡Œå™¨é€šè®¯TOKEN:非空时启用
    access-token: xxl-job
    # æ‰§è¡Œå™¨é…ç½®
    executor:
      # æ‰§è¡Œå™¨AppName:执行器心跳注册分组依据;为空则关闭自动注册
      appname: xxl-job-executor
      # æ‰§è¡Œå™¨ç«¯å£å· æ‰§è¡Œå™¨ä»Ž9101开始往后写
      port: 9101
      # æ‰§è¡Œå™¨æ³¨å†Œï¼šé»˜è®¤IP:PORT
      address:
      # æ‰§è¡Œå™¨IP:默认自动获取IP
      ip:
      # æ‰§è¡Œå™¨è¿è¡Œæ—¥å¿—文件存储磁盘路径
      logpath: ./logs/xxl-job
      # æ‰§è¡Œå™¨æ—¥å¿—文件保存天数:大于3生效
      logretentiondays: 30
--- # æ•°æ®æºé…ç½®
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
@@ -179,17 +216,3 @@
#    # è®¢é˜…模式
#    subscriptionMode: "MASTER"
--- # ç›‘控配置
spring:
  boot:
    admin:
      # Spring Boot Admin Client å®¢æˆ·ç«¯çš„相关配置
      client:
        # å¢žåŠ å®¢æˆ·ç«¯å¼€å…³
        enabled: true
        # è®¾ç½® Spring Boot Admin Server åœ°å€
        url: http://localhost:9090/admin
        instance:
          prefer-ip: true # æ³¨å†Œå®žä¾‹æ—¶ï¼Œä¼˜å…ˆä½¿ç”¨ IP
        username: ruoyi
        password: 123456
ruoyi-admin/src/main/resources/application-prod.yml
@@ -1,4 +1,41 @@
# æ•°æ®æºé…ç½®
--- # ç›‘控配置
spring:
  boot:
    admin:
      # Spring Boot Admin Client å®¢æˆ·ç«¯çš„相关配置
      client:
        # å¢žåŠ å®¢æˆ·ç«¯å¼€å…³
        enabled: true
        # è®¾ç½® Spring Boot Admin Server åœ°å€
        url: http://172.30.0.90:9090/admin
        instance:
          prefer-ip: true # æ³¨å†Œå®žä¾‹æ—¶ï¼Œä¼˜å…ˆä½¿ç”¨ IP
        username: ruoyi
        password: 123456
--- # xxl-job é…ç½®
xxl:
  job:
    # è°ƒåº¦ä¸­å¿ƒåœ°å€ï¼šå¦‚调度中心集群部署存在多个地址则用逗号分隔。
    admin-addresses: http://172.30.0.92:9100/xxl-job-admin
    # æ‰§è¡Œå™¨é€šè®¯TOKEN:非空时启用
    access-token: xxl-job
    # æ‰§è¡Œå™¨é…ç½®
    executor:
      # æ‰§è¡Œå™¨AppName:执行器心跳注册分组依据;为空则关闭自动注册
      appname: xxl-job-executor
      # æ‰§è¡Œå™¨ç«¯å£å· æ‰§è¡Œå™¨ä»Ž9101开始往后写
      port: 9101
      # æ‰§è¡Œå™¨æ³¨å†Œï¼šé»˜è®¤IP:PORT
      address:
      # æ‰§è¡Œå™¨IP:默认自动获取IP
      ip:
      # æ‰§è¡Œå™¨è¿è¡Œæ—¥å¿—文件存储磁盘路径
      logpath: ./logs/xxl-job
      # æ‰§è¡Œå™¨æ—¥å¿—文件保存天数:大于3生效
      logretentiondays: 30
--- # æ•°æ®æºé…ç½®
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
@@ -178,18 +215,3 @@
#    readMode: "SLAVE"
#    # è®¢é˜…模式
#    subscriptionMode: "MASTER"
--- # ç›‘控配置
spring:
  boot:
    admin:
      # Spring Boot Admin Client å®¢æˆ·ç«¯çš„相关配置
      client:
        # å¢žåŠ å®¢æˆ·ç«¯å¼€å…³
        enabled: true
        # è®¾ç½® Spring Boot Admin Server åœ°å€
        url: http://172.30.0.90:9090/admin
        instance:
          prefer-ip: true # æ³¨å†Œå®žä¾‹æ—¶ï¼Œä¼˜å…ˆä½¿ç”¨ IP
        username: ruoyi
        password: 123456
ruoyi-extend/ruoyi-xxl-job-admin/pom.xml
@@ -75,7 +75,6 @@
        <dependency>
            <groupId>com.xuxueli</groupId>
            <artifactId>xxl-job-core</artifactId>
            <version>${xxl-job-core-version}</version>
        </dependency>
    </dependencies>
ruoyi-job/pom.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>ruoyi-vue-plus</artifactId>
        <groupId>com.ruoyi</groupId>
        <version>3.2.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <packaging>jar</packaging>
    <artifactId>ruoyi-job</artifactId>
    <description>
        ä»»åŠ¡è°ƒåº¦
    </description>
    <dependencies>
        <!-- é€šç”¨å·¥å…·-->
        <dependency>
            <groupId>com.ruoyi</groupId>
            <artifactId>ruoyi-common</artifactId>
        </dependency>
        <!-- xxl-job-core -->
        <dependency>
            <groupId>com.xuxueli</groupId>
            <artifactId>xxl-job-core</artifactId>
        </dependency>
        <dependency>
            <groupId>com.yomahub</groupId>
            <artifactId>tlog-xxl-job</artifactId>
        </dependency>
    </dependencies>
</project>
ruoyi-job/src/main/java/com/ruoyi/job/config/XxlJobConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,44 @@
package com.ruoyi.job.config;
import com.ruoyi.job.config.properties.XxlJobProperties;
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import com.yomahub.tlog.springboot.lifecircle.TLogXxljobEnhanceInit;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
/**
 * xxl-job config
 *
 * @author Lion Li
 */
@Slf4j
@EnableConfigurationProperties(XxlJobProperties.class)
@AllArgsConstructor
public class XxlJobConfig {
    private final XxlJobProperties xxlJobProperties;
    @Bean
    public XxlJobSpringExecutor xxlJobExecutor() {
        log.info(">>>>>>>>>>> xxl-job config init.");
        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
        xxlJobSpringExecutor.setAdminAddresses(xxlJobProperties.getAdminAddresses());
        xxlJobSpringExecutor.setAccessToken(xxlJobProperties.getAccessToken());
        XxlJobProperties.Executor executor = xxlJobProperties.getExecutor();
        xxlJobSpringExecutor.setAppname(executor.getAppname());
        xxlJobSpringExecutor.setAddress(executor.getAddress());
        xxlJobSpringExecutor.setIp(executor.getIp());
        xxlJobSpringExecutor.setPort(executor.getPort());
        xxlJobSpringExecutor.setLogPath(executor.getLogPath());
        xxlJobSpringExecutor.setLogRetentionDays(executor.getLogRetentionDays());
        return xxlJobSpringExecutor;
    }
    @Bean
    public TLogXxljobEnhanceInit tLogXxljobEnhanceInit(){
        return new TLogXxljobEnhanceInit();
    }
}
ruoyi-job/src/main/java/com/ruoyi/job/config/properties/XxlJobProperties.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,38 @@
package com.ruoyi.job.config.properties;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
 * xxljob配置类
 *
 * @author Lion Li
 */
@Data
@ConfigurationProperties(prefix = "xxl.job")
public class XxlJobProperties {
    private String adminAddresses;
    private String accessToken;
    private Executor executor;
    @Data
    @NoArgsConstructor
    public static class Executor {
        private String appname;
        private String address;
        private String ip;
        private int port;
        private String logPath;
        private int logRetentionDays;
    }
}
ruoyi-job/src/main/java/com/ruoyi/job/service/SampleService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,254 @@
package com.ruoyi.job.service;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
/**
 * XxlJob开发示例(Bean模式)
 * <p>
 * å¼€å‘步骤:
 * 1、任务开发:在Spring Bean实例中,开发Job方法;
 * 2、注解配置:为Job方法添加注解 "@XxlJob(value="自定义jobhandler名称", init = "JobHandler初始化方法", destroy = "JobHandler销毁方法")",注解value值对应的是调度中心新建任务的JobHandler属性的值。
 * 3、执行日志:需要通过 "XxlJobHelper.log" æ‰“印执行日志;
 * 4、任务结果:默认任务结果为 "成功" çŠ¶æ€ï¼Œä¸éœ€è¦ä¸»åŠ¨è®¾ç½®ï¼›å¦‚æœ‰è¯‰æ±‚ï¼Œæ¯”å¦‚è®¾ç½®ä»»åŠ¡ç»“æžœä¸ºå¤±è´¥ï¼Œå¯ä»¥é€šè¿‡ "XxlJobHelper.handleFail/handleSuccess" è‡ªä¸»è®¾ç½®ä»»åŠ¡ç»“æžœï¼›
 *
 * @author xuxueli 2019-12-11 21:52:51
 */
@Slf4j
@Service
public class SampleService {
    /**
     * 1、简单任务示例(Bean模式)
     */
    @XxlJob("demoJobHandler")
    public void demoJobHandler() throws Exception {
        XxlJobHelper.log("XXL-JOB, Hello World.");
        for (int i = 0; i < 5; i++) {
            XxlJobHelper.log("beat at:" + i);
            TimeUnit.SECONDS.sleep(2);
        }
        // default success
    }
    /**
     * 2、分片广播任务
     */
    @XxlJob("shardingJobHandler")
    public void shardingJobHandler() throws Exception {
        // åˆ†ç‰‡å‚æ•°
        int shardIndex = XxlJobHelper.getShardIndex();
        int shardTotal = XxlJobHelper.getShardTotal();
        XxlJobHelper.log("分片参数:当前分片序号 = {}, æ€»åˆ†ç‰‡æ•° = {}", shardIndex, shardTotal);
        // ä¸šåŠ¡é€»è¾‘
        for (int i = 0; i < shardTotal; i++) {
            if (i == shardIndex) {
                XxlJobHelper.log("第 {} ç‰‡, å‘½ä¸­åˆ†ç‰‡å¼€å§‹å¤„理", i);
            } else {
                XxlJobHelper.log("第 {} ç‰‡, å¿½ç•¥", i);
            }
        }
    }
    /**
     * 3、命令行任务
     */
    @XxlJob("commandJobHandler")
    public void commandJobHandler() throws Exception {
        String command = XxlJobHelper.getJobParam();
        int exitValue = -1;
        BufferedReader bufferedReader = null;
        try {
            // command process
            ProcessBuilder processBuilder = new ProcessBuilder();
            processBuilder.command(command);
            processBuilder.redirectErrorStream(true);
            Process process = processBuilder.start();
            //Process process = Runtime.getRuntime().exec(command);
            BufferedInputStream bufferedInputStream = new BufferedInputStream(process.getInputStream());
            bufferedReader = new BufferedReader(new InputStreamReader(bufferedInputStream));
            // command log
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                XxlJobHelper.log(line);
            }
            // command exit
            process.waitFor();
            exitValue = process.exitValue();
        } catch (Exception e) {
            XxlJobHelper.log(e);
        } finally {
            if (bufferedReader != null) {
                bufferedReader.close();
            }
        }
        if (exitValue == 0) {
            // default success
        } else {
            XxlJobHelper.handleFail("command exit value(" + exitValue + ") is failed");
        }
    }
    /**
     * 4、跨平台Http任务
     * å‚数示例:
     * "url: http://www.baidu.com\n" +
     * "method: get\n" +
     * "data: content\n";
     */
    @XxlJob("httpJobHandler")
    public void httpJobHandler() throws Exception {
        // param parse
        String param = XxlJobHelper.getJobParam();
        if (param == null || param.trim().length() == 0) {
            XxlJobHelper.log("param[" + param + "] invalid.");
            XxlJobHelper.handleFail();
            return;
        }
        String[] httpParams = param.split("\n");
        String url = null;
        String method = null;
        String data = null;
        for (String httpParam : httpParams) {
            if (httpParam.startsWith("url:")) {
                url = httpParam.substring(httpParam.indexOf("url:") + 4).trim();
            }
            if (httpParam.startsWith("method:")) {
                method = httpParam.substring(httpParam.indexOf("method:") + 7).trim().toUpperCase();
            }
            if (httpParam.startsWith("data:")) {
                data = httpParam.substring(httpParam.indexOf("data:") + 5).trim();
            }
        }
        // param valid
        if (url == null || url.trim().length() == 0) {
            XxlJobHelper.log("url[" + url + "] invalid.");
            XxlJobHelper.handleFail();
            return;
        }
        if (method == null || !Arrays.asList("GET", "POST").contains(method)) {
            XxlJobHelper.log("method[" + method + "] invalid.");
            XxlJobHelper.handleFail();
            return;
        }
        boolean isPostMethod = method.equals("POST");
        // request
        HttpURLConnection connection = null;
        BufferedReader bufferedReader = null;
        try {
            // connection
            URL realUrl = new URL(url);
            connection = (HttpURLConnection) realUrl.openConnection();
            // connection setting
            connection.setRequestMethod(method);
            connection.setDoOutput(isPostMethod);
            connection.setDoInput(true);
            connection.setUseCaches(false);
            connection.setReadTimeout(5 * 1000);
            connection.setConnectTimeout(3 * 1000);
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
            connection.setRequestProperty("Accept-Charset", "application/json;charset=UTF-8");
            // do connection
            connection.connect();
            // data
            if (isPostMethod && data != null && data.trim().length() > 0) {
                DataOutputStream dataOutputStream = new DataOutputStream(connection.getOutputStream());
                dataOutputStream.write(data.getBytes("UTF-8"));
                dataOutputStream.flush();
                dataOutputStream.close();
            }
            // valid StatusCode
            int statusCode = connection.getResponseCode();
            if (statusCode != 200) {
                throw new RuntimeException("Http Request StatusCode(" + statusCode + ") Invalid.");
            }
            // result
            bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
            StringBuilder result = new StringBuilder();
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                result.append(line);
            }
            String responseMsg = result.toString();
            XxlJobHelper.log(responseMsg);
            return;
        } catch (Exception e) {
            XxlJobHelper.log(e);
            XxlJobHelper.handleFail();
            return;
        } finally {
            try {
                if (bufferedReader != null) {
                    bufferedReader.close();
                }
                if (connection != null) {
                    connection.disconnect();
                }
            } catch (Exception e2) {
                XxlJobHelper.log(e2);
            }
        }
    }
    /**
     * 5、生命周期任务示例:任务初始化与销毁时,支持自定义相关逻辑;
     */
    @XxlJob(value = "demoJobHandler2", init = "init", destroy = "destroy")
    public void demoJobHandler2() throws Exception {
        XxlJobHelper.log("XXL-JOB, Hello World.");
    }
    public void init() {
        log.info("init");
    }
    public void destroy() {
        log.info("destory");
    }
}