fix: 完善分布式调度恢复与批量触发

- 持久化 Quartz refire 状态并收口运行时启动关闭顺序

- 增加批量 Trigger 获取配置、校验与回归测试
This commit is contained in:
2026-08-31 14:56:41 +08:00
parent 93296eb810
commit 2d50f7de15
15 changed files with 735 additions and 18 deletions

View File

@@ -0,0 +1,53 @@
package com.easyagents.scheduler;
import java.time.Duration;
/**
* 请求调度提供方持久化重试同一次逻辑触发的异常。
*
* <p>仅用于尚未产生业务副作用、且调用方必须保证至少一次登记的短 Handler。
* {@code maxRefires=0} 表示不限制持久化重试次数Provider 正常情况下应释放当前执行线程,
* 并通过持久化的延迟触发保留原 fire 上下文。仅当 JobStore 无法写入持久重试时,
* Provider 可按相同退避暂时占用当前线程并原地重试,以避免正常完成造成 fire 丢失。</p>
*/
public class ScheduleRefireException extends RuntimeException {
private static final int DEFAULT_MAX_REFIRES = 0;
private static final Duration DEFAULT_BASE_DELAY = Duration.ofMillis(250);
private final int maxRefires;
private final Duration baseDelay;
public ScheduleRefireException(String message, Throwable cause) {
this(message, cause, DEFAULT_MAX_REFIRES, DEFAULT_BASE_DELAY);
}
public ScheduleRefireException(String message, Throwable cause,
int maxRefires, Duration baseDelay) {
super(requireMessage(message), cause);
if (maxRefires < 0) throw new IllegalArgumentException("maxRefires must not be negative");
if (baseDelay == null || baseDelay.isZero() || baseDelay.isNegative()) {
throw new IllegalArgumentException("baseDelay must be positive");
}
this.maxRefires = maxRefires;
this.baseDelay = baseDelay;
}
/**
* @return 最大重新执行次数0 表示不限制
*/
public int maxRefires() {
return maxRefires;
}
public Duration delayFor(int refireNumber) {
int shift = Math.min(4, Math.max(0, refireNumber - 1));
return baseDelay.multipliedBy(1L << shift);
}
private static String requireMessage(String message) {
if (message == null || message.isBlank()) {
throw new IllegalArgumentException("message must not be blank");
}
return message;
}
}