负载均衡重构: 子网感知选择 + 节点健康监控 + 节点注册上报API

- 新增 subnet-aware 负载策略: 根据客户端IP匹配最近/16子网节点
- 节点注册端点 (POST /api/nodes/register): 边缘节点自动注册
- 节点心跳端点 (POST /api/nodes/heartbeat): 上报CPU/RAM/带宽/连接数
- 健康汇总 (GET /api/nodes/health): 服务端汇总所有节点状态
- 配置节点新增subnet/is_origin字段
- 前端NodeManager: 实时健康状态/CPU/RAM进度条/带宽/连接数
- X-Forwarded-For/X-Real-IP透传支持客户端IP
This commit is contained in:
admin committed 2026-08-10 14:45:28 +08:00
1 parent 5f19fc6aa5
commit 980b91f41f
7 files changed
+319 -87

No files matched your search

+20 -5
View File
@@ -1,4 +1,4 @@
from fastapi import APIRouter, HTTPException, Depends, Query
from fastapi import APIRouter, HTTPException, Depends, Query, Request
from sqlalchemy.ext.asyncio import AsyncSession
from pydantic import BaseModel
from typing import Optional
@@ -26,6 +26,18 @@ class StreamUpdate(BaseModel):
scheduled_at: Optional[str] = None
def _get_client_ip(request: Request) -> str | None:
forwarded = request.headers.get("X-Forwarded-For")
if forwarded:
return forwarded.split(",")[0].strip()
client = request.headers.get("X-Real-IP")
if client:
return client
if request.client:
return request.client.host
return None
@router.get("")
async def list_streams(db: AsyncSession = Depends(get_db)):
svc = StreamService(db)
@@ -114,17 +126,19 @@ async def delete_stream(stream_id: int, db: AsyncSession = Depends(get_db),
@router.get("/{stream_id}/watch")
async def get_watch_urls(stream_id: int, db: AsyncSession = Depends(get_db)):
async def get_watch_urls(stream_id: int, request: Request, db: AsyncSession = Depends(get_db)):
svc = StreamService(db)
stream = await svc.get_stream(stream_id)
if not stream:
raise HTTPException(status_code=404, detail="Stream not found")
urls = await svc.get_watch_urls(stream.stream_key)
client_ip = _get_client_ip(request)
urls = await svc.get_watch_urls(stream.stream_key, client_ip)
return {
"stream_id": stream.id,
"title": stream.title,
"status": stream.status.value,
"multicast_available": urls["multicast_available"],
"selected_node": urls.get("selected_node"),
"lines": urls["lines"],
"qualities": urls["qualities"],
}
@@ -153,12 +167,13 @@ async def stream_end(stream_id: int, db: AsyncSession = Depends(get_db),
@router.get("/detail/{stream_key}")
async def get_stream_by_key(stream_key: str, db: AsyncSession = Depends(get_db)):
async def get_stream_by_key(stream_key: str, request: Request, db: AsyncSession = Depends(get_db)):
svc = StreamService(db)
s = await svc.get_stream_by_key(stream_key)
if not s:
raise HTTPException(status_code=404, detail="Stream not found")
urls = await svc.get_watch_urls(stream_key)
client_ip = _get_client_ip(request)
urls = await svc.get_watch_urls(stream_key, client_ip)
return {
"stream_id": s.id,
"title": s.title,