SpringBoot定时任务动态管理

1.作用

        无需对系统进行启停操作即可对定时任务进行增删改、启停和重启,对比于只使用了@Scheduled标签的定时任务更灵活多变。不推荐任务集群时使用,会重复执行。

2.前期准备

2.1 创建定时任务表

create table `cj_scheduled_task` (
  `id` varchar(50) primary key comment '主键id',
  `task_class` varchar(100) not null comment '定时任务完整类名',
  `cron_expression` varchar(20) not null comment 'cron表达式',
  `task_explain` varchar(200) default null comment '任务描述',
  `status` tinyint(1) not null comment '状态:1.启用;2.停用',
  `create_by` varchar(50) comment '创建人',
  `create_time` datetime comment '创建时间',
  `update_by` varchar(50) comment '修改人',
  `update_time` datetime comment '修改时间'
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COMMENT = '定时任务';

说明:

(1)表名自定义,一般开头为业务系统名

(2)task_class:为定时任务全路径,具体到类名

2.2 定时任务实体类

package com.trasen.modules.chunjun.desreport.entity.scheduled;

import java.io.Serializable;
import java.util.Date;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * 定时任务配置
 * 
 * @author xxx
 * @date: 2021-11-29 10:49:35
 * @Copyright: Copyright (c) 2006 - 2021
 * @Company: xxx公司
 * @Version: V1.0
 */
@TableName("cj_scheduled_task")
@Data
public class ScheduledTask implements Serializable {
    private static final long serialVersionUID = 1L;

    /** 主键id */
    @TableId(type = IdType.ASSIGN_UUID)
    @ApiModelProperty(value = "主键id")
    private String id;

    /** 定时任务完整类名 */
    @TableField("task_class")
    @ApiModelProperty(value = "定时任务完整类名")
    private String taskClass;

    /** cron表达式 */
    @TableField("cron_expression")
    @ApiModelProperty(value = "cron表达式")
    private String cronExpression;

    /** 任务描述 */
    @TableField("task_explain")
    @ApiModelProperty(value = "任务描述")
    private String taskExplain;

    /** 状态:1.启用;2.停用 */
    @TableField("status")
    @ApiModelProperty(value = "状态:1.启用;2.停用")
    private int status;

    /** 创建人. */
    @TableField("create_by")
    @ApiModelProperty(value = "创建人")
    private String createBy;

    /** 创建时间. */
    @TableField("create_time")
    @ApiModelProperty(value = "创建时间")
    private Date createTime;

    /** 修改人. */
    @TableField("update_by")
    @ApiModelProperty(value = "修改人")
    private String updateBy;

    /** 修改时间. */
    @TableField("update_time")
    @ApiModelProperty(value = "修改时间")
    private Date updateTime;
}

3.实现代码

3.1 线程池配置类:ScheduledConfig

package com.trasen.modules.chunjun.config.scheduled;

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

import lombok.extern.slf4j.Slf4j;

/**
 * 定时任务线程池配置类
 * 
 * @author xxx
 * @date: 2021-12-01 15:46:06
 * @Copyright: Copyright (c) 2006 - 2021
 * @Company: xxx公司
 * @Version: V1.0
 */
@Configuration
@Slf4j
public class ScheduledConfig {

    @Bean
    public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
        log.info("创建定时任务调度线程池 start");
        ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
        // 线程池大小
        threadPoolTaskScheduler.setPoolSize(20);
        // 线程名前缀
        threadPoolTaskScheduler.setThreadNamePrefix("taskExecutor-");
        // 停止系统时等待任务完成
        threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true);
        // 等待停止时间:秒
        threadPoolTaskScheduler.setAwaitTerminationSeconds(60);
        log.info("创建定时任务调度线程池 end");
        return threadPoolTaskScheduler;
    }
}

3.2 定时任务服务接口类

package com.trasen.modules.chunjun.desreport.service;

import java.util.List;

import com.trasen.modules.chunjun.common.vo.Result;
import com.trasen.modules.chunjun.desreport.entity.scheduled.ScheduledTask;
import com.trasen.modules.chunjun.desreport.model.ScheduledTaskVO;

/**
 * 定时任务服务接口
 * 
 * @author xxx
 * @date: 2021-11-29 14:15:31
 * @Copyright: Copyright (c) 2006 - 2021
 * @Company: xxx公司
 * @Version: V1.0
 */
public interface IScheduledTaskService {

    /**
     * 查询所有:生成定时任务所需
     * 
     * @author: xxx
     * @date: 2021-11-30 10:45:49
     * @return
     */
    List<ScheduledTask> listAll();

    /**
     * 根据完整类名分页模糊查询
     * 
     * @author: xxx
     * @date: 2021-11-30 10:45:19
     * @param taskClass 完整类名
     * @return
     */
    Result<?> pageList(String taskClass);

    /**
     * 根据id查询
     * 
     * @author: xxx
     * @date: 2021-11-30 10:56:53
     * @param id
     * @return
     */
    ScheduledTask getById(String id);

    /**
     * 根据完整类名查询
     * 
     * @author: xxx
     * @date: 2021-11-30 10:56:53
     * @param taskClass 完整类名
     * @return
     */
    ScheduledTask findByTaskClass(String taskClass);

    /**
     * 执行
     * 
     * @author: xxx
     * @date: 2021-11-30 14:15:11
     * @param id 任务id
     */
    Result<?> start(String id);

    /**
     * 停止
     * 
     * @author: xxx
     * @date: 2021-12-01 16:09:47
     * @param id
     * @return
     */
    Result<?> stop(String id);

    /**
     * 重启
     * 
     * @author: xxx
     * @date: 2021-11-30 14:15:11
     * @param id 任务id
     */
    Result<?> restart(String id);

    /**
     * 更新
     * 
     * @author: xxx
     * @date: 2021-11-30 14:25:59
     * @param id
     * @return
     */
    Result<?> update(ScheduledTaskVO st);

    /**
     * 新增
     * 
     * @author: xxx
     * @date: 2021-12-01 14:44:32
     * @param st
     * @return
     */
    Result<?> insert(ScheduledTaskVO st);

    /**
     * 初始化定时任务
     * 
     * @author: xxx
     * @date: 2021-12-01 15:51:43
     */
    void initTask();
}

3.3 定时任务服务实现类

package com.trasen.modules.chunjun.desreport.service.impl;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.locks.ReentrantLock;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.trasen.modules.chunjun.common.base.query.CriteriaQuery;
import com.trasen.modules.chunjun.common.base.service.BaseServiceImpl;
import com.trasen.modules.chunjun.common.util.LoginUserUtil;
import com.trasen.modules.chunjun.common.vo.Result;
import com.trasen.modules.chunjun.desreport.entity.scheduled.ScheduledTask;
import com.trasen.modules.chunjun.desreport.entity.scheduled.ScheduledTaskReport;
import com.trasen.modules.chunjun.desreport.mapper.ScheduledTaskMapper;
import com.trasen.modules.chunjun.desreport.model.ScheduledTaskVO;
import com.trasen.modules.chunjun.desreport.service.IScheduledTaskReportService;
import com.trasen.modules.chunjun.desreport.service.IScheduledTaskService;
import com.trasen.modules.chunjun.desreport.task.ScheduledOfTask;

import cn.hutool.extra.spring.SpringUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * 定时任务服务实现
 * 
 * @author xxx
 * @date: 2021-11-29 14:58:08
 * @Copyright: Copyright (c) 2006 - 2021
 * @Company: xxx公司
 * @Version: V1.0
 */
@Slf4j
@Service
public class ScheduledTaskServiceImpl extends BaseServiceImpl<ScheduledTaskMapper, ScheduledTask>
    implements IScheduledTaskService {

    /** 可重入锁 */
    private ReentrantLock lock = new ReentrantLock();

    /** 定时任务线程池 */
    @Autowired
    private ThreadPoolTaskScheduler threadPoolTaskScheduler;
    @Autowired
    private IScheduledTaskReportService strService;

    /** 启动状态的定时任务集合 */
    public Map<String, ScheduledFuture> scheduledFutureMap = new ConcurrentHashMap<String, ScheduledFuture>();

    @Override
    public List<ScheduledTask> listAll() {
        return this.list();
    }

    @Override
    public Result<?> pageList(String taskClass) {
        CriteriaQuery<ScheduledTask> cq = new CriteriaQuery<ScheduledTask>(this.request);
        cq.like(null != taskClass && !taskClass.isEmpty(), ScheduledTask::getTaskClass, taskClass);
        return this.pageList(cq);
    }

    @Override
    public ScheduledTask getById(String id) {
        return super.getById(id);
    }

    @Override
    public ScheduledTask findByTaskClass(String taskClass) {
        LambdaQueryWrapper<ScheduledTask> queryWrapper = new LambdaQueryWrapper<ScheduledTask>();
        queryWrapper.eq(ScheduledTask::getTaskClass, taskClass);
        return super.getOne(queryWrapper);
    }

    @Override
    public Result<?> start(String id) {
        // 根据id查询任务
        ScheduledTask task = super.getById(id);
        // 判断任务是否启用
        if (1 != task.getStatus()) {
            return Result.error("定时任务未启用,无法执行!");
        }
        String taskClass = task.getTaskClass();
        log.info("启动定时任务:" + taskClass);
        // 添加锁放一个线程启动,防止多人启动多次
        lock.lock();
        log.info("加锁完成");
        try {
            if (this.isStart(id)) {
                String msg = "当前任务在启动状态中";
                log.info(msg);
                return Result.error(msg);
            }
            // 任务启动
            this.doStartTask(task);
        } finally {
            lock.unlock();
            log.info("解锁完毕");
        }
        return Result.OK();
    }

    @Override
    public Result<?> stop(String id) {
        // 根据id查询任务
        log.info("停止任务: " + id);
        boolean flag = scheduledFutureMap.containsKey(id);
        log.info("当前实例是否存在 " + flag);
        if (flag) {
            ScheduledFuture scheduledFuture = scheduledFutureMap.get(id);
            scheduledFuture.cancel(true);
            scheduledFutureMap.remove(id);
        } else {
            String msg = "当前任务不存在!";
            log.info(msg + id);
            Result.error(msg);
        }
        return Result.OK();
    }

    @Override
    public Result<?> restart(String id) {
        log.info("重启定时任务:" + id);
        // 停止
        this.stop(id);
        // 启动
        return this.start(id);
    }

    @Override
    public Result<?> update(ScheduledTaskVO st) {
        // 赋值更新人、更新时间
        st.setUpdateBy(LoginUserUtil.getCurrentUserId());
        st.setUpdateTime(new Date());
        if (!super.updateById(st)) {
            String msg = "新增失败!";
            log.error(msg);
            return Result.error(msg);
        }
        // 批量保存关联数据
        JSONObject params = new JSONObject();
        List<ScheduledTaskReport> strList = st.getStrList();
        if (null != strList && !strList.isEmpty()) {
            // 赋值任务id
            for (ScheduledTaskReport str : strList) {
                str.setTaskId(st.getId());
            }
        }
        params.put("strList", strList);
        params.put("ids", st.getIds());
        if (!strService.saveBatch(params).isSuccess()) {
            String msg = "批量保存失败!";
            log.error(msg);
            return Result.error(msg);
        }
        return Result.OK();
    }

    @Override
    @Transactional
    public Result<?> insert(ScheduledTaskVO st) {
        // 赋值创建人、创建时间
        st.setCreateBy(LoginUserUtil.getCurrentUserId());
        st.setCreateTime(new Date());
        // 新增定时任务
        if (!super.save(st)) {
            String msg = "新增失败!";
            log.error(msg);
            return Result.error(msg);
        }
        // 新增关联数据
        JSONObject params = new JSONObject();
        List<ScheduledTaskReport> strList = st.getStrList();
        if (null != strList && !strList.isEmpty()) {
            // 赋值任务id
            for (ScheduledTaskReport str : strList) {
                str.setTaskId(st.getId());
            }
            params.put("strList", strList);
            if (!strService.saveBatch(params).isSuccess()) {
                String msg = "批量新增失败!";
                log.error(msg);
                return Result.error(msg);
            }
        }
        return Result.OK();
    }

    @Override
    public void initTask() {
        List<ScheduledTask> stList = super.list();
        if (null != stList && !stList.isEmpty()) {
            // 循环所有定时任务
            for (ScheduledTask st : stList) {
                // 判断是否启用
                if (1 != st.getStatus()) {
                    continue;
                }
                // 执行任务
                doStartTask(st);
            }
        }
    }

    /**
     * 执行启动任务
     * 
     * @author: xxx
     * @date: 2021-12-01 16:16:31
     * @param scheduledTask 定时任务
     */
    private void doStartTask(ScheduledTask scheduledTask) {
        String taskClass = scheduledTask.getTaskClass();
        log.info(taskClass);
        if (1 != scheduledTask.getStatus()) {
            return;
        }
        Class clazz;
        ScheduledOfTask task;
        try {
            clazz = Class.forName(scheduledTask.getTaskClass());
            task = (ScheduledOfTask)SpringUtil.getBean(clazz);
        } catch (ClassNotFoundException e) {
            throw new IllegalArgumentException("【" + taskClass + "】类未找到!", e);
        }
        Assert.isAssignable(ScheduledOfTask.class, task.getClass(), "定时任务类必须实现ScheduledOfTask接口");
        ScheduledFuture scheduledFuture = threadPoolTaskScheduler.schedule(task,
            (triggerContext -> new CronTrigger(scheduledTask.getCronExpression()).nextExecutionTime(triggerContext)));
        scheduledFutureMap.put(scheduledTask.getId(), scheduledFuture);
    }

    /**
     * 检查任务是否已经启动
     * 
     * @author: xxx
     * @date: 2021-12-01 16:00:17
     * @param id 定时任务id
     * @return
     */
    private Boolean isStart(String id) {
        // 校验是否已经启动
        if (scheduledFutureMap.containsKey(id)) {
            if (!scheduledFutureMap.get(id).isCancelled()) {
                return true;
            }
        }
        return false;
    }
}

说明

BaseServiceImpl:为本业务系统服务实现基础类,请根据自身框架修改

3.4 定时任务控制器

用于页面操作,维护定时任务

package com.trasen.modules.chunjun.desreport.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.trasen.modules.chunjun.common.annotation.LoginRequired;
import com.trasen.modules.chunjun.common.vo.Result;
import com.trasen.modules.chunjun.desreport.model.ScheduledTaskVO;
import com.trasen.modules.chunjun.desreport.service.IScheduledTaskService;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

/**
 * 定时任务控制器
 * 
 * @author xxx
 * @date: 2021-11-30 13:50:20
 * @Copyright: Copyright (c) 2006 - 2021
 * @Company: xxx公司
 * @Version: V1.0
 */
@RestController
@RequestMapping("/chunjunreport/scheduled/task")
@Api(tags = "定时任务控制器")
public class ScheduledTaskController {

    @Autowired
    private IScheduledTaskService scheduledTaskService;

    @GetMapping({"/pageList"})
    @LoginRequired
    @ApiOperation(value = "【定时任务】分页查询", notes = "【定时任务】分页查询")
    public Result<?> pageList(@RequestParam(name = "taskClass", required = false) String taskClass) {
        return scheduledTaskService.pageList(taskClass);
    }

    @PostMapping({"/start"})
    @LoginRequired
    @ApiOperation(value = "【定时任务】运行", notes = "【定时任务】运行")
    public Result<?> start(@RequestParam(name = "id", required = true) String id) {
        return scheduledTaskService.start(id);
    }

    @PostMapping({"/stop"})
    @LoginRequired
    @ApiOperation(value = "【定时任务】停止", notes = "【定时任务】停止")
    public Result<?> stop(@RequestParam(name = "id", required = true) String id) {
        return scheduledTaskService.stop(id);
    }

    @PostMapping({"/restart"})
    @LoginRequired
    @ApiOperation(value = "【定时任务】重启", notes = "【定时任务】重启")
    public Result<?> restart(@RequestParam(name = "id", required = true) String id) {
        return scheduledTaskService.restart(id);
    }

    @PostMapping({"/update"})
    @LoginRequired
    @ApiOperation(value = "【定时任务】更新", notes = "【定时任务】更新")
    public Result<?> update(@RequestBody ScheduledTaskVO scheduledTask) {
        return scheduledTaskService.update(scheduledTask);
    }

    @PostMapping({"/insert"})
    @LoginRequired
    @ApiOperation(value = "【定时任务】新增", notes = "【定时任务】新增")
    public Result<?> insert(@RequestBody ScheduledTaskVO scheduledTask) {
        return scheduledTaskService.insert(scheduledTask);
    }
}

3.5 自定义定时任务接口

必须集成【Runnable】

package com.trasen.modules.chunjun.desreport.task;

import com.trasen.modules.chunjun.desreport.entity.scheduled.ScheduledTask;
import com.trasen.modules.chunjun.desreport.service.IScheduledTaskService;

import cn.hutool.extra.spring.SpringUtil;

/**
 * @author xxx
 * @date: 2021-11-30 17:06:13
 * @Copyright: Copyright (c) 2006 - 2021
 * @Company: xxx公司
 * @Version: V1.0
 */
public interface ScheduledOfTask extends Runnable {
    /**
     * 定时任务方法
     * 
     * @author: xxx
     * @date: 2021-12-01 11:47:00
     * @param id 任务id
     */
    void execute(String id);

    /**
     * 实现控制定时任务启用或禁用的功能
     */
    @Override
    default void run() {
        IScheduledTaskService stService = SpringUtil.getBean(IScheduledTaskService.class);
        ScheduledTask st = stService.findByTaskClass(this.getClass().getName());
        if (1 != st.getStatus()) {
            // 任务是禁用状态
            return;
        }
        execute(st.getId());
    }
}

3.6 自定义定时任务接口实现

package com.trasen.modules.chunjun.desreport.task;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSONObject;
import com.trasen.modules.chunjun.desreport.service.IReportExportExcel;

import lombok.extern.slf4j.Slf4j;

/**
 * 生成excel任务
 * 
 * @author xxx
 * @date: 2021-11-29 17:04:35
 * @Copyright: Copyright (c) 2006 - 2021
 * @Company: xxx公司
 * @Version: V1.0
 */
@Component
@Slf4j
public class GenerateExcelTask implements ScheduledOfTask {

    @Autowired
    private IReportExportExcel reportExportExcel;
    private int i;

    @Override
    public void execute(String id) {
        // jsonObject.put("excelConfigId", "1414416708285894656");
        // jsonObject.put("queryParam", JSONObject.parseObject("{\"id\":\"1414416708285894656\",\"pageNo\":1}"));
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("taskId", id);
        jsonObject.put("pageNo", 1);
        jsonObject.put("pageSize", 10000);
        reportExportExcel.generateExcelUploadMinIO(jsonObject);
        log.info("thread id:{},DynamicPrintTask execute times:{}", Thread.currentThread().getId(), ++i);
    }
}

实现类说明:

(1)方法名可自定义,一般根据需要实现的功能定义

(2)必须实现自定义定时任务接口:ScheduledOfTask

(3)execute方法中id为定时任务id,如果定时任务需要关联业务数据时使用

(4)IReportExportExcel为业务服务接口类

3.7 项目启动时初始化定时任务

用于初始化以保存于数据库的定时任务,避免手动执行

package com.trasen.modules.chunjun.desreport.task;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

import com.trasen.modules.chunjun.desreport.service.IScheduledTaskService;

import lombok.extern.slf4j.Slf4j;

/**
 * 项目启动时初始化定时任务
 * 
 * @author xxx
 * @date: 2021-12-01 16:22:15
 * @Copyright: Copyright (c) 2006 - 2021
 * @Company: xxx公司
 * @Version: V1.0
 */
@Slf4j
@Component
public class ScheduledTaskRunner implements ApplicationRunner {

    @Autowired
    private IScheduledTaskService scheduledTaskService;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        log.info("----初始化定时任务开始----");
        scheduledTaskService.initTask();
        log.info("----初始化定时任务完成----");
    }
}
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值