master
https://github.com/Time-Machine-Lab/TmlFoundation
diff --git a/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/RetryAutoConfiguration.java b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/RetryAutoConfiguration.java
new file mode 100644
index 0000000..a867a0d
--- /dev/null
+++ b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/RetryAutoConfiguration.java
@@ -0,0 +1,51 @@
+package io.github.timemachinelab.autoconfigure.retry;
+
+import io.github.timemachinelab.autoconfigure.retry.aop.RetryInterceptor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+
+/**
+ * 重试组件自动配置类
+ *
+ * @author TimeMachineLab
+ */
+@Configuration
+@ConditionalOnClass(name = "org.aspectj.lang.annotation.Aspect")
+@ConditionalOnProperty(prefix = "tml.retry", name = "enabled", havingValue = "true", matchIfMissing = true)
+@EnableConfigurationProperties(RetryProperties.class)
+@EnableAspectJAutoProxy
+public class RetryAutoConfiguration {
+
+ private static final Logger log = LoggerFactory.getLogger(RetryAutoConfiguration.class);
+
+ public RetryAutoConfiguration() {
+ log.info("TML Retry Component Auto Configuration initialized");
+ }
+
+ /**
+ * 注册重试拦截器
+ */
+ @Bean
+ public RetryInterceptor retryInterceptor() {
+ log.info("Registering RetryInterceptor bean");
+ return new RetryInterceptor();
+ }
+
+ // TODO: 当你实现了 core 模块后,可以在这里注册更多 Bean
+ // 例如:
+ // @Bean
+ // public RetryExecutor retryExecutor(RetryProperties properties) {
+ // return new RetryExecutor(...);
+ // }
+ //
+ // @Bean
+ // public RetryBudget retryBudget(RetryProperties properties) {
+ // return new TokenBucketRetryBudget(...);
+ // }
+}
diff --git a/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/RetryProperties.java b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/RetryProperties.java
new file mode 100644
index 0000000..4b3d673
--- /dev/null
+++ b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/RetryProperties.java
@@ -0,0 +1,168 @@
+package io.github.timemachinelab.autoconfigure.retry;
+
+import io.github.timemachinelab.autoconfigure.retry.annotation.JitterType;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * 重试组件配置属性
+ *
+ * @author TimeMachineLab
+ */
+@ConfigurationProperties(prefix = "tml.retry")
+public class RetryProperties {
+
+ /**
+ * 是否启用重试功能
+ */
+ private boolean enabled = true;
+
+ /**
+ * 默认配置
+ */
+ private DefaultConfig defaultConfig = new DefaultConfig();
+
+ /**
+ * 重试预算配置
+ */
+ private BudgetConfig budget = new BudgetConfig();
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public DefaultConfig getDefaultConfig() {
+ return defaultConfig;
+ }
+
+ public void setDefaultConfig(DefaultConfig defaultConfig) {
+ this.defaultConfig = defaultConfig;
+ }
+
+ public BudgetConfig getBudget() {
+ return budget;
+ }
+
+ public void setBudget(BudgetConfig budget) {
+ this.budget = budget;
+ }
+
+ /**
+ * 默认重试配置
+ */
+ public static class DefaultConfig {
+
+ /**
+ * 最大重试次数
+ */
+ private int maxAttempts = 3;
+
+ /**
+ * 基础延迟时间(毫秒)
+ */
+ private long baseDelay = 100;
+
+ /**
+ * 最大延迟时间(毫秒)
+ */
+ private long maxDelay = 60000;
+
+ /**
+ * 抖动类型
+ */
+ private JitterType jitterType = JitterType.FULL;
+
+ public int getMaxAttempts() {
+ return maxAttempts;
+ }
+
+ public void setMaxAttempts(int maxAttempts) {
+ this.maxAttempts = maxAttempts;
+ }
+
+ public long getBaseDelay() {
+ return baseDelay;
+ }
+
+ public void setBaseDelay(long baseDelay) {
+ this.baseDelay = baseDelay;
+ }
+
+ public long getMaxDelay() {
+ return maxDelay;
+ }
+
+ public void setMaxDelay(long maxDelay) {
+ this.maxDelay = maxDelay;
+ }
+
+ public JitterType getJitterType() {
+ return jitterType;
+ }
+
+ public void setJitterType(JitterType jitterType) {
+ this.jitterType = jitterType;
+ }
+ }
+
+ /**
+ * 重试预算配置
+ */
+ public static class BudgetConfig {
+
+ /**
+ * 是否启用重试预算
+ */
+ private boolean enabled = true;
+
+ /**
+ * 滑动窗口大小(秒)
+ */
+ private int windowSize = 10;
+
+ /**
+ * 最大重试比例(0.0 - 1.0)
+ */
+ private double maxRetryRatio = 0.1;
+
+ /**
+ * 最小请求数阈值
+ */
+ private int minRequestsThreshold = 30;
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public int getWindowSize() {
+ return windowSize;
+ }
+
+ public void setWindowSize(int windowSize) {
+ this.windowSize = windowSize;
+ }
+
+ public double getMaxRetryRatio() {
+ return maxRetryRatio;
+ }
+
+ public void setMaxRetryRatio(double maxRetryRatio) {
+ this.maxRetryRatio = maxRetryRatio;
+ }
+
+ public int getMinRequestsThreshold() {
+ return minRequestsThreshold;
+ }
+
+ public void setMinRequestsThreshold(int minRequestsThreshold) {
+ this.minRequestsThreshold = minRequestsThreshold;
+ }
+ }
+}
diff --git a/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/annotation/BackoffType.java b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/annotation/BackoffType.java
new file mode 100644
index 0000000..909f561
--- /dev/null
+++ b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/annotation/BackoffType.java
@@ -0,0 +1,27 @@
+package io.github.timemachinelab.autoconfigure.retry.annotation;
+
+/**
+ * 退避策略类型枚举
+ *
+ * @author TimeMachineLab
+ */
+public enum BackoffType {
+
+ /**
+ * 固定延迟
+ * delay = baseDelay
+ */
+ FIXED,
+
+ /**
+ * 线性退避
+ * delay = baseDelay × attempt
+ */
+ LINEAR,
+
+ /**
+ * 指数退避(推荐)
+ * delay = baseDelay × 2^(attempt-1)
+ */
+ EXPONENTIAL
+}
diff --git a/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/annotation/EnableRetry.java b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/annotation/EnableRetry.java
new file mode 100644
index 0000000..13cd2fa
--- /dev/null
+++ b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/annotation/EnableRetry.java
@@ -0,0 +1,19 @@
+package io.github.timemachinelab.autoconfigure.retry.annotation;
+
+import io.github.timemachinelab.autoconfigure.retry.RetryAutoConfiguration;
+import org.springframework.context.annotation.Import;
+
+import java.lang.annotation.*;
+
+/**
+ * 启用重试功能的注解
+ * 在 Spring Boot 配置类上使用
+ *
+ * @author TimeMachineLab
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Import(RetryAutoConfiguration.class)
+public @interface EnableRetry {
+}
diff --git a/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/annotation/JitterType.java b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/annotation/JitterType.java
new file mode 100644
index 0000000..81e53b6
--- /dev/null
+++ b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/annotation/JitterType.java
@@ -0,0 +1,32 @@
+package io.github.timemachinelab.autoconfigure.retry.annotation;
+
+/**
+ * 抖动类型枚举
+ *
+ * @author TimeMachineLab
+ */
+public enum JitterType {
+
+ /**
+ * 全抖动:random(0, backoff)
+ * AWS 推荐,最大程度避免共振
+ */
+ FULL,
+
+ /**
+ * 均等抖动:backoff/2 + random(0, backoff/2)
+ * 保证最小延迟,同时添加随机性
+ */
+ EQUAL,
+
+ /**
+ * 去相关抖动:random(base, previous * 3)
+ * 考虑上次延迟,适合长时间重试场景
+ */
+ DECORRELATED,
+
+ /**
+ * 无抖动:使用原始退避时间
+ */
+ NONE
+}
diff --git a/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/annotation/Retryable.java b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/annotation/Retryable.java
new file mode 100644
index 0000000..142a5d0
--- /dev/null
+++ b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/annotation/Retryable.java
@@ -0,0 +1,72 @@
+package io.github.timemachinelab.autoconfigure.retry.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 标记方法需要重试的注解
+ *
+ * 使用示例:
+ *
+ * {@code
+ * @Retryable(
+ * backoffType = BackoffType.EXPONENTIAL,
+ * jitterType = JitterType.FULL,
+ * maxAttempts = 3,
+ * baseDelay = 100,
+ * maxDelay = 60000
+ * )
+ * public User getUser(Long id) {
+ * // 业务代码
+ * }
+ * }
+ *
+ *
+ * @author TimeMachineLab
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Retryable {
+
+ /**
+ * 退避策略类型
+ * 默认 EXPONENTIAL(指数退避)
+ */
+ BackoffType backoffType() default BackoffType.EXPONENTIAL;
+
+ /**
+ * 抖动算法类型
+ * 默认 FULL(全抖动)
+ */
+ JitterType jitterType() default JitterType.FULL;
+
+ /**
+ * 最大重试次数(包含首次调用)
+ * 默认 3 次
+ */
+ int maxAttempts() default 3;
+
+ /**
+ * 基础延迟时间(毫秒)
+ * 默认 100ms
+ */
+ long baseDelay() default 100;
+
+ /**
+ * 最大延迟时间(毫秒)
+ * 默认 60000ms (60秒)
+ */
+ long maxDelay() default 60000;
+
+ /**
+ * 需要重试的异常类型(白名单)
+ * 默认为空数组,表示重试所有异常
+ */
+ Class extends Throwable>[] include() default {};
+
+ /**
+ * 不需要重试的异常类型(黑名单)
+ * 优先级高于 include
+ */
+ Class extends Throwable>[] exclude() default {};
+}
diff --git a/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/aop/RetryInterceptor.java b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/aop/RetryInterceptor.java
new file mode 100644
index 0000000..1727ac9
--- /dev/null
+++ b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/aop/RetryInterceptor.java
@@ -0,0 +1,125 @@
+package io.github.timemachinelab.autoconfigure.retry.aop;
+
+import io.github.timemachinelab.autoconfigure.retry.annotation.Retryable;
+import io.github.timemachinelab.autoconfigure.retry.strategy.DelayCalculator;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Method;
+
+/**
+ * 重试拦截器
+ * 拦截 @Retryable 注解的方法,应用重试逻辑
+ *
+ * @author TimeMachineLab
+ */
+@Aspect
+public class RetryInterceptor {
+
+ private static final Logger log = LoggerFactory.getLogger(RetryInterceptor.class);
+
+ /**
+ * 拦截所有标注了 @Retryable 的方法
+ */
+ @Around("@annotation(io.github.timemachinelab.autoconfigure.retry.annotation.Retryable)")
+ public Object aroundRetryable(ProceedingJoinPoint joinPoint) throws Throwable {
+ MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+ Method method = signature.getMethod();
+ Retryable retryable = method.getAnnotation(Retryable.class);
+
+ if (retryable == null) {
+ return joinPoint.proceed();
+ }
+
+ // 从注解中读取配置
+ int maxAttempts = retryable.maxAttempts();
+ Class extends Throwable>[] includeExceptions = retryable.include();
+ Class extends Throwable>[] excludeExceptions = retryable.exclude();
+
+ // 创建延迟计算器(组合退避策略和抖动算法)
+ DelayCalculator delayCalculator = new DelayCalculator(
+ retryable.backoffType(),
+ retryable.jitterType(),
+ retryable.baseDelay(),
+ retryable.maxDelay()
+ );
+
+ // TODO: 这里需要调用你在 core 模块实现的 RetryExecutor 和 RetryBudget
+ // 目前先实现一个简单的重试逻辑作为占位符
+
+ Throwable lastException = null;
+ long previousDelay = retryable.baseDelay();
+
+ for (int attempt = 1; attempt <= maxAttempts; attempt++) {
+ try {
+ log.debug("Executing method {} - attempt {}/{} (backoff={}, jitter={})",
+ method.getName(), attempt, maxAttempts,
+ retryable.backoffType(), retryable.jitterType());
+
+ return joinPoint.proceed();
+
+ } catch (Throwable ex) {
+ lastException = ex;
+
+ // 检查是否应该重试此异常
+ if (!shouldRetry(ex, includeExceptions, excludeExceptions)) {
+ log.debug("Exception {} is not retryable, throwing immediately",
+ ex.getClass().getName());
+ throw ex;
+ }
+
+ // 如果是最后一次尝试,直接抛出异常
+ if (attempt >= maxAttempts) {
+ log.warn("Retry exhausted after {} attempts for method {}",
+ maxAttempts, method.getName());
+ throw ex;
+ }
+
+ // 使用延迟计算器计算延迟时间(组合退避 + 抖动)
+ long delay = delayCalculator.calculateDelay(attempt, previousDelay);
+ previousDelay = delay;
+
+ log.debug("Method {} failed on attempt {}, retrying after {}ms (backoff={}, jitter={})",
+ method.getName(), attempt, delay,
+ retryable.backoffType(), retryable.jitterType());
+
+ Thread.sleep(delay);
+ }
+ }
+
+ throw lastException;
+ }
+
+ /**
+ * 判断异常是否应该重试
+ */
+ private boolean shouldRetry(Throwable ex,
+ Class extends Throwable>[] includeExceptions,
+ Class extends Throwable>[] excludeExceptions) {
+
+ // 先检查黑名单(优先级更高)
+ for (Class extends Throwable> excludeType : excludeExceptions) {
+ if (excludeType.isAssignableFrom(ex.getClass())) {
+ return false;
+ }
+ }
+
+ // 如果没有配置白名单,默认重试所有异常
+ if (includeExceptions.length == 0) {
+ return true;
+ }
+
+ // 检查白名单
+ for (Class extends Throwable> includeType : includeExceptions) {
+ if (includeType.isAssignableFrom(ex.getClass())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/strategy/DelayCalculator.java b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/strategy/DelayCalculator.java
new file mode 100644
index 0000000..c05efe1
--- /dev/null
+++ b/tml-sdk-spring-boot-autoconfigure/src/main/java/io/github/timemachinelab/autoconfigure/retry/strategy/DelayCalculator.java
@@ -0,0 +1,112 @@
+package io.github.timemachinelab.autoconfigure.retry.strategy;
+
+import io.github.timemachinelab.autoconfigure.retry.annotation.BackoffType;
+import io.github.timemachinelab.autoconfigure.retry.annotation.JitterType;
+
+/**
+ * 延迟计算器
+ * 组合退避策略和抖动算法,计算最终的重试延迟
+ *
+ * @author TimeMachineLab
+ */
+public class DelayCalculator {
+
+ private final BackoffType backoffType;
+ private final JitterType jitterType;
+ private final long baseDelay;
+ private final long maxDelay;
+
+ public DelayCalculator(BackoffType backoffType, JitterType jitterType,
+ long baseDelay, long maxDelay) {
+ this.backoffType = backoffType;
+ this.jitterType = jitterType;
+ this.baseDelay = baseDelay;
+ this.maxDelay = maxDelay;
+ }
+
+ /**
+ * 计算延迟时间
+ *
+ * @param attempt 当前重试次数(从 1 开始)
+ * @param previousDelay 上一次的延迟(用于 Decorrelated Jitter)
+ * @return 最终延迟时间(毫秒)
+ */
+ public long calculateDelay(int attempt, long previousDelay) {
+ // 1. 根据退避策略计算基准延迟
+ long backoffDelay = calculateBackoff(attempt);
+
+ // 2. 根据抖动算法添加随机性
+ long finalDelay = applyJitter(backoffDelay, previousDelay);
+
+ return finalDelay;
+ }
+
+ /**
+ * 计算退避延迟(基准值)
+ */
+ private long calculateBackoff(int attempt) {
+ long delay;
+
+ switch (backoffType) {
+ case FIXED:
+ // 固定延迟
+ delay = baseDelay;
+ break;
+
+ case LINEAR:
+ // 线性退避:baseDelay × attempt
+ delay = baseDelay * attempt;
+ break;
+
+ case EXPONENTIAL:
+ // 指数退避:baseDelay × 2^(attempt-1)
+ delay = baseDelay * (long) Math.pow(2, attempt - 1);
+ break;
+
+ default:
+ delay = baseDelay;
+ }
+
+ // 限制在 [baseDelay, maxDelay] 范围内
+ delay = Math.max(delay, baseDelay);
+ delay = Math.min(delay, maxDelay);
+
+ return delay;
+ }
+
+ /**
+ * 应用抖动算法
+ */
+ private long applyJitter(long backoffDelay, long previousDelay) {
+ long delay;
+
+ switch (jitterType) {
+ case FULL:
+ // 全抖动:random(0, backoff)
+ delay = (long) (Math.random() * backoffDelay);
+ break;
+
+ case EQUAL:
+ // 均等抖动:backoff/2 + random(0, backoff/2)
+ delay = backoffDelay / 2 + (long) (Math.random() * (backoffDelay / 2));
+ break;
+
+ case DECORRELATED:
+ // 去相关抖动:random(baseDelay, previousDelay * 3)
+ long min = baseDelay;
+ long max = Math.min(previousDelay * 3, maxDelay);
+ delay = min + (long) (Math.random() * (max - min));
+ break;
+
+ case NONE:
+ // 无抖动:使用原始退避时间
+ delay = backoffDelay;
+ break;
+
+ default:
+ delay = backoffDelay;
+ }
+
+ return delay;
+ }
+}