From 9f05202c1673e4e49ebb9d75d8864ef33e44fb18 Mon Sep 17 00:00:00 2001 From: Xiang Date: Tue, 6 Jan 2026 10:30:01 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20xxz-job=20=E8=B0=83=E5=BA=A6=E4=B8=AD?= =?UTF-8?q?=E5=BF=83=E9=A6=96=E6=AC=A1=E6=8F=90=E4=BA=A4=20=E5=86=85?= =?UTF-8?q?=E5=AE=B9=EF=BC=9A=201.=20=E6=A0=B8=E5=BF=83=E5=8C=85=E5=BC=80?= =?UTF-8?q?=E5=8F=91=EF=BC=8C=E6=B3=A8=E8=A7=A3=E5=BC=80=E5=8F=91=202.=20?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E6=89=AB=E6=8F=8F=E4=BB=A5=E5=8F=8A=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E6=B3=A8=E5=86=8C=203.=20=E4=BB=BB=E5=8A=A1=E6=89=A7?= =?UTF-8?q?=E8=A1=8C=EF=BC=8C=E4=BB=BB=E5=8A=A1=E6=94=AF=E6=8C=81=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E5=8D=95=E6=9C=BA=E8=B0=83=E7=94=A8=204.=20=E6=A0=B7?= =?UTF-8?q?=E6=9C=AC=E6=89=A7=E8=A1=8C=E5=99=A8=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 38 +++++++++ pom.xml | 53 ++++++++++++ xservice-quartz-admin/pom.xml | 22 +++++ .../main/java/com/xiang/app/Application.java | 24 ++++++ .../admin/domain/entity/JobConfigDO.java | 84 +++++++++++++++++++ .../admin/domain/entity/JobRunningLogDO.java | 37 ++++++++ .../admin/domain/req/TaskRegisterRequest.java | 45 ++++++++++ .../admin/manage/IQuartzConfigManage.java | 21 +++++ .../admin/manage/QuartzConfigManageImpl.java | 43 ++++++++++ .../admin/mapper/IQuartzConfigMapper.java | 16 ++++ .../server/XxzJobRegisterController.java | 16 ++++ .../admin/service/ITaskConfigService.java | 26 ++++++ .../service/impl/TaskConfigServiceImpl.java | 79 +++++++++++++++++ .../app/quartz/admin/utils/CronUtil.java | 43 ++++++++++ .../src/main/resources/application-dev.yml | 34 ++++++++ .../src/main/resources/application-prod.yml | 0 .../src/main/resources/application.yml | 9 ++ xservice-quartz-common/pom.xml | 21 +++++ xservice-quartz-core/pom.xml | 35 ++++++++ .../core/quartz/annotation/EnableXxzJob.java | 20 +++++ .../xiang/core/quartz/annotation/XxzJob.java | 40 +++++++++ .../core/quartz/boostrap/JobBootstrap.java | 31 +++++++ .../config/XxzJobAutoConfiguration.java | 23 +++++ .../config/XxzJobTaskScheduleConfig.java | 22 +++++ .../core/quartz/executor/JobExecutor.java | 30 +++++++ .../quartz/holder/JobDefinitionHolder.java | 43 ++++++++++ .../core/quartz/model/JobDefinition.java | 55 ++++++++++++ .../core/quartz/model/XxzJobProperties.java | 23 +++++ .../xiang/core/quartz/scanner/JobScanner.java | 59 +++++++++++++ .../core/quartz/schedule/JobScheduler.java | 26 ++++++ .../main/resources/META-INF/spring.factories | 2 + xservice-quartz-sample/pom.xml | 34 ++++++++ .../src/main/java/com/xiang/app/Main.java | 21 +++++ .../springboot/schedule/TestXxzJob1.java | 19 +++++ .../src/main/resources/application.yml | 39 +++++++++ 35 files changed, 1133 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 xservice-quartz-admin/pom.xml create mode 100644 xservice-quartz-admin/src/main/java/com/xiang/app/Application.java create mode 100644 xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/domain/entity/JobConfigDO.java create mode 100644 xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/domain/entity/JobRunningLogDO.java create mode 100644 xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/domain/req/TaskRegisterRequest.java create mode 100644 xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/manage/IQuartzConfigManage.java create mode 100644 xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/manage/QuartzConfigManageImpl.java create mode 100644 xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/mapper/IQuartzConfigMapper.java create mode 100644 xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/server/XxzJobRegisterController.java create mode 100644 xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/service/ITaskConfigService.java create mode 100644 xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/service/impl/TaskConfigServiceImpl.java create mode 100644 xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/utils/CronUtil.java create mode 100644 xservice-quartz-admin/src/main/resources/application-dev.yml create mode 100644 xservice-quartz-admin/src/main/resources/application-prod.yml create mode 100644 xservice-quartz-admin/src/main/resources/application.yml create mode 100644 xservice-quartz-common/pom.xml create mode 100644 xservice-quartz-core/pom.xml create mode 100644 xservice-quartz-core/src/main/java/com/xiang/core/quartz/annotation/EnableXxzJob.java create mode 100644 xservice-quartz-core/src/main/java/com/xiang/core/quartz/annotation/XxzJob.java create mode 100644 xservice-quartz-core/src/main/java/com/xiang/core/quartz/boostrap/JobBootstrap.java create mode 100644 xservice-quartz-core/src/main/java/com/xiang/core/quartz/config/XxzJobAutoConfiguration.java create mode 100644 xservice-quartz-core/src/main/java/com/xiang/core/quartz/config/XxzJobTaskScheduleConfig.java create mode 100644 xservice-quartz-core/src/main/java/com/xiang/core/quartz/executor/JobExecutor.java create mode 100644 xservice-quartz-core/src/main/java/com/xiang/core/quartz/holder/JobDefinitionHolder.java create mode 100644 xservice-quartz-core/src/main/java/com/xiang/core/quartz/model/JobDefinition.java create mode 100644 xservice-quartz-core/src/main/java/com/xiang/core/quartz/model/XxzJobProperties.java create mode 100644 xservice-quartz-core/src/main/java/com/xiang/core/quartz/scanner/JobScanner.java create mode 100644 xservice-quartz-core/src/main/java/com/xiang/core/quartz/schedule/JobScheduler.java create mode 100644 xservice-quartz-core/src/main/resources/META-INF/spring.factories create mode 100644 xservice-quartz-sample/pom.xml create mode 100644 xservice-quartz-sample/src/main/java/com/xiang/app/Main.java create mode 100644 xservice-quartz-sample/src/main/java/com/xiang/app/quartz/sample/springboot/schedule/TestXxzJob1.java create mode 100644 xservice-quartz-sample/src/main/resources/application.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..0de3323 --- /dev/null +++ b/pom.xml @@ -0,0 +1,53 @@ + + + 4.0.0 + + + com.xiang.pom + xmc-web-starter + 1.0 + + + com.xiang + xservice-quartz + 1.0-SNAPSHOT + pom + + xservice-quartz-admin + xservice-quartz-core + xservice-quartz-common + xservice-quartz-sample + + + + 17 + 17 + UTF-8 + + + + + com.xiang.starter + xmc-common + 1.0 + + + com.xiang.starter + xmc-mysql-starter + 1.0 + + + com.xiang.starter + xmc-cache-starter + 1.0 + + + com.xiang.starter + xmc-logger-starter + 1.0 + + + + \ No newline at end of file diff --git a/xservice-quartz-admin/pom.xml b/xservice-quartz-admin/pom.xml new file mode 100644 index 0000000..c649007 --- /dev/null +++ b/xservice-quartz-admin/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + com.xiang + xservice-quartz + 1.0-SNAPSHOT + + + com.xiang.app + xservice-quartz-admin + 1.0-SNAPSHOT + + + 17 + 17 + UTF-8 + + + \ No newline at end of file diff --git a/xservice-quartz-admin/src/main/java/com/xiang/app/Application.java b/xservice-quartz-admin/src/main/java/com/xiang/app/Application.java new file mode 100644 index 0000000..643703d --- /dev/null +++ b/xservice-quartz-admin/src/main/java/com/xiang/app/Application.java @@ -0,0 +1,24 @@ +package com.xiang.app; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; + + +/** + * @Author: xiang + * @Date: 2026-01-06 09:09 + */ +@SpringBootApplication +@EnableScheduling +public class Application { + + private static final Logger log = LoggerFactory.getLogger(Application.class); + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + log.info("✅✅✅X-service quartz 任务调度中心启动成功✅✅✅"); + } +} \ No newline at end of file diff --git a/xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/domain/entity/JobConfigDO.java b/xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/domain/entity/JobConfigDO.java new file mode 100644 index 0000000..ccff15a --- /dev/null +++ b/xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/domain/entity/JobConfigDO.java @@ -0,0 +1,84 @@ +package com.xiang.app.quartz.admin.domain.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * @Author: xiang + * @Date: 2025-12-30 09:42 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@TableName("xmc_quartz_config") +public class JobConfigDO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * id + */ + private Long id; + /** + * 应用名称 + */ + private String applicationName; + + /** + * 应用地址 + */ + private String applicationAddress; + + /** + * 定时任务名称 + */ + private String beanName; + + /** + * 任务执行调度时间 + */ + private String cron; + + /** + * 类名 + */ + private String clazz; + + /** + * 方法 + */ + private String method; + + /** + * 任务开关 + * 0:关闭 1:开启 + */ + private Integer jobSwitch; + + /** + * 删除标识(0:未删除 1:已删除) + */ + private Integer delFlag; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 修改时间 + */ + private LocalDateTime updateTime; + + /** + * 上次执行时间 + */ + private LocalDateTime lastRunningTime; +} diff --git a/xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/domain/entity/JobRunningLogDO.java b/xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/domain/entity/JobRunningLogDO.java new file mode 100644 index 0000000..73da4eb --- /dev/null +++ b/xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/domain/entity/JobRunningLogDO.java @@ -0,0 +1,37 @@ +package com.xiang.app.quartz.admin.domain.entity; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * @Author: xiang + * @Date: 2026-01-05 16:14 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class JobRunningLogDO { + + /** + * id + */ + private Long id; + + /** + * 任务id + */ + private Long quartzId; + + /** + * 执行时间 + */ + private LocalDateTime runningTime; + + /** + * 执行状态(0:未执行 1:执行成功 2:执行失败) + */ + private Integer runningStatus; +} diff --git a/xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/domain/req/TaskRegisterRequest.java b/xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/domain/req/TaskRegisterRequest.java new file mode 100644 index 0000000..9620a41 --- /dev/null +++ b/xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/domain/req/TaskRegisterRequest.java @@ -0,0 +1,45 @@ +package com.xiang.app.quartz.admin.domain.req; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @Author: xiang + * @Date: 2026-01-05 15:49 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class TaskRegisterRequest { + + /** + * 应用名称 + */ + private String applicationName; + + /** + * 应用地址 + */ + private String applicationAddress; + + /** + * 定时任务名称 + */ + private String beanName; + + /** + * 任务执行调度时间 + */ + private String cron; + + /** + * 类名 + */ + private String clazz; + + /** + * 方法 + */ + private String method; +} diff --git a/xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/manage/IQuartzConfigManage.java b/xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/manage/IQuartzConfigManage.java new file mode 100644 index 0000000..d2a5c12 --- /dev/null +++ b/xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/manage/IQuartzConfigManage.java @@ -0,0 +1,21 @@ +package com.xiang.app.quartz.admin.manage; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.xiang.app.quartz.admin.domain.entity.JobConfigDO; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * @Author: xiang + * @Date: 2026-01-05 15:53 + */ +public interface IQuartzConfigManage extends IService { + + + List selectByAppName(String name); + + List loadEnabledJobs(); + + boolean updateJobLastTriggerTime(String name, LocalDateTime now); +} diff --git a/xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/manage/QuartzConfigManageImpl.java b/xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/manage/QuartzConfigManageImpl.java new file mode 100644 index 0000000..e3489e5 --- /dev/null +++ b/xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/manage/QuartzConfigManageImpl.java @@ -0,0 +1,43 @@ +package com.xiang.app.quartz.admin.manage; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.xiang.app.quartz.admin.domain.entity.JobConfigDO; +import com.xiang.app.quartz.admin.mapper.IQuartzConfigMapper; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * @Author: xiang + * @Date: 2026-01-05 15:53 + */ +@Service +public class QuartzConfigManageImpl extends ServiceImpl implements IQuartzConfigManage { + @Override + public List selectByAppName(String name) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(JobConfigDO::getApplicationName, name); + lqw.eq(JobConfigDO::getDelFlag, 0); + return baseMapper.selectList(lqw); + } + + @Override + public List loadEnabledJobs() { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(JobConfigDO::getJobSwitch, 1); + lqw.eq(JobConfigDO::getDelFlag, 0); + return baseMapper.selectList(lqw); + } + + @Override + public boolean updateJobLastTriggerTime(String name, LocalDateTime now) { + LambdaUpdateWrapper luw = Wrappers.lambdaUpdate(); + luw.eq(JobConfigDO::getBeanName, name); + luw.set(JobConfigDO::getLastRunningTime, now); + return baseMapper.update(luw) > 0; + } +} diff --git a/xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/mapper/IQuartzConfigMapper.java b/xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/mapper/IQuartzConfigMapper.java new file mode 100644 index 0000000..82c5fa4 --- /dev/null +++ b/xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/mapper/IQuartzConfigMapper.java @@ -0,0 +1,16 @@ +package com.xiang.app.quartz.admin.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.xiang.app.quartz.admin.domain.entity.JobConfigDO; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Repository; + +/** + * @Author: xiang + * @Date: 2026-01-05 15:54 + */ +@Repository +@Mapper +public interface IQuartzConfigMapper extends BaseMapper { + +} diff --git a/xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/server/XxzJobRegisterController.java b/xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/server/XxzJobRegisterController.java new file mode 100644 index 0000000..30f9f2b --- /dev/null +++ b/xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/server/XxzJobRegisterController.java @@ -0,0 +1,16 @@ +package com.xiang.app.quartz.admin.server; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.RestController; + +/** + * @Author: xiang + * @Date: 2026-01-06 09:18 + */ + +@RestController +@RequiredArgsConstructor +public class XxzJobRegisterController { + + +} diff --git a/xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/service/ITaskConfigService.java b/xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/service/ITaskConfigService.java new file mode 100644 index 0000000..3cd2e1b --- /dev/null +++ b/xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/service/ITaskConfigService.java @@ -0,0 +1,26 @@ +package com.xiang.app.quartz.admin.service; + +import com.xiang.app.quartz.admin.domain.req.TaskRegisterRequest; + +import java.util.List; + +/** + * @Author: xiang + * @Date: 2026-01-05 15:51 + */ +public interface ITaskConfigService { + + /** + * 注册单个任务 + * @param request + * @return + */ + boolean registerTask(TaskRegisterRequest request); + + /** + * 批量注册任务 + * @param request + * @return + */ + boolean registerTasks(List request); +} diff --git a/xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/service/impl/TaskConfigServiceImpl.java b/xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/service/impl/TaskConfigServiceImpl.java new file mode 100644 index 0000000..53c9495 --- /dev/null +++ b/xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/service/impl/TaskConfigServiceImpl.java @@ -0,0 +1,79 @@ +package com.xiang.app.quartz.admin.service.impl; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.xiang.app.quartz.admin.domain.entity.JobConfigDO; +import com.xiang.app.quartz.admin.domain.req.TaskRegisterRequest; +import com.xiang.app.quartz.admin.manage.IQuartzConfigManage; +import com.xiang.app.quartz.admin.service.ITaskConfigService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * @Author: xiang + * @Date: 2026-01-05 15:52 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class TaskConfigServiceImpl implements ITaskConfigService { + + private final IQuartzConfigManage quartzConfigManage; + + @Override + public boolean registerTask(TaskRegisterRequest request) { + return registerTasks(Collections.singletonList(request)); + } + + @Override + public boolean registerTasks(List request) { + + if (CollectionUtils.isEmpty(request)) { + return false; + } + String applicationName = request.get(0).getApplicationName(); + List jobs = quartzConfigManage.selectByAppName(applicationName); + Map existJobs = Maps.newHashMap(); + if (CollectionUtils.isNotEmpty(jobs)) { + existJobs.putAll(jobs.stream().collect(Collectors.toMap(JobConfigDO::getBeanName, Function.identity(), (a, b) -> a))); + } + List insertList = Lists.newArrayList(); + List upudateList = Lists.newArrayList(); + request.forEach(item -> { + if (existJobs.containsKey(item.getBeanName())) { + JobConfigDO jobConfigDO = existJobs.get(item.getBeanName()); + putDataIntoDo(item, jobConfigDO, applicationName); + upudateList.add(jobConfigDO); + } else { + JobConfigDO jobConfigDO = new JobConfigDO(); + putDataIntoDo(item, jobConfigDO, applicationName); + insertList.add(jobConfigDO); + } + }); + if (CollectionUtils.isNotEmpty(insertList)) { + quartzConfigManage.saveBatch(insertList); + } + if (CollectionUtils.isNotEmpty(upudateList)) { + quartzConfigManage.updateBatchById(upudateList); + } + return true; + } + + private static void putDataIntoDo(TaskRegisterRequest item, JobConfigDO jobConfigDO, String applicationName) { + jobConfigDO.setApplicationName(applicationName); + jobConfigDO.setApplicationAddress(item.getApplicationAddress()); + jobConfigDO.setBeanName(item.getBeanName()); + jobConfigDO.setCron(item.getCron()); + jobConfigDO.setClazz(item.getClazz()); + jobConfigDO.setMethod(item.getMethod()); + jobConfigDO.setJobSwitch(0); + } +} diff --git a/xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/utils/CronUtil.java b/xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/utils/CronUtil.java new file mode 100644 index 0000000..9c0d70c --- /dev/null +++ b/xservice-quartz-admin/src/main/java/com/xiang/app/quartz/admin/utils/CronUtil.java @@ -0,0 +1,43 @@ +package com.xiang.app.quartz.admin.utils; + +import org.springframework.scheduling.support.CronExpression; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +/** + * @Author: xiang + * @Date: 2026-01-05 16:16 + */ +public class CronUtil { + + /** + * 判断: + * 从 lastTriggerTime 到现在 + * cron 是否命中过至少一次 + */ + public static boolean isMatch(String cron, LocalDateTime lastTriggerTime) { + if (cron == null || cron.isEmpty()) { + return false; + } + + CronExpression expression = CronExpression.parse(cron); + + ZoneId zoneId = ZoneId.systemDefault(); + ZonedDateTime now = ZonedDateTime.now(zoneId); + + // 第一次执行(从未触发过) + if (lastTriggerTime == null) { + // 给一个足够早的时间,防止任务永远不触发 + ZonedDateTime base = now.minusYears(1); + ZonedDateTime next = expression.next(base); + return next != null && !next.isAfter(now); + } + + ZonedDateTime last = lastTriggerTime.atZone(zoneId); + ZonedDateTime next = expression.next(last); + + return next != null && !next.isAfter(now); + } +} diff --git a/xservice-quartz-admin/src/main/resources/application-dev.yml b/xservice-quartz-admin/src/main/resources/application-dev.yml new file mode 100644 index 0000000..0685ca8 --- /dev/null +++ b/xservice-quartz-admin/src/main/resources/application-dev.yml @@ -0,0 +1,34 @@ +spring: + cloud: + nacos: + discovery: + group: DEFAULT_GROUP + namespace: 00131110-3ecb-4a35-8bbb-624edde1d937 + server-addr: general.xiangtech.xyz:8848 + username: nacos + password: nacos + datasource: + dynamic: + primary: master + datasource: + master: + url: jdbc:mysql://120.27.153.87:3306/xservice_quartz?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true + username: quartz + password: quartz@123 + driver-class-name: com.mysql.cj.jdbc.Driver + sshConnect: false + + redis: + host: r-bp1wt59a6nfyt4e3ltpd.redis.rds.aliyuncs.com + port: 6379 + password: Xiang0000 # 如果无密码可以省略 + database: 10 + timeout: 5000 + lettuce: + pool: + max-active: 8 + max-idle: 8 + min-idle: 0 + max-wait: 1000 +xxz-job: + appName: xservice-quartz-admin \ No newline at end of file diff --git a/xservice-quartz-admin/src/main/resources/application-prod.yml b/xservice-quartz-admin/src/main/resources/application-prod.yml new file mode 100644 index 0000000..e69de29 diff --git a/xservice-quartz-admin/src/main/resources/application.yml b/xservice-quartz-admin/src/main/resources/application.yml new file mode 100644 index 0000000..e855b85 --- /dev/null +++ b/xservice-quartz-admin/src/main/resources/application.yml @@ -0,0 +1,9 @@ +server: + port: 30030 +spring: + profiles: + active: dev + application: + name: xservice-quartz-admin + main: + allow-bean-definition-overriding: true diff --git a/xservice-quartz-common/pom.xml b/xservice-quartz-common/pom.xml new file mode 100644 index 0000000..cf19144 --- /dev/null +++ b/xservice-quartz-common/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + com.xiang + xservice-quartz + 1.0-SNAPSHOT + + + com.xiang.starter + xservice-quartz-common + + + 17 + 17 + UTF-8 + + + \ No newline at end of file diff --git a/xservice-quartz-core/pom.xml b/xservice-quartz-core/pom.xml new file mode 100644 index 0000000..1fb80df --- /dev/null +++ b/xservice-quartz-core/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + com.xiang + xservice-quartz + 1.0-SNAPSHOT + + + com.xiang.starter + xservice-quartz-core + 1.0-SNAPSHOT + + + 17 + 17 + UTF-8 + + + + + com.xiang.starter + xmc-common + 1.0 + + + com.xiang.starter + xmc-cache-starter + 1.0 + + + + \ No newline at end of file diff --git a/xservice-quartz-core/src/main/java/com/xiang/core/quartz/annotation/EnableXxzJob.java b/xservice-quartz-core/src/main/java/com/xiang/core/quartz/annotation/EnableXxzJob.java new file mode 100644 index 0000000..786c815 --- /dev/null +++ b/xservice-quartz-core/src/main/java/com/xiang/core/quartz/annotation/EnableXxzJob.java @@ -0,0 +1,20 @@ +package com.xiang.core.quartz.annotation; + +import com.xiang.core.quartz.config.XxzJobAutoConfiguration; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @Author: xiang + * @Date: 2025-12-30 10:49 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Import(XxzJobAutoConfiguration.class) +public @interface EnableXxzJob { + String[] basePackages() default {}; +} diff --git a/xservice-quartz-core/src/main/java/com/xiang/core/quartz/annotation/XxzJob.java b/xservice-quartz-core/src/main/java/com/xiang/core/quartz/annotation/XxzJob.java new file mode 100644 index 0000000..be690d3 --- /dev/null +++ b/xservice-quartz-core/src/main/java/com/xiang/core/quartz/annotation/XxzJob.java @@ -0,0 +1,40 @@ +package com.xiang.core.quartz.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @Author: xiang + * @Date: 2025-12-30 08:48 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface XxzJob { + + /** + * bean的名称 + * + * @return + */ + String name(); + + /** + * cron调度方法 + * @return + */ + String cron(); + + boolean enabled() default true; + /** + * 是否支持多机分布式运行 + * 若为false:每台机器都会执行一次 + * 若为true:仅一台机器会执行 + * + * @return + */ + boolean distributed() default true; +} diff --git a/xservice-quartz-core/src/main/java/com/xiang/core/quartz/boostrap/JobBootstrap.java b/xservice-quartz-core/src/main/java/com/xiang/core/quartz/boostrap/JobBootstrap.java new file mode 100644 index 0000000..d27834f --- /dev/null +++ b/xservice-quartz-core/src/main/java/com/xiang/core/quartz/boostrap/JobBootstrap.java @@ -0,0 +1,31 @@ +package com.xiang.core.quartz.boostrap; + +import com.xiang.core.quartz.holder.JobDefinitionHolder; +import com.xiang.core.quartz.model.JobDefinition; +import com.xiang.core.quartz.schedule.JobScheduler; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; + +/** + * @Author: xiang + * @Date: 2026-01-06 09:50 + */ +@Slf4j +@Component +public class JobBootstrap implements ApplicationRunner { + + @Autowired + private JobScheduler scheduler; + + @Override + public void run(ApplicationArguments args) { + JobDefinitionHolder.getAll() + .stream() + .filter(JobDefinition::isEnabled) + .forEach(scheduler::schedule); + } +} + diff --git a/xservice-quartz-core/src/main/java/com/xiang/core/quartz/config/XxzJobAutoConfiguration.java b/xservice-quartz-core/src/main/java/com/xiang/core/quartz/config/XxzJobAutoConfiguration.java new file mode 100644 index 0000000..e54977f --- /dev/null +++ b/xservice-quartz-core/src/main/java/com/xiang/core/quartz/config/XxzJobAutoConfiguration.java @@ -0,0 +1,23 @@ +package com.xiang.core.quartz.config; + +import com.xiang.core.quartz.executor.JobExecutor; +import com.xiang.core.quartz.model.XxzJobProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + * @Author: xiang + * @Date: 2026-01-06 09:29 + */ +@Configuration +@EnableConfigurationProperties(XxzJobProperties.class) +@EnableScheduling +public class XxzJobAutoConfiguration { + + @Bean + public JobExecutor jobExecutor() { + return new JobExecutor(); + } +} diff --git a/xservice-quartz-core/src/main/java/com/xiang/core/quartz/config/XxzJobTaskScheduleConfig.java b/xservice-quartz-core/src/main/java/com/xiang/core/quartz/config/XxzJobTaskScheduleConfig.java new file mode 100644 index 0000000..617e69e --- /dev/null +++ b/xservice-quartz-core/src/main/java/com/xiang/core/quartz/config/XxzJobTaskScheduleConfig.java @@ -0,0 +1,22 @@ +package com.xiang.core.quartz.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; + +/** + * @Author: xiang + * @Date: 2026-01-06 10:20 + */ +@Configuration +public class XxzJobTaskScheduleConfig { + @Bean + public TaskScheduler taskScheduler() { + ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); + scheduler.setPoolSize(4); + scheduler.setThreadNamePrefix("xxz-job-"); + scheduler.initialize(); + return scheduler; + } +} diff --git a/xservice-quartz-core/src/main/java/com/xiang/core/quartz/executor/JobExecutor.java b/xservice-quartz-core/src/main/java/com/xiang/core/quartz/executor/JobExecutor.java new file mode 100644 index 0000000..87841fc --- /dev/null +++ b/xservice-quartz-core/src/main/java/com/xiang/core/quartz/executor/JobExecutor.java @@ -0,0 +1,30 @@ +package com.xiang.core.quartz.executor; + +import com.xiang.core.quartz.model.JobDefinition; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +/** + * job任务执行期 + * + * @Author: xiang + * @Date: 2026-01-06 09:42 + */ +@Component +public class JobExecutor { + + private static final Logger log = LoggerFactory.getLogger(JobExecutor.class); + + /** + * v1版本 本地反射调用 + * @param jobDefinition + */ + public void executor(JobDefinition jobDefinition) { + try { + jobDefinition.getMethod().invoke(jobDefinition.getBean()); + } catch (Exception e) { + log.error("xxz-job execute error, job={}", jobDefinition.getName(), e); + } + } +} diff --git a/xservice-quartz-core/src/main/java/com/xiang/core/quartz/holder/JobDefinitionHolder.java b/xservice-quartz-core/src/main/java/com/xiang/core/quartz/holder/JobDefinitionHolder.java new file mode 100644 index 0000000..dd57373 --- /dev/null +++ b/xservice-quartz-core/src/main/java/com/xiang/core/quartz/holder/JobDefinitionHolder.java @@ -0,0 +1,43 @@ +package com.xiang.core.quartz.holder; + +import com.google.common.collect.Maps; +import com.xiang.core.quartz.model.JobDefinition; + +import java.util.List; +import java.util.Map; + +/** + * @Author: xiang + * @Date: 2025-12-30 08:59 + */ +public class JobDefinitionHolder { + + private static final Map MAP = Maps.newConcurrentMap(); + + private JobDefinitionHolder() { + } + + public static void register(JobDefinition job) { + if (!MAP.containsKey(job.getName())) { + MAP.put(job.getName(), job); + } + } + + public static List getAll() { + return MAP.values().stream().toList(); + } + + public static JobDefinition getOne(String name) { + return MAP.get(name); + } + + public static void updateOne(JobDefinition jobDefinition) { + if (MAP.containsKey(jobDefinition.getName())) { + MAP.put(jobDefinition.getName(), jobDefinition); + } + } + + public static void delOne(JobDefinition jobDefinition) { + MAP.remove(jobDefinition.getName()); + } +} diff --git a/xservice-quartz-core/src/main/java/com/xiang/core/quartz/model/JobDefinition.java b/xservice-quartz-core/src/main/java/com/xiang/core/quartz/model/JobDefinition.java new file mode 100644 index 0000000..a9c3946 --- /dev/null +++ b/xservice-quartz-core/src/main/java/com/xiang/core/quartz/model/JobDefinition.java @@ -0,0 +1,55 @@ +package com.xiang.core.quartz.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.lang.reflect.Method; +import java.time.LocalDateTime; + +/** + * @Author: xiang + * @Date: 2026-01-06 09:30 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class JobDefinition { + /** + * 应用名称 + */ + private String appName; + + /** + * bean名称 + */ + private String name; + /** + * cron + */ + private String cron; + /** + * 要执行的方法 + */ + private Method method; + /** + * spring bean 实例 + */ + private Object bean; + /** + * 是否支持分布式 + */ + private boolean distributed; + /** + * 是否启用 + */ + private boolean enabled; + /** + * 旧cron + */ + private String oldCron; + /** + * 上次执行时间 + */ + private LocalDateTime lastRunningTime; +} diff --git a/xservice-quartz-core/src/main/java/com/xiang/core/quartz/model/XxzJobProperties.java b/xservice-quartz-core/src/main/java/com/xiang/core/quartz/model/XxzJobProperties.java new file mode 100644 index 0000000..31b834c --- /dev/null +++ b/xservice-quartz-core/src/main/java/com/xiang/core/quartz/model/XxzJobProperties.java @@ -0,0 +1,23 @@ +package com.xiang.core.quartz.model; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * @Author: xiang + * @Date: 2026-01-06 09:30 + */ +@ConfigurationProperties(prefix = "xxz-job") +@Data +public class XxzJobProperties { + + /** + * 应用名称 + */ + private String appName; + + /** + * DB刷新任务间隔(毫秒) + */ + private int refreshInterval = 5000; +} diff --git a/xservice-quartz-core/src/main/java/com/xiang/core/quartz/scanner/JobScanner.java b/xservice-quartz-core/src/main/java/com/xiang/core/quartz/scanner/JobScanner.java new file mode 100644 index 0000000..d937de8 --- /dev/null +++ b/xservice-quartz-core/src/main/java/com/xiang/core/quartz/scanner/JobScanner.java @@ -0,0 +1,59 @@ +package com.xiang.core.quartz.scanner; + +import com.xiang.core.quartz.annotation.XxzJob; +import com.xiang.core.quartz.holder.JobDefinitionHolder; +import com.xiang.core.quartz.model.JobDefinition; +import lombok.extern.slf4j.Slf4j; +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.stereotype.Component; +import org.springframework.util.ReflectionUtils; + +import java.lang.reflect.Method; +import java.util.Map; +import java.util.Objects; + +/** + * @Author: xiang + * @Date: 2026-01-06 09:40 + */ +@Slf4j +@Component +public class JobScanner implements SmartInitializingSingleton { + + @Autowired + private ApplicationContext applicationContext; + + @Override + public void afterSingletonsInstantiated() { + + Map beans = + applicationContext.getBeansWithAnnotation(Component.class); + + for (Object bean : beans.values()) { + Class targetClass = AopUtils.getTargetClass(bean); + ReflectionUtils.doWithMethods( + targetClass, + method -> registerJob(bean, method), + method -> AnnotationUtils.findAnnotation(method, XxzJob.class) != null + ); + } + } + private void registerJob(Object bean, Method method) { + XxzJob xxzJob = AnnotationUtils.findAnnotation(method, XxzJob.class); + if (Objects.nonNull(xxzJob)) { + JobDefinition job = new JobDefinition(); + job.setName(xxzJob.name()); + job.setDistributed(xxzJob.distributed()); + job.setBean(bean); + job.setMethod(method); + job.setEnabled(xxzJob.enabled()); + job.setCron(xxzJob.cron()); + JobDefinitionHolder.register(job); + } + } +} + diff --git a/xservice-quartz-core/src/main/java/com/xiang/core/quartz/schedule/JobScheduler.java b/xservice-quartz-core/src/main/java/com/xiang/core/quartz/schedule/JobScheduler.java new file mode 100644 index 0000000..334b9c9 --- /dev/null +++ b/xservice-quartz-core/src/main/java/com/xiang/core/quartz/schedule/JobScheduler.java @@ -0,0 +1,26 @@ +package com.xiang.core.quartz.schedule; + +import com.xiang.core.quartz.executor.JobExecutor; +import com.xiang.core.quartz.model.JobDefinition; +import lombok.RequiredArgsConstructor; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.support.CronTrigger; +import org.springframework.stereotype.Component; + +/** + * @Author: xiang + * @Date: 2026-01-06 09:47 + */ +@Component +@RequiredArgsConstructor +public class JobScheduler { + private final TaskScheduler taskScheduler; + private final JobExecutor jobExecutor; + + public void schedule(JobDefinition job) { + taskScheduler.schedule( + () -> jobExecutor.executor(job), + new CronTrigger(job.getCron()) + ); + } +} diff --git a/xservice-quartz-core/src/main/resources/META-INF/spring.factories b/xservice-quartz-core/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..e587c63 --- /dev/null +++ b/xservice-quartz-core/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +com.xiang.core.quartz.config.XxzJobAutoConfiguration \ No newline at end of file diff --git a/xservice-quartz-sample/pom.xml b/xservice-quartz-sample/pom.xml new file mode 100644 index 0000000..2cdf89b --- /dev/null +++ b/xservice-quartz-sample/pom.xml @@ -0,0 +1,34 @@ + + + 4.0.0 + + com.xiang + xservice-quartz + 1.0-SNAPSHOT + + + com.xiang.starter + xservice-quartz-sample + + + 17 + 17 + UTF-8 + + + + + com.xiang.starter + xservice-quartz-core + 1.0-SNAPSHOT + + + + com.xiang.starter + xmc-mysql-starter + 1.0 + + + \ No newline at end of file diff --git a/xservice-quartz-sample/src/main/java/com/xiang/app/Main.java b/xservice-quartz-sample/src/main/java/com/xiang/app/Main.java new file mode 100644 index 0000000..175cb14 --- /dev/null +++ b/xservice-quartz-sample/src/main/java/com/xiang/app/Main.java @@ -0,0 +1,21 @@ +package com.xiang.app; + +import com.xiang.core.quartz.annotation.EnableXxzJob; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +/** + * @Author: xiang + * @Date: 2026-01-06 09:52 + */ +@EnableXxzJob(basePackages = {"com.xiang.app.quartz.sample.springboot.schedule.TestXxzJob1"}) +@SpringBootApplication +@ComponentScan(basePackages = { + "com.xiang.*" +}) +public class Main { + public static void main(String[] args) { + SpringApplication.run(Main.class, args); + } +} \ No newline at end of file diff --git a/xservice-quartz-sample/src/main/java/com/xiang/app/quartz/sample/springboot/schedule/TestXxzJob1.java b/xservice-quartz-sample/src/main/java/com/xiang/app/quartz/sample/springboot/schedule/TestXxzJob1.java new file mode 100644 index 0000000..2f2a6c3 --- /dev/null +++ b/xservice-quartz-sample/src/main/java/com/xiang/app/quartz/sample/springboot/schedule/TestXxzJob1.java @@ -0,0 +1,19 @@ +package com.xiang.app.quartz.sample.springboot.schedule; + +import com.xiang.core.quartz.annotation.XxzJob; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * @Author: xiang + * @Date: 2026-01-06 09:57 + */ +@Slf4j +@Component +public class TestXxzJob1 { + + @XxzJob(name = "TestXxzJob", cron = "0/1 * * * * ? ", enabled = true) + public void test() { + log.info("任务调度开始"); + } +} diff --git a/xservice-quartz-sample/src/main/resources/application.yml b/xservice-quartz-sample/src/main/resources/application.yml new file mode 100644 index 0000000..5767634 --- /dev/null +++ b/xservice-quartz-sample/src/main/resources/application.yml @@ -0,0 +1,39 @@ +xxz-job: + app-name: springboot-quartz-test + +spring: + main: + allow-bean-definition-overriding: true + application: + name: springboot-quartz-test + cloud: + nacos: + discovery: + group: DEFAULT_GROUP + namespace: 00131110-3ecb-4a35-8bbb-624edde1d937 + server-addr: general.xiangtech.xyz:8848 + username: nacos + password: nacos + datasource: + dynamic: + primary: master + datasource: + master: + url: jdbc:mysql://120.27.153.87:3306/xservice_quartz?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true + username: quartz + password: quartz@123 + driver-class-name: com.mysql.cj.jdbc.Driver + sshConnect: false + + redis: + host: r-bp1wt59a6nfyt4e3ltpd.redis.rds.aliyuncs.com + port: 6379 + password: Xiang0000 # 如果无密码可以省略 + database: 10 + timeout: 5000 + lettuce: + pool: + max-active: 8 + max-idle: 8 + min-idle: 0 + max-wait: 1000 \ No newline at end of file