Commit ·
29f247e
1
Parent(s): 006c5d6
fix: 错误次数计数、调试配置持久化、用量统计计算、日志输出
Browse files- src/api/admin.py +4 -4
- src/core/database.py +120 -2
- src/core/logger.py +9 -0
- src/core/models.py +13 -0
- src/main.py +4 -0
- src/services/generation_handler.py +15 -1
- src/services/token_manager.py +14 -13
- static/manage.html +1 -1
src/api/admin.py
CHANGED
|
@@ -594,11 +594,11 @@ async def update_debug_config(
|
|
| 594 |
):
|
| 595 |
"""Update debug configuration"""
|
| 596 |
try:
|
| 597 |
-
#
|
| 598 |
-
|
| 599 |
|
| 600 |
-
#
|
| 601 |
-
|
| 602 |
|
| 603 |
status = "enabled" if request.enabled else "disabled"
|
| 604 |
return {"success": True, "message": f"Debug mode {status}", "enabled": request.enabled}
|
|
|
|
| 594 |
):
|
| 595 |
"""Update debug configuration"""
|
| 596 |
try:
|
| 597 |
+
# Update debug config in database
|
| 598 |
+
await db.update_debug_config(enabled=request.enabled)
|
| 599 |
|
| 600 |
+
# 🔥 Hot reload: sync database config to memory
|
| 601 |
+
await db.reload_config_to_memory()
|
| 602 |
|
| 603 |
status = "enabled" if request.enabled else "disabled"
|
| 604 |
return {"success": True, "message": f"Debug mode {status}", "enabled": request.enabled}
|
src/core/database.py
CHANGED
|
@@ -127,6 +127,27 @@ class Database:
|
|
| 127 |
VALUES (1, ?, ?, ?)
|
| 128 |
""", (cache_enabled, cache_timeout, cache_base_url))
|
| 129 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
async def check_and_migrate_db(self, config_dict: dict = None):
|
| 131 |
"""Check database integrity and perform migrations if needed
|
| 132 |
|
|
@@ -198,6 +219,7 @@ class Database:
|
|
| 198 |
("today_video_count", "INTEGER DEFAULT 0"),
|
| 199 |
("today_error_count", "INTEGER DEFAULT 0"),
|
| 200 |
("today_date", "DATE"),
|
|
|
|
| 201 |
]
|
| 202 |
|
| 203 |
for col_name, col_type in stats_columns_to_add:
|
|
@@ -273,6 +295,7 @@ class Database:
|
|
| 273 |
today_video_count INTEGER DEFAULT 0,
|
| 274 |
today_error_count INTEGER DEFAULT 0,
|
| 275 |
today_date DATE,
|
|
|
|
| 276 |
FOREIGN KEY (token_id) REFERENCES tokens(id)
|
| 277 |
)
|
| 278 |
""")
|
|
@@ -355,6 +378,19 @@ class Database:
|
|
| 355 |
)
|
| 356 |
""")
|
| 357 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 358 |
# Create indexes
|
| 359 |
await db.execute("CREATE INDEX IF NOT EXISTS idx_task_id ON tasks(task_id)")
|
| 360 |
await db.execute("CREATE INDEX IF NOT EXISTS idx_token_st ON tokens(st)")
|
|
@@ -669,7 +705,13 @@ class Database:
|
|
| 669 |
await db.commit()
|
| 670 |
|
| 671 |
async def increment_error_count(self, token_id: int):
|
| 672 |
-
"""Increment error count with daily reset
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 673 |
from datetime import date
|
| 674 |
async with aiosqlite.connect(self.db_path) as db:
|
| 675 |
today = str(date.today())
|
|
@@ -682,16 +724,18 @@ class Database:
|
|
| 682 |
await db.execute("""
|
| 683 |
UPDATE token_stats
|
| 684 |
SET error_count = error_count + 1,
|
|
|
|
| 685 |
today_error_count = 1,
|
| 686 |
today_date = ?,
|
| 687 |
last_error_at = CURRENT_TIMESTAMP
|
| 688 |
WHERE token_id = ?
|
| 689 |
""", (today, token_id))
|
| 690 |
else:
|
| 691 |
-
# Same day, just increment
|
| 692 |
await db.execute("""
|
| 693 |
UPDATE token_stats
|
| 694 |
SET error_count = error_count + 1,
|
|
|
|
| 695 |
today_error_count = today_error_count + 1,
|
| 696 |
today_date = ?,
|
| 697 |
last_error_at = CURRENT_TIMESTAMP
|
|
@@ -699,6 +743,21 @@ class Database:
|
|
| 699 |
""", (today, token_id))
|
| 700 |
await db.commit()
|
| 701 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 702 |
# Config operations
|
| 703 |
async def get_admin_config(self) -> Optional[AdminConfig]:
|
| 704 |
"""Get admin configuration"""
|
|
@@ -876,6 +935,11 @@ class Database:
|
|
| 876 |
config.set_image_timeout(generation_config.image_timeout)
|
| 877 |
config.set_video_timeout(generation_config.video_timeout)
|
| 878 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 879 |
# Cache config operations
|
| 880 |
async def get_cache_config(self) -> CacheConfig:
|
| 881 |
"""Get cache configuration"""
|
|
@@ -924,3 +988,57 @@ class Database:
|
|
| 924 |
""", (new_enabled, new_timeout, new_base_url))
|
| 925 |
|
| 926 |
await db.commit()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
VALUES (1, ?, ?, ?)
|
| 128 |
""", (cache_enabled, cache_timeout, cache_base_url))
|
| 129 |
|
| 130 |
+
# Ensure debug_config has a row
|
| 131 |
+
cursor = await db.execute("SELECT COUNT(*) FROM debug_config")
|
| 132 |
+
count = await cursor.fetchone()
|
| 133 |
+
if count[0] == 0:
|
| 134 |
+
debug_enabled = False
|
| 135 |
+
log_requests = True
|
| 136 |
+
log_responses = True
|
| 137 |
+
mask_token = True
|
| 138 |
+
|
| 139 |
+
if config_dict:
|
| 140 |
+
debug_config = config_dict.get("debug", {})
|
| 141 |
+
debug_enabled = debug_config.get("enabled", False)
|
| 142 |
+
log_requests = debug_config.get("log_requests", True)
|
| 143 |
+
log_responses = debug_config.get("log_responses", True)
|
| 144 |
+
mask_token = debug_config.get("mask_token", True)
|
| 145 |
+
|
| 146 |
+
await db.execute("""
|
| 147 |
+
INSERT INTO debug_config (id, enabled, log_requests, log_responses, mask_token)
|
| 148 |
+
VALUES (1, ?, ?, ?, ?)
|
| 149 |
+
""", (debug_enabled, log_requests, log_responses, mask_token))
|
| 150 |
+
|
| 151 |
async def check_and_migrate_db(self, config_dict: dict = None):
|
| 152 |
"""Check database integrity and perform migrations if needed
|
| 153 |
|
|
|
|
| 219 |
("today_video_count", "INTEGER DEFAULT 0"),
|
| 220 |
("today_error_count", "INTEGER DEFAULT 0"),
|
| 221 |
("today_date", "DATE"),
|
| 222 |
+
("consecutive_error_count", "INTEGER DEFAULT 0"), # 🆕 连续错误计数
|
| 223 |
]
|
| 224 |
|
| 225 |
for col_name, col_type in stats_columns_to_add:
|
|
|
|
| 295 |
today_video_count INTEGER DEFAULT 0,
|
| 296 |
today_error_count INTEGER DEFAULT 0,
|
| 297 |
today_date DATE,
|
| 298 |
+
consecutive_error_count INTEGER DEFAULT 0,
|
| 299 |
FOREIGN KEY (token_id) REFERENCES tokens(id)
|
| 300 |
)
|
| 301 |
""")
|
|
|
|
| 378 |
)
|
| 379 |
""")
|
| 380 |
|
| 381 |
+
# Debug config table
|
| 382 |
+
await db.execute("""
|
| 383 |
+
CREATE TABLE IF NOT EXISTS debug_config (
|
| 384 |
+
id INTEGER PRIMARY KEY DEFAULT 1,
|
| 385 |
+
enabled BOOLEAN DEFAULT 0,
|
| 386 |
+
log_requests BOOLEAN DEFAULT 1,
|
| 387 |
+
log_responses BOOLEAN DEFAULT 1,
|
| 388 |
+
mask_token BOOLEAN DEFAULT 1,
|
| 389 |
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
| 390 |
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
| 391 |
+
)
|
| 392 |
+
""")
|
| 393 |
+
|
| 394 |
# Create indexes
|
| 395 |
await db.execute("CREATE INDEX IF NOT EXISTS idx_task_id ON tasks(task_id)")
|
| 396 |
await db.execute("CREATE INDEX IF NOT EXISTS idx_token_st ON tokens(st)")
|
|
|
|
| 705 |
await db.commit()
|
| 706 |
|
| 707 |
async def increment_error_count(self, token_id: int):
|
| 708 |
+
"""Increment error count with daily reset
|
| 709 |
+
|
| 710 |
+
Updates two counters:
|
| 711 |
+
- error_count: Historical total errors (never reset)
|
| 712 |
+
- consecutive_error_count: Consecutive errors (reset on success/enable)
|
| 713 |
+
- today_error_count: Today's errors (reset on date change)
|
| 714 |
+
"""
|
| 715 |
from datetime import date
|
| 716 |
async with aiosqlite.connect(self.db_path) as db:
|
| 717 |
today = str(date.today())
|
|
|
|
| 724 |
await db.execute("""
|
| 725 |
UPDATE token_stats
|
| 726 |
SET error_count = error_count + 1,
|
| 727 |
+
consecutive_error_count = consecutive_error_count + 1,
|
| 728 |
today_error_count = 1,
|
| 729 |
today_date = ?,
|
| 730 |
last_error_at = CURRENT_TIMESTAMP
|
| 731 |
WHERE token_id = ?
|
| 732 |
""", (today, token_id))
|
| 733 |
else:
|
| 734 |
+
# Same day, just increment all counters
|
| 735 |
await db.execute("""
|
| 736 |
UPDATE token_stats
|
| 737 |
SET error_count = error_count + 1,
|
| 738 |
+
consecutive_error_count = consecutive_error_count + 1,
|
| 739 |
today_error_count = today_error_count + 1,
|
| 740 |
today_date = ?,
|
| 741 |
last_error_at = CURRENT_TIMESTAMP
|
|
|
|
| 743 |
""", (today, token_id))
|
| 744 |
await db.commit()
|
| 745 |
|
| 746 |
+
async def reset_error_count(self, token_id: int):
|
| 747 |
+
"""Reset consecutive error count (only reset consecutive_error_count, keep error_count and today_error_count)
|
| 748 |
+
|
| 749 |
+
This is called when:
|
| 750 |
+
- Token is manually enabled by admin
|
| 751 |
+
- Request succeeds (resets consecutive error counter)
|
| 752 |
+
|
| 753 |
+
Note: error_count (total historical errors) is NEVER reset
|
| 754 |
+
"""
|
| 755 |
+
async with aiosqlite.connect(self.db_path) as db:
|
| 756 |
+
await db.execute("""
|
| 757 |
+
UPDATE token_stats SET consecutive_error_count = 0 WHERE token_id = ?
|
| 758 |
+
""", (token_id,))
|
| 759 |
+
await db.commit()
|
| 760 |
+
|
| 761 |
# Config operations
|
| 762 |
async def get_admin_config(self) -> Optional[AdminConfig]:
|
| 763 |
"""Get admin configuration"""
|
|
|
|
| 935 |
config.set_image_timeout(generation_config.image_timeout)
|
| 936 |
config.set_video_timeout(generation_config.video_timeout)
|
| 937 |
|
| 938 |
+
# Reload debug config
|
| 939 |
+
debug_config = await self.get_debug_config()
|
| 940 |
+
if debug_config:
|
| 941 |
+
config.set_debug_enabled(debug_config.enabled)
|
| 942 |
+
|
| 943 |
# Cache config operations
|
| 944 |
async def get_cache_config(self) -> CacheConfig:
|
| 945 |
"""Get cache configuration"""
|
|
|
|
| 988 |
""", (new_enabled, new_timeout, new_base_url))
|
| 989 |
|
| 990 |
await db.commit()
|
| 991 |
+
|
| 992 |
+
# Debug config operations
|
| 993 |
+
async def get_debug_config(self) -> 'DebugConfig':
|
| 994 |
+
"""Get debug configuration"""
|
| 995 |
+
from .models import DebugConfig
|
| 996 |
+
async with aiosqlite.connect(self.db_path) as db:
|
| 997 |
+
db.row_factory = aiosqlite.Row
|
| 998 |
+
cursor = await db.execute("SELECT * FROM debug_config WHERE id = 1")
|
| 999 |
+
row = await cursor.fetchone()
|
| 1000 |
+
if row:
|
| 1001 |
+
return DebugConfig(**dict(row))
|
| 1002 |
+
# Return default if not found
|
| 1003 |
+
return DebugConfig(enabled=False, log_requests=True, log_responses=True, mask_token=True)
|
| 1004 |
+
|
| 1005 |
+
async def update_debug_config(
|
| 1006 |
+
self,
|
| 1007 |
+
enabled: bool = None,
|
| 1008 |
+
log_requests: bool = None,
|
| 1009 |
+
log_responses: bool = None,
|
| 1010 |
+
mask_token: bool = None
|
| 1011 |
+
):
|
| 1012 |
+
"""Update debug configuration"""
|
| 1013 |
+
async with aiosqlite.connect(self.db_path) as db:
|
| 1014 |
+
db.row_factory = aiosqlite.Row
|
| 1015 |
+
# Get current values
|
| 1016 |
+
cursor = await db.execute("SELECT * FROM debug_config WHERE id = 1")
|
| 1017 |
+
row = await cursor.fetchone()
|
| 1018 |
+
|
| 1019 |
+
if row:
|
| 1020 |
+
current = dict(row)
|
| 1021 |
+
# Use new values if provided, otherwise keep existing
|
| 1022 |
+
new_enabled = enabled if enabled is not None else current.get("enabled", False)
|
| 1023 |
+
new_log_requests = log_requests if log_requests is not None else current.get("log_requests", True)
|
| 1024 |
+
new_log_responses = log_responses if log_responses is not None else current.get("log_responses", True)
|
| 1025 |
+
new_mask_token = mask_token if mask_token is not None else current.get("mask_token", True)
|
| 1026 |
+
|
| 1027 |
+
await db.execute("""
|
| 1028 |
+
UPDATE debug_config
|
| 1029 |
+
SET enabled = ?, log_requests = ?, log_responses = ?, mask_token = ?, updated_at = CURRENT_TIMESTAMP
|
| 1030 |
+
WHERE id = 1
|
| 1031 |
+
""", (new_enabled, new_log_requests, new_log_responses, new_mask_token))
|
| 1032 |
+
else:
|
| 1033 |
+
# Insert default row if not exists
|
| 1034 |
+
new_enabled = enabled if enabled is not None else False
|
| 1035 |
+
new_log_requests = log_requests if log_requests is not None else True
|
| 1036 |
+
new_log_responses = log_responses if log_responses is not None else True
|
| 1037 |
+
new_mask_token = mask_token if mask_token is not None else True
|
| 1038 |
+
|
| 1039 |
+
await db.execute("""
|
| 1040 |
+
INSERT INTO debug_config (id, enabled, log_requests, log_responses, mask_token)
|
| 1041 |
+
VALUES (1, ?, ?, ?, ?)
|
| 1042 |
+
""", (new_enabled, new_log_requests, new_log_responses, new_mask_token))
|
| 1043 |
+
|
| 1044 |
+
await db.commit()
|
src/core/logger.py
CHANGED
|
@@ -239,5 +239,14 @@ class DebugLogger:
|
|
| 239 |
except Exception as e:
|
| 240 |
self.logger.error(f"Error logging info: {e}")
|
| 241 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
# Global debug logger instance
|
| 243 |
debug_logger = DebugLogger()
|
|
|
|
| 239 |
except Exception as e:
|
| 240 |
self.logger.error(f"Error logging info: {e}")
|
| 241 |
|
| 242 |
+
def log_warning(self, message: str):
|
| 243 |
+
"""Log warning message to log.txt"""
|
| 244 |
+
if not config.debug_enabled:
|
| 245 |
+
return
|
| 246 |
+
try:
|
| 247 |
+
self.logger.warning(f"⚠️ [{self._format_timestamp()}] {message}")
|
| 248 |
+
except Exception as e:
|
| 249 |
+
self.logger.error(f"Error logging warning: {e}")
|
| 250 |
+
|
| 251 |
# Global debug logger instance
|
| 252 |
debug_logger = DebugLogger()
|
src/core/models.py
CHANGED
|
@@ -64,6 +64,8 @@ class TokenStats(BaseModel):
|
|
| 64 |
today_video_count: int = 0
|
| 65 |
today_error_count: int = 0
|
| 66 |
today_date: Optional[str] = None
|
|
|
|
|
|
|
| 67 |
|
| 68 |
|
| 69 |
class Task(BaseModel):
|
|
@@ -127,6 +129,17 @@ class CacheConfig(BaseModel):
|
|
| 127 |
updated_at: Optional[datetime] = None
|
| 128 |
|
| 129 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
# OpenAI Compatible Request Models
|
| 131 |
class ChatMessage(BaseModel):
|
| 132 |
"""Chat message"""
|
|
|
|
| 64 |
today_video_count: int = 0
|
| 65 |
today_error_count: int = 0
|
| 66 |
today_date: Optional[str] = None
|
| 67 |
+
# 连续错误计数 (用于自动禁用判断)
|
| 68 |
+
consecutive_error_count: int = 0
|
| 69 |
|
| 70 |
|
| 71 |
class Task(BaseModel):
|
|
|
|
| 129 |
updated_at: Optional[datetime] = None
|
| 130 |
|
| 131 |
|
| 132 |
+
class DebugConfig(BaseModel):
|
| 133 |
+
"""Debug configuration"""
|
| 134 |
+
id: int = 1
|
| 135 |
+
enabled: bool = False
|
| 136 |
+
log_requests: bool = True
|
| 137 |
+
log_responses: bool = True
|
| 138 |
+
mask_token: bool = True
|
| 139 |
+
created_at: Optional[datetime] = None
|
| 140 |
+
updated_at: Optional[datetime] = None
|
| 141 |
+
|
| 142 |
+
|
| 143 |
# OpenAI Compatible Request Models
|
| 144 |
class ChatMessage(BaseModel):
|
| 145 |
"""Chat message"""
|
src/main.py
CHANGED
|
@@ -62,6 +62,10 @@ async def lifespan(app: FastAPI):
|
|
| 62 |
config.set_image_timeout(generation_config.image_timeout)
|
| 63 |
config.set_video_timeout(generation_config.video_timeout)
|
| 64 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
# Initialize concurrency manager
|
| 66 |
tokens = await token_manager.get_all_tokens()
|
| 67 |
await concurrency_manager.initialize(tokens)
|
|
|
|
| 62 |
config.set_image_timeout(generation_config.image_timeout)
|
| 63 |
config.set_video_timeout(generation_config.video_timeout)
|
| 64 |
|
| 65 |
+
# Load debug configuration from database
|
| 66 |
+
debug_config = await db.get_debug_config()
|
| 67 |
+
config.set_debug_enabled(debug_config.enabled)
|
| 68 |
+
|
| 69 |
# Initialize concurrency manager
|
| 70 |
tokens = await token_manager.get_all_tokens()
|
| 71 |
await concurrency_manager.initialize(tokens)
|
src/services/generation_handler.py
CHANGED
|
@@ -440,10 +440,17 @@ class GenerationHandler:
|
|
| 440 |
yield self._create_stream_chunk("缓存图片中...\n")
|
| 441 |
cached_filename = await self.file_cache.download_and_cache(image_url, "image")
|
| 442 |
local_url = f"{self._get_base_url()}/tmp/{cached_filename}"
|
|
|
|
|
|
|
| 443 |
except Exception as e:
|
| 444 |
debug_logger.log_error(f"Failed to cache image: {str(e)}")
|
| 445 |
# 缓存失败不影响结果返回,使用原始URL
|
| 446 |
local_url = image_url
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 447 |
|
| 448 |
# 返回结果
|
| 449 |
if stream:
|
|
@@ -689,13 +696,20 @@ class GenerationHandler:
|
|
| 689 |
if config.cache_enabled:
|
| 690 |
try:
|
| 691 |
if stream:
|
| 692 |
-
yield self._create_stream_chunk("缓存视频
|
| 693 |
cached_filename = await self.file_cache.download_and_cache(video_url, "video")
|
| 694 |
local_url = f"{self._get_base_url()}/tmp/{cached_filename}"
|
|
|
|
|
|
|
| 695 |
except Exception as e:
|
| 696 |
debug_logger.log_error(f"Failed to cache video: {str(e)}")
|
| 697 |
# 缓存失败不影响结果返回,使用原始URL
|
| 698 |
local_url = video_url
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 699 |
|
| 700 |
# 更新数据库
|
| 701 |
task_id = operation["operation"]["name"]
|
|
|
|
| 440 |
yield self._create_stream_chunk("缓存图片中...\n")
|
| 441 |
cached_filename = await self.file_cache.download_and_cache(image_url, "image")
|
| 442 |
local_url = f"{self._get_base_url()}/tmp/{cached_filename}"
|
| 443 |
+
if stream:
|
| 444 |
+
yield self._create_stream_chunk("✅ 图片缓存成功,准备返回缓存地址...\n")
|
| 445 |
except Exception as e:
|
| 446 |
debug_logger.log_error(f"Failed to cache image: {str(e)}")
|
| 447 |
# 缓存失败不影响结果返回,使用原始URL
|
| 448 |
local_url = image_url
|
| 449 |
+
if stream:
|
| 450 |
+
yield self._create_stream_chunk(f"⚠️ 缓存失败: {str(e)}\n正在返回源链接...\n")
|
| 451 |
+
else:
|
| 452 |
+
if stream:
|
| 453 |
+
yield self._create_stream_chunk("缓存已关闭,正在返回源链接...\n")
|
| 454 |
|
| 455 |
# 返回结果
|
| 456 |
if stream:
|
|
|
|
| 696 |
if config.cache_enabled:
|
| 697 |
try:
|
| 698 |
if stream:
|
| 699 |
+
yield self._create_stream_chunk("正在缓存视频文件...\n")
|
| 700 |
cached_filename = await self.file_cache.download_and_cache(video_url, "video")
|
| 701 |
local_url = f"{self._get_base_url()}/tmp/{cached_filename}"
|
| 702 |
+
if stream:
|
| 703 |
+
yield self._create_stream_chunk("✅ 视频缓存成功,准备返回缓存地址...\n")
|
| 704 |
except Exception as e:
|
| 705 |
debug_logger.log_error(f"Failed to cache video: {str(e)}")
|
| 706 |
# 缓存失败不影响结果返回,使用原始URL
|
| 707 |
local_url = video_url
|
| 708 |
+
if stream:
|
| 709 |
+
yield self._create_stream_chunk(f"⚠️ 缓存失败: {str(e)}\n正在返回源链接...\n")
|
| 710 |
+
else:
|
| 711 |
+
if stream:
|
| 712 |
+
yield self._create_stream_chunk("缓存已关闭,正在返回源链接...\n")
|
| 713 |
|
| 714 |
# 更新数据库
|
| 715 |
task_id = operation["operation"]["name"]
|
src/services/token_manager.py
CHANGED
|
@@ -37,17 +37,10 @@ class TokenManager:
|
|
| 37 |
|
| 38 |
async def enable_token(self, token_id: int):
|
| 39 |
"""Enable a token and reset error count"""
|
|
|
|
| 40 |
await self.db.update_token(token_id, is_active=True)
|
| 41 |
-
# Reset error count when enabling
|
| 42 |
-
|
| 43 |
-
import aiosqlite
|
| 44 |
-
async with aiosqlite.connect(self.db.db_path) as db:
|
| 45 |
-
await db.execute("""
|
| 46 |
-
UPDATE token_stats
|
| 47 |
-
SET error_count = 0, today_error_count = 0
|
| 48 |
-
WHERE token_id = ?
|
| 49 |
-
""", (token_id,))
|
| 50 |
-
await db.commit()
|
| 51 |
|
| 52 |
async def disable_token(self, token_id: int):
|
| 53 |
"""Disable a token"""
|
|
@@ -365,17 +358,25 @@ class TokenManager:
|
|
| 365 |
"""Record token error and auto-disable if threshold reached"""
|
| 366 |
await self.db.increment_token_stats(token_id, "error")
|
| 367 |
|
| 368 |
-
# Check if should auto-disable token
|
| 369 |
stats = await self.db.get_token_stats(token_id)
|
| 370 |
admin_config = await self.db.get_admin_config()
|
| 371 |
|
| 372 |
-
if stats and stats.
|
| 373 |
debug_logger.log_warning(
|
| 374 |
-
f"[TOKEN_BAN] Token {token_id} error count ({stats.
|
| 375 |
f"reached threshold ({admin_config.error_ban_threshold}), auto-disabling"
|
| 376 |
)
|
| 377 |
await self.disable_token(token_id)
|
| 378 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 379 |
# ========== 余额刷新 ==========
|
| 380 |
|
| 381 |
async def refresh_credits(self, token_id: int) -> int:
|
|
|
|
| 37 |
|
| 38 |
async def enable_token(self, token_id: int):
|
| 39 |
"""Enable a token and reset error count"""
|
| 40 |
+
# Enable the token
|
| 41 |
await self.db.update_token(token_id, is_active=True)
|
| 42 |
+
# Reset error count when enabling (only reset total error_count, keep today_error_count)
|
| 43 |
+
await self.db.reset_error_count(token_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
async def disable_token(self, token_id: int):
|
| 46 |
"""Disable a token"""
|
|
|
|
| 358 |
"""Record token error and auto-disable if threshold reached"""
|
| 359 |
await self.db.increment_token_stats(token_id, "error")
|
| 360 |
|
| 361 |
+
# Check if should auto-disable token (based on consecutive errors)
|
| 362 |
stats = await self.db.get_token_stats(token_id)
|
| 363 |
admin_config = await self.db.get_admin_config()
|
| 364 |
|
| 365 |
+
if stats and stats.consecutive_error_count >= admin_config.error_ban_threshold:
|
| 366 |
debug_logger.log_warning(
|
| 367 |
+
f"[TOKEN_BAN] Token {token_id} consecutive error count ({stats.consecutive_error_count}) "
|
| 368 |
f"reached threshold ({admin_config.error_ban_threshold}), auto-disabling"
|
| 369 |
)
|
| 370 |
await self.disable_token(token_id)
|
| 371 |
|
| 372 |
+
async def record_success(self, token_id: int):
|
| 373 |
+
"""Record successful request (reset consecutive error count)
|
| 374 |
+
|
| 375 |
+
This method resets error_count to 0, which is used for auto-disable threshold checking.
|
| 376 |
+
Note: today_error_count and historical statistics are NOT reset.
|
| 377 |
+
"""
|
| 378 |
+
await self.db.reset_error_count(token_id)
|
| 379 |
+
|
| 380 |
# ========== 余额刷新 ==========
|
| 381 |
|
| 382 |
async def refresh_credits(self, token_id: int) -> int:
|
static/manage.html
CHANGED
|
@@ -287,7 +287,7 @@
|
|
| 287 |
<input type="checkbox" id="cfgDebugEnabled" class="h-4 w-4 rounded border-input" onchange="toggleDebugMode()">
|
| 288 |
<span class="text-sm font-medium">启用调试模式</span>
|
| 289 |
</label>
|
| 290 |
-
<p class="text-xs text-muted-foreground mt-2">开启后,详细的上游API请求和响应日志将写入 <code class="bg-muted px-1 py-0.5 rounded">logs.txt</code> 文件
|
| 291 |
</div>
|
| 292 |
<div class="rounded-md bg-yellow-50 dark:bg-yellow-900/20 p-3 border border-yellow-200 dark:border-yellow-800">
|
| 293 |
<p class="text-xs text-yellow-800 dark:text-yellow-200">
|
|
|
|
| 287 |
<input type="checkbox" id="cfgDebugEnabled" class="h-4 w-4 rounded border-input" onchange="toggleDebugMode()">
|
| 288 |
<span class="text-sm font-medium">启用调试模式</span>
|
| 289 |
</label>
|
| 290 |
+
<p class="text-xs text-muted-foreground mt-2">开启后,详细的上游API请求和响应日志将写入 <code class="bg-muted px-1 py-0.5 rounded">logs.txt</code> 文件</p>
|
| 291 |
</div>
|
| 292 |
<div class="rounded-md bg-yellow-50 dark:bg-yellow-900/20 p-3 border border-yellow-200 dark:border-yellow-800">
|
| 293 |
<p class="text-xs text-yellow-800 dark:text-yellow-200">
|