diff --git a/browser_pool_worker.py b/browser_pool_worker.py index e9abb06..3875884 100755 --- a/browser_pool_worker.py +++ b/browser_pool_worker.py @@ -7,8 +7,26 @@ import threading import queue import time from typing import Callable, Optional, Dict, Any -import nest_asyncio -nest_asyncio.apply() +import nest_asyncio + +_NEST_ASYNCIO_APPLIED = False +_NEST_ASYNCIO_LOCK = threading.Lock() + + +def _apply_nest_asyncio_once() -> None: + """按需应用 nest_asyncio,避免 import 时产生全局副作用。""" + global _NEST_ASYNCIO_APPLIED + + if _NEST_ASYNCIO_APPLIED: + return + with _NEST_ASYNCIO_LOCK: + if _NEST_ASYNCIO_APPLIED: + return + try: + nest_asyncio.apply() + except Exception: + pass + _NEST_ASYNCIO_APPLIED = True # 安全修复: 将魔法数字提取为可配置常量 BROWSER_IDLE_TIMEOUT = int(os.environ.get('BROWSER_IDLE_TIMEOUT', '300')) # 空闲超时(秒),默认5分钟 @@ -204,13 +222,15 @@ class BrowserWorkerPool: else: print(f"[浏览器池] {message}") - def initialize(self): - """初始化工作线程池(按需模式,启动时不创建浏览器)""" - with self.lock: - if self.initialized: - return - - self.log(f"正在初始化工作线程池({self.pool_size}个worker,按需启动浏览器)...") + def initialize(self): + """初始化工作线程池(按需模式,启动时不创建浏览器)""" + with self.lock: + if self.initialized: + return + + _apply_nest_asyncio_once() + + self.log(f"正在初始化工作线程池({self.pool_size}个worker,按需启动浏览器)...") for i in range(self.pool_size): worker = BrowserWorker( diff --git a/db_pool.py b/db_pool.py index c1eca05..311bc69 100755 --- a/db_pool.py +++ b/db_pool.py @@ -111,6 +111,7 @@ class ConnectionPool: if self._pool.qsize() < self.pool_size: try: new_conn = self._create_connection() + self._created_connections += 1 self._pool.put(new_conn, block=False) except Full: # 在获取锁期间池被填满了,关闭新建的连接 @@ -170,6 +171,7 @@ class PooledConnection: if self._cursor: self._cursor.close() + self._cursor = None except Exception as e: print(f"关闭游标失败: {e}") finally: @@ -180,7 +182,8 @@ class PooledConnection: def cursor(self): """获取游标""" - self._cursor = self._conn.cursor() + if self._cursor is None: + self._cursor = self._conn.cursor() return self._cursor def commit(self): diff --git a/requirements.txt b/requirements.txt index 996e9ee..16d0382 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,6 @@ flask-socketio==5.3.5 flask-login==0.6.3 python-socketio==5.10.0 playwright==1.40.0 -eventlet==0.33.3 schedule==1.2.0 psutil==5.9.6 pytz==2024.1 diff --git a/routes/api_auth.py b/routes/api_auth.py index 00d2275..59e2daa 100644 --- a/routes/api_auth.py +++ b/routes/api_auth.py @@ -43,6 +43,10 @@ def register(): if not username or not password: return jsonify({"error": "用户名和密码不能为空"}), 400 + is_valid, error_msg = validate_password(password) + if not is_valid: + return jsonify({"error": error_msg}), 400 + client_ip = get_client_ip() allowed, error_msg = check_ip_rate_limit(client_ip) diff --git a/routes/api_user.py b/routes/api_user.py index ca43c06..b4e1bdb 100644 --- a/routes/api_user.py +++ b/routes/api_user.py @@ -5,7 +5,7 @@ from __future__ import annotations import database import email_service from app_logger import get_logger -from app_security import require_ip_not_locked, validate_email +from app_security import require_ip_not_locked, validate_email, validate_password from flask import Blueprint, jsonify, request from flask_login import current_user, login_required from routes.pages import render_app_spa_or_legacy @@ -119,8 +119,9 @@ def change_user_password(): if not current_password or not new_password: return jsonify({"error": "请填写完整信息"}), 400 - if len(new_password) < 6: - return jsonify({"error": "新密码至少6位"}), 400 + is_valid, error_msg = validate_password(new_password) + if not is_valid: + return jsonify({"error": error_msg}), 400 user = database.get_user_by_id(current_user.id) if not user: @@ -290,4 +291,3 @@ def get_run_stats(): "today_attachments": stats.get("total_attachments", 0), } ) - diff --git a/routes/decorators.py b/routes/decorators.py index 5a0d80a..798babf 100644 --- a/routes/decorators.py +++ b/routes/decorators.py @@ -4,7 +4,7 @@ from __future__ import annotations from functools import wraps -from flask import jsonify, request, session +from flask import jsonify, redirect, request, session, url_for from services.runtime import get_logger @@ -18,9 +18,11 @@ def admin_required(f): logger.debug(f"[admin_required] 检查会话,admin_id存在: {'admin_id' in session}") if "admin_id" not in session: logger.warning(f"[admin_required] 拒绝访问 {request.path} - session中无admin_id") - return jsonify({"error": "需要管理员权限"}), 403 + is_api = request.blueprint == "admin_api" or request.path.startswith("/yuyx/api") + if is_api: + return jsonify({"error": "需要管理员权限"}), 403 + return redirect(url_for("pages.admin_login_page")) logger.info(f"[admin_required] 管理员 {session.get('admin_username')} 访问 {request.path}") return f(*args, **kwargs) return decorated_function -