复查修复(二): 同步模块与安全遗留

- 修复 remove_sync_source 引入的 stop_sync KeyError 回归(容错 get)
- stop_sync 不再删除存活线程条目(防双 worker);FTP 子目录递归传真名(不再 KeyError)
- 远程文件名统一 _safe_remote_name 校验(FTP/SFTP/HTTP 防路径穿越)
- temp 任务名加随机后缀防碰撞;temp 状态/线程完成后清理(防 sync_state.json 膨胀)
- save_sync_state 快照+原子写(临时文件+os.replace),持锁调用不死锁
- URL 打印脱敏(user:pass@ -> ***@)
- _need_sync_http size=0 不再全量重下(仅按存在性)
- cron 同一分钟去重;PyPI 进度只更新当前源
- v2 start_sync 不再把 bool 当 task_id
- UserRecord.to_dict 脱敏(不返回 password_hash/token),新增 to_dict_private/get_user_with_password
- config_hotreload 单配置源(set+persist 与热重载一致);server.py 用 get_all()
- 会话创建时顺带清理过期项;JSON stats/历史读改写加锁
- 解压目标目录先校验;FTP RETR 命令注入防护;rsync --delete 目标保护
- git/urlopen 补超时;cleanup_completed_tasks 删旧留新
This commit is contained in:
HYC Fixer
2026-09-02 00:39:12 +08:00
parent fc72759a92
commit 82875b710a
10 changed files with 957 additions and 810 deletions
+57 -25
View File
@@ -24,6 +24,37 @@ class APIv2(APIv1):
# 管理员API处理器(始终创建,auth_type检查在装饰器中处理)
self.admin_api = AdminAPI(config)
# ==================== 状态型管理器(惰性单例,跨请求保留状态) ====================
def _get_health_checker(self):
"""健康检查器单例(原每请求新建导致状态丢失)"""
if not hasattr(self, '_health_checker'):
from core.health_check import HealthChecker
self._health_checker = self._get_health_checker()
return self._health_checker
def _get_failover_manager(self):
"""故障转移管理器单例"""
if not hasattr(self, '_failover_manager'):
from core.health_check import MirrorFailoverManager
self._failover_manager = MirrorFailoverManager(self.config)
self._failover_manager.initialize()
return self._failover_manager
def _get_restart_manager(self):
"""优雅重启管理器单例"""
if not hasattr(self, '_restart_manager'):
from core.graceful_restart import GracefulRestartManager
self._restart_manager = self._get_restart_manager()
return self._restart_manager
def _get_prewarmer(self):
"""缓存预热器单例(原每请求新建导致队列/状态丢失)"""
if not hasattr(self, '_prewarmer'):
from core.cache_prewarm import CachePrewarmer
self._prewarmer = self._get_prewarmer()
return self._prewarmer
def handle_request(self, handler, method, path, query_params):
"""处理API v2请求"""
import sys
@@ -271,7 +302,7 @@ class APIv2(APIv1):
elif path == 'health/stats':
if method == 'GET':
from core.health_check import HealthChecker
checker = HealthChecker(self.config.get('health_check', {}))
checker = self._get_health_checker()
handler.send_json_response(checker.get_stats())
else:
handler.send_error(405)
@@ -2283,11 +2314,12 @@ class APIv2(APIv1):
if source_name is None:
return super().api_start_sync(handler)
if hasattr(handler, 'sync_manager') and handler.sync_manager:
task_id = handler.sync_manager.start_sync(source_name)
if task_id:
# MirrorSyncManager.start_sync 返回 bool(True=已启动/已在运行)
started = handler.sync_manager.start_sync(source_name)
if started:
handler.send_json_response({
"success": True,
"task_id": task_id,
"started": True,
"source_name": source_name
})
else:
@@ -2573,7 +2605,7 @@ class APIv2(APIv1):
from core.health_check import HealthChecker, HealthStatus
mirrors = self.config.get('mirrors', {})
checker = HealthChecker(self.config.get('health_check', {}))
checker = self._get_health_checker()
results = []
for mirror_type, mirror_config in mirrors.items():
@@ -2614,7 +2646,7 @@ class APIv2(APIv1):
try:
from core.health_check import HealthChecker, HealthStatus
checker = HealthChecker(self.config.get('health_check', {}))
checker = self._get_health_checker()
mirrors = self.config.get('mirrors', {})
# 查找源对应的镜像类型
@@ -2659,7 +2691,7 @@ class APIv2(APIv1):
try:
from core.health_check import MirrorFailoverManager
failover = MirrorFailoverManager(self.config)
failover = self._get_failover_manager()
failover.initialize()
handler.send_json_response({
@@ -2679,7 +2711,7 @@ class APIv2(APIv1):
try:
from core.health_check import MirrorFailoverManager
failover = MirrorFailoverManager(self.config)
failover = self._get_failover_manager()
failover.initialize()
success = failover.perform_failover(mirror_type)
@@ -3285,7 +3317,7 @@ class APIv2(APIv1):
# 数据库验证
if db:
user = db.get_user(username)
user = db.get_user_with_password(username)
if user and db.verify_password(password, user['password_hash']):
# 数据库验证成功,生成 token
import secrets
@@ -3388,7 +3420,7 @@ class APIv2(APIv1):
config_pass = config.get('auth_pass', '')
# 验证旧密码(优先验证数据库,没有则验证配置文件)—— 强制要求,防止无旧密码改密
user = db.get_user(username) if db else None
user = db.get_user_with_password(username) if db else None
if user:
# 验证数据库密码
if not db.verify_password(old_password, user['password_hash']):
@@ -3985,7 +4017,7 @@ class APIv2(APIv1):
"""获取重启状态"""
from core.graceful_restart import GracefulRestartManager, ServerState
restart_manager = GracefulRestartManager(self.config.get('restart', {}))
restart_manager = self._get_restart_manager()
stats = restart_manager.get_stats()
handler.send_json_response({
@@ -3999,7 +4031,7 @@ class APIv2(APIv1):
"""获取待处理请求"""
from core.graceful_restart import GracefulRestartManager
restart_manager = GracefulRestartManager(self.config.get('restart', {}))
restart_manager = self._get_restart_manager()
pending = restart_manager.get_pending_requests()
handler.send_json_response({
@@ -4018,7 +4050,7 @@ class APIv2(APIv1):
except ValueError:
strategy = RestartStrategy.GRACEFUL
restart_manager = GracefulRestartManager(self.config.get('restart', {}))
restart_manager = self._get_restart_manager()
# 准备重启
prepare_result = restart_manager.prepare_restart()
@@ -4061,7 +4093,7 @@ class APIv2(APIv1):
except json.JSONDecodeError:
restart_strategy = RestartStrategy.GRACEFUL
restart_manager = GracefulRestartManager(self.config.get('restart', {}))
restart_manager = self._get_restart_manager()
# 执行重启
result = restart_manager.perform_restart(strategy=restart_strategy)
@@ -4072,7 +4104,7 @@ class APIv2(APIv1):
"""立即重启服务器"""
from core.graceful_restart import GracefulRestartManager
restart_manager = GracefulRestartManager(self.config.get('restart', {}))
restart_manager = self._get_restart_manager()
# 获取脚本路径
script_path = self.config.get('main_script', 'main.py')
@@ -4088,7 +4120,7 @@ class APIv2(APIv1):
"""获取重启历史"""
from core.graceful_restart import GracefulRestartManager
restart_manager = GracefulRestartManager(self.config.get('restart', {}))
restart_manager = self._get_restart_manager()
history = restart_manager.get_restart_history()
handler.send_json_response({
@@ -4182,7 +4214,7 @@ class APIv2(APIv1):
"""获取缓存预热状态"""
from core.cache_prewarm import CachePrewarmer
prewarmer = CachePrewarmer(self.config.get('cache_prewarm', {}))
prewarmer = self._get_prewarmer()
status = prewarmer.get_status()
handler.send_json_response(status)
@@ -4191,7 +4223,7 @@ class APIv2(APIv1):
"""获取缓存预热统计"""
from core.cache_prewarm import CachePrewarmer
prewarmer = CachePrewarmer(self.config.get('cache_prewarm', {}))
prewarmer = self._get_prewarmer()
stats = prewarmer.get_stats()
handler.send_json_response(stats)
@@ -4205,7 +4237,7 @@ class APIv2(APIv1):
mirror_type = query_params.get('mirror_type', [None])[0]
limit = int(query_params.get('limit', [50])[0])
prewarmer = CachePrewarmer(self.config.get('cache_prewarm', {}))
prewarmer = self._get_prewarmer()
items = prewarmer.get_items(status=status, mirror_type=mirror_type, limit=limit)
handler.send_json_response({
@@ -4217,7 +4249,7 @@ class APIv2(APIv1):
"""获取预热历史"""
from core.cache_prewarm import CachePrewarmer
prewarmer = CachePrewarmer(self.config.get('cache_prewarm', {}))
prewarmer = self._get_prewarmer()
history = prewarmer.get_history()
handler.send_json_response({
@@ -4234,7 +4266,7 @@ class APIv2(APIv1):
limit = int(query_params.get('limit', [50])[0])
priority = query_params.get('priority', ['medium'])[0]
prewarmer = CachePrewarmer(self.config.get('cache_prewarm', {}))
prewarmer = self._get_prewarmer()
# 如果指定了镜像类型,只预热该类型
targets = None
@@ -4277,7 +4309,7 @@ class APIv2(APIv1):
from core.cache_prewarm import CachePrewarmer
prewarmer = CachePrewarmer(self.config.get('cache_prewarm', {}))
prewarmer = self._get_prewarmer()
prewarmer.add_items_batch(mirror_type, items, priority)
handler.send_json_response({
@@ -4313,7 +4345,7 @@ class APIv2(APIv1):
}, 400)
return
prewarmer = CachePrewarmer(self.config.get('cache_prewarm', {}))
prewarmer = self._get_prewarmer()
popular = prewarmer.get_popular_items(mirror_type)
if limit:
@@ -4335,7 +4367,7 @@ class APIv2(APIv1):
mirror_type = query_params.get('mirror_type', [None])[0]
prewarmer = CachePrewarmer(self.config.get('cache_prewarm', {}))
prewarmer = self._get_prewarmer()
if mirror_type:
items = prewarmer.get_popular_items(mirror_type)
@@ -4355,7 +4387,7 @@ class APIv2(APIv1):
"""清空预热队列"""
from core.cache_prewarm import CachePrewarmer
prewarmer = CachePrewarmer(self.config.get('cache_prewarm', {}))
prewarmer = self._get_prewarmer()
prewarmer.clear_items()
handler.send_json_response({