feat: escalate overdue approval tasks via processDue

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
0264408
2026-09-16 09:23:30 +08:00
co-authored by Cursor
parent 850583f326
commit 30047f3612
32 changed files with 736 additions and 42 deletions
@@ -0,0 +1,68 @@
package com.jetlumen.ordo.spring;
import com.jetlumen.ordo.api.OrdoEngine;
import org.springframework.context.SmartLifecycle;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/** Optional poller that calls {@link OrdoEngine#processDue(int)} when {@code ordo.due.poll-ms} is positive. */
public final class OrdoDuePoller implements SmartLifecycle {
private static final Logger LOG = System.getLogger("ordo");
private static final int BATCH_SIZE = 100;
private final OrdoEngine ordoEngine;
private final long pollMs;
private ScheduledExecutorService executor;
private volatile boolean running;
public OrdoDuePoller(OrdoEngine ordoEngine, long pollMs) {
this.ordoEngine = Objects.requireNonNull(ordoEngine, "ordoEngine must not be null");
this.pollMs = pollMs;
}
@Override
public boolean isAutoStartup() {
return pollMs > 0;
}
@Override
public void start() {
if (pollMs <= 0 || running) {
return;
}
executor = Executors.newSingleThreadScheduledExecutor(runnable -> {
Thread thread = new Thread(runnable, "ordo-due-poll");
thread.setDaemon(true);
return thread;
});
executor.scheduleWithFixedDelay(this::tick, pollMs, pollMs, TimeUnit.MILLISECONDS);
running = true;
}
private void tick() {
try {
ordoEngine.processDue(BATCH_SIZE);
} catch (RuntimeException e) {
LOG.log(Level.WARNING, "processDue failed", e);
}
}
@Override
public void stop() {
running = false;
if (executor != null) {
executor.shutdownNow();
executor = null;
}
}
@Override
public boolean isRunning() {
return running;
}
}
@@ -146,4 +146,10 @@ public class OrdoJdbcAutoConfiguration {
public OrdoDefinitionLoader ordoDefinitionLoader(OrdoEngine ordoEngine, OrdoProperties ordoProperties) {
return new OrdoDefinitionLoader(ordoEngine, ordoProperties.getDefinitions().getLocation());
}
@Bean
@ConditionalOnMissingBean
public OrdoDuePoller ordoDuePoller(OrdoEngine ordoEngine, OrdoProperties ordoProperties) {
return new OrdoDuePoller(ordoEngine, ordoProperties.getDue().getPollMs());
}
}
@@ -10,6 +10,7 @@ public class OrdoProperties {
private boolean enabled = true;
private final Definitions definitions = new Definitions();
private final Due due = new Due();
public boolean isEnabled() {
return enabled;
@@ -23,6 +24,10 @@ public class OrdoProperties {
return definitions;
}
public Due getDue() {
return due;
}
public static class Definitions {
private String location = "classpath*:ordo/*.json";
@@ -34,4 +39,17 @@ public class OrdoProperties {
this.location = location;
}
}
public static class Due {
/** Poll interval in milliseconds. {@code 0} disables scheduling. */
private long pollMs;
public long getPollMs() {
return pollMs;
}
public void setPollMs(long pollMs) {
this.pollMs = pollMs;
}
}
}