diff --git a/pom.xml b/pom.xml
index 79eac08..b248ca1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,13 +6,14 @@
com.xiang
xservice-basic
- 1.0
+ 1.1-SNAPSHOT
pom
xservice-common
xservice-cache
xservice-third-part
xservice-mysql-starter
+ xservice-schedule-starter
diff --git a/xservice-common/pom.xml b/xservice-common/pom.xml
index 2562933..d57d05f 100644
--- a/xservice-common/pom.xml
+++ b/xservice-common/pom.xml
@@ -8,7 +8,7 @@
xservice-basic
1.0-SNAPSHOT
- 1.0.1-SNAPSHOT
+ 1.0.2-SNAPSHOT
xservice-common
diff --git a/xservice-common/src/main/java/com/xiang/xservice/basic/utils/PrimaryKeyUtils.java b/xservice-common/src/main/java/com/xiang/xservice/basic/utils/PrimaryKeyUtils.java
new file mode 100644
index 0000000..d98e4f7
--- /dev/null
+++ b/xservice-common/src/main/java/com/xiang/xservice/basic/utils/PrimaryKeyUtils.java
@@ -0,0 +1,65 @@
+package com.xiang.xservice.basic.utils;
+
+
+public class PrimaryKeyUtils {
+ /**
+ * 自定义起始时间戳,例如 2025-01-01
+ */
+ private static final long START_TIMESTAMP = 1735660800000L;
+ private static final long DATA_CENTER_ID_BITS = 5L;
+ private static final long WORKER_ID_BITS = 5L;
+ private static final long SEQUENCE_BITS = 12L;
+
+ private static final long MAX_DATA_CENTER_ID = ~(-1L << DATA_CENTER_ID_BITS);
+ private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
+
+ private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;
+ private static final long DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
+ private static final long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATA_CENTER_ID_BITS;
+
+ private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);
+
+ // 可通过配置设置
+ private static final long DATA_CENTER_ID = 1L;
+ private static final long WORKER_ID = 1L;
+
+ private static long sequence = 0L;
+ private static long lastTimestamp = -1L;
+
+ // 分布式下可使用分布式锁替代 synchronized
+ public static synchronized long snowflakeId() {
+ long timestamp = currentTimestamp();
+
+ if (timestamp < lastTimestamp) {
+ throw new RuntimeException("Clock moved backwards. Refusing to generate id");
+ }
+
+ if (timestamp == lastTimestamp) {
+ sequence = (sequence + 1) & SEQUENCE_MASK;
+ if (sequence == 0) {
+ timestamp = waitNextMillis(timestamp);
+ }
+ } else {
+ sequence = 0L;
+ }
+
+ lastTimestamp = timestamp;
+
+ return ((timestamp - START_TIMESTAMP) << TIMESTAMP_LEFT_SHIFT)
+ | (DATA_CENTER_ID << DATA_CENTER_ID_SHIFT)
+ | (WORKER_ID << WORKER_ID_SHIFT)
+ | sequence;
+ }
+
+ private static long waitNextMillis(long currentTimestamp) {
+ long timestamp = currentTimestamp();
+ while (timestamp <= currentTimestamp) {
+ timestamp = currentTimestamp();
+ }
+ return timestamp;
+ }
+
+ private static long currentTimestamp() {
+ return System.currentTimeMillis();
+ }
+}
diff --git a/xservice-schedule-starter/pom.xml b/xservice-schedule-starter/pom.xml
new file mode 100644
index 0000000..21ec7b3
--- /dev/null
+++ b/xservice-schedule-starter/pom.xml
@@ -0,0 +1,29 @@
+
+
+ 4.0.0
+
+ com.xiang
+ xservice-basic
+ 1.0-SNAPSHOT
+
+ 1.1-SNAPSHOT
+
+ xservice-schedule-starter
+
+
+ 17
+ 17
+ UTF-8
+
+
+
+
+ com.xiang
+ xservice-common
+ 1.0-SNAPSHOT
+
+
+
+
\ No newline at end of file
diff --git a/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/config/DynamicSchedulerAutoConfiguration.java b/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/config/DynamicSchedulerAutoConfiguration.java
new file mode 100644
index 0000000..769249a
--- /dev/null
+++ b/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/config/DynamicSchedulerAutoConfiguration.java
@@ -0,0 +1,14 @@
+package com.xiang.xservice.schedule.config;
+
+import com.xiang.xservice.schedule.core.DynamicTaskScheduler;
+import com.xiang.xservice.schedule.core.TaskRegistry;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.Import;
+
+@AutoConfiguration
+@MapperScan("com.xiang.xservice.schedule.mapper")
+@Import({DynamicTaskSchedulerConfig.class, DynamicTaskScheduler.class, TaskRegistry.class})
+public class DynamicSchedulerAutoConfiguration {
+
+}
diff --git a/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/config/DynamicTaskSchedulerConfig.java b/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/config/DynamicTaskSchedulerConfig.java
new file mode 100644
index 0000000..1b79893
--- /dev/null
+++ b/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/config/DynamicTaskSchedulerConfig.java
@@ -0,0 +1,19 @@
+package com.xiang.xservice.schedule.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+
+@Configuration
+public class DynamicTaskSchedulerConfig {
+ @Bean
+ public ThreadPoolTaskScheduler globalTaskScheduler() {
+ ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
+ scheduler.setPoolSize(10);
+ scheduler.setThreadNamePrefix("dynamic-task-");
+ scheduler.setWaitForTasksToCompleteOnShutdown(true);
+ scheduler.setAwaitTerminationSeconds(30);
+ scheduler.initialize();
+ return scheduler;
+ }
+}
diff --git a/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/core/DynamicTaskScheduler.java b/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/core/DynamicTaskScheduler.java
new file mode 100644
index 0000000..a5f7913
--- /dev/null
+++ b/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/core/DynamicTaskScheduler.java
@@ -0,0 +1,48 @@
+package com.xiang.xservice.schedule.core;
+
+import com.xiang.xservice.schedule.entity.TaskConfig;
+import lombok.RequiredArgsConstructor;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.Date;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledFuture;
+
+@Component
+@RequiredArgsConstructor
+public class DynamicTaskScheduler {
+ private final ThreadPoolTaskScheduler taskScheduler;
+ private final Map> taskMap = new ConcurrentHashMap<>();
+
+
+ public void schedule(TaskConfig config, Runnable task) {
+ LocalDateTime time = config.getExecutionTime();
+ if (time.isBefore(LocalDateTime.now())) return;
+
+ ScheduledFuture> future = taskScheduler.schedule(() -> {
+ try {
+ task.run();
+ } finally {
+ taskMap.remove(config.getTaskId());
+ }
+ }, Date.from(time.atZone(ZoneId.systemDefault()).toInstant()));
+ taskMap.put(config.getTaskId(), future);
+ }
+
+ public void cancel(Long taskId) {
+ ScheduledFuture> future = taskMap.get(taskId);
+ if (future != null) {
+ future.cancel(true);
+ taskMap.remove(taskId);
+ }
+ }
+
+ public Boolean contains(Long taskId) {
+ return taskMap.containsKey(taskId);
+ }
+}
diff --git a/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/core/TaskRegistry.java b/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/core/TaskRegistry.java
new file mode 100644
index 0000000..d679683
--- /dev/null
+++ b/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/core/TaskRegistry.java
@@ -0,0 +1,21 @@
+package com.xiang.xservice.schedule.core;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+
+public class TaskRegistry {
+ private final ConcurrentHashMap> handlers = new ConcurrentHashMap<>();
+
+ public void register(Long taskId, Consumer consumer) {
+ handlers.put(taskId, consumer);
+ }
+
+ public void run(Long taskId) {
+ Consumer consumer = handlers.get(taskId);
+ if (consumer != null) consumer.accept(taskId);
+ }
+
+ public void unregister(Long taskId) {
+ handlers.remove(taskId);
+ }
+}
diff --git a/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/entity/ScheduledTaskEntity.java b/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/entity/ScheduledTaskEntity.java
new file mode 100644
index 0000000..552a6f0
--- /dev/null
+++ b/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/entity/ScheduledTaskEntity.java
@@ -0,0 +1,21 @@
+package com.xiang.xservice.schedule.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDateTime;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ScheduledTaskEntity {
+ private Long id;
+ private String taskName;
+ private String taskGroup;
+ private LocalDateTime runTime;
+ private Integer status;
+ private String parameters;
+ private LocalDateTime createdTime;
+ private LocalDateTime updatedTime;
+}
diff --git a/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/entity/TaskConfig.java b/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/entity/TaskConfig.java
new file mode 100644
index 0000000..4097387
--- /dev/null
+++ b/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/entity/TaskConfig.java
@@ -0,0 +1,19 @@
+package com.xiang.xservice.schedule.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDateTime;
+import java.util.Map;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class TaskConfig {
+ private Long taskId;
+ private String taskName;
+ private String taskGroup;
+ private LocalDateTime executionTime;
+ private Map parameters;
+}
diff --git a/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/enums/TaskGroupEnum.java b/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/enums/TaskGroupEnum.java
new file mode 100644
index 0000000..b4cdca6
--- /dev/null
+++ b/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/enums/TaskGroupEnum.java
@@ -0,0 +1,17 @@
+package com.xiang.xservice.schedule.enums;
+
+import lombok.Getter;
+
+@Getter
+public enum TaskGroupEnum {
+ SERVICE_FWD_SCHEDULE("xs-fwd", "芬玩岛定时任务");
+
+
+ private final String code;
+ private final String desc;
+
+ TaskGroupEnum(String code, String desc) {
+ this.code = code;
+ this.desc = desc;
+ }
+}
diff --git a/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/enums/TaskStatusEnum.java b/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/enums/TaskStatusEnum.java
new file mode 100644
index 0000000..cd266b3
--- /dev/null
+++ b/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/enums/TaskStatusEnum.java
@@ -0,0 +1,20 @@
+package com.xiang.xservice.schedule.enums;
+
+import lombok.Getter;
+
+@Getter
+public enum TaskStatusEnum {
+ UN_START(1, "未开始"),
+ PROCEED(2, "进行中"),
+ FINISHED(3, "已完成"),
+ CANCELED(4, "取消")
+ ;
+
+ private final Integer code;
+ private final String desc;
+
+ TaskStatusEnum(Integer code, String desc) {
+ this.code = code;
+ this.desc = desc;
+ }
+}
diff --git a/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/helper/TaskExecutor.java b/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/helper/TaskExecutor.java
new file mode 100644
index 0000000..0c27f21
--- /dev/null
+++ b/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/helper/TaskExecutor.java
@@ -0,0 +1,8 @@
+package com.xiang.xservice.schedule.helper;
+
+
+import com.xiang.xservice.schedule.entity.TaskConfig;
+
+public interface TaskExecutor {
+ void execute(TaskConfig taskInfo);
+}
diff --git a/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/mapper/ScheduledTaskMapper.java b/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/mapper/ScheduledTaskMapper.java
new file mode 100644
index 0000000..302046c
--- /dev/null
+++ b/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/mapper/ScheduledTaskMapper.java
@@ -0,0 +1,17 @@
+package com.xiang.xservice.schedule.mapper;
+
+import com.xiang.xservice.schedule.entity.ScheduledTaskEntity;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.springframework.stereotype.Repository;
+
+@Mapper
+@Repository
+public interface ScheduledTaskMapper {
+
+ int save(ScheduledTaskEntity entity);
+
+ int update(ScheduledTaskEntity entity);
+
+ ScheduledTaskEntity getTask(@Param("id") Long taskId);
+}
diff --git a/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/service/DynamicTaskSchedulerServiceImpl.java b/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/service/DynamicTaskSchedulerServiceImpl.java
new file mode 100644
index 0000000..69e7178
--- /dev/null
+++ b/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/service/DynamicTaskSchedulerServiceImpl.java
@@ -0,0 +1,65 @@
+package com.xiang.xservice.schedule.service;
+
+import com.xiang.xservice.schedule.entity.ScheduledTaskEntity;
+import com.xiang.xservice.schedule.enums.TaskStatusEnum;
+import com.xiang.xservice.schedule.mapper.ScheduledTaskMapper;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.Objects;
+
+@Service
+@RequiredArgsConstructor
+public class DynamicTaskSchedulerServiceImpl implements IDynamicTaskSchedulerService{
+
+ private final ScheduledTaskMapper scheduledTaskMapper;
+
+ @Override
+ public Boolean saveTask(ScheduledTaskEntity entity) {
+ return scheduledTaskMapper.save(entity) > 0;
+ }
+
+ @Override
+ public Boolean updateTask(ScheduledTaskEntity entity) {
+ return scheduledTaskMapper.update(entity) > 0;
+ }
+
+ @Override
+ public ScheduledTaskEntity getTask(Long taskId) {
+ return scheduledTaskMapper.getTask(taskId);
+ }
+
+ @Override
+ public Boolean updateProcess(Long taskId) {
+ ScheduledTaskEntity task = scheduledTaskMapper.getTask(taskId);
+ if (Objects.nonNull(task)) {
+ task.setStatus(TaskStatusEnum.PROCEED.getCode());
+ task.setUpdatedTime(LocalDateTime.now());
+ return scheduledTaskMapper.update(task) > 0;
+ }
+ return Boolean.FALSE;
+ }
+
+ @Override
+ public Boolean cancelTask(Long taskId) {
+ ScheduledTaskEntity task = scheduledTaskMapper.getTask(taskId);
+ if (Objects.nonNull(task)) {
+ task.setStatus(TaskStatusEnum.CANCELED.getCode());
+ task.setUpdatedTime(LocalDateTime.now());
+ return scheduledTaskMapper.update(task) > 0;
+ }
+ return Boolean.FALSE;
+ }
+
+ @Override
+ public Boolean finishTask(Long taskId) {
+ ScheduledTaskEntity task = scheduledTaskMapper.getTask(taskId);
+ if (Objects.nonNull(task)) {
+ task.setStatus(TaskStatusEnum.FINISHED.getCode());
+ task.setUpdatedTime(LocalDateTime.now());
+ return scheduledTaskMapper.update(task) > 0;
+ }
+ return Boolean.FALSE;
+ }
+}
diff --git a/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/service/IDynamicTaskSchedulerService.java b/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/service/IDynamicTaskSchedulerService.java
new file mode 100644
index 0000000..c2a91da
--- /dev/null
+++ b/xservice-schedule-starter/src/main/java/com/xiang/xservice/schedule/service/IDynamicTaskSchedulerService.java
@@ -0,0 +1,13 @@
+package com.xiang.xservice.schedule.service;
+
+import com.xiang.xservice.schedule.entity.ScheduledTaskEntity;
+
+
+public interface IDynamicTaskSchedulerService {
+ Boolean saveTask(ScheduledTaskEntity entity);
+ Boolean updateTask(ScheduledTaskEntity entity);
+ ScheduledTaskEntity getTask(Long taskId);
+ Boolean updateProcess(Long taskId);
+ Boolean cancelTask(Long taskId);
+ Boolean finishTask(Long taskId);
+}
diff --git a/xservice-schedule-starter/src/main/resources/META-INF/spring.factories b/xservice-schedule-starter/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..0eff330
--- /dev/null
+++ b/xservice-schedule-starter/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+com.xiang.xservice.schedule.config.DynamicSchedulerAutoConfiguration
\ No newline at end of file
diff --git a/xservice-schedule-starter/src/main/resources/mapper/ScheduledTaskMapper.xml b/xservice-schedule-starter/src/main/resources/mapper/ScheduledTaskMapper.xml
new file mode 100644
index 0000000..f3db068
--- /dev/null
+++ b/xservice-schedule-starter/src/main/resources/mapper/ScheduledTaskMapper.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ id, task_name, task_group, run_time, status, parameters, create_time, update_time
+
+
+
+
+ INSERT INTO script_schedule_task (id, task_name, task_group, run_time, status, parameters, create_time, update_time)
+ VALUES (#{id}, #{taskName}, #{taskGroup}, #{runTime}, #{status}, #{parameters}, #{createdTime}, #{updatedTime})
+
+
+
+
+ UPDATE script_schedule_task
+ SET task_name = #{taskName},
+ task_group = #{taskGroup},
+ run_time = #{runTime},
+ status = #{status},
+ parameters = #{parameters},
+ create_time = #{createdTime},
+ update_time = #{updatedTime}
+ WHERE id = #{id}
+
+
+
+
+
\ No newline at end of file