refactor: 修复9项中等架构问题
安全修复: - WebSocket 端点添加认证(cookie/token),防止未授权窃听日志 - SPA serve_spa 添加路径遍历防护(resolve + relative_to 检查) - Token 改用 httpOnly Cookie 存储,移除前端 localStorage token(防 XSS 窃取) - 添加安全响应头中间件(X-Content-Type-Options/X-Frame-Options/Referrer-Policy) - HTTP 请求日志脱敏请求体中的 password/secret/token 等敏感字段 - 权限检查统一使用 user_has_permission(考虑自定义权限,修复 has_permission 忽略 custom_permissions 的缺陷) 性能与稳定性: - cookies.py 列表接口修复 N+1 查询(改为批量查询 Account) - login_service.py run() 结束时关闭 DB Session(防止连接泄漏) - _active_batches/_active_tests 全局字典添加 threading.Lock(防止并发竞态) 配置优化: - CORS 源支持环境变量 CORS_ORIGINS 配置 - Uvicorn reload 支持环境变量 UVICORN_RELOAD 控制(生产环境默认关闭) - Cookie 安全标志支持环境变量 COOKIE_SECURE 配置(HTTPS 部署时启用) - logs.py 权限不足返回 HTTP 403(原来返回 200 + message)
This commit is contained in:
@@ -13,7 +13,6 @@ from core.douyu import DouyuLogin
|
||||
from core.models import Account, ProxyConfig as DouyuProxyConfig
|
||||
from core.douyu.proxy import resolve_working_proxy, get_proxy_manager
|
||||
from ..models import Account as AccountModel, LoginTask, ProxyConfig as ProxyConfigModel
|
||||
from ..permissions import has_permission
|
||||
|
||||
|
||||
class LoginBatchRunner:
|
||||
@@ -24,7 +23,7 @@ class LoginBatchRunner:
|
||||
db: Session,
|
||||
account_ids: list[int],
|
||||
created_by: int,
|
||||
creator_role: str,
|
||||
creator_permissions: list[str],
|
||||
max_geetest_retries: int = 5,
|
||||
max_proxy_retries: int = 10,
|
||||
proxy_config: Optional[ProxyConfigModel] = None,
|
||||
@@ -35,7 +34,7 @@ class LoginBatchRunner:
|
||||
self.db = db
|
||||
self.account_ids = account_ids
|
||||
self.created_by = created_by
|
||||
self.creator_role = creator_role
|
||||
self.creator_permissions = creator_permissions
|
||||
self.max_geetest_retries = max_geetest_retries
|
||||
self.max_proxy_retries = max_proxy_retries
|
||||
self.proxy_config = proxy_config
|
||||
@@ -175,87 +174,91 @@ class LoginBatchRunner:
|
||||
concurrency = self.concurrency
|
||||
self._push_log("info", f"批量登录任务 {batch_id} 开始,共 {len(self.account_ids)} 个账号,并发数: {concurrency}")
|
||||
|
||||
# 创建或复用任务记录(顺序执行,线程安全)
|
||||
task_infos: list[dict] = [] # {task_id, acc_info}
|
||||
for aid in self.account_ids:
|
||||
acc = self.db.query(AccountModel).filter(AccountModel.id == aid).first()
|
||||
if not acc:
|
||||
continue
|
||||
# 权限检查:客服只能跑分配给自己的
|
||||
if not has_permission(self.creator_role, "login:view_all"):
|
||||
if acc.assigned_to != self.created_by:
|
||||
self._push_log("warning", f"跳过无权账号: {acc.username}")
|
||||
try:
|
||||
# 创建或复用任务记录(顺序执行,线程安全)
|
||||
task_infos: list[dict] = [] # {task_id, acc_info}
|
||||
for aid in self.account_ids:
|
||||
acc = self.db.query(AccountModel).filter(AccountModel.id == aid).first()
|
||||
if not acc:
|
||||
continue
|
||||
# 权限检查:客服只能跑分配给自己的
|
||||
if "login:view_all" not in self.creator_permissions:
|
||||
if acc.assigned_to != self.created_by:
|
||||
self._push_log("warning", f"跳过无权账号: {acc.username}")
|
||||
continue
|
||||
|
||||
# 复用该账号最近一条失败任务记录,避免重复产生多条
|
||||
existing_task = (
|
||||
self.db.query(LoginTask)
|
||||
.filter(LoginTask.account_id == aid, LoginTask.status.in_(["failed", "error"]))
|
||||
.order_by(LoginTask.id.desc())
|
||||
.first()
|
||||
)
|
||||
if existing_task:
|
||||
existing_task.batch_id = batch_id
|
||||
existing_task.status = "pending"
|
||||
existing_task.cookie = ""
|
||||
existing_task.message = ""
|
||||
existing_task.finished_at = None
|
||||
task = existing_task
|
||||
else:
|
||||
task = LoginTask(
|
||||
batch_id=batch_id,
|
||||
account_id=aid,
|
||||
status="pending",
|
||||
created_by=self.created_by,
|
||||
# 复用该账号最近一条失败任务记录,避免重复产生多条
|
||||
existing_task = (
|
||||
self.db.query(LoginTask)
|
||||
.filter(LoginTask.account_id == aid, LoginTask.status.in_(["failed", "error"]))
|
||||
.order_by(LoginTask.id.desc())
|
||||
.first()
|
||||
)
|
||||
self.db.add(task)
|
||||
if existing_task:
|
||||
existing_task.batch_id = batch_id
|
||||
existing_task.status = "pending"
|
||||
existing_task.cookie = ""
|
||||
existing_task.message = ""
|
||||
existing_task.finished_at = None
|
||||
task = existing_task
|
||||
else:
|
||||
task = LoginTask(
|
||||
batch_id=batch_id,
|
||||
account_id=aid,
|
||||
status="pending",
|
||||
created_by=self.created_by,
|
||||
)
|
||||
self.db.add(task)
|
||||
|
||||
self.db.flush() # 获取 task.id
|
||||
self.db.flush() # 获取 task.id
|
||||
|
||||
task_infos.append({
|
||||
"task_id": task.id,
|
||||
"acc_info": {
|
||||
"username": acc.username,
|
||||
"password": acc.password,
|
||||
"email": acc.email,
|
||||
"email_password": acc.email_password,
|
||||
"email_imap_server": acc.email_imap_server or "",
|
||||
"email_imap_port": acc.email_imap_port or 993,
|
||||
"email_imap_ssl": acc.email_imap_ssl if acc.email_imap_ssl is not None else True,
|
||||
},
|
||||
})
|
||||
task_infos.append({
|
||||
"task_id": task.id,
|
||||
"acc_info": {
|
||||
"username": acc.username,
|
||||
"password": acc.password,
|
||||
"email": acc.email,
|
||||
"email_password": acc.email_password,
|
||||
"email_imap_server": acc.email_imap_server or "",
|
||||
"email_imap_port": acc.email_imap_port or 993,
|
||||
"email_imap_ssl": acc.email_imap_ssl if acc.email_imap_ssl is not None else True,
|
||||
},
|
||||
})
|
||||
|
||||
self.db.commit()
|
||||
total = len(task_infos)
|
||||
if total == 0:
|
||||
self._push_log("warning", "没有可执行的账号")
|
||||
self.db.commit()
|
||||
total = len(task_infos)
|
||||
if total == 0:
|
||||
self._push_log("warning", "没有可执行的账号")
|
||||
self._push_log("result", "")
|
||||
return
|
||||
|
||||
# 并发执行登录,每个账号独立获取代理
|
||||
with ThreadPoolExecutor(max_workers=concurrency) as executor:
|
||||
futures = []
|
||||
for item in task_infos:
|
||||
if self._stop.is_set():
|
||||
self._push_log("warning", "任务已停止,跳过剩余账号")
|
||||
break
|
||||
future = executor.submit(
|
||||
self._execute_one,
|
||||
item["task_id"],
|
||||
item["acc_info"],
|
||||
total,
|
||||
)
|
||||
futures.append(future)
|
||||
|
||||
# 等待所有任务完成
|
||||
for future in as_completed(futures):
|
||||
try:
|
||||
future.result()
|
||||
except Exception as e:
|
||||
self._push_log("error", f"Worker 异常: {e}")
|
||||
|
||||
self._push_log("info", f"批量登录任务 {batch_id} 完成")
|
||||
self._push_log("result", "")
|
||||
return
|
||||
|
||||
# 并发执行登录,每个账号独立获取代理
|
||||
with ThreadPoolExecutor(max_workers=concurrency) as executor:
|
||||
futures = []
|
||||
for item in task_infos:
|
||||
if self._stop.is_set():
|
||||
self._push_log("warning", "任务已停止,跳过剩余账号")
|
||||
break
|
||||
future = executor.submit(
|
||||
self._execute_one,
|
||||
item["task_id"],
|
||||
item["acc_info"],
|
||||
total,
|
||||
)
|
||||
futures.append(future)
|
||||
|
||||
# 等待所有任务完成
|
||||
for future in as_completed(futures):
|
||||
try:
|
||||
future.result()
|
||||
except Exception as e:
|
||||
self._push_log("error", f"Worker 异常: {e}")
|
||||
|
||||
self._push_log("info", f"批量登录任务 {batch_id} 完成")
|
||||
self._push_log("result", "")
|
||||
finally:
|
||||
# 确保 DB Session 被关闭,避免连接泄漏
|
||||
self.db.close()
|
||||
|
||||
|
||||
# 在模块末尾导入 SessionLocal(避免循环导入)
|
||||
|
||||
Reference in New Issue
Block a user