deploy/deploy.yml
@@ -25,7 +25,7 @@ imagePullSecrets: - name: aliyun #提前在项目下配置访问阿里云的账号密码 containers: - image: registry.cn-hangzhou.aliyuncs.com/arsn/zhbaw:SNAPSHOT-3 - image: registry.cn-hangzhou.aliyuncs.com/arsn/zhbaw:SNAPSHOT-4 # - image: $REGISTRY/$DOCKERHUB_NAMESPACE/zhba:SNAPSHOT-$BUILD_NUMBER imagePullPolicy: Always name: zhbaw pom.xml
@@ -268,6 +268,17 @@ <artifactId>javacsv</artifactId> <version>2.0</version> </dependency> <!-- quartz 定时任务--> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.2</version> </dependency> <!--springboot-quartz--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency> </dependencies> <profiles> src/main/java/org/springblade/modules/crontab/Crontab.java
@@ -24,7 +24,7 @@ * 定时任务,每天凌晨1点执行一次, * 自动处理之前报了名,申请了考试又没有去考试的,做缺考标记,并将报名状态修改为已取消报名 */ @Scheduled(cron = "0 0 23 * * ?") // @Scheduled(cron = "0 0 23 * * ?") // @Scheduled(cron = "*/10 * * * * ?") @GetMapping("/examApplyStatus") public void examApplyStatus(){ @@ -37,8 +37,8 @@ /** * 定时任务,处理考试中的人员 */ @Scheduled(cron = "0 0 22 * * ?") @GetMapping("/examLoading") // @Scheduled(cron = "0 0 22 * * ?") // @GetMapping("/examLoading") public void aa(){ System.out.println("定时任务2:执行时间:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); //自动处理之前报了名,考试忘记提交或者中断考试的 src/main/java/org/springblade/modules/quartz/config/QuartzJob.java
New file @@ -0,0 +1,37 @@ package org.springblade.modules.quartz.config; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.quartz.DisallowConcurrentExecution; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.springblade.modules.quartz.entity.ScheduledJob; import org.springblade.modules.quartz.event.ScheduleEvent; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; /** * 该类将会被org.springframework.scheduling.quartz.SpringBeanJobFactory 实例化 * 并使@Autowired 生效 * @author zhongrj * @since 2022-03-17 */ @Slf4j @DisallowConcurrentExecution public class QuartzJob implements Job { @Autowired private ApplicationEventPublisher eventPublisher; /** * 任务调度参数key */ public static final String JOB_PARAM_KEY = "JOB_PARAM_KEY"; @Override @SneakyThrows public void execute(JobExecutionContext jobExecutionContext){ ScheduledJob scheduledCron = (ScheduledJob)jobExecutionContext.getMergedJobDataMap().get(JOB_PARAM_KEY); eventPublisher.publishEvent(new ScheduleEvent(scheduledCron)); } } src/main/java/org/springblade/modules/quartz/config/ScheduledManager.java
New file @@ -0,0 +1,165 @@ package org.springblade.modules.quartz.config; import lombok.AllArgsConstructor; import org.springblade.modules.quartz.entity.ScheduledJob; import org.springblade.modules.quartz.enums.ScheduledStatus; import org.springframework.stereotype.Component; import org.quartz.*; /** * 任务管理器 * @author zhongrj * @since 2022-03-17 */ @Component @AllArgsConstructor public class ScheduledManager { private final static String JOB_NAME = "TASK_"; private final Scheduler scheduler; /** * 获取触发器key */ private TriggerKey getTriggerKey(ScheduledJob scheduledJob){ return TriggerKey.triggerKey(JOB_NAME + scheduledJob.getId()); } /** * 获取jobKey */ private JobKey getJobKey(ScheduledJob scheduledJob) { return JobKey.jobKey(JOB_NAME + scheduledJob.getId()); } /** * 获取表达式触发器 */ public CronTrigger getCronTrigger(ScheduledJob scheduledJob) { try { return (CronTrigger) scheduler.getTrigger(getTriggerKey(scheduledJob)); } catch (SchedulerException e) { throw new RuntimeException("获取定时任务CronTrigger出现异常", e); } } /** * 创建定时任务 */ public void createScheduledJob(ScheduledJob scheduledJob) { try { //构建job信息 JobDetail jobDetail = JobBuilder.newJob(QuartzJob.class).withIdentity(getJobKey(scheduledJob)).build(); //表达式调度构建器,可以根据scheduledJob修改withMisfireHandling方法,但是使用异步执行定时任务,没必要 CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduledJob.getCronExpression()) .withMisfireHandlingInstructionFireAndProceed(); //按新的cronExpression表达式构建一个新的trigger CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(scheduledJob)).withSchedule(scheduleBuilder).build(); //放入参数,运行时的方法可以获取 jobDetail.getJobDataMap().put(QuartzJob.JOB_PARAM_KEY, scheduledJob); scheduler.scheduleJob(jobDetail, trigger); //暂停任务 if (scheduledJob.getStatus().equals(ScheduledStatus.PAUSE.getType())) { pauseJob(scheduledJob); } } catch (SchedulerException e) { throw new RuntimeException("创建定时任务失败", e); } } /** * 更新定时任务 */ public void updateScheduledJob(ScheduledJob scheduleJob) { try { TriggerKey triggerKey = getTriggerKey(scheduleJob); //表达式调度构建器 CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression()).withMisfireHandlingInstructionFireAndProceed(); CronTrigger trigger = getCronTrigger(scheduleJob); // 如果定时任务不存在,则创建定时任务 if (trigger == null) { createScheduledJob(scheduleJob); return; } //按新的cronExpression表达式重新构建trigger trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build(); //参数 trigger.getJobDataMap().put(QuartzJob.JOB_PARAM_KEY, scheduleJob); scheduler.rescheduleJob(triggerKey, trigger); //暂停任务 if (scheduleJob.getStatus().equals(ScheduledStatus.PAUSE.getType())) { pauseJob(scheduleJob); } } catch (SchedulerException e) { throw new RuntimeException("更新定时任务失败", e); } } /** * 立即执行任务 */ public void run(ScheduledJob scheduleJob) { try { //参数 JobDataMap dataMap = new JobDataMap(); dataMap.put(QuartzJob.JOB_PARAM_KEY, scheduleJob); scheduler.triggerJob(getJobKey(scheduleJob), dataMap); } catch (SchedulerException e) { throw new RuntimeException("立即执行定时任务失败", e); } } /** * 暂停任务 */ public void pauseJob(ScheduledJob scheduledJob) { try { scheduler.pauseJob(getJobKey(scheduledJob)); } catch (SchedulerException e) { throw new RuntimeException("暂停定时任务失败", e); } } /** * 恢复任务 */ public void resumeJob(ScheduledJob scheduleJob) { try { scheduler.resumeJob(getJobKey(scheduleJob)); } catch (SchedulerException e) { throw new RuntimeException("恢复定时任务失败", e); } } /** * 删除定时任务 */ public void deleteScheduledJob(ScheduledJob scheduleJob) { try { // 停止触发器 scheduler.pauseTrigger(getTriggerKey(scheduleJob)); //移除触发器 scheduler.unscheduleJob(getTriggerKey(scheduleJob)); //删除任务 scheduler.deleteJob(getJobKey(scheduleJob)); } catch (SchedulerException e) { throw new RuntimeException("删除定时任务失败", e); } } } src/main/java/org/springblade/modules/quartz/controller/ScheduledController.java
New file @@ -0,0 +1,132 @@ package org.springblade.modules.quartz.controller; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import lombok.AllArgsConstructor; import org.springblade.core.mp.support.Condition; import org.springblade.core.mp.support.Query; import org.springblade.core.tool.api.R; import org.springblade.core.tool.utils.Func; import org.springblade.modules.quartz.config.ScheduledManager; import org.springblade.modules.quartz.entity.ScheduledJob; import org.springblade.modules.quartz.service.ScheduledJobService; import org.springblade.modules.quartz.vo.ScheduledJobVO; import org.springframework.web.bind.annotation.*; import javax.validation.constraints.NotNull; import java.util.Arrays; import java.util.Date; import java.util.List; /** * 定时任务控制层 * @author zhongrj * @since 2022-03-07 */ @AllArgsConstructor @RestController @RequestMapping("/scheduled") public class ScheduledController { private final ScheduledJobService scheduledJobService; private final ScheduledManager scheduleManager; /** * 查询定时任务的分页数据 * @param page * @param scheduledJob * @return */ @GetMapping("/list") public R list(Query query, ScheduledJob scheduledJob){ return R.data(scheduledJobService.page(Condition.getPage(query),Condition.getQueryWrapper(scheduledJob))); } /** * 自定义定时任务的分页数据 * @param page * @param scheduledJob * @return */ @GetMapping("/page") public R page(ScheduledJobVO scheduledJob,Query query){ return R.data(scheduledJobService.selectPageScheduledJobList(Condition.getPage(query),scheduledJob)); } /** * 定时任务详情信息 * @param scheduledJob * @return */ @GetMapping("/detail") public R detail(ScheduledJob scheduledJob){ return R.data(scheduledJobService.getOne(new QueryWrapper<>(scheduledJob))); } /** * 定时任务新增 * @param scheduledJob * @return */ @PostMapping("/save") public R save(@RequestBody ScheduledJob scheduledJob){ scheduledJob.setCreateTime(new Date()); boolean status = scheduledJobService.save(scheduledJob); if (status){ //创建定时任务 scheduleManager.createScheduledJob(scheduledJob); } return R.status(status); } /** * 定时任务修改 * @param scheduledJob * @return */ @PostMapping("/update") public R update(@RequestBody ScheduledJob scheduledJob){ scheduledJob.setUpdateTime(new Date()); boolean status = scheduledJobService.updateById(scheduledJob); if (status){ //修改定时任务 scheduleManager.updateScheduledJob(scheduledJob); } return R.status(status); } /** * 删除,批量删除 * @param ids * @return */ @PostMapping("/remove") public R remove(@NotNull String ids){ List<String> list = Arrays.asList(ids.split(",")); list.forEach(s -> { ScheduledJob scheduledJob = new ScheduledJob(); scheduledJob.setId(Integer.parseInt(s)); //删除定时任务 scheduleManager.deleteScheduledJob(scheduledJob); }); return R.status(scheduledJobService.removeByIds(Func.toLongList(ids))); } /** * 定时任务恢复 * @param scheduledJob * @return */ @PostMapping("/activateQuartz") public R activateQuartz(@RequestBody ScheduledJob scheduledJob){ scheduledJob.setUpdateTime(new Date()); boolean status = scheduledJobService.updateById(scheduledJob); if (status){ //恢复定时任务 scheduleManager.resumeJob(scheduledJob); } return R.status(status); } } src/main/java/org/springblade/modules/quartz/entity/ScheduledJob.java
New file @@ -0,0 +1,84 @@ package org.springblade.modules.quartz.entity; 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 com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; import org.springframework.format.annotation.DateTimeFormat; import java.io.Serializable; import java.util.Date; /** * 定时任务entity * @author zhongrj * @since 2022-03-17 */ @Data @TableName("sys_scheduled_job") public class ScheduledJob implements Serializable { private static final long serialVersionUID = 6350214655057614043L; /** * 主键 */ @TableId(value = "id",type = IdType.AUTO) private Integer id; /** * 名称 */ private String name; /** * 方法名 */ private String methodName; /** * spring bean 名称 */ private String BeanName; /** * 参数 */ private String params; /** * 任务表达式 */ private String cronExpression; /** * 描述/备注 */ private String remark; /** * 状态 1:正常 2:停止 */ private Integer status; /** * 创建时间 */ @TableField("create_time") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(locale = "zh",pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") private Date createTime; /** * 创建时间 */ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(locale = "zh",pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") private Date updateTime; } src/main/java/org/springblade/modules/quartz/enums/ScheduledStatus.java
New file @@ -0,0 +1,29 @@ package org.springblade.modules.quartz.enums; import lombok.AllArgsConstructor; import lombok.Getter; /** * 任务状态 * @author zhongrj * @since 2022-03-17 */ @Getter @AllArgsConstructor public enum ScheduledStatus { /** * 正常 */ NORMAL(1), /** * 暂停 */ PAUSE(2); /** * 类型 1:正常 2:暂停 */ private final Integer type; } src/main/java/org/springblade/modules/quartz/event/ScheduleEvent.java
New file @@ -0,0 +1,15 @@ package org.springblade.modules.quartz.event; import lombok.AllArgsConstructor; import lombok.Getter; import org.springblade.modules.quartz.entity.ScheduledJob; /** * @author zhongrj * @since 2022-03-17 */ @Getter @AllArgsConstructor public class ScheduleEvent{ private final ScheduledJob scheduledJob; } src/main/java/org/springblade/modules/quartz/listener/SchedulerListener.java
New file @@ -0,0 +1,34 @@ package org.springblade.modules.quartz.listener; import lombok.AllArgsConstructor; import org.springblade.modules.quartz.entity.ScheduledJob; import org.springblade.modules.quartz.event.ScheduleEvent; import org.springblade.modules.quartz.util.SpringBeanTaskUtil; import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.stereotype.Component; /** * 异步监听定时任务事件,解决job线程无故丢失的问题 * @author zhongrj * @since 2022-03-10 */ @Component @AllArgsConstructor @EnableAsync public class SchedulerListener { @Async @EventListener(ScheduleEvent.class) public void scheduledJobListener(ScheduleEvent event){ //获取事件对象 ScheduledJob scheduledJob = event.getScheduledJob(); System.out.println("开始执行任务......"); //开始执行任务 SpringBeanTaskUtil.invokeMethod(scheduledJob); System.out.println("任务执行结束......"); } } src/main/java/org/springblade/modules/quartz/mapper/ScheduledJobMapper.java
New file @@ -0,0 +1,25 @@ package org.springblade.modules.quartz.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.metadata.IPage; import org.apache.ibatis.annotations.Param; import org.springblade.modules.quartz.entity.ScheduledJob; import org.springblade.modules.quartz.vo.ScheduledJobVO; import java.util.List; /** * mapper 映射层 * @author zhongrj * @since 2022-03-17 */ public interface ScheduledJobMapper extends BaseMapper<ScheduledJob> { /** * 自定义定时任务分页数据 * @param page * @param scheduledJob * @return */ List<ScheduledJobVO> selectPageScheduledJobPage(@Param("page") IPage<ScheduledJobVO> page, @Param("scheduledJob")ScheduledJobVO scheduledJob); } src/main/java/org/springblade/modules/quartz/service/ScheduledJobService.java
New file @@ -0,0 +1,21 @@ package org.springblade.modules.quartz.service; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.IService; import org.springblade.modules.quartz.entity.ScheduledJob; import org.springblade.modules.quartz.vo.ScheduledJobVO; /** * 任务接口层 * @author zhongrj * @since 2022-03-17 */ public interface ScheduledJobService extends IService<ScheduledJob> { /** * 自定义定时任务分页数据 * @param page * @param scheduledJob * @return */ IPage<ScheduledJobVO> selectPageScheduledJobList(IPage<ScheduledJobVO> page, ScheduledJobVO scheduledJob); } src/main/java/org/springblade/modules/quartz/service/impl/ScheduledJobServiceImpl.java
New file @@ -0,0 +1,63 @@ package org.springblade.modules.quartz.service.impl; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.quartz.CronTrigger; import org.springblade.modules.quartz.config.ScheduledManager; import org.springblade.modules.quartz.entity.ScheduledJob; import org.springblade.modules.quartz.enums.ScheduledStatus; import org.springblade.modules.quartz.mapper.ScheduledJobMapper; import org.springblade.modules.quartz.service.ScheduledJobService; import org.springblade.modules.quartz.vo.ScheduledJobVO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import java.util.List; /** * 任务接口实现层 * @author zhongrj * @since 2022-03-17 */ @Service public class ScheduledJobServiceImpl extends ServiceImpl<ScheduledJobMapper, ScheduledJob> implements ScheduledJobService { @Autowired private ScheduledManager scheduledManager; /** * 初始化任务 */ @PostConstruct public void init(){ List<ScheduledJob> list = list(); if (list.size()>0) { list.forEach(scheduledCron -> { //获取 triggerKey CronTrigger cronTrigger = scheduledManager.getCronTrigger(scheduledCron); if (null == cronTrigger) { //创建定时任务 scheduledManager.createScheduledJob(scheduledCron); }else if(ScheduledStatus.NORMAL.getType().equals(scheduledCron.getStatus())){ //恢复定时任务 scheduledManager.resumeJob(scheduledCron); }else if(ScheduledStatus.PAUSE.getType().equals(scheduledCron.getStatus())){ //暂停任务 scheduledManager.pauseJob(scheduledCron); } }); } } /** * 自定义定时任务分页数据 * @param page * @param scheduledJob * @return */ @Override public IPage<ScheduledJobVO> selectPageScheduledJobList(IPage<ScheduledJobVO> page, ScheduledJobVO scheduledJob) { return page.setRecords(baseMapper.selectPageScheduledJobPage(page,scheduledJob)); } } src/main/java/org/springblade/modules/quartz/task/Task.java
New file @@ -0,0 +1,48 @@ package org.springblade.modules.quartz.task; import org.springblade.modules.training.service.TrainingRegistrationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.GetMapping; import java.text.SimpleDateFormat; import java.util.Date; /** * 定时任务 * @author zhongrj * @since 2022-03-10 */ @Component("task") public class Task { @Autowired private TrainingRegistrationService trainingRegistrationService; public void testTask(){ System.out.println("测试定时任务执行-----------------"); } /** * 定时任务,每天凌晨1点执行一次, * 自动处理之前报了名,申请了考试又没有去考试的,做缺考标记,并将报名状态修改为已取消报名 */ // @Scheduled(cron = "0 0 23 * * ?") public void examApplyStatus(){ System.out.println("定时任务1:执行时间:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); //自动处理之前报了名,申请了考试又没有去考试的,做缺考标记,并将报名状态修改为已取消报名 trainingRegistrationService.examApplyStatus(); } /** * 定时任务,处理考试中的人员 */ // @Scheduled(cron = "0 0 22 * * ?") public void examLoading(){ System.out.println("定时任务2:执行时间:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); //自动处理之前报了名,考试忘记提交或者中断考试的 trainingRegistrationService.examLoading(); } } src/main/java/org/springblade/modules/quartz/util/SpringBeanTaskUtil.java
New file @@ -0,0 +1,34 @@ package org.springblade.modules.quartz.util; import liquibase.util.StringUtils; import lombok.extern.slf4j.Slf4j; import org.springblade.modules.quartz.entity.ScheduledJob; import org.springframework.util.ReflectionUtils; import java.lang.reflect.Method; /** * 定时任务spring bean 执行定时任务 * @author LGH */ @Slf4j public class SpringBeanTaskUtil { public static void invokeMethod(ScheduledJob scheduleJob) { Object target = SpringContextUtils.getBean(scheduleJob.getBeanName()); try { if (StringUtils.isNotEmpty(scheduleJob.getParams())) { Method method = target.getClass().getDeclaredMethod(scheduleJob.getMethodName(), String.class); ReflectionUtils.makeAccessible(method); method.invoke(target, scheduleJob.getParams()); } else { Method method = target.getClass().getDeclaredMethod(scheduleJob.getMethodName()); ReflectionUtils.makeAccessible(method); method.invoke(target); } } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("执行定时任务失败", e); } } } src/main/java/org/springblade/modules/quartz/util/SpringContextUtils.java
New file @@ -0,0 +1,42 @@ package org.springblade.modules.quartz.util; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; /** * Spring Context 工具类 * @author zhongrj */ @Component public class SpringContextUtils implements ApplicationContextAware { public static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringContextUtils.applicationContext = applicationContext; } public static Object getBean(String name) { return applicationContext.getBean(name); } public static <T> T getBean(String name, Class<T> requiredType) { return applicationContext.getBean(name, requiredType); } public static boolean containsBean(String name) { return applicationContext.containsBean(name); } public static boolean isSingleton(String name) { return applicationContext.isSingleton(name); } public static Class<? extends Object> getType(String name) { return applicationContext.getType(name); } } src/main/java/org/springblade/modules/quartz/vo/ScheduledJobVO.java
New file @@ -0,0 +1,13 @@ package org.springblade.modules.quartz.vo; import lombok.Data; import org.springblade.modules.quartz.entity.ScheduledJob; /** * vo * @author zhongrj * @since 2022-03-17 */ @Data public class ScheduledJobVO extends ScheduledJob { } src/main/resources/application-test.yml
@@ -13,9 +13,9 @@ # commandTimeout: 5000 datasource: # MySql url: jdbc:mysql://223.82.109.183:2083/zhbaw-test?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true url: jdbc:mysql://localhost:3306/zhbaw?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true username: root password: ZHba@0112 password: 123456 # PostgreSQL #url: jdbc:postgresql://127.0.0.1:5432/bladex_boot