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