Merge branch 'feat/dev_schedule_v1' into test
# Conflicts: # pom.xml # xservice-common/pom.xml
This commit is contained in:
3
pom.xml
3
pom.xml
@@ -6,13 +6,14 @@
|
|||||||
|
|
||||||
<groupId>com.xiang</groupId>
|
<groupId>com.xiang</groupId>
|
||||||
<artifactId>xservice-basic</artifactId>
|
<artifactId>xservice-basic</artifactId>
|
||||||
<version>1.0</version>
|
<version>1.1-SNAPSHOT</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<modules>
|
<modules>
|
||||||
<module>xservice-common</module>
|
<module>xservice-common</module>
|
||||||
<module>xservice-cache</module>
|
<module>xservice-cache</module>
|
||||||
<module>xservice-third-part</module>
|
<module>xservice-third-part</module>
|
||||||
<module>xservice-mysql-starter</module>
|
<module>xservice-mysql-starter</module>
|
||||||
|
<module>xservice-schedule-starter</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<artifactId>xservice-basic</artifactId>
|
<artifactId>xservice-basic</artifactId>
|
||||||
<version>1.0-SNAPSHOT</version>
|
<version>1.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<version>1.0.1-SNAPSHOT</version>
|
<version>1.0.2-SNAPSHOT</version>
|
||||||
|
|
||||||
<artifactId>xservice-common</artifactId>
|
<artifactId>xservice-common</artifactId>
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
29
xservice-schedule-starter/pom.xml
Normal file
29
xservice-schedule-starter/pom.xml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.xiang</groupId>
|
||||||
|
<artifactId>xservice-basic</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<version>1.1-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<artifactId>xservice-schedule-starter</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.xiang</groupId>
|
||||||
|
<artifactId>xservice-common</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -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 {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Long, ScheduledFuture<?>> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Long, Consumer<Long>> handlers = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public void register(Long taskId, Consumer<Long> consumer) {
|
||||||
|
handlers.put(taskId, consumer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run(Long taskId) {
|
||||||
|
Consumer<Long> consumer = handlers.get(taskId);
|
||||||
|
if (consumer != null) consumer.accept(taskId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregister(Long taskId) {
|
||||||
|
handlers.remove(taskId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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<String, Object> parameters;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.xiang.xservice.schedule.helper;
|
||||||
|
|
||||||
|
|
||||||
|
import com.xiang.xservice.schedule.entity.TaskConfig;
|
||||||
|
|
||||||
|
public interface TaskExecutor {
|
||||||
|
void execute(TaskConfig taskInfo);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||||
|
com.xiang.xservice.schedule.config.DynamicSchedulerAutoConfiguration
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.xiang.xservice.schedule.mapper.ScheduledTaskMapper">
|
||||||
|
|
||||||
|
<resultMap id="BaseResultMap" type="com.xiang.xservice.schedule.entity.ScheduledTaskEntity" >
|
||||||
|
<result column="id" property="id"/>
|
||||||
|
<result column="task_name" property="taskName"/>
|
||||||
|
<result column="task_group" property="taskGroup"/>
|
||||||
|
<result column="run_time" property="runTime"/>
|
||||||
|
<result column="parameters" property="status"/>
|
||||||
|
<result column="create_time" property="parameters"/>
|
||||||
|
<result column="update_time" property="createdTime"/>
|
||||||
|
<result column="status" property="updatedTime"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<sql id="Base_Column_List">
|
||||||
|
id, task_name, task_group, run_time, status, parameters, create_time, update_time
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<!-- 插入一条任务 -->
|
||||||
|
<insert id="save" useGeneratedKeys="true" keyProperty="id" parameterType="com.xiang.xservice.schedule.entity.ScheduledTaskEntity">
|
||||||
|
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})
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<!-- 更新任务 -->
|
||||||
|
<update id="update" parameterType="com.xiang.xservice.schedule.entity.ScheduledTaskEntity">
|
||||||
|
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}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<select id="getTask" resultMap="BaseResultMap" parameterType="java.lang.Long">
|
||||||
|
select <include refid="Base_Column_List"/>
|
||||||
|
from script_schedule_task where id = #{id}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
Reference in New Issue
Block a user