feat(self-operated): expose private runtime configuration snapshots

This commit is contained in:
2026-10-13 22:23:48 +08:00
parent 1d0b0918ba
commit 54f79b3902
11 changed files with 356 additions and 11 deletions
@@ -0,0 +1,32 @@
package com.baiyue.integration.controller;
import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.integration.config.LingxingWmsProperties;
import com.baiyue.integration.dto.lingxing.LingxingEgressRuntimeSnapshot;
import com.baiyue.integration.provider.lingxing.LingxingEgressGate;
import com.baiyue.integration.security.InternalServiceTokenGuard;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
/** Internal token is checked before the active gate is inspected. Never calls the provider. */
@RestController
public class LingxingEgressRuntimeController {
private final InternalServiceTokenGuard guard;
private final LingxingEgressGate gate;
private final LingxingWmsProperties provider;
public LingxingEgressRuntimeController(InternalServiceTokenGuard guard, LingxingEgressGate gate,
LingxingWmsProperties provider) {
this.guard = guard; this.gate = gate; this.provider = provider;
}
@Operation(summary = "读取实际领星出口绑定配置的脱敏快照,无供应商或许可副作用")
@GetMapping("/internal/integrations/lingxing/wms/egress/runtime")
public ApiResponse<LingxingEgressRuntimeSnapshot> runtime(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token) {
guard.verify(token);
return ApiResponse.success(gate.runtimeSnapshot(provider.enabled()));
}
}
@@ -0,0 +1,9 @@
package com.baiyue.integration.dto.lingxing;
import java.time.Instant;
/** Bound runtime configuration only. No credentials, addresses or execution attestation. */
public record LingxingEgressRuntimeSnapshot(int schemaVersion, String service, Instant observedAt,
String mode, String environment, boolean continuousEnabled, String sourceAccountFingerprint,
boolean grantPresent, String grantFingerprint, long intervalMs, int permitMaxSeconds,
boolean providerEnabled, String budgetAuthorityFingerprint) { }
@@ -8,12 +8,18 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Clock;
import java.time.Instant;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HexFormat;
import com.baiyue.integration.dto.lingxing.LingxingEgressRuntimeSnapshot;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/** Local serialization supplements the exclusive grant and durable account protection. */
@Component
public final class LingxingEgressGate {
private static final int PERMIT_MAX_SECONDS = 30;
private static final Logger log = LoggerFactory.getLogger(LingxingEgressGate.class);
private final LingxingEgressProperties properties;
private final LingxingBudgetAuthority authority;
@@ -30,6 +36,23 @@ public final class LingxingEgressGate {
LingxingEgressGate(LingxingEgressProperties properties, LingxingBudgetAuthority authority, Clock clock, Waiter waiter) {
this.properties = properties; this.authority = authority; this.clock = clock; this.waiter = waiter;
}
/** The very properties and clock used by execute; no semaphore, authority, budget or HTTP work. */
public LingxingEgressRuntimeSnapshot runtimeSnapshot(boolean providerEnabled) {
String grant = properties.externalExclusiveGrantId();
return new LingxingEgressRuntimeSnapshot(1, "integration", clock.instant(),
properties.collectionMode().name(), properties.environmentLabel(),
properties.continuousBusinessCollectionEnabled(), fingerprint("source-account", properties.sourceAccount()),
grant != null && !grant.isBlank(), fingerprint("grant", grant), properties.minStartInterval().toMillis(),
PERMIT_MAX_SECONDS, providerEnabled, fingerprint("budget-authority", properties.budgetAuthorityBaseUri() == null ? null
: properties.budgetAuthorityBaseUri().toString()));
}
private static String fingerprint(String domain, String value) {
if (value == null || value.isBlank()) return null;
try {
return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256")
.digest(("selfop-runtime:v1:" + domain + "\n" + value).getBytes(StandardCharsets.UTF_8)));
} catch (NoSuchAlgorithmException impossible) { throw new IllegalStateException("SHA256_UNAVAILABLE", impossible); }
}
public <T> T execute(EgressContext context, Operation operation, String hash, AuthorizedCall<T> call) {
try { return guarded(context, operation, hash, call); }
catch (LingxingWmsException error) {
@@ -61,7 +84,7 @@ public final class LingxingEgressGate {
if (permit == null || !context.equals(permit.context()) || operation != permit.operation()
|| !hash.equals(permit.requestHash()) || permit.allowedAttempts() != 1 || !permit.exclusiveConfirmed()
|| !permit.budgetConsumed() || permit.expiresAt() == null || !clock.instant().isBefore(permit.expiresAt())
|| permit.expiresAt().isAfter(clock.instant().plusSeconds(30))
|| permit.expiresAt().isAfter(clock.instant().plusSeconds(PERMIT_MAX_SECONDS))
|| (!business() && permit.expiresAt().isAfter(properties.externalExclusiveUntil()))) {
recoverableRatePause = false; paused = true; throw failure("BUDGET_PERMIT_INVALID");
}