mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-01 17:00:21 +08:00
fix(proxy-node): 心跳和健康检查增加 tunnel 节点状态修正能力
- 心跳处理:收到心跳说明 tunnel 连通,若状态非 ONLINE 则修正 - 健康检查:移除 OFFLINE 过滤,允许检测 tunnel 重连后的状态恢复 - 前端配额刷新:就地更新 key metadata,避免重拉列表导致分页重置
This commit is contained in:
@@ -1767,6 +1767,17 @@ function shouldAutoRefreshKiroQuota(): boolean {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 将配额刷新结果就地应用到现有 key 上,避免重新拉列表导致分页重置
|
||||||
|
function applyQuotaResults(results: { key_id: string; status: string; metadata?: Record<string, unknown> }[]) {
|
||||||
|
for (const r of results) {
|
||||||
|
if (r.status !== 'success' || !r.metadata) continue
|
||||||
|
const target = providerKeys.value.find(k => k.id === r.key_id)
|
||||||
|
if (target) {
|
||||||
|
target.upstream_metadata = { ...target.upstream_metadata, ...r.metadata } as typeof target.upstream_metadata
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 通用的自动刷新配额函数(支持 Codex、Antigravity 和 Kiro)
|
// 通用的自动刷新配额函数(支持 Codex、Antigravity 和 Kiro)
|
||||||
async function autoRefreshQuotaInBackground() {
|
async function autoRefreshQuotaInBackground() {
|
||||||
if (!props.providerId) return
|
if (!props.providerId) return
|
||||||
@@ -1799,8 +1810,8 @@ async function autoRefreshQuotaInBackground() {
|
|||||||
try {
|
try {
|
||||||
const result = await refreshProviderQuota(props.providerId)
|
const result = await refreshProviderQuota(props.providerId)
|
||||||
if (result.success > 0) {
|
if (result.success > 0) {
|
||||||
// 重新加载 keys 以更新配额显示
|
// 就地更新 key 的 upstream_metadata,避免重新拉列表导致分页重置
|
||||||
await loadEndpoints()
|
applyQuotaResults(result.results)
|
||||||
} else if (!hadCachedQuota && providerType === 'antigravity') {
|
} else if (!hadCachedQuota && providerType === 'antigravity') {
|
||||||
showError('没有获取到配额信息(请检查账号是否已授权、project_id 是否存在)', '提示')
|
showError('没有获取到配额信息(请检查账号是否已授权、project_id 是否存在)', '提示')
|
||||||
}
|
}
|
||||||
@@ -1824,7 +1835,7 @@ async function openAntigravityQuotaDialog(key: EndpointAPIKey) {
|
|||||||
try {
|
try {
|
||||||
const result = await refreshProviderQuota(props.providerId)
|
const result = await refreshProviderQuota(props.providerId)
|
||||||
if (result.success > 0) {
|
if (result.success > 0) {
|
||||||
await loadEndpoints()
|
applyQuotaResults(result.results)
|
||||||
// 更新弹窗引用的 key 数据
|
// 更新弹窗引用的 key 数据
|
||||||
const updated = allKeys.value.find(({ key: k }) => k.id === key.id)
|
const updated = allKeys.value.find(({ key: k }) => k.id === key.id)
|
||||||
if (updated) {
|
if (updated) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
ProxyNode 心跳检测调度器
|
ProxyNode 心跳检测调度器
|
||||||
|
|
||||||
定期检查 proxy_nodes 的 tunnel 连接状态,更新节点状态:
|
定期检查 proxy_nodes 的 tunnel 连接状态,更新节点状态:
|
||||||
- tunnel_connected=True -> ONLINE
|
- tunnel 实际连接中 -> ONLINE(包括从 OFFLINE 恢复的情况)
|
||||||
- tunnel 刚断开 (<60s) -> UNHEALTHY(缓冲期,避免正在进行的请求被立即切走)
|
- tunnel 刚断开 (<60s) -> UNHEALTHY(缓冲期,避免正在进行的请求被立即切走)
|
||||||
- tunnel 断开超过 60s -> OFFLINE
|
- tunnel 断开超过 60s -> OFFLINE
|
||||||
"""
|
"""
|
||||||
@@ -59,12 +59,12 @@ class ProxyNodeHealthScheduler:
|
|||||||
db = create_session()
|
db = create_session()
|
||||||
try:
|
try:
|
||||||
now = datetime.now(timezone.utc)
|
now = datetime.now(timezone.utc)
|
||||||
# 仅检查非手动节点(手动节点无心跳,始终保持 ONLINE)
|
# 检查所有非手动节点(手动节点无心跳,始终保持 ONLINE)
|
||||||
# 非手动节点均为 tunnel 模式,由 tunnel 连接状态决定
|
# 包括 OFFLINE 节点:tunnel 重连后如果 _update_tunnel_status 失败,
|
||||||
|
# 健康检查需要能根据 TunnelManager 内存状态将其恢复为 ONLINE
|
||||||
nodes = (
|
nodes = (
|
||||||
db.query(ProxyNode)
|
db.query(ProxyNode)
|
||||||
.filter(
|
.filter(
|
||||||
ProxyNode.status != ProxyNodeStatus.OFFLINE,
|
|
||||||
ProxyNode.is_manual == False, # noqa: E712
|
ProxyNode.is_manual == False, # noqa: E712
|
||||||
)
|
)
|
||||||
.all()
|
.all()
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ class ProxyNodeService:
|
|||||||
total_requests: int | None = None,
|
total_requests: int | None = None,
|
||||||
avg_latency_ms: float | None = None,
|
avg_latency_ms: float | None = None,
|
||||||
) -> ProxyNode:
|
) -> ProxyNode:
|
||||||
"""处理节点心跳(仅 tunnel 模式节点,心跳更新指标但不改变状态)"""
|
"""处理节点心跳(仅 tunnel 模式节点,更新指标并修正状态不一致)"""
|
||||||
node = db.query(ProxyNode).filter(ProxyNode.id == node_id).first()
|
node = db.query(ProxyNode).filter(ProxyNode.id == node_id).first()
|
||||||
if not node:
|
if not node:
|
||||||
raise NotFoundException(f"ProxyNode {node_id} 不存在", "proxy_node")
|
raise NotFoundException(f"ProxyNode {node_id} 不存在", "proxy_node")
|
||||||
@@ -323,7 +323,12 @@ class ProxyNodeService:
|
|||||||
)
|
)
|
||||||
|
|
||||||
now = datetime.now(timezone.utc)
|
now = datetime.now(timezone.utc)
|
||||||
# 状态由 tunnel WebSocket 连接建立/断开时决定,心跳仅更新指标
|
# 心跳通过 tunnel 连接传输,能收到心跳说明 tunnel 一定连通。
|
||||||
|
# 如果状态不是 ONLINE(例如 _update_tunnel_status 执行失败),修正状态。
|
||||||
|
if node.status != ProxyNodeStatus.ONLINE:
|
||||||
|
node.status = ProxyNodeStatus.ONLINE
|
||||||
|
node.tunnel_connected = True
|
||||||
|
node.updated_at = now
|
||||||
node.last_heartbeat_at = now
|
node.last_heartbeat_at = now
|
||||||
if heartbeat_interval is not None:
|
if heartbeat_interval is not None:
|
||||||
node.heartbeat_interval = heartbeat_interval
|
node.heartbeat_interval = heartbeat_interval
|
||||||
|
|||||||
Reference in New Issue
Block a user