diff --git a/docs/实现说明.md b/docs/实现说明.md index 616e324..aad755d 100644 --- a/docs/实现说明.md +++ b/docs/实现说明.md @@ -115,7 +115,7 @@ (不再是"网关不装 torch/mediapipe/opencv",只是仍不需要 torch/transformers/scikit-image 等重依赖)。 - 配置 `gateway/config.json`(不入 git):`workers` 列表、`shared_password`、 `ark` 的 api_key/base_url/model、`public_base_url`、超时(**生发接口慢,`request_timeout_seconds` 调大 ≥120s**)。 -- 托管 `/static/annotations/`(落盘的图),定期清理。 +- 托管 `/static/annotations/`(落盘的图),永久保留,不自动清理。 --- diff --git a/gateway/app.py b/gateway/app.py index 35bc49e..dc3fb13 100644 --- a/gateway/app.py +++ b/gateway/app.py @@ -4,13 +4,10 @@ 不跑任何算法(无 torch/mediapipe/opencv 依赖)。 """ -import asyncio import base64 import json import logging -import time from contextlib import asynccontextmanager -from io import BytesIO from pathlib import Path from typing import Optional @@ -67,32 +64,15 @@ async def lifespan(app: FastAPI): else: app.state._pool_shutdown = None - # 确保标注图目录存在 + # 确保标注图目录存在(永久保留,不做定期清理) static_dir = Path(cfg["static_dir"]) static_dir.mkdir(parents=True, exist_ok=True) - 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 + logger.info("标注图目录: %s(永久保留,不自动清理)", static_dir) yield # 关闭 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: await app.state._pool_shutdown() logger.info("网关已关闭") @@ -132,51 +112,6 @@ def _get_pool_status_safe(): 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) async def gateway_health(): """网关自身健康检查(区别于 worker 的 /health)。""" diff --git a/gateway/config.example.json b/gateway/config.example.json index b76a439..d977fff 100644 --- a/gateway/config.example.json +++ b/gateway/config.example.json @@ -22,9 +22,5 @@ "request_timeout_seconds": 600, "retry_on_failure": true, "max_retries": 1 - }, - "cleanup": { - "interval_minutes": 60, - "max_age_hours": 24 } } diff --git a/gateway/config.py b/gateway/config.py index 629075e..b6f0b03 100644 --- a/gateway/config.py +++ b/gateway/config.py @@ -31,10 +31,6 @@ DEFAULTS = { "retry_on_failure": True, "max_retries": 1, }, - "cleanup": { - "interval_minutes": 60, - "max_age_hours": 24, - }, "request_log": { "enabled": True, "log_file": "gateway/request_log.jsonl", diff --git a/gateway/reqlog.py b/gateway/reqlog.py index 17bdeb6..8d5cdcd 100644 --- a/gateway/reqlog.py +++ b/gateway/reqlog.py @@ -2,8 +2,8 @@ 设计原则(与用户约定): - 入参/出参日志里**绝不内嵌图片 base64**。图片统一存盘后用 URL 引用,保持日志短小。 -- 入参图片(image_file / *_base64)当前网关不存盘——这里补存到 static_dir(in_ 前缀), - 复用现有 24h 清理循环自动回收;url 输入图本身就在远端,不重新下载,直接记 URL。 +- 入参图片(image_file / *_base64)补存到 static_dir(in_ 前缀),永久保留; + url 输入图本身就在远端,不重新下载,直接记 URL。 - 出参响应在 forward.py 已把 base64 改写成 *_url(无大图),记录完整 data, 但用递归摘要器截断超长结构(landmarks、长字符串),单条上限 ~8KB。 @@ -39,7 +39,7 @@ def save_image_bytes( """把图片字节存盘并返回公网 URL。失败返回 None(不抛异常)。 存到 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: return None