diff --git a/src/main/java/com/hospital/front/api/admin/AdminApi.java b/src/main/java/com/hospital/front/api/admin/AdminApi.java index 72525bd..4378e98 100644 --- a/src/main/java/com/hospital/front/api/admin/AdminApi.java +++ b/src/main/java/com/hospital/front/api/admin/AdminApi.java @@ -23,6 +23,7 @@ import org.noear.solon.core.handle.MethodType; import com.hospital.front.adapter.HospitalRouter; import com.hospital.front.dto.Result; +import com.hospital.front.infra.DbHealthMonitor; import com.hospital.front.infra.exception.BusinessException; /** @@ -43,6 +44,10 @@ public class AdminApi { @Inject private HospitalRouter router; + /** DB 健康缓存监测(health 接口读缓存秒回,避免 DB 慢时阻塞被 Agent 误判宕机) */ + @Inject + private DbHealthMonitor dbHealthMonitor; + /** 日志目录(与 logback.xml 的 log.path 保持一致) */ private File getLogDir() { return new File(Solon.cfg().get("log.path", "./log")); @@ -59,20 +64,21 @@ public class AdminApi { // ================= 1. 健康与指标 ================= - /** 健康检查(含数据库探测) */ + /** 健康检查(进程健康即时返回;DB 状态读后台探测缓存,不阻塞) */ @Mapping("/health") public Result> health(Context ctx) { checkToken(ctx); Runtime mem = Runtime.getRuntime(); - Result db = router.active().healthCheck(); + Result db = dbHealthMonitor.last(); Map out = new LinkedHashMap<>(); - out.put("status", db.isSuccess() ? "UP" : "DEGRADED"); + // db=null 表示后台首轮探测未完成(启动/唤醒初期),进程本身健康,报 STARTING 而非 DEGRADED + out.put("status", db == null ? "STARTING" : (db.isSuccess() ? "UP" : "DEGRADED")); out.put("hospital", router.activeCode()); out.put("hospitalName", router.active().displayName()); out.put("registeredHospitals", router.registeredCodes()); Map database = new HashMap<>(); - database.put("ok", db.isSuccess()); - database.put("msg", db.getMsg()); + database.put("ok", db != null && db.isSuccess()); + database.put("msg", db != null ? db.getMsg() : "DB 探测进行中"); out.put("database", database); Map jvm = new HashMap<>(); jvm.put("totalMemoryMb", mem.totalMemory() / 1048576); diff --git a/src/main/java/com/hospital/front/infra/DbHealthMonitor.java b/src/main/java/com/hospital/front/infra/DbHealthMonitor.java new file mode 100644 index 0000000..212cf19 --- /dev/null +++ b/src/main/java/com/hospital/front/infra/DbHealthMonitor.java @@ -0,0 +1,45 @@ +package com.hospital.front.infra; + +import org.noear.solon.annotation.Component; +import org.noear.solon.annotation.Inject; +import org.noear.solon.scheduling.annotation.Scheduled; + +import com.hospital.front.adapter.HospitalRouter; +import com.hospital.front.dto.Result; + +import java.util.concurrent.atomic.AtomicReference; + +/** + * 数据库健康后台监测(缓存式)。 + * + * 后台周期探测 DB(默认 30 秒),/api/admin/health 直接读缓存秒回—— + * 避免 DB 建连慢(如机器休眠唤醒后网络未就绪,实测可达 4 分钟)时 + * 健康接口长时间阻塞,导致 Agent 看门狗 30s 超时误判主程序宕机而反复重启。 + * + * 启动初期缓存为空视为 STARTING(探测线程尚未跑完第一轮),不算故障。 + */ +@Component +public class DbHealthMonitor { + + @Inject + private HospitalRouter router; + + /** 最近一次探测结果(null = 尚未完成首轮探测) */ + private final AtomicReference> last = new AtomicReference<>(); + + /** 周期探测(首轮延迟由调度器决定,失败不影响后续轮次) */ + @Scheduled(cron = "*/30 * * * * *") + public void probe() { + try { + last.set(router.active().healthCheck()); + } catch (Exception e) { + // healthCheck 内部已捕获,此处兜底防止调度线程中断 + last.set(Result.error("探测异常:" + e.getMessage())); + } + } + + /** 最近一次探测结果;未完成首轮探测时返回 null(调用方按 STARTING 处理) */ + public Result last() { + return last.get(); + } +}