修复所有bug并添加新功能

- 修复添加账号按钮无反应问题
- 添加账号备注字段(可选)
- 添加账号设置按钮(修改密码/备注)
- 修复用户反馈���能
- 添加定时任务执行日志
- 修复容器重启后账号加载问题
- 修复所有JavaScript语法错误
- 优化账号加载机制(4层保障)

🤖 Generated with Claude Code
This commit is contained in:
Yu Yon
2025-12-10 11:19:16 +08:00
parent 0fd7137cea
commit b5344cd55e
67 changed files with 38235 additions and 3271 deletions

160
browser_pool.py Executable file
View File

@@ -0,0 +1,160 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""浏览器池管理 - 线程本地存储,每个线程复用自己的浏览器"""
import threading
import time
import nest_asyncio
nest_asyncio.apply()
# 线程本地存储
_thread_local = threading.local()
class BrowserPool:
"""浏览器池 - 使用线程本地存储,每个线程有自己的浏览器"""
def __init__(self, pool_size=3, log_callback=None):
self.pool_size = pool_size
self.log_callback = log_callback
self.lock = threading.Lock()
self.all_browsers = [] # 追踪所有浏览器(用于关闭)
self.initialized = True
def log(self, message):
if self.log_callback:
self.log_callback(message)
else:
print(f"[浏览器池] {message}")
def initialize(self):
"""初始化(线程本地模式下不预热)"""
self.log(f"浏览器池已就绪(线程本地模式,每线程独立浏览器)")
self.initialized = True
def _create_browser(self):
"""创建一个浏览器实例"""
try:
from playwright.sync_api import sync_playwright
playwright = sync_playwright().start()
browser = playwright.chromium.launch(
headless=True,
args=[
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-gpu',
'--single-process'
]
)
instance = {
'playwright': playwright,
'browser': browser,
'thread_id': threading.current_thread().ident,
'created_at': time.time(),
'use_count': 0
}
with self.lock:
self.all_browsers.append(instance)
return instance
except Exception as e:
self.log(f"创建浏览器失败: {e}")
return None
def acquire(self, timeout=60):
"""获取当前线程的浏览器实例(如果没有则创建)"""
# 检查当前线程是否已有浏览器
browser_instance = getattr(_thread_local, 'browser_instance', None)
if browser_instance:
# 检查浏览器是否还有效
try:
if browser_instance['browser'].is_connected():
browser_instance['use_count'] += 1
self.log(f"复用线程浏览器(第{browser_instance['use_count']}次使用)")
return browser_instance
except:
pass
# 浏览器已失效,清理
self._close_browser(browser_instance)
_thread_local.browser_instance = None
# 为当前线程创建新浏览器
self.log("为当前线程创建新浏览器...")
browser_instance = self._create_browser()
if browser_instance:
browser_instance['use_count'] = 1
_thread_local.browser_instance = browser_instance
return browser_instance
def release(self, browser_instance):
"""释放浏览器(线程本地模式下保留不关闭)"""
if browser_instance is None:
return
# 检查浏览器是否还有效
try:
if browser_instance['browser'].is_connected():
self.log(f"浏览器保持活跃(已使用{browser_instance['use_count']}次)")
return
except:
pass
# 浏览器已断开,清理
self.log("浏览器已断开,清理资源")
self._close_browser(browser_instance)
if getattr(_thread_local, 'browser_instance', None) == browser_instance:
_thread_local.browser_instance = None
def _close_browser(self, browser_instance):
"""关闭单个浏览器实例"""
try:
if browser_instance.get('browser'):
browser_instance['browser'].close()
if browser_instance.get('playwright'):
browser_instance['playwright'].stop()
with self.lock:
if browser_instance in self.all_browsers:
self.all_browsers.remove(browser_instance)
except Exception as e:
self.log(f"关闭浏览器失败: {e}")
def shutdown(self):
"""关闭所有浏览器"""
self.log("正在关闭所有浏览器...")
for browser_instance in list(self.all_browsers):
self._close_browser(browser_instance)
self.all_browsers.clear()
self.initialized = False
self.log("浏览器池已关闭")
def get_status(self):
"""获取池状态"""
return {
'pool_size': self.pool_size,
'total_browsers': len(self.all_browsers),
'initialized': self.initialized,
'mode': 'thread_local'
}
# 全局浏览器池实例
_browser_pool = None
_pool_lock = threading.Lock()
def get_browser_pool(pool_size=3, log_callback=None):
"""获取全局浏览器池实例"""
global _browser_pool
with _pool_lock:
if _browser_pool is None:
_browser_pool = BrowserPool(pool_size=pool_size, log_callback=log_callback)
return _browser_pool
def init_browser_pool(pool_size=3, log_callback=None):
"""初始化浏览器池"""
pool = get_browser_pool(pool_size, log_callback)
pool.initialize()
return pool