yu commited on
Commit ·
930cea8
1
Parent(s): 8172aec
feat: 支持 Hugging Face Spaces 和容器持久化存储
Browse files- 自动检测环境:HF Pro 使用 /data,本地使用 ./data
- 统一所有数据文件路径:
- accounts.json: data/accounts.json
- settings.yaml: data/settings.yaml
- stats.json: data/stats.json
- images/: data/images/
- Dockerfile +4 -2
- core/account.py +5 -2
- core/config.py +7 -1
- main.py +31 -8
Dockerfile
CHANGED
|
@@ -8,12 +8,14 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
| 8 |
&& apt-get autoremove -y \
|
| 9 |
&& rm -rf /var/lib/apt/lists/*
|
| 10 |
COPY main.py .
|
| 11 |
-
# 复制 uptime_tracker 模块
|
| 12 |
-
COPY uptime_tracker.py .
|
| 13 |
# 复制 core 模块
|
| 14 |
COPY core ./core
|
| 15 |
# 复制 util 目录
|
| 16 |
COPY util ./util
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
# 创建数据目录
|
| 18 |
RUN mkdir -p ./data/images
|
| 19 |
# 声明数据卷(运行时需要 -v 挂载才能持久化)
|
|
|
|
| 8 |
&& apt-get autoremove -y \
|
| 9 |
&& rm -rf /var/lib/apt/lists/*
|
| 10 |
COPY main.py .
|
|
|
|
|
|
|
| 11 |
# 复制 core 模块
|
| 12 |
COPY core ./core
|
| 13 |
# 复制 util 目录
|
| 14 |
COPY util ./util
|
| 15 |
+
# 复制 templates 目录
|
| 16 |
+
COPY templates ./templates
|
| 17 |
+
# 复制 static 目录
|
| 18 |
+
COPY static ./static
|
| 19 |
# 创建数据目录
|
| 20 |
RUN mkdir -p ./data/images
|
| 21 |
# 声明数据卷(运行时需要 -v 挂载才能持久化)
|
core/account.py
CHANGED
|
@@ -18,8 +18,11 @@ if TYPE_CHECKING:
|
|
| 18 |
|
| 19 |
logger = logging.getLogger(__name__)
|
| 20 |
|
| 21 |
-
# 配置文件路径
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
|
| 25 |
@dataclass
|
|
|
|
| 18 |
|
| 19 |
logger = logging.getLogger(__name__)
|
| 20 |
|
| 21 |
+
# 配置文件路径 - 自动检测环境
|
| 22 |
+
if os.path.exists("/data"):
|
| 23 |
+
ACCOUNTS_FILE = "/data/accounts.json" # HF Pro 持久化
|
| 24 |
+
else:
|
| 25 |
+
ACCOUNTS_FILE = "data/accounts.json" # 本地存储(统一到 data 目录)
|
| 26 |
|
| 27 |
|
| 28 |
@dataclass
|
core/config.py
CHANGED
|
@@ -87,7 +87,13 @@ class AppConfig(BaseModel):
|
|
| 87 |
class ConfigManager:
|
| 88 |
"""配置管理器(单例)"""
|
| 89 |
|
| 90 |
-
def __init__(self, yaml_path: str =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
self.yaml_path = Path(yaml_path)
|
| 92 |
self._config: Optional[AppConfig] = None
|
| 93 |
self.load()
|
|
|
|
| 87 |
class ConfigManager:
|
| 88 |
"""配置管理器(单例)"""
|
| 89 |
|
| 90 |
+
def __init__(self, yaml_path: str = None):
|
| 91 |
+
# 自动检测环境并设置默认路径
|
| 92 |
+
if yaml_path is None:
|
| 93 |
+
if os.path.exists("/data"):
|
| 94 |
+
yaml_path = "/data/settings.yaml" # HF Pro 持久化
|
| 95 |
+
else:
|
| 96 |
+
yaml_path = "data/settings.yaml" # 本地存储
|
| 97 |
self.yaml_path = Path(yaml_path)
|
| 98 |
self._config: Optional[AppConfig] = None
|
| 99 |
self.load()
|
main.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
import json, time, os, asyncio, uuid, ssl, re, yaml
|
| 2 |
from datetime import datetime, timezone, timedelta
|
| 3 |
from typing import List, Optional, Union, Dict, Any
|
| 4 |
from pathlib import Path
|
|
@@ -15,6 +15,27 @@ from util.streaming_parser import parse_json_array_stream_async
|
|
| 15 |
from collections import deque
|
| 16 |
from threading import Lock
|
| 17 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
# 导入认证模块
|
| 19 |
from core.auth import verify_api_key
|
| 20 |
from core.session_auth import is_logged_in, login_user, logout_user, require_login, generate_session_secret
|
|
@@ -59,7 +80,6 @@ log_buffer = deque(maxlen=3000)
|
|
| 59 |
log_lock = Lock()
|
| 60 |
|
| 61 |
# 统计数据持久化
|
| 62 |
-
STATS_FILE = "data/stats.json"
|
| 63 |
stats_lock = asyncio.Lock() # 改为异步锁
|
| 64 |
|
| 65 |
async def load_stats():
|
|
@@ -142,12 +162,6 @@ CHAT_URL = config.public_display.chat_url
|
|
| 142 |
IMAGE_GENERATION_ENABLED = config.image_generation.enabled
|
| 143 |
IMAGE_GENERATION_MODELS = config.image_generation.supported_models
|
| 144 |
|
| 145 |
-
# ---------- 图片存储配置 ----------
|
| 146 |
-
if os.path.exists("/data"):
|
| 147 |
-
IMAGE_DIR = "/data/images" # HF Pro持久化存储
|
| 148 |
-
else:
|
| 149 |
-
IMAGE_DIR = "./data/images" # 本地持久化存储
|
| 150 |
-
|
| 151 |
# ---------- 重试配置 ----------
|
| 152 |
MAX_NEW_SESSION_TRIES = config.retry.max_new_session_tries
|
| 153 |
MAX_REQUEST_RETRIES = config.retry.max_request_retries
|
|
@@ -315,6 +329,15 @@ async def startup_event():
|
|
| 315 |
"""应用启动时初始化后台任务"""
|
| 316 |
global global_stats
|
| 317 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 318 |
# 加载统计数据
|
| 319 |
global_stats = await load_stats()
|
| 320 |
logger.info(f"[SYSTEM] 统计数据已加载: {global_stats['total_requests']} 次请求, {global_stats['total_visitors']} 位访客")
|
|
|
|
| 1 |
+
import json, time, os, asyncio, uuid, ssl, re, yaml, shutil
|
| 2 |
from datetime import datetime, timezone, timedelta
|
| 3 |
from typing import List, Optional, Union, Dict, Any
|
| 4 |
from pathlib import Path
|
|
|
|
| 15 |
from collections import deque
|
| 16 |
from threading import Lock
|
| 17 |
|
| 18 |
+
# ---------- 数据目录配置 ----------
|
| 19 |
+
# 自动检测环境:HF Spaces Pro 使用 /data,本地使用 ./data
|
| 20 |
+
if os.path.exists("/data"):
|
| 21 |
+
DATA_DIR = "/data" # HF Pro 持久化存储
|
| 22 |
+
logger_prefix = "[HF-PRO]"
|
| 23 |
+
else:
|
| 24 |
+
DATA_DIR = "./data" # 本地持久化存储
|
| 25 |
+
logger_prefix = "[LOCAL]"
|
| 26 |
+
|
| 27 |
+
# 确保数据目录存在
|
| 28 |
+
os.makedirs(DATA_DIR, exist_ok=True)
|
| 29 |
+
|
| 30 |
+
# 统一的数据文件路径
|
| 31 |
+
ACCOUNTS_FILE = os.path.join(DATA_DIR, "accounts.json")
|
| 32 |
+
SETTINGS_FILE = os.path.join(DATA_DIR, "settings.yaml")
|
| 33 |
+
STATS_FILE = os.path.join(DATA_DIR, "stats.json")
|
| 34 |
+
IMAGE_DIR = os.path.join(DATA_DIR, "images")
|
| 35 |
+
|
| 36 |
+
# 确保图片目录存在
|
| 37 |
+
os.makedirs(IMAGE_DIR, exist_ok=True)
|
| 38 |
+
|
| 39 |
# 导入认证模块
|
| 40 |
from core.auth import verify_api_key
|
| 41 |
from core.session_auth import is_logged_in, login_user, logout_user, require_login, generate_session_secret
|
|
|
|
| 80 |
log_lock = Lock()
|
| 81 |
|
| 82 |
# 统计数据持久化
|
|
|
|
| 83 |
stats_lock = asyncio.Lock() # 改为异步锁
|
| 84 |
|
| 85 |
async def load_stats():
|
|
|
|
| 162 |
IMAGE_GENERATION_ENABLED = config.image_generation.enabled
|
| 163 |
IMAGE_GENERATION_MODELS = config.image_generation.supported_models
|
| 164 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
# ---------- 重试配置 ----------
|
| 166 |
MAX_NEW_SESSION_TRIES = config.retry.max_new_session_tries
|
| 167 |
MAX_REQUEST_RETRIES = config.retry.max_request_retries
|
|
|
|
| 329 |
"""应用启动时初始化后台任务"""
|
| 330 |
global global_stats
|
| 331 |
|
| 332 |
+
# 文件迁移逻辑:将根目录的旧文件迁移到 data 目录
|
| 333 |
+
old_accounts = "accounts.json"
|
| 334 |
+
if os.path.exists(old_accounts) and not os.path.exists(ACCOUNTS_FILE):
|
| 335 |
+
try:
|
| 336 |
+
shutil.copy(old_accounts, ACCOUNTS_FILE)
|
| 337 |
+
logger.info(f"{logger_prefix} 已迁移 {old_accounts} -> {ACCOUNTS_FILE}")
|
| 338 |
+
except Exception as e:
|
| 339 |
+
logger.warning(f"{logger_prefix} 文件迁移失败: {e}")
|
| 340 |
+
|
| 341 |
# 加载统计数据
|
| 342 |
global_stats = await load_stats()
|
| 343 |
logger.info(f"[SYSTEM] 统计数据已加载: {global_stats['total_requests']} 次请求, {global_stats['total_visitors']} 位访客")
|