From 198f456655bc471160f223c3a8b4bc7365c33c83 Mon Sep 17 00:00:00 2001 From: zzlgreat Date: Thu, 11 Dec 2025 22:36:02 +0800 Subject: [PATCH] update pay ui --- app.py | 27 +++++++++++++++++++++++--- gunicorn_app_config.py | 44 ++++++++++++++++++++++++++++-------------- requirements.txt | 2 +- 3 files changed, 54 insertions(+), 19 deletions(-) diff --git a/app.py b/app.py index 5c5833ac..fc80955f 100755 --- a/app.py +++ b/app.py @@ -54,6 +54,7 @@ from clickhouse_driver import Client as Cclient from elasticsearch import Elasticsearch from flask_cors import CORS import redis +from flask_session import Session from collections import defaultdict from functools import lru_cache @@ -286,15 +287,30 @@ MAIL_DEFAULT_SENDER = 'admin@valuefrontier.cn' # 重要:生产环境请使用环境变量配置,不要硬编码 import os app.config['SECRET_KEY'] = os.environ.get('FLASK_SECRET_KEY', 'vf_production_secret_key_2024_valuefrontier_cn') -app.config['SESSION_COOKIE_SECURE'] = False # 如果生产环境使用HTTPS,应设为True + +# ============ Redis Session 配置(支持多进程/多 Worker)============ +# 使用 Redis 存储 session,确保多个 Gunicorn worker 共享 session +app.config['SESSION_TYPE'] = 'redis' +app.config['SESSION_REDIS'] = redis.Redis(host='localhost', port=6379, db=1) # 使用 db=1,与微信 session 的 db=0 分开 +app.config['SESSION_PERMANENT'] = True +app.config['SESSION_USE_SIGNER'] = True # 对 session cookie 签名,提高安全性 +app.config['SESSION_KEY_PREFIX'] = 'vf_session:' # session key 前缀 +# ============ Redis Session 配置结束 ============ + +# Cookie 配置 - 重要:HTTPS 环境必须设置 SECURE=True +app.config['SESSION_COOKIE_SECURE'] = True # 生产环境使用 HTTPS,必须为 True app.config['SESSION_COOKIE_HTTPONLY'] = True # 生产环境应设为True,防止XSS攻击 app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' # 使用'Lax'以平衡安全性和功能性 app.config['SESSION_COOKIE_DOMAIN'] = None # 不限制域名 app.config['SESSION_COOKIE_PATH'] = '/' # 设置cookie路径 app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7) # session持续7天 app.config['REMEMBER_COOKIE_DURATION'] = timedelta(days=30) # 记住登录30天 -app.config['REMEMBER_COOKIE_SECURE'] = False # 记住登录cookie不要求HTTPS -app.config['REMEMBER_COOKIE_HTTPONLY'] = False # 允许JavaScript访问 +app.config['REMEMBER_COOKIE_SECURE'] = True # 生产环境使用 HTTPS,必须为 True +app.config['REMEMBER_COOKIE_HTTPONLY'] = True # 防止XSS攻击 + +# 初始化 Flask-Session(Redis 存储) +Session(app) +print("✅ Flask-Session (Redis) 已初始化,支持多 Worker 共享 session") # 配置邮件 app.config['MAIL_SERVER'] = MAIL_SERVER @@ -395,16 +411,21 @@ def _detect_async_mode(): _async_mode = _detect_async_mode() print(f"📡 Flask-SocketIO async_mode: {_async_mode}") +# Redis 消息队列 URL(支持多 Worker 之间的消息同步) +SOCKETIO_MESSAGE_QUEUE = 'redis://localhost:6379/2' # 使用 db=2,与 session 和微信 session 分开 + socketio = SocketIO( app, cors_allowed_origins=["http://localhost:3000", "http://127.0.0.1:3000", "http://localhost:5173", "https://valuefrontier.cn", "http://valuefrontier.cn"], async_mode=_async_mode, + message_queue=SOCKETIO_MESSAGE_QUEUE, # 启用 Redis 消息队列,支持多 Worker logger=True, engineio_logger=False, ping_timeout=120, # 心跳超时时间(秒),客户端120秒内无响应才断开 ping_interval=25 # 心跳检测间隔(秒),每25秒发送一次ping ) +print(f"✅ Flask-SocketIO 已配置 Redis 消息队列,支持多 Worker") @login_manager.user_loader diff --git a/gunicorn_app_config.py b/gunicorn_app_config.py index c09efaba..c6f3bbdb 100644 --- a/gunicorn_app_config.py +++ b/gunicorn_app_config.py @@ -1,14 +1,18 @@ # -*- coding: utf-8 -*- """ -Gunicorn 配置文件 - app.py 生产环境配置(支持 Flask-SocketIO + WebSocket) +Gunicorn 配置文件 - app.py 生产环境配置(支持 Flask-SocketIO + WebSocket + 多进程) 使用方式: - # 推荐方式: 使用此配置文件启动 + # 推荐方式: 使用此配置文件启动(多 Worker 模式) gunicorn -c gunicorn_app_config.py app:app - # 如果遇到 502 错误,可以尝试安装 eventlet 后使用: - pip install eventlet - gunicorn -k eventlet -w 1 -b 0.0.0.0:5001 --timeout 300 app:app + # 单 Worker 调试模式: + gunicorn -c gunicorn_app_config.py -w 1 app:app + +多进程架构说明: + - Flask Session 使用 Redis 存储(db=1),所有 Worker 共享 + - SocketIO 使用 Redis 消息队列(db=2),跨 Worker 消息同步 + - 微信登录状态使用 Redis 存储(db=0),所有 Worker 共享 """ import os @@ -18,8 +22,10 @@ import os # 绑定地址和端口 bind = '0.0.0.0:5001' -# Worker 进程数(WebSocket 需要单 worker) -workers = 1 +# Worker 进程数 +# 对于 16 核心机器,使用 4-8 个 gevent workers 即可(每个可处理数千并发) +# 建议: min(8, CPU_CORES / 2) 因为 gevent 本身是异步的,不需要太多进程 +workers = 4 # 4 个 gevent workers,每个可处理 2000 并发连接 = 8000 总并发 # Worker 类型 - 使用 geventwebsocket 支持 WebSocket # Flask-SocketIO 需要异步 worker 来支持 WebSocket @@ -28,9 +34,10 @@ worker_class = 'geventwebsocket.gunicorn.workers.GeventWebSocketWorker' # Worker 连接数(gevent 异步模式下可以处理大量并发连接) worker_connections = 2000 -# 每个 worker 处理的最大请求数 -max_requests = 0 # 禁用自动重启,避免 WebSocket 连接中断 -max_requests_jitter = 0 +# 每个 worker 处理的最大请求数(防止内存泄漏) +# 对于 WebSocket 长连接,设置一个较大的值,不能是 0(否则内存泄漏无法恢复) +max_requests = 10000 # 处理 10000 个请求后重启 worker +max_requests_jitter = 1000 # 随机抖动,避免所有 worker 同时重启 # ==================== 超时配置 ==================== @@ -73,24 +80,31 @@ preload_app = False def on_starting(server): """服务器启动时调用""" - print("=" * 60) - print("🚀 Gunicorn + Flask-SocketIO 服务器正在启动...") + print("=" * 70) + print("🚀 Gunicorn + Flask-SocketIO 多进程服务器正在启动...") print(f" Workers: {server.app.cfg.workers}") print(f" Worker Class: {server.app.cfg.worker_class}") print(f" Bind: {server.app.cfg.bind}") print(f" Worker Connections: {server.app.cfg.worker_connections}") - print("=" * 60) + print(f" Max Requests: {server.app.cfg.max_requests}") + print("-" * 70) + print(" Redis 存储分配:") + print(" - db=0: 微信登录状态") + print(" - db=1: Flask Session") + print(" - db=2: SocketIO 消息队列") + print("=" * 70) def when_ready(server): """服务准备就绪时调用""" - print("✅ Gunicorn 服务准备就绪! WebSocket 支持已启用") + print(f"✅ Gunicorn 服务准备就绪! {server.app.cfg.workers} 个 Worker 已启动") + print(" 多进程支持已启用 (Redis Session + SocketIO Message Queue)") def post_worker_init(worker): """Worker 初始化完成后调用""" # gevent monkey patching 在这里自动完成 - print(f"✅ Worker {worker.pid} 已初始化 (gevent 异步模式)") + print(f"✅ Worker {worker.pid} 已初始化 (gevent 异步模式, 可处理 2000 并发连接)") def worker_abort(worker): diff --git a/requirements.txt b/requirements.txt index 0dd7ca0e..24d85a1f 100755 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ Flask-Compress==1.14 Flask-SocketIO==5.3.6 Flask-Mail==0.9.1 Flask-Migrate==4.0.5 -Flask-Session==0.5.0 +Flask-Session==0.8.0 redis==5.0.1 pandas==2.0.3 numpy==1.24.3