fix(server): 修复路由遮蔽、流式背压与鉴权限流顺序
- 专辑搜索静态路由优先注册,避免被 /album/{source}/{id}/{page} 捕获
- mg 流改为异步背压队列,慢客户端不再 QueueFull 悬挂,错误中断响应
- 限流移到鉴权外层,401 爆破计入限流
- 酷我 token 重试移出锁,修复 asyncio.Lock 递归死锁
- 补 CORS、5sing 音质、parse _kind、wy 首次登录 cookie、stream 签名溢出
- mg 流直连、lifespan 关闭 aiohttp 会话、cookie 登录路由与 auth_store 健壮性
- 测试改用 aiohttp + 进程内 uvicorn,移除 httpx/TestClient
- 更新 start.bat 与文档
This commit is contained in:
1 parent
39565a3913
commit
0df18f048b
19 files changed
+534
-241
No files matched your search
+15
-18
@@ -17,8 +17,9 @@ from collections.abc import AsyncIterator
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, FastAPI, Request
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
|
||||
from starlette.exceptions import HTTPException as StarletteHTTPException
|
||||
|
||||
@@ -28,6 +29,7 @@ from app.http.auth_gate import auth_gate
|
||||
from app.http.rate_limit import make_rate_limiter
|
||||
from app.openapi.components import COMPONENT_SCHEMAS
|
||||
from app.openapi.ui import redoc_html
|
||||
from app.sdk.common.request import close_session
|
||||
|
||||
API_TITLE = "MusicServer 聚合音乐 API"
|
||||
API_DESCRIPTION = (
|
||||
@@ -38,21 +40,6 @@ API_DESCRIPTION = (
|
||||
)
|
||||
|
||||
|
||||
class Envelope(JSONResponse):
|
||||
"""统一成功响应:{ code: 0, data }。文档里由路由的 responses 引用领域模型。"""
|
||||
|
||||
def render(self, content: Any) -> bytes:
|
||||
return json.dumps({"code": 0, "data": content}, ensure_ascii=False).encode("utf-8")
|
||||
|
||||
|
||||
def ok(data: Any, status_code: int = 200) -> Envelope:
|
||||
return Envelope(data, status_code=status_code)
|
||||
|
||||
|
||||
def make_router(tag: str) -> APIRouter:
|
||||
return APIRouter(tags=[tag])
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def _lifespan(_app: FastAPI) -> AsyncIterator[None]:
|
||||
"""启动期副作用 —— 登录态自动续期(启动后延时首刷 + 每 24h 一次,与 TS 版一致)。"""
|
||||
@@ -63,6 +50,7 @@ async def _lifespan(_app: FastAPI) -> AsyncIterator[None]:
|
||||
yield
|
||||
finally:
|
||||
await stop_refresh_scheduler()
|
||||
await close_session()
|
||||
|
||||
|
||||
def create_app() -> FastAPI:
|
||||
@@ -89,9 +77,10 @@ def create_app() -> FastAPI:
|
||||
app.state.started_at = time.time()
|
||||
app.state.auth_key = config.auth_key()
|
||||
|
||||
# ---- 中间行为:限流 → 鉴权(限流先计数,401 爆破同样受限) ----
|
||||
app.middleware("http")(make_rate_limiter(config.rate_limit()))
|
||||
# ---- 中间件顺序(后注册者在外层):CORS → no-store → 限流 → 鉴权 ----
|
||||
# 限流必须在鉴权之外:错误秘钥的 401 也要计数,才能真正防爆破。
|
||||
app.middleware("http")(auth_gate)
|
||||
app.middleware("http")(make_rate_limiter(config.rate_limit()))
|
||||
|
||||
@app.middleware("http")
|
||||
async def _no_store(request: Request, call_next):
|
||||
@@ -99,6 +88,14 @@ def create_app() -> FastAPI:
|
||||
response.headers["Cache-Control"] = "no-store"
|
||||
return response
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
max_age=600,
|
||||
)
|
||||
|
||||
# ---- 异常处理 ----
|
||||
@app.exception_handler(ApiError)
|
||||
async def _api_error(_: Request, exc: ApiError):
|
||||
|
||||
Reference in new issue
Block a user