This commit is contained in:
Ubuntu
2026-08-02 01:27:04 +08:00
parent c20606c003
commit 9379fbb8f8
5 changed files with 6 additions and 79 deletions
+1 -1
View File
@@ -115,7 +115,7 @@
(不再是"网关不装 torch/mediapipe/opencv",只是仍不需要 torch/transformers/scikit-image 等重依赖)。 (不再是"网关不装 torch/mediapipe/opencv",只是仍不需要 torch/transformers/scikit-image 等重依赖)。
- 配置 `gateway/config.json`(不入 git)`workers` 列表、`shared_password` - 配置 `gateway/config.json`(不入 git)`workers` 列表、`shared_password`
`ark` 的 api_key/base_url/model、`public_base_url`、超时(**生发接口慢,`request_timeout_seconds` 调大 ≥120s**)。 `ark` 的 api_key/base_url/model、`public_base_url`、超时(**生发接口慢,`request_timeout_seconds` 调大 ≥120s**)。
- 托管 `/static/annotations/`(落盘的图)定期清理。 - 托管 `/static/annotations/`(落盘的图)永久保留,不自动清理。
--- ---
+2 -67
View File
@@ -4,13 +4,10 @@
不跑任何算法(无 torch/mediapipe/opencv 依赖)。 不跑任何算法(无 torch/mediapipe/opencv 依赖)。
""" """
import asyncio
import base64 import base64
import json import json
import logging import logging
import time
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from io import BytesIO
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional
@@ -67,32 +64,15 @@ async def lifespan(app: FastAPI):
else: else:
app.state._pool_shutdown = None app.state._pool_shutdown = None
# 确保标注图目录存在 # 确保标注图目录存在(永久保留,不做定期清理)
static_dir = Path(cfg["static_dir"]) static_dir = Path(cfg["static_dir"])
static_dir.mkdir(parents=True, exist_ok=True) static_dir.mkdir(parents=True, exist_ok=True)
logger.info("标注图目录: %s", static_dir) logger.info("标注图目录: %s(永久保留,不自动清理)", static_dir)
# 启动定期清理任务(阶段四)
cleanup_shutdown = asyncio.Event()
cleanup_task = asyncio.create_task(
_cleanup_loop(static_dir, cfg, cleanup_shutdown)
)
app.state._cleanup_shutdown = cleanup_shutdown
app.state._cleanup_task = cleanup_task
yield yield
# 关闭 # 关闭
logger.info("网关关闭中...") logger.info("网关关闭中...")
# 停止清理任务
if app.state._cleanup_shutdown:
app.state._cleanup_shutdown.set()
if app.state._cleanup_task:
app.state._cleanup_task.cancel()
try:
await app.state._cleanup_task
except asyncio.CancelledError:
pass
if app.state._pool_shutdown: if app.state._pool_shutdown:
await app.state._pool_shutdown() await app.state._pool_shutdown()
logger.info("网关已关闭") logger.info("网关已关闭")
@@ -132,51 +112,6 @@ def _get_pool_status_safe():
return {"total": 0, "healthy": 0, "busy": 0} return {"total": 0, "healthy": 0, "busy": 0}
async def _cleanup_loop(annotations_dir: Path, cfg: dict, shutdown: asyncio.Event):
"""定期清理 static/annotations/ 中过期的标注图文件。
配置项(可选,在 config.json 中设定):
- cleanup.interval_minutes: 清理间隔,默认 60
- cleanup.max_age_hours: 文件保留时长(小时),默认 24
"""
cleanup_cfg = cfg.get("cleanup", {})
interval_s = cleanup_cfg.get("interval_minutes", 60) * 60
max_age_s = cleanup_cfg.get("max_age_hours", 24) * 3600
logger.info(
"清理任务启动 | 间隔=%dmin | 保留=%dh | 目录=%s",
interval_s // 60, max_age_s // 3600, annotations_dir,
)
while not shutdown.is_set():
try:
await asyncio.wait_for(shutdown.wait(), timeout=interval_s)
break # shutdown
except asyncio.TimeoutError:
pass # 正常到时,执行清理
now = time.time()
deleted = 0
for f in annotations_dir.iterdir():
if f.name == ".gitkeep":
continue
if not f.is_file():
continue
try:
age_s = now - f.stat().st_mtime
if age_s > max_age_s:
f.unlink()
deleted += 1
logger.debug("清理过期文件: %s (age=%.1fh)", f.name, age_s / 3600)
except Exception:
logger.warning("清理文件失败: %s", f.name, exc_info=True)
if deleted:
logger.info("清理完成: 删除 %d 个过期文件", deleted)
logger.info("清理任务已停止")
@app.get("/gateway-health", include_in_schema=False) @app.get("/gateway-health", include_in_schema=False)
async def gateway_health(): async def gateway_health():
"""网关自身健康检查(区别于 worker 的 /health)。""" """网关自身健康检查(区别于 worker 的 /health)。"""
-4
View File
@@ -22,9 +22,5 @@
"request_timeout_seconds": 600, "request_timeout_seconds": 600,
"retry_on_failure": true, "retry_on_failure": true,
"max_retries": 1 "max_retries": 1
},
"cleanup": {
"interval_minutes": 60,
"max_age_hours": 24
} }
} }
-4
View File
@@ -31,10 +31,6 @@ DEFAULTS = {
"retry_on_failure": True, "retry_on_failure": True,
"max_retries": 1, "max_retries": 1,
}, },
"cleanup": {
"interval_minutes": 60,
"max_age_hours": 24,
},
"request_log": { "request_log": {
"enabled": True, "enabled": True,
"log_file": "gateway/request_log.jsonl", "log_file": "gateway/request_log.jsonl",
+3 -3
View File
@@ -2,8 +2,8 @@
设计原则(与用户约定): 设计原则(与用户约定):
- 入参/出参日志里**绝不内嵌图片 base64**。图片统一存盘后用 URL 引用,保持日志短小。 - 入参/出参日志里**绝不内嵌图片 base64**。图片统一存盘后用 URL 引用,保持日志短小。
- 入参图片(image_file / *_base64当前网关不存盘——这里补存到 static_dirin_ 前缀), - 入参图片(image_file / *_base64)补存到 static_dirin_ 前缀),永久保留;
复用现有 24h 清理循环自动回收;url 输入图本身就在远端,不重新下载,直接记 URL。 url 输入图本身就在远端,不重新下载,直接记 URL。
- 出参响应在 forward.py 已把 base64 改写成 *_url(无大图),记录完整 data, - 出参响应在 forward.py 已把 base64 改写成 *_url(无大图),记录完整 data,
但用递归摘要器截断超长结构(landmarks、长字符串),单条上限 ~8KB。 但用递归摘要器截断超长结构(landmarks、长字符串),单条上限 ~8KB。
@@ -39,7 +39,7 @@ def save_image_bytes(
"""把图片字节存盘并返回公网 URL。失败返回 None(不抛异常)。 """把图片字节存盘并返回公网 URL。失败返回 None(不抛异常)。
存到 static_dir/{prefix}{uuid}.{ext}URL = {public_base_url}/static/annotations/{file} 存到 static_dir/{prefix}{uuid}.{ext}URL = {public_base_url}/static/annotations/{file}
与 forward.py 的 rewrite_base64_to_url 落盘路径/URL 规则一致,可被同一清理循环回收 与 forward.py 的 rewrite_base64_to_url 落盘路径/URL 规则一致;文件永久保留,不自动清理
""" """
if not data: if not data:
return None return None