├── Courier-Core ├── src │ └── main │ │ ├── resources │ │ └── META-INF │ │ │ └── spring.factories │ │ └── java │ │ └── com │ │ └── courier │ │ └── core │ │ ├── custom │ │ ├── CourierFrameworkAlerter.java │ │ └── TaskTimeRangeQuery.java │ │ ├── annotation │ │ ├── EnableCourierTask.java │ │ ├── ComponentScanConfig.java │ │ ├── CourierTaskSelector.java │ │ └── CourierTask.java │ │ ├── exception │ │ └── CourierException.java │ │ ├── service │ │ ├── TaskEngineExecutor.java │ │ ├── CourierTaskService.java │ │ ├── CourierTaskInstance.java │ │ ├── TaskScheduleManager.java │ │ ├── CourierTaskServiceImpl.java │ │ └── TaskEngineExecutorService.java │ │ ├── utils │ │ ├── AopTaskInitPrevent.java │ │ ├── DateUtils.java │ │ ├── DefaultValueUtils.java │ │ ├── ExpressionUtils.java │ │ ├── ReflectUtils.java │ │ └── SpringUtils.java │ │ ├── CourierTaskStatusEnum.java │ │ ├── config │ │ ├── CourierFallbackConfigProperties.java │ │ ├── CourierConsistencyConfigProperties.java │ │ ├── CourierConsistencyConfig.java │ │ ├── ThreadPoolConfig.java │ │ └── CourierConfigAutoConfiguration.java │ │ ├── ThreadEnum.java │ │ ├── ExecutionEnum.java │ │ ├── aspect │ │ └── CourierTaskAspect.java │ │ └── mapper │ │ └── TaskStoreMapper.java └── pom.xml ├── .gitignore ├── README.md ├── Courier-Demo └── pom.xml └── pom.xml /Courier-Core/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.courier.core.config.CourierConfigAutoConfiguration -------------------------------------------------------------------------------- /Courier-Core/src/main/java/com/courier/core/custom/CourierFrameworkAlerter.java: -------------------------------------------------------------------------------- 1 | package com.courier.core.custom; 2 | 3 | import com.courier.core.service.CourierTaskInstance; 4 | 5 | /** 6 | * @author Anthony 7 | * @create 2022/1/19 8 | * @desc implement this class to send alert when fallback fails 9 | */ 10 | public interface CourierFrameworkAlerter { 11 | void sendAlertNotice(CourierTaskInstance courierTaskInstance); 12 | } 13 | -------------------------------------------------------------------------------- /Courier-Core/src/main/java/com/courier/core/annotation/EnableCourierTask.java: -------------------------------------------------------------------------------- 1 | package com.courier.core.annotation; 2 | 3 | import org.springframework.context.annotation.Import; 4 | 5 | import java.lang.annotation.*; 6 | 7 | /** 8 | * @author Anthony 9 | * @create 2022/1/16 10 | * @desc 11 | */ 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Documented 14 | @Target(ElementType.TYPE) 15 | @Import({CourierTaskSelector.class}) 16 | public @interface EnableCourierTask { 17 | } 18 | -------------------------------------------------------------------------------- /Courier-Core/src/main/java/com/courier/core/exception/CourierException.java: -------------------------------------------------------------------------------- 1 | package com.courier.core.exception; 2 | 3 | /** 4 | * @author Anthony 5 | * @create 2022/1/19 6 | * @desc 7 | */ 8 | public class CourierException extends RuntimeException{ 9 | public CourierException() { 10 | } 11 | 12 | public CourierException(Exception e) { 13 | super(e); 14 | } 15 | 16 | public CourierException(String message) { 17 | super(message); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IntelliJ project files 2 | .idea/ 3 | *.iml 4 | out 5 | gen 6 | 7 | # Visual Studio Code 8 | .history/ 9 | 10 | # Maven 11 | target/ 12 | pom.xml.tag 13 | pom.xml.releaseBackup 14 | pom.xml.versionsBackup 15 | pom.xml.next 16 | release.properties 17 | dependency-reduced-pom.xml 18 | buildNumber.properties 19 | .mvn/timing.properties 20 | !/.mvn/wrapper/maven-wrapper.jar 21 | 22 | # Eclipse 23 | .classpath 24 | .settings/ 25 | .project 26 | bin/ 27 | 28 | # System related 29 | *.DS_Store 30 | Thumbs.db 31 | -------------------------------------------------------------------------------- /Courier-Core/src/main/java/com/courier/core/service/TaskEngineExecutor.java: -------------------------------------------------------------------------------- 1 | package com.courier.core.service; 2 | 3 | /** 4 | * @author Anthony 5 | * @create 2022/1/18 6 | * @desc 7 | */ 8 | public interface TaskEngineExecutor { 9 | 10 | 11 | void executeTaskInstance(CourierTaskInstance instance); 12 | 13 | /** 14 | * execute when {@link #executeTaskInstance(CourierTaskInstance)} fails 15 | * @param instance 16 | */ 17 | void fallbackTaskInstance(CourierTaskInstance instance); 18 | } 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Courier is responsible for Data Consistency. 2 | 3 | Courier是一个分布式消息投递的使者,确保消息的最终一致性 4 | 5 | - 使用Spring 自动配置机制和环绕通知对方法上的注解进行解析和环绕通知处理, 6 | - 引入mybatis插入对应的接口数据,通过数据执行成功/失败更新对应的sql 7 | - 在异常情况下会触发fallback机制和告警 8 | 9 | 本地消息表方案:在本地事务中将要执行的异步操作记录在消息表中,如果执行失败,可以通过定时任务来补偿 10 | 11 | - with spring auto configurations and around aspect to proceed methods 12 | - import mybatis to insert a pre sql and post update sql with success or fail operation 13 | - with fallback and fallback error(will trigger alarm )support 14 | -------------------------------------------------------------------------------- /Courier-Core/src/main/java/com/courier/core/annotation/ComponentScanConfig.java: -------------------------------------------------------------------------------- 1 | package com.courier.core.annotation; 2 | 3 | import org.mybatis.spring.annotation.MapperScan; 4 | import org.springframework.context.annotation.ComponentScan; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | /** 8 | * @author Anthony 9 | * @create 2022/1/16 10 | * @desc 11 | */ 12 | @Configuration 13 | @ComponentScan(value = {"com.courier.core"}) 14 | @MapperScan(basePackages = {"com.courier.core.mapper"}) 15 | public class ComponentScanConfig { 16 | } 17 | -------------------------------------------------------------------------------- /Courier-Core/src/main/java/com/courier/core/utils/AopTaskInitPrevent.java: -------------------------------------------------------------------------------- 1 | package com.courier.core.utils; 2 | 3 | import java.util.function.Supplier; 4 | 5 | /** 6 | * @author Anthony 7 | * @create 2022/1/23 8 | * @desc 9 | */ 10 | public class AopTaskInitPrevent { 11 | private static final ThreadLocal FLAG = ThreadLocal.withInitial(() -> false); 12 | 13 | public static Boolean shouldPrevent() { 14 | return FLAG.get(); 15 | } 16 | 17 | public static void setPrevent(Boolean value) { 18 | FLAG.set(value); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Courier-Core/src/main/java/com/courier/core/annotation/CourierTaskSelector.java: -------------------------------------------------------------------------------- 1 | package com.courier.core.annotation; 2 | 3 | import org.springframework.context.annotation.ImportSelector; 4 | import org.springframework.core.type.AnnotationMetadata; 5 | 6 | /** 7 | * @author Anthony 8 | * @create 2022/1/16 9 | * @desc 10 | */ 11 | public class CourierTaskSelector implements ImportSelector { 12 | @Override 13 | public String[] selectImports(AnnotationMetadata importingClassMetadata) { 14 | return new String[]{ComponentScanConfig.class.getName()}; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Courier-Core/src/main/java/com/courier/core/CourierTaskStatusEnum.java: -------------------------------------------------------------------------------- 1 | package com.courier.core; 2 | 3 | /** 4 | * @author Anthony 5 | * @create 2022/1/16 6 | * @desc 7 | */ 8 | public enum CourierTaskStatusEnum { 9 | INIT(1, "init status"), 10 | START(2, "init status"), 11 | SUCCESS(3, "success status"), 12 | FAIL(4, "fail status"); 13 | 14 | public int code; 15 | public String desc; 16 | 17 | CourierTaskStatusEnum(int code, String desc) { 18 | this.code = code; 19 | this.desc = desc; 20 | } 21 | 22 | public String getDesc() { 23 | return desc; 24 | } 25 | 26 | public int getCode() { 27 | return code; 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /Courier-Core/src/main/java/com/courier/core/config/CourierFallbackConfigProperties.java: -------------------------------------------------------------------------------- 1 | package com.courier.core.config; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import org.springframework.boot.context.properties.ConfigurationProperties; 8 | 9 | /** 10 | * @author Anthony 11 | * @create 2022/1/19 12 | * @desc 13 | */ 14 | @Data 15 | @Builder 16 | @NoArgsConstructor 17 | @AllArgsConstructor 18 | @ConfigurationProperties(prefix = "courier.consistency.action") 19 | public class CourierFallbackConfigProperties { 20 | /** 21 | * trigger fallback 22 | */ 23 | public Integer failCountThreshold = 0; 24 | } 25 | -------------------------------------------------------------------------------- /Courier-Demo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | Courier 7 | com.courier 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | Courier-Demo 13 | 14 | 15 | 8 16 | 8 17 | 18 | 19 | -------------------------------------------------------------------------------- /Courier-Core/src/main/java/com/courier/core/ThreadEnum.java: -------------------------------------------------------------------------------- 1 | package com.courier.core; 2 | 3 | /** 4 | * @author Anthony 5 | * @create 2022/1/16 6 | * @desc 7 | */ 8 | public enum ThreadEnum { 9 | SYNC(1, "synchronizing mode"), 10 | ASYNC(2, "asynchronizing mode"); 11 | 12 | public int code; 13 | public String desc; 14 | 15 | ThreadEnum(int code, String desc) { 16 | this.code = code; 17 | this.desc = desc; 18 | } 19 | 20 | public int getCode() { 21 | return code; 22 | } 23 | 24 | public String getDesc() { 25 | return desc; 26 | } 27 | 28 | public void setCode(int code) { 29 | this.code = code; 30 | } 31 | 32 | public void setDesc(String desc) { 33 | this.desc = desc; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Courier-Core/src/main/java/com/courier/core/ExecutionEnum.java: -------------------------------------------------------------------------------- 1 | package com.courier.core; 2 | 3 | /** 4 | * @author Anthony 5 | * @create 2022/1/16 6 | * @desc 7 | */ 8 | public enum ExecutionEnum { 9 | EXECUTE_NOW(1, "execute right now"), 10 | EXECUTE_SCHEDULE(2, "execute later"); 11 | 12 | public int code; 13 | public String desc; 14 | 15 | ExecutionEnum(int code, String desc) { 16 | this.code = code; 17 | this.desc = desc; 18 | } 19 | 20 | public int getCode() { 21 | return code; 22 | } 23 | 24 | public String getDesc() { 25 | return desc; 26 | } 27 | 28 | public void setCode(int code) { 29 | this.code = code; 30 | } 31 | 32 | public void setDesc(String desc) { 33 | this.desc = desc; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Courier-Core/src/main/java/com/courier/core/utils/DateUtils.java: -------------------------------------------------------------------------------- 1 | package com.courier.core.utils; 2 | 3 | import java.util.Calendar; 4 | import java.util.Date; 5 | import java.util.GregorianCalendar; 6 | 7 | /** 8 | * @author Anthony 9 | * @create 2022/1/19 10 | * @desc 11 | */ 12 | public class DateUtils { 13 | 14 | /** 15 | * get num days before date 16 | * @param startDate 17 | * @param calendarUnit 18 | * @param num 19 | * @return 20 | */ 21 | public static Date getDateByDayNum(Date startDate, int calendarUnit, int num) { 22 | if (num == 0) { 23 | return startDate; 24 | } 25 | Calendar calendar = new GregorianCalendar(); 26 | calendar.setTime(startDate); 27 | calendar.add(calendarUnit, num); 28 | return calendar.getTime(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Courier-Core/src/main/java/com/courier/core/service/CourierTaskService.java: -------------------------------------------------------------------------------- 1 | package com.courier.core.service; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * @author Anthony 7 | * @create 2022/1/16 8 | * @desc 9 | */ 10 | public interface CourierTaskService { 11 | 12 | void initTask(CourierTaskInstance courierTaskInstance); 13 | 14 | int turnOnTask(CourierTaskInstance courierTaskInstance); 15 | 16 | int markTaskFail(CourierTaskInstance courierTaskInstance); 17 | 18 | int markTaskSuccess(CourierTaskInstance courierTaskInstance); 19 | 20 | void markTaskFallbackFail(CourierTaskInstance courierTaskInstance); 21 | 22 | List listUnfinishedTasks(); 23 | 24 | CourierTaskInstance getTaskById(Long id, Long shardKey); 25 | 26 | void submitTaskInstance(CourierTaskInstance courierTaskInstance); 27 | } 28 | -------------------------------------------------------------------------------- /Courier-Core/src/main/java/com/courier/core/annotation/CourierTask.java: -------------------------------------------------------------------------------- 1 | package com.courier.core.annotation; 2 | 3 | import com.courier.core.ExecutionEnum; 4 | import com.courier.core.ThreadEnum; 5 | 6 | import java.lang.annotation.*; 7 | 8 | /** 9 | * @author Anthony 10 | * @create 2022/1/16 11 | * @desc 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Documented 15 | @Target(ElementType.METHOD) 16 | public @interface CourierTask { 17 | 18 | String id() default ""; 19 | 20 | Class fallbackClass() default void.class; 21 | 22 | String alarmBeanName() default ""; 23 | 24 | int executeIntervalSec() default 60; 25 | 26 | String alertExpression() default "executeTimes > 1 && executeTimes < 5"; 27 | 28 | int delayTime() default 60; 29 | 30 | ExecutionEnum executeMode() default ExecutionEnum.EXECUTE_NOW; 31 | 32 | ThreadEnum threadMode() default ThreadEnum.ASYNC; 33 | } 34 | -------------------------------------------------------------------------------- /Courier-Core/src/main/java/com/courier/core/custom/TaskTimeRangeQuery.java: -------------------------------------------------------------------------------- 1 | package com.courier.core.custom; 2 | 3 | import java.util.Calendar; 4 | import java.util.Date; 5 | 6 | import static com.courier.core.utils.DateUtils.getDateByDayNum; 7 | 8 | /** 9 | * task query range , 10 | * remember to implement this class when you want to customize your own time range query 11 | */ 12 | public interface TaskTimeRangeQuery { 13 | 14 | 15 | Date getStartTime(); 16 | 17 | 18 | Date getEndTime(); 19 | 20 | 21 | Long limitTaskCount(); 22 | 23 | 24 | /** 25 | * default for{@link #getStartTime()} 26 | * 27 | * @return 28 | */ 29 | static Date getStartTimeByStatic() { 30 | return getDateByDayNum(new Date(), Calendar.HOUR, -1); 31 | } 32 | 33 | 34 | /** 35 | * default for{@link #getEndTime()} ()} 36 | * 37 | * @return 38 | */ 39 | static Date getEndTimeByStatic() { 40 | return new Date(); 41 | } 42 | 43 | /** 44 | * default for{@link #limitTaskCount()} ()} 45 | * 46 | * @return 47 | */ 48 | static Long limitTaskCountByStatic() { 49 | return 1000L; 50 | } 51 | } -------------------------------------------------------------------------------- /Courier-Core/src/main/java/com/courier/core/utils/DefaultValueUtils.java: -------------------------------------------------------------------------------- 1 | package com.courier.core.utils; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.util.ObjectUtils; 5 | import org.springframework.util.StringUtils; 6 | 7 | 8 | 9 | /** 10 | * default value utils 11 | */ 12 | @Slf4j 13 | public class DefaultValueUtils { 14 | 15 | 16 | public static String getOrDefault(String value, String defaultValue) { 17 | if (StringUtils.isEmpty(value)) { 18 | return defaultValue; 19 | } 20 | return value; 21 | } 22 | 23 | 24 | public static Integer getOrDefault(Integer value, Integer defaultValue) { 25 | if (ObjectUtils.isEmpty(value)) { 26 | return defaultValue; 27 | } 28 | return value; 29 | } 30 | 31 | 32 | public static Long getOrDefault(Long value, Long defaultValue) { 33 | if (ObjectUtils.isEmpty(value)) { 34 | return defaultValue; 35 | } 36 | return value; 37 | } 38 | 39 | public static Boolean getOrDefault(Boolean value, Boolean defaultValue) { 40 | if (ObjectUtils.isEmpty(value)) { 41 | return defaultValue; 42 | } 43 | return value; 44 | } 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /Courier-Core/src/main/java/com/courier/core/config/CourierConsistencyConfigProperties.java: -------------------------------------------------------------------------------- 1 | package com.courier.core.config; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import org.springframework.boot.context.properties.ConfigurationProperties; 8 | 9 | /** 10 | * @author Anthony 11 | * @create 2022/1/19 12 | * @desc 13 | */ 14 | @Data 15 | @Builder 16 | @NoArgsConstructor 17 | @AllArgsConstructor 18 | @ConfigurationProperties(prefix = "courier.consistency.parallel.pool") 19 | public class CourierConsistencyConfigProperties { 20 | /** 21 | * core pool size for schedule task 22 | */ 23 | public Integer threadCorePoolSize = 5; 24 | /** 25 | * max pool size for schedule task 26 | */ 27 | public Integer threadMaxPoolSize = 5; 28 | /** 29 | * queue size for schedule task 30 | */ 31 | public Integer threadPoolQueueSize = 100; 32 | /** 33 | * keep alive time 34 | */ 35 | public Long threadPoolKeepAliveTime = 60L; 36 | /** 37 | * alternatives:[SECONDS,MINUTES,HOURS,DAYS,NANOSECONDS,MICROSECONDS,MILLISECONDS] for keep alive time 38 | */ 39 | public String threadPoolKeepAliveTimeUnit = "SECONDS"; 40 | 41 | /** 42 | * time range class full name is required here , which must implement {@link com.courier.core.custom.TaskTimeRangeQuery} 43 | */ 44 | private String taskScheduleTimeRangeClassName = ""; 45 | 46 | } 47 | -------------------------------------------------------------------------------- /Courier-Core/src/main/java/com/courier/core/config/CourierConsistencyConfig.java: -------------------------------------------------------------------------------- 1 | package com.courier.core.config; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import org.springframework.boot.context.properties.ConfigurationProperties; 8 | 9 | /** 10 | * @author Anthony 11 | * @create 2022/1/19 12 | * @desc 13 | */ 14 | @Data 15 | @Builder 16 | @NoArgsConstructor 17 | @AllArgsConstructor 18 | public class CourierConsistencyConfig { 19 | /** 20 | * core pool size for schedule task 21 | */ 22 | public Integer threadCorePoolSize; 23 | /** 24 | * max pool size for schedule task 25 | */ 26 | public Integer threadMaxPoolSize; 27 | /** 28 | * queue size for schedule task 29 | */ 30 | public Integer threadPoolQueueSize; 31 | /** 32 | * keep alive time 33 | */ 34 | public Long threadPoolKeepAliveTime; 35 | /** 36 | * alternatives:[SECONDS,MINUTES,HOURS,DAYS,NANOSECONDS,MICROSECONDS,MILLISECONDS] for keep alive time 37 | */ 38 | public String threadPoolKeepAliveTimeUnit; 39 | /** 40 | * fallback trigger when execution times is bigger than this threshold 41 | */ 42 | public Integer failCountThreshold; 43 | 44 | /** 45 | * time range class full name is required here , which must implement {@link com.courier.core.custom.TaskTimeRangeQuery} 46 | */ 47 | private String taskScheduleTimeRangeClassName = ""; 48 | 49 | } 50 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.courier 8 | Courier 9 | pom 10 | 1.0-SNAPSHOT 11 | 12 | Courier-Core 13 | Courier-Demo 14 | 15 | 16 | 17 | 18 | UTF-8 19 | UTF-8 20 | 1.8 21 | 1.8 22 | 1.8 23 | 3.24.1-GA 24 | 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-dependencies 31 | 2.3.2.RELEASE 32 | pom 33 | import 34 | 35 | 36 | 37 | org.projectlombok 38 | lombok 39 | 1.18.2 40 | 41 | 42 | 43 | cn.hutool 44 | hutool-all 45 | 5.4.1 46 | 47 | 48 | 49 | org.javassist 50 | javassist 51 | ${javassist.version} 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /Courier-Core/src/main/java/com/courier/core/service/CourierTaskInstance.java: -------------------------------------------------------------------------------- 1 | package com.courier.core.service; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.Date; 9 | 10 | /** 11 | * @author Anthony 12 | * @create 2022/1/16 13 | * @desc 14 | */ 15 | @Data 16 | @Builder 17 | @AllArgsConstructor 18 | @NoArgsConstructor 19 | public class CourierTaskInstance { 20 | 21 | private Long id; 22 | /** 23 | * user define task id 24 | */ 25 | private String taskId; 26 | /** 27 | * full method sign name 28 | */ 29 | private String methodSignName; 30 | /** 31 | * method name 32 | */ 33 | private String methodName; 34 | /** 35 | * parameter class path 36 | */ 37 | private String parameterTypes; 38 | /** 39 | * parameter in Json 40 | */ 41 | private String taskParameter; 42 | /** 43 | * task status 44 | */ 45 | private int taskStatus; 46 | /** 47 | * default 60s interval 48 | */ 49 | private int executeIntervalSec; 50 | /** 51 | * default 60s delay 52 | */ 53 | private int delayTime; 54 | /** 55 | * execute times 56 | */ 57 | private int executeTimes; 58 | /** 59 | * execute time 60 | */ 61 | private Long executeTime; 62 | /** 63 | * error message when process 64 | */ 65 | private String errorMsg; 66 | /** 67 | * execute mode 68 | */ 69 | private Integer executeMode; 70 | /** 71 | * thread mode 72 | */ 73 | private Integer threadMode; 74 | /** 75 | * alarm expression 76 | */ 77 | private String alertExpression; 78 | /** 79 | * Alarm action bean name is required to implement CourierFrameworkAlerter method and add to Spring 80 | */ 81 | private String alertActionBeanName; 82 | /** 83 | * fallback class 84 | */ 85 | private String fallbackClassName; 86 | /** 87 | * fallback error message 88 | */ 89 | private String fallbackErrorMsg; 90 | 91 | 92 | private Date gmtCreate; 93 | 94 | private Date gmtModified; 95 | } 96 | -------------------------------------------------------------------------------- /Courier-Core/src/main/java/com/courier/core/config/ThreadPoolConfig.java: -------------------------------------------------------------------------------- 1 | package com.courier.core.config; 2 | 3 | import com.courier.core.service.CourierTaskInstance; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.stereotype.Component; 7 | 8 | import java.util.concurrent.*; 9 | import java.util.concurrent.atomic.AtomicInteger; 10 | 11 | /** 12 | * @author Anthony 13 | * @create 2022/1/23 14 | * @desc 15 | */ 16 | @Component 17 | public class ThreadPoolConfig { 18 | 19 | private static final String COURIER_TASK_THREAD_POOL_PREFIX = "CourierThreadPool_"; 20 | 21 | 22 | private static final String ALERT_THREAD_POOL_PREFIX = "AlertThreadPool_"; 23 | 24 | @Autowired 25 | private CourierConsistencyConfig tendConsistencyConfiguration; 26 | 27 | 28 | @Bean 29 | public CompletionService consistencyTaskPool() { 30 | LinkedBlockingQueue asyncConsistencyTaskThreadPoolQueue = 31 | new LinkedBlockingQueue<>(tendConsistencyConfiguration.getThreadPoolQueueSize()); 32 | ThreadPoolExecutor asyncReleaseResourceExecutorPool = new ThreadPoolExecutor( 33 | tendConsistencyConfiguration.getThreadCorePoolSize(), 34 | tendConsistencyConfiguration.getThreadCorePoolSize(), 35 | tendConsistencyConfiguration.getThreadPoolKeepAliveTime(), 36 | TimeUnit.valueOf(tendConsistencyConfiguration.getThreadPoolKeepAliveTimeUnit()), 37 | asyncConsistencyTaskThreadPoolQueue, 38 | createThreadFactory(COURIER_TASK_THREAD_POOL_PREFIX) 39 | ); 40 | return new ExecutorCompletionService<>(asyncReleaseResourceExecutorPool); 41 | } 42 | 43 | 44 | @Bean 45 | public ThreadPoolExecutor alertNoticePool() { 46 | LinkedBlockingQueue asyncAlertNoticeThreadPoolQueue = 47 | new LinkedBlockingQueue<>(100); 48 | return new ThreadPoolExecutor( 49 | 3, 50 | 5, 51 | 60, 52 | TimeUnit.SECONDS, 53 | asyncAlertNoticeThreadPoolQueue, 54 | createThreadFactory(ALERT_THREAD_POOL_PREFIX) 55 | ); 56 | } 57 | 58 | 59 | private ThreadFactory createThreadFactory(String threadPoolPrefix) { 60 | return new ThreadFactory() { 61 | private AtomicInteger threadIndex = new AtomicInteger(0); 62 | 63 | @Override 64 | public Thread newThread(Runnable r) { 65 | return new Thread(r, threadPoolPrefix + this.threadIndex.incrementAndGet()); 66 | } 67 | }; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /Courier-Core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | Courier 7 | com.courier 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | Courier-Core 13 | 14 | 15 | 8 16 | 8 17 | 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-web 23 | 24 | 25 | 26 | org.springframework 27 | spring-aspects 28 | 29 | 30 | 31 | org.springframework 32 | spring-tx 33 | provided 34 | 35 | 36 | 37 | org.javassist 38 | javassist 39 | ${javassist.version} 40 | 41 | 42 | 43 | mysql 44 | mysql-connector-java 45 | test 46 | 47 | 48 | 49 | org.mybatis.spring.boot 50 | mybatis-spring-boot-starter 51 | 2.1.4 52 | 53 | 54 | 55 | org.projectlombok 56 | lombok 57 | 58 | 59 | 60 | cn.hutool 61 | hutool-all 62 | 63 | 64 | 65 | org.springframework.boot 66 | spring-boot-configuration-processor 67 | true 68 | 69 | 70 | 71 | org.springframework.boot 72 | spring-boot-autoconfigure 73 | 74 | 75 | 76 | com.google.guava 77 | guava 78 | 18.0 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /Courier-Core/src/main/java/com/courier/core/service/TaskScheduleManager.java: -------------------------------------------------------------------------------- 1 | package com.courier.core.service; 2 | 3 | import cn.hutool.core.util.ObjectUtil; 4 | import cn.hutool.extra.spring.SpringUtil; 5 | import com.courier.core.exception.CourierException; 6 | import com.courier.core.utils.AopTaskInitPrevent; 7 | import com.courier.core.utils.ReflectUtils; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | 11 | import org.springframework.stereotype.Component; 12 | import org.springframework.util.ObjectUtils; 13 | 14 | import javax.annotation.Resource; 15 | import java.lang.reflect.InvocationTargetException; 16 | import java.lang.reflect.Method; 17 | import java.util.concurrent.CompletionService; 18 | 19 | /** 20 | * @author Anthony 21 | * @create 2022/1/19 22 | * @desc task manager for schedule type task 23 | */ 24 | @Slf4j 25 | @Component 26 | public class TaskScheduleManager { 27 | @Autowired 28 | CourierTaskService courierTaskService; 29 | @Autowired 30 | private CompletionService consistencyTaskPool; 31 | 32 | @Resource 33 | private TaskEngineExecutor taskEngineExecutor; 34 | 35 | public void executeTask(CourierTaskInstance instance) { 36 | String methodSignName = instance.getMethodSignName(); 37 | Class clazz = getTaskMethodClass(methodSignName.split(ReflectUtils.CLAZZ_SEPARATOR)[0]); 38 | if (ObjectUtils.isEmpty(clazz)) { 39 | return; 40 | } 41 | Object bean = SpringUtil.getBean(clazz); 42 | if (ObjectUtils.isEmpty(bean)) { 43 | return; 44 | } 45 | 46 | String methodName = instance.getMethodName(); 47 | String[] parameterTypes = instance.getParameterTypes().split(ReflectUtils.METHOD_SEPARATOR); 48 | Class[] parameterClasses = ReflectUtils.buildTypeClassArray(parameterTypes); 49 | Method targetMethod = getTargetMethod(methodName, parameterClasses, clazz); 50 | if (ObjectUtils.isEmpty(targetMethod)) { 51 | return; 52 | } 53 | Object[] args = ReflectUtils.buildArgs(instance.getTaskParameter(), parameterClasses); 54 | 55 | 56 | try { 57 | AopTaskInitPrevent.setPrevent(true); 58 | targetMethod.invoke(bean, args); 59 | AopTaskInitPrevent.setPrevent(false); 60 | } catch (InvocationTargetException e) { 61 | log.error("[Courier Consistency]load target method error", e); 62 | Throwable target = e.getTargetException(); 63 | throw new CourierException((Exception) target); 64 | } catch (Exception ex) { 65 | throw new CourierException(ex); 66 | } 67 | } 68 | 69 | private Class getTaskMethodClass(String className) { 70 | Class clazz; 71 | try { 72 | clazz = Class.forName(className); 73 | return clazz; 74 | } catch (ClassNotFoundException e) { 75 | log.error("[Courier Consistency]load target class error", e); 76 | e.printStackTrace(); 77 | return null; 78 | } 79 | } 80 | 81 | /** 82 | * get Method object 83 | * parameterTypes – 84 | * 85 | * @param methodName the name of the method 86 | * @param parameterTypeClassArray the list of parameters 87 | * @param clazz class name 88 | * @return 89 | */ 90 | private Method getTargetMethod(String methodName, Class[] parameterTypeClassArray, Class clazz) { 91 | try { 92 | return clazz.getMethod(methodName, parameterTypeClassArray); 93 | } catch (NoSuchMethodException e) { 94 | log.error("[Courier Consistency]load target method error", e); 95 | return null; 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Courier-Core/src/main/java/com/courier/core/aspect/CourierTaskAspect.java: -------------------------------------------------------------------------------- 1 | package com.courier.core.aspect; 2 | 3 | import cn.hutool.json.JSONUtil; 4 | import com.courier.core.CourierTaskStatusEnum; 5 | import com.courier.core.ExecutionEnum; 6 | import com.courier.core.annotation.CourierTask; 7 | import com.courier.core.service.CourierTaskInstance; 8 | import com.courier.core.service.CourierTaskService; 9 | import com.courier.core.utils.AopTaskInitPrevent; 10 | import com.courier.core.utils.ReflectUtils; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.aspectj.lang.ProceedingJoinPoint; 13 | import org.aspectj.lang.annotation.Around; 14 | import org.aspectj.lang.annotation.Aspect; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.stereotype.Component; 17 | import org.springframework.util.StringUtils; 18 | 19 | import java.util.Date; 20 | 21 | /** 22 | * @author Anthony 23 | * @create 2022/1/16 24 | * @desc 25 | */ 26 | @Slf4j 27 | @Aspect 28 | @Component 29 | public class CourierTaskAspect { 30 | @Autowired 31 | CourierTaskService courierTaskService; 32 | 33 | 34 | @Around("@annotation(courierTask)") 35 | public Object initCourierTask(ProceedingJoinPoint joinPoint, CourierTask courierTask) throws Throwable { 36 | log.info("access method:{} is called on {} args {}", joinPoint.getSignature().getName(), joinPoint.getThis(), joinPoint.getArgs()); 37 | if(AopTaskInitPrevent.shouldPrevent()){ 38 | return joinPoint.proceed(); 39 | } 40 | 41 | CourierTaskInstance courierTaskInstance = createCourierTaskInstance(joinPoint, courierTask); 42 | courierTaskService.initTask(courierTaskInstance); 43 | return null; 44 | } 45 | 46 | private CourierTaskInstance createCourierTaskInstance(ProceedingJoinPoint point, CourierTask courierTask) { 47 | Class[] argsClazz = ReflectUtils.getArgsClass(point.getArgs()); 48 | String fullyQualifiedName = ReflectUtils.getTargetMethodFullyQualifiedName(point, argsClazz); 49 | String parameterTypes = ReflectUtils.getArgsClassNames(point.getSignature()); 50 | 51 | Date date = new Date(); 52 | 53 | CourierTaskInstance instance = CourierTaskInstance.builder() 54 | .taskId(StringUtils.isEmpty(courierTask.id()) ? fullyQualifiedName : courierTask.id()) 55 | .methodName(point.getSignature().getName()) 56 | .parameterTypes(parameterTypes) 57 | //add full qualified name to this instance 58 | .methodSignName(fullyQualifiedName) 59 | //json serialization args 60 | .taskParameter(JSONUtil.toJsonStr(point.getArgs())) 61 | //now or later 62 | .executeMode(courierTask.executeMode().getCode()) 63 | .threadMode(courierTask.threadMode().getCode()) 64 | .executeIntervalSec(courierTask.executeIntervalSec()) 65 | .delayTime(courierTask.delayTime()) 66 | .executeTimes(0) 67 | .taskStatus(CourierTaskStatusEnum.INIT.getCode()) 68 | .errorMsg("") 69 | .alertExpression(StringUtils.isEmpty(courierTask.alertExpression()) ? "" : courierTask.alertExpression()) 70 | .alertActionBeanName(StringUtils.isEmpty(courierTask.alarmBeanName()) ? "" : courierTask.alarmBeanName()) 71 | .fallbackClassName(ReflectUtils.getFullyQualifiedClassName(courierTask.fallbackClass())) 72 | .fallbackErrorMsg("") 73 | .gmtCreate(date) 74 | .gmtModified(date) 75 | .build(); 76 | 77 | instance.setExecuteTime(getExecuteTime(instance)); 78 | 79 | return instance; 80 | } 81 | 82 | private Long getExecuteTime(CourierTaskInstance instance) { 83 | if (ExecutionEnum.EXECUTE_SCHEDULE.getCode() == instance.getExecuteMode()) { 84 | long delayTimeMillSecond = instance.getDelayTime() * 1000L; 85 | return System.currentTimeMillis() + delayTimeMillSecond; 86 | } else { 87 | return System.currentTimeMillis(); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Courier-Core/src/main/java/com/courier/core/utils/ExpressionUtils.java: -------------------------------------------------------------------------------- 1 | package com.courier.core.utils; 2 | 3 | import com.courier.core.service.CourierTaskInstance; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.expression.EvaluationContext; 6 | import org.springframework.expression.Expression; 7 | import org.springframework.expression.common.TemplateParserContext; 8 | import org.springframework.expression.spel.standard.SpelExpressionParser; 9 | import org.springframework.expression.spel.support.StandardEvaluationContext; 10 | import org.springframework.util.StringUtils; 11 | 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | import java.util.StringJoiner; 15 | 16 | /** 17 | * @author Anthony 18 | * @create 2022/1/23 19 | * @desc 20 | */ 21 | @Slf4j 22 | public class ExpressionUtils { 23 | 24 | private static final String START_MARK = "\\$\\{"; 25 | 26 | public static final String RESULT_FLAG = "true"; 27 | 28 | /** 29 | * rewrite expression 30 | * 31 | * @param alertExpression 32 | * @return 33 | */ 34 | public static String rewriteExpr(String alertExpression) { 35 | String exprExpr = StringUtils.replace(alertExpression, "executeTimes", "#taskInstance.executeTimes"); 36 | StringJoiner exprJoiner = new StringJoiner("", "${", "}"); 37 | exprJoiner.add(exprExpr); 38 | return exprJoiner.toString(); 39 | } 40 | 41 | public static Map buildDataMap(Object object) { 42 | Map dataMap = new HashMap<>(1); 43 | dataMap.put("taskInstance", object); 44 | return dataMap; 45 | } 46 | 47 | 48 | 49 | public static String readExpr(String expr, Map dataMap) { 50 | try { 51 | expr = formatExpr(expr); 52 | // expression context 53 | EvaluationContext context = new StandardEvaluationContext(); 54 | // put object into context 55 | for (Map.Entry entry : dataMap.entrySet()) { 56 | // key -> ref value -> iterator.next().getValue() 57 | context.setVariable(entry.getKey(), entry.getValue()); 58 | } 59 | // comparison with executeTimes 60 | SpelExpressionParser parser = new SpelExpressionParser(); 61 | Expression expression = parser.parseExpression(expr, new TemplateParserContext()); 62 | return expression.getValue(context, String.class); 63 | } catch (Exception e) { 64 | log.error("parse expression error {}", expr, e); 65 | return ""; 66 | } 67 | } 68 | 69 | 70 | /** 71 | * public static Map buildDataMap(Object object) { 72 | * Map dataMap = new HashMap<>(1); 73 | * dataMap.put("taskInstance", object); 74 | * return dataMap; 75 | * } 76 | *

77 | * /** 78 | * expression converter ${xxx.name} -> #{xxx.name} 79 | * 80 | * @param expr expression 81 | */ 82 | private static String formatExpr(String expr) { 83 | return expr.replaceAll(START_MARK, "#{"); 84 | } 85 | 86 | public static void main(String[] args) { 87 | CourierTaskInstance instance = CourierTaskInstance.builder() 88 | .executeTimes(4) 89 | .build(); 90 | Map dataMap = new HashMap<>(2); 91 | dataMap.put("taskInstance", instance); 92 | 93 | String expr = "executeTimes > 1 && executeTimes < 5"; 94 | String executeTimesExpr = StringUtils.replace(expr, "executeTimes", "#taskInstance.executeTimes"); 95 | System.out.println(executeTimesExpr); 96 | System.out.println(readExpr("${" + executeTimesExpr + "}", dataMap)); 97 | 98 | String expr2 = "executeTimes % 2 == 0"; 99 | String executeTimesExpr2 = StringUtils.replace(expr2, "executeTimes", "#taskInstance.executeTimes"); 100 | System.out.println(executeTimesExpr2); 101 | System.out.println(readExpr("${" + executeTimesExpr2 + "}", dataMap)); 102 | 103 | System.out.println(readExpr(rewriteExpr(expr), dataMap)); 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /Courier-Core/src/main/java/com/courier/core/config/CourierConfigAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.courier.core.config; 2 | 3 | import com.courier.core.custom.TaskTimeRangeQuery; 4 | import com.courier.core.exception.CourierException; 5 | import com.courier.core.utils.ReflectUtils; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.util.ObjectUtils; 12 | import org.springframework.util.StringUtils; 13 | 14 | import java.util.concurrent.TimeUnit; 15 | 16 | import static com.courier.core.utils.DefaultValueUtils.getOrDefault; 17 | 18 | /** 19 | * @author Anthony 20 | * @create 2022/1/19 21 | * @desc spring boot Auto Configuration 22 | */ 23 | @Slf4j 24 | @Configuration 25 | @EnableConfigurationProperties(value = { 26 | CourierConsistencyConfigProperties.class, 27 | CourierFallbackConfigProperties.class 28 | }) 29 | public class CourierConfigAutoConfiguration { 30 | @Autowired 31 | CourierConsistencyConfigProperties courierConsistencyConfigProperties; 32 | 33 | @Autowired 34 | CourierFallbackConfigProperties courierFallbackConfigProperties; 35 | 36 | @Bean 37 | public CourierConsistencyConfig buildCourierConsistencyConfig() { 38 | doConfigCheck(courierConsistencyConfigProperties); 39 | 40 | return CourierConsistencyConfig 41 | .builder() 42 | .threadCorePoolSize(getOrDefault(courierConsistencyConfigProperties.getThreadCorePoolSize(), 5)) 43 | .threadMaxPoolSize(getOrDefault(courierConsistencyConfigProperties.getThreadMaxPoolSize(), 5)) 44 | .threadPoolQueueSize(getOrDefault(courierConsistencyConfigProperties.getThreadPoolQueueSize(), 100)) 45 | .threadPoolKeepAliveTime(getOrDefault(courierConsistencyConfigProperties.getThreadPoolKeepAliveTime(), 60L)) 46 | .threadPoolKeepAliveTimeUnit(getOrDefault(courierConsistencyConfigProperties.getThreadPoolKeepAliveTimeUnit(), "SECONDS")) 47 | .taskScheduleTimeRangeClassName(getOrDefault(courierConsistencyConfigProperties.getTaskScheduleTimeRangeClassName(), "")) 48 | .failCountThreshold(getOrDefault(courierFallbackConfigProperties.getFailCountThreshold(), 2)) 49 | .build(); 50 | 51 | } 52 | 53 | 54 | private void doConfigCheck(CourierConsistencyConfigProperties courierConsistencyConfigProperties) { 55 | TimeUnit timeUnit = null; 56 | if (!StringUtils.isEmpty(courierConsistencyConfigProperties.getThreadPoolKeepAliveTimeUnit())) { 57 | try { 58 | timeUnit = TimeUnit.valueOf(courierConsistencyConfigProperties.getThreadPoolKeepAliveTimeUnit()); 59 | } catch (IllegalArgumentException e) { 60 | log.error("check threadPoolKeepAliveTimeUnit error", e); 61 | String errMsg = "threadPoolKeepAliveTimeUnit config error!alternative: [SECONDS,MINUTES,HOURS,DAYS,NANOSECONDS,MICROSECONDS,MILLISECONDS]"; 62 | throw new CourierException(errMsg); 63 | } 64 | } 65 | 66 | if (!StringUtils.isEmpty(courierConsistencyConfigProperties.getTaskScheduleTimeRangeClassName())) { 67 | // exists check 68 | Class taskScheduleTimeRangeClass = ReflectUtils.checkClassByName(courierConsistencyConfigProperties.getTaskScheduleTimeRangeClassName()); 69 | if (ObjectUtils.isEmpty(taskScheduleTimeRangeClass)) { 70 | String errMsg = String.format("could not find class %s ,check the class path", courierConsistencyConfigProperties.getTaskScheduleTimeRangeClassName()); 71 | throw new CourierException(errMsg); 72 | } 73 | //implements check 74 | boolean result = ReflectUtils.isRealizeTargetInterface(taskScheduleTimeRangeClass, 75 | TaskTimeRangeQuery.class.getName()); 76 | if (!result) { 77 | String errMsg = String.format("class %s ,not implement TaskTimeRangeQuery", courierConsistencyConfigProperties.getTaskScheduleTimeRangeClassName()); 78 | throw new CourierException(errMsg); 79 | } 80 | } 81 | 82 | 83 | } 84 | 85 | 86 | } 87 | -------------------------------------------------------------------------------- /Courier-Core/src/main/java/com/courier/core/utils/ReflectUtils.java: -------------------------------------------------------------------------------- 1 | package com.courier.core.utils; 2 | 3 | import cn.hutool.json.JSONArray; 4 | import cn.hutool.json.JSONUtil; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.aspectj.lang.JoinPoint; 7 | import org.aspectj.lang.Signature; 8 | import org.aspectj.lang.reflect.MethodSignature; 9 | import org.springframework.asm.Type; 10 | import org.springframework.cglib.core.ClassInfo; 11 | import org.springframework.util.ObjectUtils; 12 | 13 | import java.util.HashMap; 14 | import java.util.StringJoiner; 15 | 16 | /** 17 | * @author Anthony 18 | * @create 2022/1/16 19 | * @desc 20 | */ 21 | @Slf4j 22 | public class ReflectUtils { 23 | 24 | private static final HashMap> PRIMITIVE_MAP = new HashMap>() { 25 | { 26 | put("java.lang.Integer", int.class); 27 | put("java.lang.Double", double.class); 28 | put("java.lang.Float", float.class); 29 | put("java.lang.Long", long.class); 30 | put("java.lang.Short", short.class); 31 | put("java.lang.Boolean", boolean.class); 32 | put("java.lang.Char", char.class); 33 | } 34 | }; 35 | public static final String METHOD_SEPARATOR = ","; 36 | public static final String CLAZZ_SEPARATOR = "#"; 37 | 38 | 39 | public static Class[] buildTypeClassArray(String[] parameterTypes) { 40 | Class[] parameterTypeClassArray = new Class[parameterTypes.length]; 41 | for (int i = parameterTypes.length - 1; i >= 0; i--) { 42 | try { 43 | parameterTypeClassArray[i] = Class.forName(parameterTypes[i]); 44 | } catch (ClassNotFoundException e) { 45 | e.printStackTrace(); 46 | } 47 | } 48 | return parameterTypeClassArray; 49 | } 50 | 51 | 52 | public static Class getClassByName(String className) { 53 | try { 54 | return Class.forName(className); 55 | } catch (ClassNotFoundException e) { 56 | log.error("could not find class, loading class: {}", className, e); 57 | return null; 58 | } 59 | } 60 | 61 | 62 | public static Class checkClassByName(String className) { 63 | try { 64 | return Class.forName(className); 65 | } catch (ClassNotFoundException e) { 66 | return null; 67 | } 68 | } 69 | 70 | /** 71 | * build parameter by JSON String parameter 72 | * @param parameterText JSON String parameter 73 | * @param parameterTypeClassArray 74 | * @return 75 | */ 76 | public static Object[] buildArgs(String parameterText, Class[] parameterTypeClassArray) { 77 | JSONArray paramJsonArray = JSONUtil.parseArray(parameterText); 78 | Object[] args = new Object[paramJsonArray.size()]; 79 | 80 | for (int i = paramJsonArray.size() - 1; i >= 0; i--) { 81 | if (paramJsonArray.getStr(i).startsWith("{")) { 82 | args[i] = JSONUtil.toBean(paramJsonArray.getStr(i), parameterTypeClassArray[i]); 83 | } else { 84 | args[i] = paramJsonArray.get(i); 85 | } 86 | } 87 | return args; 88 | } 89 | 90 | 91 | public static String getArgsClassNames(Signature signature) { 92 | MethodSignature methodSignature = (MethodSignature) signature; 93 | Class[] parameterTypes = methodSignature.getParameterTypes(); 94 | StringBuilder parameterStrTypes = new StringBuilder(); 95 | for (int i = 0; i < parameterTypes.length; i++) { 96 | parameterStrTypes.append(parameterTypes[i].getName()); 97 | if (parameterTypes.length != (i + 1)) { 98 | parameterStrTypes.append(METHOD_SEPARATOR); 99 | } 100 | } 101 | return parameterStrTypes.toString(); 102 | } 103 | 104 | 105 | public static String getTargetMethodFullyQualifiedName(JoinPoint point, Class[] argsClazz) { 106 | StringJoiner methodSignNameJoiner = new StringJoiner("", "", ""); 107 | methodSignNameJoiner 108 | .add(point.getTarget().getClass().getName()) 109 | .add(CLAZZ_SEPARATOR) 110 | .add(point.getSignature().getName()); 111 | methodSignNameJoiner.add("("); 112 | for (int i = 0; i < argsClazz.length; i++) { 113 | String className = argsClazz[i].getName(); 114 | methodSignNameJoiner.add(className); 115 | if (argsClazz.length != (i + 1)) { 116 | methodSignNameJoiner.add(METHOD_SEPARATOR); 117 | } 118 | } 119 | methodSignNameJoiner.add(")"); 120 | return methodSignNameJoiner.toString(); 121 | } 122 | 123 | 124 | public static Class[] getArgsClass(Object[] args) { 125 | Class[] clazz = new Class[args.length]; 126 | for (int k = 0; k < args.length; k++) { 127 | if (!args[k].getClass().isPrimitive()) { 128 | String result = args[k].getClass().getName(); 129 | Class typeClazz = PRIMITIVE_MAP.get(result); 130 | clazz[k] = ObjectUtils.isEmpty(typeClazz) ? args[k].getClass() : typeClazz; 131 | } 132 | } 133 | return clazz; 134 | } 135 | 136 | 137 | public static String getFullyQualifiedClassName(Class clazz) { 138 | if (ObjectUtils.isEmpty(clazz)) { 139 | return ""; 140 | } 141 | return clazz.getName(); 142 | } 143 | 144 | 145 | public static boolean isRealizeTargetInterface(Class targetClass, String targetInterfaceClassName) { 146 | ClassInfo classInfo = org.springframework.cglib.core.ReflectUtils.getClassInfo(targetClass); 147 | for (Type anInterface : classInfo.getInterfaces()) { 148 | if (anInterface.getClassName().equals(targetInterfaceClassName)) { 149 | return true; 150 | } 151 | } 152 | return false; 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /Courier-Core/src/main/java/com/courier/core/service/CourierTaskServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.courier.core.service; 2 | 3 | import cn.hutool.extra.spring.SpringUtil; 4 | import com.courier.core.CourierTaskStatusEnum; 5 | import com.courier.core.ExecutionEnum; 6 | import com.courier.core.ThreadEnum; 7 | import com.courier.core.config.CourierConsistencyConfig; 8 | import com.courier.core.custom.TaskTimeRangeQuery; 9 | import com.courier.core.exception.CourierException; 10 | import com.courier.core.mapper.TaskStoreMapper; 11 | import com.courier.core.utils.ReflectUtils; 12 | import lombok.extern.slf4j.Slf4j; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.stereotype.Service; 15 | import org.springframework.transaction.support.TransactionSynchronizationAdapter; 16 | import org.springframework.transaction.support.TransactionSynchronizationManager; 17 | import org.springframework.util.StringUtils; 18 | 19 | import java.util.Date; 20 | import java.util.List; 21 | import java.util.Map; 22 | import java.util.concurrent.CompletionService; 23 | 24 | /** 25 | * @author Anthony 26 | * @create 2022/1/18 27 | * @desc 28 | */ 29 | @Slf4j 30 | @Service 31 | public class CourierTaskServiceImpl implements CourierTaskService { 32 | @Autowired 33 | private TaskStoreMapper taskStoreMapper; 34 | 35 | @Autowired 36 | private TaskEngineExecutor taskEngineExecutor; 37 | 38 | @Autowired 39 | private CompletionService consistencyTaskPool; 40 | 41 | @Autowired 42 | private CourierConsistencyConfig courierConsistencyConfig; 43 | 44 | /** 45 | * init task and execute it only if {@link ExecutionEnum.EXECUTE_NOW} mode 46 | * 47 | * @param courierTaskInstance 48 | */ 49 | @Override 50 | public void initTask(CourierTaskInstance courierTaskInstance) { 51 | Long result = taskStoreMapper.initTask(courierTaskInstance); 52 | log.info("[Courier Consistency] init task result [{}]", result > 0); 53 | 54 | if (ExecutionEnum.EXECUTE_NOW.getCode() != courierTaskInstance.getExecuteMode()) { 55 | return; 56 | } 57 | 58 | // if transaction synchronization is active for the current thread, 59 | boolean synchronizationActive = TransactionSynchronizationManager.isSynchronizationActive(); 60 | if (synchronizationActive) { 61 | TransactionSynchronizationManager.registerSynchronization( 62 | new TransactionSynchronizationAdapter() { 63 | @Override 64 | public void afterCommit() { 65 | submitTaskInstance(courierTaskInstance); 66 | } 67 | } 68 | ); 69 | } else { 70 | submitTaskInstance(courierTaskInstance); 71 | } 72 | } 73 | 74 | @Override 75 | public int turnOnTask(CourierTaskInstance courierTaskInstance) { 76 | courierTaskInstance.setExecuteTime(System.currentTimeMillis()); 77 | courierTaskInstance.setTaskStatus(CourierTaskStatusEnum.START.getCode()); 78 | return taskStoreMapper.turnOnTask(courierTaskInstance); 79 | } 80 | 81 | @Override 82 | public int markTaskFail(CourierTaskInstance courierTaskInstance) { 83 | return taskStoreMapper.markFallbackFail(courierTaskInstance); 84 | } 85 | 86 | @Override 87 | public int markTaskSuccess(CourierTaskInstance courierTaskInstance) { 88 | return taskStoreMapper.markSuccess(courierTaskInstance); 89 | } 90 | 91 | @Override 92 | public void markTaskFallbackFail(CourierTaskInstance courierTaskInstance) { 93 | 94 | } 95 | 96 | 97 | @Override 98 | public List listUnfinishedTasks() { 99 | Date startTime, endTime; 100 | Long limitTaskCount; 101 | try { 102 | // 获取TaskTimeLineQuery实现类 103 | if (!StringUtils.isEmpty(courierConsistencyConfig.getTaskScheduleTimeRangeClassName())) { 104 | // 获取Spring容器中所有对于TaskTimeRangeQuery接口的实现类 105 | Map beansOfTypeMap = SpringUtil.getBeansOfType(TaskTimeRangeQuery.class); 106 | TaskTimeRangeQuery taskTimeRangeQuery = getTaskTimeLineQuery(beansOfTypeMap); 107 | startTime = taskTimeRangeQuery.getStartTime(); 108 | endTime = taskTimeRangeQuery.getEndTime(); 109 | limitTaskCount = taskTimeRangeQuery.limitTaskCount(); 110 | return taskStoreMapper.listByUnFinishTask(startTime.getTime(), endTime.getTime(), limitTaskCount); 111 | } else { 112 | startTime = TaskTimeRangeQuery.getStartTimeByStatic(); 113 | endTime = TaskTimeRangeQuery.getEndTimeByStatic(); 114 | limitTaskCount = TaskTimeRangeQuery.limitTaskCountByStatic(); 115 | } 116 | } catch (Exception e) { 117 | log.error("[一致性任务框架] 调用业务服务实现具体的告警通知类时,发生异常", e); 118 | throw new CourierException(e); 119 | } 120 | return taskStoreMapper.listByUnFinishTask(startTime.getTime(), endTime.getTime(), limitTaskCount); 121 | } 122 | 123 | private TaskTimeRangeQuery getTaskTimeLineQuery(Map beansOfTypeMap) { 124 | 125 | if (beansOfTypeMap.size() == 1) { 126 | String[] beanNamesForType = SpringUtil.getBeanNamesForType(TaskTimeRangeQuery.class); 127 | return (TaskTimeRangeQuery) SpringUtil.getBean(beanNamesForType[0]); 128 | } 129 | 130 | Class clazz = ReflectUtils.getClassByName(courierConsistencyConfig.getTaskScheduleTimeRangeClassName()); 131 | return (TaskTimeRangeQuery) SpringUtil.getBean(clazz); 132 | } 133 | 134 | @Override 135 | public CourierTaskInstance getTaskById(Long id, Long shardKey) { 136 | return taskStoreMapper.getTaskByIdAndShardKey(id, shardKey); 137 | } 138 | 139 | @Override 140 | public void submitTaskInstance(CourierTaskInstance courierTaskInstance) { 141 | if (ThreadEnum.SYNC.getCode() == courierTaskInstance.getThreadMode()) { 142 | taskEngineExecutor.executeTaskInstance(courierTaskInstance); 143 | } else if (ThreadEnum.ASYNC.getCode() == courierTaskInstance.getThreadMode()) { 144 | consistencyTaskPool.submit(() -> { 145 | taskEngineExecutor.executeTaskInstance(courierTaskInstance); 146 | return courierTaskInstance; 147 | }); 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /Courier-Core/src/main/java/com/courier/core/utils/SpringUtils.java: -------------------------------------------------------------------------------- 1 | package com.courier.core.utils; 2 | 3 | import cn.hutool.core.exceptions.UtilException; 4 | import cn.hutool.core.lang.TypeReference; 5 | import cn.hutool.core.util.ArrayUtil; 6 | import org.springframework.beans.BeansException; 7 | import org.springframework.beans.factory.ListableBeanFactory; 8 | import org.springframework.beans.factory.config.BeanFactoryPostProcessor; 9 | import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 10 | import org.springframework.beans.factory.support.DefaultSingletonBeanRegistry; 11 | import org.springframework.context.ApplicationContext; 12 | import org.springframework.context.ApplicationContextAware; 13 | import org.springframework.context.ApplicationEvent; 14 | import org.springframework.context.ConfigurableApplicationContext; 15 | import org.springframework.core.ResolvableType; 16 | import org.springframework.stereotype.Component; 17 | 18 | import java.lang.reflect.ParameterizedType; 19 | import java.util.Arrays; 20 | import java.util.Map; 21 | 22 | /** 23 | * @author Anthony 24 | * @create 2022/1/23 25 | * @desc 26 | */ 27 | @Component 28 | public class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware { 29 | 30 | 31 | private static ConfigurableListableBeanFactory beanFactory; 32 | 33 | private static ApplicationContext applicationContext; 34 | 35 | @Override 36 | public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { 37 | SpringUtils.beanFactory = beanFactory; 38 | } 39 | 40 | @Override 41 | public void setApplicationContext(ApplicationContext applicationContext) { 42 | SpringUtils.applicationContext = applicationContext; 43 | } 44 | 45 | 46 | public static ApplicationContext getApplicationContext() { 47 | return applicationContext; 48 | } 49 | 50 | /** 51 | * get {@link ListableBeanFactory},{@link ConfigurableListableBeanFactory} or {@link ApplicationContextAware} 52 | * 53 | * @return {@link ListableBeanFactory} 54 | */ 55 | public static ListableBeanFactory getBeanFactory() { 56 | return null == beanFactory ? applicationContext : beanFactory; 57 | } 58 | 59 | 60 | /** 61 | * get {@link ConfigurableListableBeanFactory} 62 | * @return 63 | * @throws UtilException 64 | */ 65 | public static ConfigurableListableBeanFactory getConfigurableBeanFactory() throws UtilException { 66 | final ConfigurableListableBeanFactory factory; 67 | if (null != beanFactory) { 68 | factory = beanFactory; 69 | } else if (applicationContext instanceof ConfigurableApplicationContext) { 70 | factory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory(); 71 | } else { 72 | throw new UtilException("No ConfigurableListableBeanFactory from context!"); 73 | } 74 | return factory; 75 | } 76 | 77 | /** 78 | * get Bean by name 79 | * @param name 80 | * @param 81 | * @return 82 | */ 83 | @SuppressWarnings("unchecked") 84 | public static T getBean(String name) { 85 | return (T) getBeanFactory().getBean(name); 86 | } 87 | 88 | 89 | /** 90 | * get bean by clazz 91 | * @param clazz 92 | * @param 93 | * @return 94 | */ 95 | public static T getBean(Class clazz) { 96 | return getBeanFactory().getBean(clazz); 97 | } 98 | 99 | 100 | /** 101 | * get bean by name and clazz 102 | * @param name 103 | * @param clazz 104 | * @param 105 | * @return 106 | */ 107 | public static T getBean(String name, Class clazz) { 108 | return getBeanFactory().getBean(name, clazz); 109 | } 110 | 111 | 112 | /** 113 | * get fallback bean from Spring 114 | * @param fallbackClass 115 | * @param paramValues 116 | * @return 117 | */ 118 | public static Object getBean(Class fallbackClass, Object[] paramValues) { 119 | return getBeanFactory().getBean(fallbackClass, paramValues); 120 | } 121 | 122 | 123 | /** 124 | * get bean by TypeReference 125 | * @param reference 126 | * @param 127 | * @return 128 | */ 129 | @SuppressWarnings("unchecked") 130 | public static T getBean(TypeReference reference) { 131 | final ParameterizedType parameterizedType = (ParameterizedType) reference.getType(); 132 | final Class rawType = (Class) parameterizedType.getRawType(); 133 | final Class[] genericTypes = Arrays.stream(parameterizedType.getActualTypeArguments()).map(type -> (Class) type).toArray(Class[]::new); 134 | final String[] beanNames = getBeanFactory().getBeanNamesForType(ResolvableType.forClassWithGenerics(rawType, genericTypes)); 135 | return getBean(beanNames[0], rawType); 136 | } 137 | 138 | 139 | /** 140 | * get all beans by type ,including subclasses 141 | * @param type 142 | * @param 143 | * @return 144 | */ 145 | public static Map getBeansOfType(Class type) { 146 | return getBeanFactory().getBeansOfType(type); 147 | } 148 | 149 | 150 | /** 151 | * get all beans by type ,including subclasses 152 | * @param type 153 | * @return 154 | */ 155 | public static String[] getBeanNamesForType(Class type) { 156 | return getBeanFactory().getBeanNamesForType(type); 157 | } 158 | 159 | 160 | /** 161 | * get property 162 | * @param key 163 | * @return 164 | */ 165 | public static String getProperty(String key) { 166 | if (null == applicationContext) { 167 | return null; 168 | } 169 | return applicationContext.getEnvironment().getProperty(key); 170 | } 171 | 172 | 173 | /** 174 | * get application name 175 | * @return 176 | */ 177 | public static String getApplicationName() { 178 | return getProperty("spring.application.name"); 179 | } 180 | 181 | 182 | /** 183 | * get active profiles 184 | * @return 185 | */ 186 | public static String[] getActiveProfiles() { 187 | if (null == applicationContext) { 188 | return null; 189 | } 190 | return applicationContext.getEnvironment().getActiveProfiles(); 191 | } 192 | 193 | 194 | /** 195 | * get active profiles, only first return 196 | * @return 197 | */ 198 | public static String getActiveProfile() { 199 | final String[] activeProfiles = getActiveProfiles(); 200 | return ArrayUtil.isNotEmpty(activeProfiles) ? activeProfiles[0] : null; 201 | } 202 | 203 | 204 | /** 205 | * register bean to spring 206 | * @param beanName 207 | * @param bean 208 | * @param 209 | */ 210 | public static void registerBean(String beanName, T bean) { 211 | final ConfigurableListableBeanFactory factory = getConfigurableBeanFactory(); 212 | factory.autowireBean(bean); 213 | factory.registerSingleton(beanName, bean); 214 | } 215 | 216 | 217 | /** 218 | * unregister bean from Spring 219 | * @param beanName 220 | */ 221 | public static void unregisterBean(String beanName) { 222 | final ConfigurableListableBeanFactory factory = getConfigurableBeanFactory(); 223 | if (factory instanceof DefaultSingletonBeanRegistry) { 224 | DefaultSingletonBeanRegistry registry = (DefaultSingletonBeanRegistry) factory; 225 | registry.destroySingleton(beanName); 226 | } else { 227 | throw new UtilException("Can not unregister bean, the factory is not a DefaultSingletonBeanRegistry!"); 228 | } 229 | } 230 | 231 | 232 | /** 233 | * publish event 234 | * @param event 235 | */ 236 | public static void publishEvent(ApplicationEvent event) { 237 | if (null != applicationContext) { 238 | applicationContext.publishEvent(event); 239 | } 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /Courier-Core/src/main/java/com/courier/core/mapper/TaskStoreMapper.java: -------------------------------------------------------------------------------- 1 | package com.courier.core.mapper; 2 | 3 | import com.courier.core.service.CourierTaskInstance; 4 | import org.apache.ibatis.annotations.*; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.List; 8 | 9 | 10 | /** 11 | * @author Anthony 12 | * @create 2022/1/16 13 | * @desc 14 | */ 15 | @Mapper 16 | @Repository 17 | public interface TaskStoreMapper { 18 | 19 | /** 20 | * save courier task for data consistency 21 | * @param courierTaskInstance 22 | * @return 23 | */ 24 | @Insert("INSERT INTO courier_consistency_task(" 25 | + "task_id," 26 | + "task_status," 27 | + "execute_times," 28 | + "execute_time," 29 | + "parameter_types," 30 | + "method_name," 31 | + "method_sign_name," 32 | + "execute_interval_sec," 33 | + "delay_time," 34 | + "task_parameter," 35 | + "performance_way," 36 | + "thread_way," 37 | + "error_msg," 38 | + "alert_expression," 39 | + "alert_action_bean_name," 40 | + "fallback_class_name," 41 | + "fallback_error_msg," 42 | + "shard_key," 43 | + "gmt_create," 44 | + "gmt_modified" 45 | + ") VALUES(" 46 | + "#{taskId}," 47 | + "#{taskStatus}," 48 | + "#{executeTimes}," 49 | + "#{executeTime}," 50 | + "#{parameterTypes}," 51 | + "#{methodName}," 52 | + "#{methodSignName}," 53 | + "#{executeIntervalSec}," 54 | + "#{delayTime}," 55 | + "#{taskParameter}," 56 | + "#{performanceWay}," 57 | + "#{threadWay}," 58 | + "#{errorMsg}," 59 | + "#{alertExpression}," 60 | + "#{alertActionBeanName}," 61 | + "#{fallbackClassName}," 62 | + "#{fallbackErrorMsg}," 63 | + "#{shardKey}," 64 | + "#{gmtCreate}," 65 | + "#{gmtModified}" 66 | + ")") 67 | @Options(keyColumn = "id", keyProperty = "id", useGeneratedKeys = true) 68 | Long initTask(CourierTaskInstance courierTaskInstance); 69 | 70 | 71 | /** 72 | * get task by id 73 | * @param id 74 | * @param shardKey 75 | * @return 76 | */ 77 | @Select("SELECT " + 78 | "id,task_id,task_status,execute_times,execute_time,parameter_types,method_name,method_sign_name, " + 79 | "execute_interval_sec,delay_time,task_parameter,performance_way," + 80 | "thread_way, error_msg, alert_expression, " + 81 | "alert_action_bean_name, fallback_class_name, fallback_error_msg,shard_key," + 82 | "gmt_create, gmt_modified " + 83 | "FROM courier_consistency_task " + 84 | "where " + 85 | "id = #{id} AND shard_key = #{shardKey}") 86 | @Results({ 87 | @Result(column = "id", property = "id", id = true), 88 | @Result(column = "task_id", property = "taskId"), 89 | @Result(column = "task_status", property = "taskStatus"), 90 | @Result(column = "execute_times", property = "executeTimes"), 91 | @Result(column = "execute_time", property = "executeTime"), 92 | @Result(column = "parameter_types", property = "parameterTypes"), 93 | @Result(column = "method_name", property = "methodName"), 94 | @Result(column = "method_sign_name", property = "methodSignName"), 95 | @Result(column = "execute_interval_sec", property = "executeIntervalSec"), 96 | @Result(column = "delay_time", property = "delayTime"), 97 | @Result(column = "bean_class_name", property = "beanClassName"), 98 | @Result(column = "task_parameter", property = "taskParameter"), 99 | @Result(column = "performance_way", property = "performanceWay"), 100 | @Result(column = "thread_way", property = "threadWay"), 101 | @Result(column = "error_msg", property = "errorMsg"), 102 | @Result(column = "alert_expression", property = "alertExpression"), 103 | @Result(column = "alert_action_bean_name", property = "alertActionBeanName"), 104 | @Result(column = "fallback_class_name", property = "fallbackClassName"), 105 | @Result(column = "fallback_error_msg", property = "fallbackErrorMsg"), 106 | @Result(column = "shard_key", property = "shardKey"), 107 | @Result(column = "gmt_create", property = "gmtCreate"), 108 | @Result(column = "gmt_modified", property = "gmtModified") 109 | }) 110 | CourierTaskInstance getTaskByIdAndShardKey(@Param("id") Long id, @Param("shardKey") Long shardKey); 111 | 112 | /** 113 | * get uncompleted task 114 | * @param startTime 115 | * @param endTime 116 | * @param limitTaskCount 117 | * @return 118 | */ 119 | @Select("SELECT " + 120 | "id,task_id,task_status,execute_times,execute_time,parameter_types,method_name,method_sign_name, " + 121 | "execute_interval_sec,delay_time,task_parameter,performance_way," + 122 | "thread_way, error_msg, alert_expression, " + 123 | "alert_action_bean_name, fallback_class_name, fallback_error_msg,shard_key," + 124 | "gmt_create, gmt_modified " + 125 | "FROM courier_consistency_task " + 126 | "WHERE " + 127 | "task_status <= 2 " + 128 | "AND execute_time>=#{startTime} AND execute_time<=#{endTime} " + 129 | "order by execute_time desc " + 130 | "LIMIT #{limitTaskCount}") 131 | @Results({ 132 | @Result(column = "id", property = "id", id = true), 133 | @Result(column = "task_id", property = "taskId"), 134 | @Result(column = "task_status", property = "taskStatus"), 135 | @Result(column = "execute_times", property = "executeTimes"), 136 | @Result(column = "execute_time", property = "executeTime"), 137 | @Result(column = "parameter_types", property = "parameterTypes"), 138 | @Result(column = "method_name", property = "methodName"), 139 | @Result(column = "method_sign_name", property = "methodSignName"), 140 | @Result(column = "execute_interval_sec", property = "executeIntervalSec"), 141 | @Result(column = "delay_time", property = "delayTime"), 142 | @Result(column = "task_parameter", property = "taskParameter"), 143 | @Result(column = "performance_way", property = "performanceWay"), 144 | @Result(column = "thread_way", property = "threadWay"), 145 | @Result(column = "error_msg", property = "errorMsg"), 146 | @Result(column = "alert_expression", property = "alertExpression"), 147 | @Result(column = "alert_action_bean_name", property = "alertActionBeanName"), 148 | @Result(column = "fallback_class_name", property = "fallbackClassName"), 149 | @Result(column = "fallback_error_msg", property = "fallbackErrorMsg"), 150 | @Result(column = "shard_key", property = "shardKey"), 151 | @Result(column = "gmt_create", property = "gmtCreate"), 152 | @Result(column = "gmt_modified", property = "gmtModified") 153 | }) 154 | List listByUnFinishTask(@Param("startTime") Long startTime, @Param("endTime") Long endTime, @Param("limitTaskCount") Long limitTaskCount); 155 | 156 | 157 | /** 158 | * start task 159 | * @param courierTaskInstance 160 | * @return 161 | */ 162 | @Update("UPDATE " 163 | + "courier_consistency_task " 164 | + "SET " 165 | + "task_status=#{taskStatus}," 166 | + "execute_times=execute_times+1," 167 | + "execute_time=#{executeTime} " 168 | + "WHERE id=#{id} and task_status!=1 and shard_key=#{shardKey}" 169 | ) 170 | int turnOnTask(CourierTaskInstance courierTaskInstance); 171 | 172 | 173 | /** 174 | * mark task success means delete 175 | * @param courierTaskInstance 176 | * @return 177 | */ 178 | @Delete("DELETE FROM courier_consistency_task WHERE id=#{id} and shard_key=#{shardKey}") 179 | int markSuccess(CourierTaskInstance courierTaskInstance); 180 | 181 | 182 | /** 183 | * mark task fail means update 184 | * @param courierTaskInstance 185 | * @return 186 | */ 187 | @Update("UPDATE courier_consistency_task SET task_status=2, error_msg=#{errorMsg}, execute_time=#{executeTime} WHERE id=#{id} and shard_key=#{shardKey}") 188 | int markFail(CourierTaskInstance courierTaskInstance); 189 | 190 | 191 | /** 192 | * mark task fallback fail 193 | * @param courierTaskInstance 194 | * @return 195 | */ 196 | @Update("UPDATE courier_consistency_task SET fallback_error_msg=#{fallbackErrorMsg} WHERE id=#{id} and shard_key=#{shardKey}") 197 | int markFallbackFail(CourierTaskInstance courierTaskInstance); 198 | 199 | } 200 | -------------------------------------------------------------------------------- /Courier-Core/src/main/java/com/courier/core/service/TaskEngineExecutorService.java: -------------------------------------------------------------------------------- 1 | package com.courier.core.service; 2 | 3 | import cn.hutool.core.util.ReflectUtil; 4 | import cn.hutool.json.JSONUtil; 5 | import com.courier.core.config.CourierConsistencyConfig; 6 | import com.courier.core.custom.CourierFrameworkAlerter; 7 | import com.courier.core.exception.CourierException; 8 | import com.courier.core.utils.ExpressionUtils; 9 | import com.courier.core.utils.ReflectUtils; 10 | import com.courier.core.utils.SpringUtils; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.transaction.annotation.Transactional; 14 | import org.springframework.util.CollectionUtils; 15 | import org.springframework.util.ObjectUtils; 16 | import org.springframework.util.StringUtils; 17 | 18 | import java.lang.reflect.InvocationTargetException; 19 | import java.lang.reflect.Method; 20 | import java.text.SimpleDateFormat; 21 | import java.util.Map; 22 | import java.util.StringJoiner; 23 | import java.util.concurrent.ThreadPoolExecutor; 24 | 25 | /** 26 | * @author Anthony 27 | * @create 2022/1/19 28 | * @desc 29 | */ 30 | @Slf4j 31 | public class TaskEngineExecutorService implements TaskEngineExecutor { 32 | 33 | @Autowired 34 | private CourierTaskService courierTaskService; 35 | 36 | @Autowired 37 | private TaskScheduleManager taskScheduleManager; 38 | 39 | @Autowired 40 | private ThreadPoolExecutor alertNoticePool; 41 | 42 | @Autowired 43 | private CourierConsistencyConfig courierConsistencyConfig; 44 | 45 | @Override 46 | @Transactional(rollbackFor = Exception.class) 47 | public void executeTaskInstance(CourierTaskInstance instance) { 48 | try { 49 | //task start mark 50 | int result = courierTaskService.turnOnTask(instance); 51 | if (result <= 0) { 52 | log.warn("[Courier Consistency]task started,exit. task:{}", JSONUtil.toJsonStr(instance)); 53 | return; 54 | } 55 | instance = courierTaskService.getTaskById(instance.getId(), 0L); 56 | taskScheduleManager.executeTask(instance); 57 | //task succeed mark 58 | int success = courierTaskService.markTaskSuccess(instance); 59 | log.info("[Courier Consistency]task succeed and result is:{}", success); 60 | } catch (Exception e) { 61 | log.error("[Courier Consistency]task instance :{} failed with error:", JSONUtil.toJsonStr(instance), e); 62 | instance.setErrorMsg(getErrorMsg(e)); 63 | instance.setExecuteTime(getNextExecutionTime(instance)); 64 | //task fail mark 65 | int failResult = courierTaskService.markTaskFail(instance); 66 | log.info("[Courier Consistency]task failed with result:{},next execute time:{}", failResult > 0, instance.getExecuteTime()); 67 | fallbackTaskInstance(instance); 68 | } 69 | } 70 | 71 | private Long getNextExecutionTime(CourierTaskInstance instance) { 72 | return null; 73 | } 74 | 75 | private String getErrorMsg(Exception e) { 76 | if ("".equals(e.getMessage())) { 77 | return ""; 78 | } 79 | String errorMsg = e.getMessage(); 80 | if (StringUtils.isEmpty(errorMsg)) { 81 | if (e instanceof IllegalAccessException) { 82 | IllegalAccessException illegalAccessException = (IllegalAccessException) e; 83 | errorMsg = illegalAccessException.getMessage(); 84 | } else if (e instanceof IllegalArgumentException) { 85 | IllegalArgumentException illegalArgumentException = (IllegalArgumentException) e; 86 | errorMsg = illegalArgumentException.getMessage(); 87 | } else if (e instanceof InvocationTargetException) { 88 | InvocationTargetException invocationTargetException = (InvocationTargetException) e; 89 | errorMsg = invocationTargetException.getTargetException().getMessage(); 90 | } 91 | } 92 | return errorMsg.substring(0, Math.min(errorMsg.length(), 200)); 93 | } 94 | 95 | /** 96 | * fallback when execute failed 97 | * 98 | * @param instance 99 | */ 100 | @Override 101 | public void fallbackTaskInstance(CourierTaskInstance instance) { 102 | if (StringUtils.isEmpty(instance.getFallbackClassName())) { 103 | parseExpressionAndDoAlert(instance); 104 | return; 105 | } 106 | if (instance.getExecuteTimes() <= courierConsistencyConfig.getFailCountThreshold()) { 107 | return; 108 | } 109 | log.info("[Courier Consistency]execute fallback of instanceId: {}}...", instance.getId()); 110 | Class fallbackClass = ReflectUtils.getClassByName(instance.getFallbackClassName()); 111 | if (ObjectUtils.isEmpty(fallbackClass)) { 112 | return; 113 | } 114 | 115 | String taskParameterText = instance.getTaskParameter(); 116 | String parameterTypes = instance.getParameterTypes(); 117 | Class[] paramTypes = getParamTypes(parameterTypes); 118 | Object[] paramValues = ReflectUtils.buildArgs(taskParameterText, paramTypes); 119 | Object fallbackClassBean = SpringUtils.getBean(fallbackClass, paramValues); 120 | 121 | // get fallback method 122 | Method fallbackMethod = ReflectUtil.getMethod(fallbackClass, instance.getMethodName(), paramTypes); 123 | try { 124 | //method invoke 125 | fallbackMethod.invoke(fallbackClassBean, paramValues); 126 | int successResult = courierTaskService.markTaskSuccess(instance); 127 | 128 | log.info("[Courier Consistency]task fallback succeed with result [{}]", successResult > 0); 129 | } catch (Exception e) { 130 | parseExpressionAndDoAlert(instance); 131 | instance.setFallbackErrorMsg(getErrorMsg(e)); 132 | int failResult = courierTaskService.markTaskFail(instance); 133 | log.error("[Courier Consistency]task fallback fail with result: [{}] next schedule time [{} - {}]", failResult > 0, 134 | instance.getExecuteTime(), getFormatTime(instance.getExecuteTime()), e); 135 | } 136 | } 137 | 138 | private Class[] getParamTypes(String parameterTypes) { 139 | return ReflectUtils.buildTypeClassArray(parameterTypes.split(",")); 140 | 141 | } 142 | 143 | private void parseExpressionAndDoAlert(CourierTaskInstance instance) { 144 | try { 145 | if (StringUtils.isEmpty(instance.getAlertExpression())) { 146 | return; 147 | } 148 | 149 | alertNoticePool.submit(() -> { 150 | String expr = rewriteExpr(instance.getAlertExpression()); 151 | String exprResult = ExpressionUtils.readExpr(expr, ExpressionUtils.buildDataMap(instance)); 152 | doAlert(exprResult, instance); 153 | }); 154 | } catch (Exception e) { 155 | log.error("[Courier Consistency]send alert with error", e); 156 | } 157 | } 158 | 159 | 160 | 161 | private void doAlert(String exprResult, CourierTaskInstance instance) { 162 | if (StringUtils.isEmpty(exprResult)) { 163 | return; 164 | } 165 | if (!ExpressionUtils.RESULT_FLAG.equals(exprResult)) { 166 | return; 167 | } 168 | log.warn("[Courier Consistency]Alert instance id: {}, task:{}", instance.getId(), JSONUtil.toJsonPrettyStr(instance)); 169 | if (StringUtils.isEmpty(instance.getAlertActionBeanName())) { 170 | return; 171 | } 172 | sendAlertNotice(instance); 173 | } 174 | 175 | private void sendAlertNotice(CourierTaskInstance courierTaskInstance) { 176 | Map beansOfTypeMap = SpringUtils.getBeansOfType(CourierFrameworkAlerter.class); 177 | 178 | if (CollectionUtils.isEmpty(beansOfTypeMap)) { 179 | log.warn("[Courier Consistency]send alert failed because of lack CourierFrameworkAlerter implementation..."); 180 | return; 181 | } 182 | 183 | try { 184 | // get CourierFrameworkAlerter implementation 185 | getCourierFrameworkAlerterImpl(beansOfTypeMap, courierTaskInstance) 186 | .sendAlertNotice(courierTaskInstance); 187 | } catch (Exception e) { 188 | log.error("[Courier Consistency]send alert fail with error", e); 189 | throw new CourierException(e); 190 | } 191 | } 192 | 193 | private CourierFrameworkAlerter getCourierFrameworkAlerterImpl(Map beansOfTypeMap, CourierTaskInstance courierTaskInstance) { 194 | if (beansOfTypeMap.size() == 1) { 195 | String[] beanNamesForType = SpringUtils.getBeanNamesForType(CourierFrameworkAlerter.class); 196 | return (CourierFrameworkAlerter) SpringUtils.getBean(beanNamesForType[0]); 197 | } 198 | 199 | return beansOfTypeMap.get(courierTaskInstance.getAlertActionBeanName()); 200 | } 201 | 202 | private String rewriteExpr(String alertExpression) { 203 | String exprExpr = StringUtils.replace(alertExpression, "executeTimes", "#taskInstance.executeTimes"); 204 | StringJoiner exprJoiner = new StringJoiner("", "${", "}"); 205 | exprJoiner.add(exprExpr); 206 | return exprJoiner.toString(); 207 | } 208 | 209 | private String getFormatTime(long timestamp) { 210 | SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 211 | return format.format(timestamp); 212 | } 213 | } 214 | --------------------------------------------------------------------------------