修复bug

This commit is contained in:
dekun
2026-05-19 02:16:43 +08:00
parent 809a7cc38f
commit b454cf134f
4 changed files with 27 additions and 13 deletions
+5 -1
View File
@@ -54,7 +54,7 @@ apt install -y python3 python3-venv python3-pip git curl
| 组件 | 版本建议 | 用途 | | 组件 | 版本建议 | 用途 |
|------|----------|------| |------|----------|------|
| Python | 3.10+`python3 -V` 确认) | 运行网关 | | Python | **3.8+**(推荐 3.10+`python3 -V` 确认) | 运行网关 |
| Node.js | LTS | 安装 PM2 | | Node.js | LTS | 安装 PM2 |
| PM2 | 最新 | 进程守护 | | PM2 | 最新 | 进程守护 |
| 宝塔 | 7.x+ | Nginx 反代、SSL | | 宝塔 | 7.x+ | Nginx 反代、SSL |
@@ -98,6 +98,8 @@ cd /opt/openai_node
python3 -m venv venv python3 -m venv venv
source venv/bin/activate source venv/bin/activate
pip install -r requirements.txt -U pip install -r requirements.txt -U
# 若曾装过新版 bcrypt 导致 passlib 报错,可强制重装:
# pip install 'bcrypt>=4.0.0,<4.1.0' -U --force-reinstall
``` ```
### 3.3 配置文件 ### 3.3 配置文件
@@ -364,6 +366,8 @@ Web 保存节点配置会写入 `nodes.json` 并热加载;仅改 `ecosystem.co
| 统计 IP 不对 | 宝塔是否传递 `X-Forwarded-For`(§5.2 | | 统计 IP 不对 | 宝塔是否传递 `X-Forwarded-For`(§5.2 |
| Token 为 0 | 上游 `usage`;流式 `include_usage` | | Token 为 0 | 上游 `usage`;流式 `include_usage` |
| PM2 找不到模块 | 确认使用 `/opt/openai_node/venv` 内 Python | | PM2 找不到模块 | 确认使用 `/opt/openai_node/venv` 内 Python |
| `asyncio` 无 `to_thread` | Python 3.8:拉最新代码(已用 `run_in_thread` 兼容) |
| bcrypt `__about__` 报错 | `pip install 'bcrypt>=4.0.0,<4.1.0' -U --force-reinstall` 后重启 PM2 |
--- ---
Binary file not shown.
+21 -11
View File
@@ -27,7 +27,7 @@ import threading
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from typing import Any, AsyncIterator, Dict, List, Optional, Tuple from typing import Any, AsyncIterator, Callable, Dict, List, Optional, Tuple, TypeVar
try: try:
from typing import Annotated # Python 3.9+ from typing import Annotated # Python 3.9+
@@ -35,6 +35,16 @@ except ImportError: # Python 3.8
from typing_extensions import Annotated from typing_extensions import Annotated
import httpx import httpx
_T = TypeVar("_T")
async def run_in_thread(func: Callable[..., _T], /, *args: Any) -> _T:
"""在线程池执行阻塞函数(兼容 Python 3.8,无 asyncio.to_thread)。"""
if hasattr(asyncio, "to_thread"):
return await run_in_thread(func, *args) # type: ignore[attr-defined]
loop = asyncio.get_running_loop()
return await loop.run_in_executor(None, func, *args)
from fastapi import Depends, FastAPI, HTTPException, Query, Request from fastapi import Depends, FastAPI, HTTPException, Query, Request
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import HTMLResponse, JSONResponse, StreamingResponse from fastapi.responses import HTMLResponse, JSONResponse, StreamingResponse
@@ -634,7 +644,7 @@ async def record_api_log(
usage: Dict[str, int], usage: Dict[str, int],
node_id: Optional[str] = None, node_id: Optional[str] = None,
) -> None: ) -> None:
await asyncio.to_thread( await run_in_thread(
_record_api_log_sync, _record_api_log_sync,
client_ip, client_ip,
model, model,
@@ -1541,14 +1551,14 @@ async def api_me(
@app.get("/api/models/cards") @app.get("/api/models/cards")
async def api_model_cards() -> List[Dict[str, Any]]: async def api_model_cards() -> List[Dict[str, Any]]:
return await asyncio.to_thread(build_model_cards) return await run_in_thread(build_model_cards)
@app.get("/api/nodes") @app.get("/api/nodes")
async def api_nodes( async def api_nodes(
_: Annotated[GateSessionUser, Depends(get_current_web_user)], _: Annotated[GateSessionUser, Depends(get_current_web_user)],
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
return await asyncio.to_thread(list_nodes_with_status) return await run_in_thread(list_nodes_with_status)
@app.post("/api/nodes") @app.post("/api/nodes")
@@ -1566,7 +1576,7 @@ async def api_nodes_create(
"models": [{"id": m.id.strip(), "label": (m.label or m.id).strip()} for m in body.models], "models": [{"id": m.id.strip(), "label": (m.label or m.id).strip()} for m in body.models],
} }
nodes = list(_NODES.get("nodes", [])) + [node] nodes = list(_NODES.get("nodes", [])) + [node]
await asyncio.to_thread(save_nodes_config, {"nodes": nodes}) await run_in_thread(save_nodes_config, {"nodes": nodes})
return node return node
@@ -1589,7 +1599,7 @@ async def api_nodes_update(
"max_concurrent": body.max_concurrent, "max_concurrent": body.max_concurrent,
"models": [{"id": m.id.strip(), "label": (m.label or m.id).strip()} for m in body.models], "models": [{"id": m.id.strip(), "label": (m.label or m.id).strip()} for m in body.models],
} }
await asyncio.to_thread(save_nodes_config, {"nodes": nodes}) await run_in_thread(save_nodes_config, {"nodes": nodes})
return nodes[idx] return nodes[idx]
@@ -1601,7 +1611,7 @@ async def api_nodes_delete(
nodes = [n for n in _NODES.get("nodes", []) if str(n.get("id")) != node_id] nodes = [n for n in _NODES.get("nodes", []) if str(n.get("id")) != node_id]
if len(nodes) == len(_NODES.get("nodes", [])): if len(nodes) == len(_NODES.get("nodes", [])):
raise HTTPException(status_code=404, detail="节点不存在") raise HTTPException(status_code=404, detail="节点不存在")
await asyncio.to_thread(save_nodes_config, {"nodes": nodes}) await run_in_thread(save_nodes_config, {"nodes": nodes})
return JSONResponse({"ok": True}) return JSONResponse({"ok": True})
@@ -1628,14 +1638,14 @@ async def api_nodes_test(
async def api_stats_summary( async def api_stats_summary(
_: Annotated[GateSessionUser, Depends(get_current_web_user)], _: Annotated[GateSessionUser, Depends(get_current_web_user)],
) -> Dict[str, Any]: ) -> Dict[str, Any]:
return await asyncio.to_thread(_query_stats_summary) return await run_in_thread(_query_stats_summary)
@app.get("/api/stats/ips") @app.get("/api/stats/ips")
async def api_stats_ips( async def api_stats_ips(
_: Annotated[GateSessionUser, Depends(get_current_web_user)], _: Annotated[GateSessionUser, Depends(get_current_web_user)],
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
return await asyncio.to_thread(_query_stats_ips) return await run_in_thread(_query_stats_ips)
@app.get("/api/stats/billing") @app.get("/api/stats/billing")
@@ -1643,7 +1653,7 @@ async def api_stats_billing(
_: Annotated[GateSessionUser, Depends(get_current_web_user)], _: Annotated[GateSessionUser, Depends(get_current_web_user)],
days: int = Query(30, ge=1, le=365), days: int = Query(30, ge=1, le=365),
) -> Dict[str, Any]: ) -> Dict[str, Any]:
return await asyncio.to_thread(_query_stats_billing, days) return await run_in_thread(_query_stats_billing, days)
@app.get("/api/stats/logs") @app.get("/api/stats/logs")
@@ -1652,7 +1662,7 @@ async def api_stats_logs(
limit: int = Query(50, ge=1, le=500), limit: int = Query(50, ge=1, le=500),
offset: int = Query(0, ge=0), offset: int = Query(0, ge=0),
) -> Dict[str, Any]: ) -> Dict[str, Any]:
items, total = await asyncio.to_thread(_query_stats_logs, limit, offset) items, total = await run_in_thread(_query_stats_logs, limit, offset)
return {"items": items, "total": total, "limit": limit, "offset": offset} return {"items": items, "total": total, "limit": limit, "offset": offset}
+1 -1
View File
@@ -2,6 +2,6 @@ typing_extensions>=4.5.0
fastapi>=0.110.0 fastapi>=0.110.0
uvicorn[standard]>=0.27.0 uvicorn[standard]>=0.27.0
passlib[bcrypt]>=1.7.4 passlib[bcrypt]>=1.7.4
bcrypt>=4.0.0,<5.0.0 bcrypt>=4.0.0,<4.1.0
python-jose[cryptography]>=3.3.0 python-jose[cryptography]>=3.3.0
httpx>=0.27.0 httpx>=0.27.0