feat: xxz-job 调度中心首次提交
内容: 1. 核心包开发,注解开发 2. 任务扫描以及本地注册 3. 任务执行,任务支持本地单机调用 4. 样本执行器生成
This commit is contained in:
@@ -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 任务调度中心启动成功✅✅✅");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<JobConfigDO> {
|
||||
|
||||
|
||||
List<JobConfigDO> selectByAppName(String name);
|
||||
|
||||
List<JobConfigDO> loadEnabledJobs();
|
||||
|
||||
boolean updateJobLastTriggerTime(String name, LocalDateTime now);
|
||||
}
|
||||
@@ -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<IQuartzConfigMapper, JobConfigDO> implements IQuartzConfigManage {
|
||||
@Override
|
||||
public List<JobConfigDO> selectByAppName(String name) {
|
||||
LambdaQueryWrapper<JobConfigDO> lqw = Wrappers.lambdaQuery();
|
||||
lqw.eq(JobConfigDO::getApplicationName, name);
|
||||
lqw.eq(JobConfigDO::getDelFlag, 0);
|
||||
return baseMapper.selectList(lqw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JobConfigDO> loadEnabledJobs() {
|
||||
LambdaQueryWrapper<JobConfigDO> 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<JobConfigDO> luw = Wrappers.lambdaUpdate();
|
||||
luw.eq(JobConfigDO::getBeanName, name);
|
||||
luw.set(JobConfigDO::getLastRunningTime, now);
|
||||
return baseMapper.update(luw) > 0;
|
||||
}
|
||||
}
|
||||
@@ -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<JobConfigDO> {
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
|
||||
}
|
||||
@@ -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<TaskRegisterRequest> request);
|
||||
}
|
||||
@@ -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<TaskRegisterRequest> request) {
|
||||
|
||||
if (CollectionUtils.isEmpty(request)) {
|
||||
return false;
|
||||
}
|
||||
String applicationName = request.get(0).getApplicationName();
|
||||
List<JobConfigDO> jobs = quartzConfigManage.selectByAppName(applicationName);
|
||||
Map<String, JobConfigDO> existJobs = Maps.newHashMap();
|
||||
if (CollectionUtils.isNotEmpty(jobs)) {
|
||||
existJobs.putAll(jobs.stream().collect(Collectors.toMap(JobConfigDO::getBeanName, Function.identity(), (a, b) -> a)));
|
||||
}
|
||||
List<JobConfigDO> insertList = Lists.newArrayList();
|
||||
List<JobConfigDO> 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
34
xservice-quartz-admin/src/main/resources/application-dev.yml
Normal file
34
xservice-quartz-admin/src/main/resources/application-dev.yml
Normal file
@@ -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
|
||||
9
xservice-quartz-admin/src/main/resources/application.yml
Normal file
9
xservice-quartz-admin/src/main/resources/application.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
server:
|
||||
port: 30030
|
||||
spring:
|
||||
profiles:
|
||||
active: dev
|
||||
application:
|
||||
name: xservice-quartz-admin
|
||||
main:
|
||||
allow-bean-definition-overriding: true
|
||||
Reference in New Issue
Block a user