TheSmallHanCat commited on
Commit
a665c0c
·
1 Parent(s): c86abf4

feat: 触发429自动禁用和解禁

Browse files
src/core/database.py CHANGED
@@ -193,6 +193,8 @@ class Database:
193
  ("video_enabled", "BOOLEAN DEFAULT 1"),
194
  ("image_concurrency", "INTEGER DEFAULT -1"),
195
  ("video_concurrency", "INTEGER DEFAULT -1"),
 
 
196
  ]
197
 
198
  for col_name, col_type in columns_to_add:
@@ -262,7 +264,9 @@ class Database:
262
  image_enabled BOOLEAN DEFAULT 1,
263
  video_enabled BOOLEAN DEFAULT 1,
264
  image_concurrency INTEGER DEFAULT -1,
265
- video_concurrency INTEGER DEFAULT -1
 
 
266
  )
267
  """)
268
 
 
193
  ("video_enabled", "BOOLEAN DEFAULT 1"),
194
  ("image_concurrency", "INTEGER DEFAULT -1"),
195
  ("video_concurrency", "INTEGER DEFAULT -1"),
196
+ ("ban_reason", "TEXT"), # 禁用原因
197
+ ("banned_at", "TIMESTAMP"), # 禁用时间
198
  ]
199
 
200
  for col_name, col_type in columns_to_add:
 
264
  image_enabled BOOLEAN DEFAULT 1,
265
  video_enabled BOOLEAN DEFAULT 1,
266
  image_concurrency INTEGER DEFAULT -1,
267
+ video_concurrency INTEGER DEFAULT -1,
268
+ ban_reason TEXT,
269
+ banned_at TIMESTAMP
270
  )
271
  """)
272
 
src/core/models.py CHANGED
@@ -38,6 +38,10 @@ class Token(BaseModel):
38
  image_concurrency: int = -1 # -1表示无限制
39
  video_concurrency: int = -1 # -1表示无限制
40
 
 
 
 
 
41
 
42
  class Project(BaseModel):
43
  """Project model for VideoFX"""
 
38
  image_concurrency: int = -1 # -1表示无限制
39
  video_concurrency: int = -1 # -1表示无限制
40
 
41
+ # 429禁用相关
42
+ ban_reason: Optional[str] = None # 禁用原因: "429_rate_limit" 或 None
43
+ banned_at: Optional[datetime] = None # 禁用时间
44
+
45
 
46
  class Project(BaseModel):
47
  """Project model for VideoFX"""
src/main.py CHANGED
@@ -73,10 +73,24 @@ async def lifespan(app: FastAPI):
73
  # Start file cache cleanup task
74
  await generation_handler.file_cache.start_cleanup_task()
75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  print(f"✓ Database initialized")
77
  print(f"✓ Total tokens: {len(tokens)}")
78
  print(f"✓ Cache: {'Enabled' if config.cache_enabled else 'Disabled'} (timeout: {config.cache_timeout}s)")
79
  print(f"✓ File cache cleanup task started")
 
80
  print(f"✓ Server running on http://{config.server_host}:{config.server_port}")
81
  print("=" * 60)
82
 
@@ -86,7 +100,14 @@ async def lifespan(app: FastAPI):
86
  print("Flow2API Shutting down...")
87
  # Stop file cache cleanup task
88
  await generation_handler.file_cache.stop_cleanup_task()
 
 
 
 
 
 
89
  print("✓ File cache cleanup task stopped")
 
90
 
91
 
92
  # Initialize components
 
73
  # Start file cache cleanup task
74
  await generation_handler.file_cache.start_cleanup_task()
75
 
76
+ # Start 429 auto-unban task
77
+ import asyncio
78
+ async def auto_unban_task():
79
+ """定时任务:每小时检查并解禁429被禁用的token"""
80
+ while True:
81
+ try:
82
+ await asyncio.sleep(3600) # 每小时执行一次
83
+ await token_manager.auto_unban_429_tokens()
84
+ except Exception as e:
85
+ print(f"❌ Auto-unban task error: {e}")
86
+
87
+ auto_unban_task_handle = asyncio.create_task(auto_unban_task())
88
+
89
  print(f"✓ Database initialized")
90
  print(f"✓ Total tokens: {len(tokens)}")
91
  print(f"✓ Cache: {'Enabled' if config.cache_enabled else 'Disabled'} (timeout: {config.cache_timeout}s)")
92
  print(f"✓ File cache cleanup task started")
93
+ print(f"✓ 429 auto-unban task started (runs every hour)")
94
  print(f"✓ Server running on http://{config.server_host}:{config.server_port}")
95
  print("=" * 60)
96
 
 
100
  print("Flow2API Shutting down...")
101
  # Stop file cache cleanup task
102
  await generation_handler.file_cache.stop_cleanup_task()
103
+ # Stop auto-unban task
104
+ auto_unban_task_handle.cancel()
105
+ try:
106
+ await auto_unban_task_handle
107
+ except asyncio.CancelledError:
108
+ pass
109
  print("✓ File cache cleanup task stopped")
110
+ print("✓ 429 auto-unban task stopped")
111
 
112
 
113
  # Initialize components
src/services/generation_handler.py CHANGED
@@ -358,7 +358,12 @@ class GenerationHandler:
358
  if stream:
359
  yield self._create_stream_chunk(f"❌ {error_msg}\n")
360
  if token:
361
- await self.token_manager.record_error(token.id)
 
 
 
 
 
362
  yield self._create_error_response(error_msg)
363
 
364
  # 记录失败日志
 
358
  if stream:
359
  yield self._create_stream_chunk(f"❌ {error_msg}\n")
360
  if token:
361
+ # 检测429错误,立即禁用token
362
+ if "429" in str(e) or "HTTP Error 429" in str(e):
363
+ debug_logger.log_warning(f"[429_BAN] Token {token.id} 遇到429错误,立即禁用")
364
+ await self.token_manager.ban_token_for_429(token.id)
365
+ else:
366
+ await self.token_manager.record_error(token.id)
367
  yield self._create_error_response(error_msg)
368
 
369
  # 记录失败日志
src/services/token_manager.py CHANGED
@@ -179,7 +179,10 @@ class TokenManager:
179
  image_concurrency: Optional[int] = None,
180
  video_concurrency: Optional[int] = None
181
  ):
182
- """Update token (支持修改project_id和project_name)"""
 
 
 
183
  update_fields = {}
184
 
185
  if st is not None:
@@ -203,6 +206,25 @@ class TokenManager:
203
  if video_concurrency is not None:
204
  update_fields["video_concurrency"] = video_concurrency
205
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  if update_fields:
207
  await self.db.update_token(token_id, **update_fields)
208
 
@@ -377,6 +399,79 @@ class TokenManager:
377
  """
378
  await self.db.reset_error_count(token_id)
379
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
380
  # ========== 余额刷新 ==========
381
 
382
  async def refresh_credits(self, token_id: int) -> int:
 
179
  image_concurrency: Optional[int] = None,
180
  video_concurrency: Optional[int] = None
181
  ):
182
+ """Update token (支持修改project_id和project_name)
183
+
184
+ 当用户编辑保存token时,如果token未过期,自动清空429禁用状态
185
+ """
186
  update_fields = {}
187
 
188
  if st is not None:
 
206
  if video_concurrency is not None:
207
  update_fields["video_concurrency"] = video_concurrency
208
 
209
+ # 检查token是否因429被禁用,如果是且未过期,则清空429状态
210
+ token = await self.db.get_token(token_id)
211
+ if token and token.ban_reason == "429_rate_limit":
212
+ # 检查token是否过期
213
+ is_expired = False
214
+ if token.at_expires:
215
+ now = datetime.now(timezone.utc)
216
+ if token.at_expires.tzinfo is None:
217
+ at_expires_aware = token.at_expires.replace(tzinfo=timezone.utc)
218
+ else:
219
+ at_expires_aware = token.at_expires
220
+ is_expired = at_expires_aware <= now
221
+
222
+ # 如果未过期,清空429禁用状态
223
+ if not is_expired:
224
+ debug_logger.log_info(f"[UPDATE_TOKEN] Token {token_id} 编辑保存,清空429禁用状态")
225
+ update_fields["ban_reason"] = None
226
+ update_fields["banned_at"] = None
227
+
228
  if update_fields:
229
  await self.db.update_token(token_id, **update_fields)
230
 
 
399
  """
400
  await self.db.reset_error_count(token_id)
401
 
402
+ async def ban_token_for_429(self, token_id: int):
403
+ """因429错误立即禁用token
404
+
405
+ Args:
406
+ token_id: Token ID
407
+ """
408
+ debug_logger.log_warning(f"[429_BAN] 禁用Token {token_id} (原因: 429 Rate Limit)")
409
+ await self.db.update_token(
410
+ token_id,
411
+ is_active=False,
412
+ ban_reason="429_rate_limit",
413
+ banned_at=datetime.now(timezone.utc)
414
+ )
415
+
416
+ async def auto_unban_429_tokens(self):
417
+ """自动解禁因429被禁用的token
418
+
419
+ 规则:
420
+ - 距离禁用时间12小时后自动解禁
421
+ - 仅解禁未过期的token
422
+ - 仅解禁因429被禁用的token
423
+ """
424
+ all_tokens = await self.db.get_all_tokens()
425
+ now = datetime.now(timezone.utc)
426
+
427
+ for token in all_tokens:
428
+ # 跳过非429禁用的token
429
+ if token.ban_reason != "429_rate_limit":
430
+ continue
431
+
432
+ # 跳过未禁用的token
433
+ if token.is_active:
434
+ continue
435
+
436
+ # 跳过没有禁用时间的token
437
+ if not token.banned_at:
438
+ continue
439
+
440
+ # 检查token是否已过期
441
+ if token.at_expires:
442
+ # 确保时区一致
443
+ if token.at_expires.tzinfo is None:
444
+ at_expires_aware = token.at_expires.replace(tzinfo=timezone.utc)
445
+ else:
446
+ at_expires_aware = token.at_expires
447
+
448
+ # 如果已过期,跳过
449
+ if at_expires_aware <= now:
450
+ debug_logger.log_info(f"[AUTO_UNBAN] Token {token.id} 已过期,跳过解禁")
451
+ continue
452
+
453
+ # 确保banned_at时区一致
454
+ if token.banned_at.tzinfo is None:
455
+ banned_at_aware = token.banned_at.replace(tzinfo=timezone.utc)
456
+ else:
457
+ banned_at_aware = token.banned_at
458
+
459
+ # 检查是否已过12小时
460
+ time_since_ban = now - banned_at_aware
461
+ if time_since_ban.total_seconds() >= 12 * 3600: # 12小时
462
+ debug_logger.log_info(
463
+ f"[AUTO_UNBAN] 解禁Token {token.id} (禁用时间: {banned_at_aware}, "
464
+ f"已过 {time_since_ban.total_seconds() / 3600:.1f} 小时)"
465
+ )
466
+ await self.db.update_token(
467
+ token.id,
468
+ is_active=True,
469
+ ban_reason=None,
470
+ banned_at=None
471
+ )
472
+ # 重置错误计数
473
+ await self.db.reset_error_count(token.id)
474
+
475
  # ========== 余额刷新 ==========
476
 
477
  async def refresh_credits(self, token_id: int) -> int: