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("----初始化定时任务完成----");
}
}