动态执行任务,可以添加和修改任务

构建数据库表格(Oracle)

CREATE TABLE SCENE_REPTILE_CONFIG (
	EVENT_TYPE VARCHAR2(16) NULL,
	WEB_NAME VARCHAR2(100) NULL,
	WEB_URL VARCHAR2(100) NULL,
	AREA VARCHAR2(255) NULL,
	KEYWORD VARCHAR2(255) NULL,
	START_TIME VARCHAR2(100) NULL,
	END_TIME VARCHAR2(100) NULL,
	RUN_TIME VARCHAR2(64) NULL,
	IS_ENABLE VARCHAR2(16) NULL,
	UUID VARCHAR2(100) NOT NULL,
	UPDATE_TIME VARCHAR2(100) NOT NULL,
	START_TIME_REAL VARCHAR2(100) NULL,
	END_TIME_REAL VARCHAR2(100) NULL,
	RUN_TIME_REAL VARCHAR2(100) NULL
);

pom文件用到的依赖

 <dependencies>
        <!-- spring boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.6.6</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.26</version>
        </dependency>
        <!--httpclient-->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.12</version>
        </dependency>
        <!--json-->
        <dependency>
            <groupId>net.sf.json-lib</groupId>
            <artifactId>json-lib</artifactId>
            <classifier>jdk15</classifier>
            <version>2.4</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.5.1</version>
            <scope>compile</scope>
        </dependency>
        <!--Java HTML解析库-->
        <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.10.2</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>4.10.0</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.3.1</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        <dependency>
            <groupId>com.oracle.ojdbc</groupId>
            <artifactId>ojdbc8</artifactId>
            <version>19.3.0.0</version>
        </dependency>
        <!-- 解决报错:java.sql.SQLException: 不支持的字符集 (在类路径中添加 orai18n.jar): ZHS16GBK-->
        <dependency>
            <groupId>com.oracle.database.nls</groupId>
            <artifactId>orai18n</artifactId>
            <version>19.3.0.0</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.70</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

application.properties

# 配置数据库驱动,这里用的是Oracle数据库
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
# 连接数据库的地址
spring.datasource.url=jdbc:oracle:thin:@ip:端口号:服务名
spring.datasource.username=用户名
spring.datasource.password=密码

# 配置日志文件位置
logging.file.name=./log/aaa.log
# 配置日志文件大小
logging.file.max-size=300MB

实体类

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.io.Serializable;

/**
 * @version 1.0
 * @date 2024/4/15 9:34
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@TableName(value ="SCENE_REPTILE_CONFIG")
public class SceneReptileConfig implements Serializable {

    /**
     * 唯一id
     */
    @TableField(value = "UUID")
    private String uuid;

    /**
     * 事件类型
     */
    @ColumnWidth(15)
    @ExcelProperty(value = { "事件类型" }, index = 0)
    @TableField(value = "EVENT_TYPE")
    private String eventType;

    /**
     * 网址中文名称
     */
    @ColumnWidth(15)
    @ExcelProperty(value = { "网站名称" }, index = 1)
    @TableField(value = "WEB_NAME")
    private String webName;

    /**
     * 网址url
     */
    @ColumnWidth(15)
    @ExcelProperty(value = { "网址URL" }, index = 2)
    @TableField(value = "WEB_URL")
    private String webUrl;

    /**
     * 区域
     */
    @ColumnWidth(15)
    @ExcelProperty(value = { "区域" }, index = 3)
    @TableField(value = "AREA")
    private String area;

    /**
     * 关键字
     */
    @ColumnWidth(15)
    @ExcelProperty(value = { "关键字" }, index = 4)
    @TableField(value = "KEYWORD")
    private String keyword;

    /**
     * 任务开始时间(前端展示配置 ) 0:立即执行
     */
    @ColumnWidth(15)
    @ExcelProperty(value = { "任务开始时间" }, index = 5)
    @TableField(value = "START_TIME")
    private String startTime;

    /**
     * 任务结束时间(前端展示配置 )0:无限期
     */
    @TableField(value = "END_TIME")
    private String endTime;

    /**
     * 执行频率(前端展示配置 )0:只执行一次
     */
    @TableField(value = "RUN_TIME")
    private String runTime;

    /**
     * 是否启用:1 启用、0 禁止
     */
    @ColumnWidth(15)
    @ExcelProperty(value = { "操作: 1 启用、0 禁止" }, index = 6)
    @TableField(value = "IS_ENABLE")
    private String isEnable;

    /**
     * 更新时间
     */

    @TableField(value = "UPDATE_TIME")
    private String updateTime;

    /**
     * 任务开始时间(后端实际读取配置)
     */
    @TableField(value = "START_TIME_REAL")
    private String startTimeReal;

    /**
     * 任务结束时间(后端实际读取配置)
     */
    @TableField(value = "END_TIME_REAL")
    private String endTimeReal;

    /**
     * 执行频率(后端实际读取配置)必须是cron表达式
     */
    @TableField(value = "RUN_TIME_REAL")
    private String runTimeReal;

}

自定义线程池

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @version 1.0
 * @date 2024/4/22 18:57
 */
@Configuration
public class DynamicScheduledPoolConfig {
    private static int poolSize = 10;//核心线程
    private static int awaitTermination = 60;//挂壁时间
    private static String threadNamePrefix = "task-job-";
    private static volatile ThreadPoolTaskScheduler taskScheduler;
    @Bean
    public static Executor getMySchedulerPool(){
        if(taskScheduler == null){
            synchronized (ThreadPoolTaskScheduler.class){
                if(taskScheduler == null){
                    taskScheduler = new ThreadPoolTaskScheduler();
                    taskScheduler.setThreadNamePrefix(threadNamePrefix);
                    taskScheduler.setPoolSize(poolSize);
//                    taskScheduler.setThreadFactory(Executors.defaultThreadFactory());
                    taskScheduler.setAwaitTerminationSeconds(awaitTermination);//演示挂壁的时间为60s
                    /**设置为false,关闭线程池中的任务时,直接执行shutdownNow() 延时关闭 开启*/
                    taskScheduler.setWaitForTasksToCompleteOnShutdown(true);
                    taskScheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());//线程池对拒绝任务(无线程可用的)的处理策略
                    taskScheduler.initialize();//初始化
                }
            }
        }
        return taskScheduler;
    }

}

mapper

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;
import java.util.Map;

/**
 * @version 1.0
 * @date 2024/4/15 9:55
 */
@Mapper
public interface SceneReptileConfigMapper extends BaseMapper<SceneReptileConfig> {
}

读取数据库中的配置并执行定时任务

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.config.TriggerTask;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;

@EnableScheduling
@Component
public class SchedulingConfigure implements SchedulingConfigurer {

    Logger logger = LoggerFactory.getLogger(this.getClass());

    // 用于存储所有计划任务及其相关信息 key:任务的UUID value:ScheduledTaskWrapper对象
    private final Map<String, ScheduledTaskWrapper> taskMap = new HashMap<>();

    // 任务注册器,用于注册和管理计划任务
    private ScheduledTaskRegistrar taskRegistrar;

    @Autowired
    private SceneReptileConfigMapper sceneReptileConfigMapper;

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        this.taskRegistrar = taskRegistrar;
        // 使用自定义的线程池
        taskRegistrar.setScheduler(DynamicScheduledPoolConfig.getMySchedulerPool());

        // 初始化任务配置
        List<SceneReptileConfig> configList = sceneReptileConfigMapper.selectList(null);
        // 根据配置情况执行对应的任务
        for (SceneReptileConfig config : configList) {
            addOrUpdateTask(config);
        }
    }

    @Scheduled(fixedRate = 60000) // 每分钟检查一次数据库
    public void checkForNewTasks() {
        List<SceneReptileConfig> newConfigList = sceneReptileConfigMapper.selectList(null);
        for (SceneReptileConfig newConfig : newConfigList) {
            addOrUpdateTask(newConfig);
        }
    }

    // 保存和管理ScheduledTaskWrapper实例
    private void addOrUpdateTask(SceneReptileConfig config) {
        String taskId = config.getUuid();
        ScheduledTaskWrapper existingTask = taskMap.get(taskId);
        // 如果任务列表为空或者发生变化
        if (existingTask == null || isConfigChanged(existingTask.getConfig(), config)) {
            if (existingTask != null) {
                // 取消现有任务,如果任务正在执行,中断并移除该任务
                existingTask.getFuture().cancel(true);
                taskMap.remove(taskId);
            }
            // 创建一个新的Runnable任务,该任务会调用doMyJob方法并传入新的配置
            Runnable taskRunnable = () -> doMyJob(config);
            Trigger trigger = createTrigger(config);
            TriggerTask newTask = new TriggerTask(taskRunnable, trigger);
            // 使用任务注册器的调度器来调度新的任务
            ScheduledFuture<?> future = taskRegistrar.getScheduler().schedule(newTask.getRunnable(), newTask.getTrigger());
            // 将新的任务和配置以及ScheduledFuture一起添加到任务映射中
            taskMap.put(taskId, new ScheduledTaskWrapper(newTask, config, future));
        }
    }

    // 在这里处理具体的业务逻辑
    private void doMyJob(SceneReptileConfig config) {
        // 如果当前任务时间在范围内
        if(isCurrentDateBetween(config.getStartTimeReal(),config.getEndTimeReal())){
            // 是否启用:1启用 0禁用 2启用一次
            if ("1".equals(config.getIsEnable())) {
                useWhichTask(config);
            }else if("0".equals(config.getRunTime())){
                useWhichTask(config);
                // 执行完一次之后,停止该规则
                QueryWrapper<SceneReptileConfig> queryWrapper = new QueryWrapper();
                queryWrapper.eq("UUID",config.getUuid());
                config.setIsEnable("0");
                sceneReptileConfigMapper.update(config,queryWrapper);
            }
        }
    }

    // 执行具体事件类型任务
    private void useWhichTask(SceneReptileConfig config){
        switch (config.getEventType()) {
            case "A任务":
               // 执行对应的任务
                break;
            case "B任务":
                //  执行对应的任务
                break;
            case "C任务":
                //  执行对应的任务
                break;
            default:
                // 未知事件类型
                logger.info("未知事件类型:" + config.getEventType());
                break;
        }
    }
    
    // 检查当前日期是否在开始日期和结束日期之间
    private boolean isCurrentDateBetween(String startTime, String endTime) {
        // 定义日期格式
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

        // 将字符串转换为LocalDate对象
        LocalDate start = LocalDate.parse(startTime, formatter);
        LocalDate end = LocalDate.parse(endTime, formatter);
        LocalDate today = LocalDate.now(); // 获取当前日期

        // 如果结束日期是今天的日期,也认为今天在范围内
        return !today.isBefore(start) && !today.isAfter(end);
    }

    // 判断配置信息是否有变化
    private boolean isConfigChanged(SceneReptileConfig oldConfig, SceneReptileConfig newConfig) {
        return !oldConfig.getUpdateTime().equals(newConfig.getUpdateTime());
    }


    // 允许在实例化时传递ScheduledFuture对象
    private static class ScheduledTaskWrapper {
        private final TriggerTask task;
        private final SceneReptileConfig config;
        private final ScheduledFuture<?> future;

        public ScheduledTaskWrapper(TriggerTask task, SceneReptileConfig config, ScheduledFuture<?> future) {
            this.task = task;
            this.config = config;
            this.future = future;
        }

        public TriggerTask getTask() {
            return task;
        }

        public SceneReptileConfig getConfig() {
            return config;
        }

        public ScheduledFuture<?> getFuture() {
            return future;
        }
    }

    private Trigger createTrigger(SceneReptileConfig config) {
        return new CronTrigger(config.getRunTimeReal());
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值