From 04f8b9f951e23f57630474c10081a54cfdb5f246 Mon Sep 17 00:00:00 2001 From: freedakgmail Date: Fri, 17 Jul 2026 19:12:58 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=A6=81=E7=94=A8DB=20SSL=E8=BF=9E?= =?UTF-8?q?=E6=8E=A5=EF=BC=8C=E4=BF=AE=E5=A4=8Dgunicorn=20preload=5Fapp?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E7=9A=84=E8=BF=9E=E6=8E=A5=E6=B1=A0=E6=8D=9F?= =?UTF-8?q?=E5=9D=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- stock-html/db.py | 92 ++++++++++++++++++++++++++++--------- stock-html/gunicorn.conf.py | 15 ++++++ 2 files changed, 86 insertions(+), 21 deletions(-) create mode 100644 stock-html/gunicorn.conf.py diff --git a/stock-html/db.py b/stock-html/db.py index a97efab..29a65e4 100644 --- a/stock-html/db.py +++ b/stock-html/db.py @@ -2,22 +2,55 @@ 数据库连接和用户认证 """ import psycopg2 +from psycopg2 import pool from psycopg2.extras import RealDictCursor from werkzeug.security import generate_password_hash, check_password_hash from flask import session import functools from config import Config +_pool = None + + +def init_db_pool(): + """初始化数据库连接池(应用启动时调用一次)""" + global _pool + if _pool is not None: + return + try: + _pool = pool.ThreadedConnectionPool( + minconn=2, + maxconn=20, + host=Config.DB_HOST, + port=Config.DB_PORT, + database=Config.DB_NAME, + user=Config.DB_USER, + password=Config.DB_PASSWORD, sslmode='disable', + ) + print(f"[DB] 连接池初始化成功 (min=2, max=20)") + except Exception as e: + print(f"[DB] 连接池初始化失败,降级为逐次建连: {e}") + _pool = None + def get_db(): - """获取数据库连接""" + """从连接池获取数据库连接(用完必须调用 put_db(conn) 归还)""" + global _pool + if _pool is not None: + try: + conn = _pool.getconn() + if conn and not conn.closed: + conn.autocommit = False + return conn + except Exception as e: + print(f"[DB] 连接池获取失败: {e}") try: conn = psycopg2.connect( host=Config.DB_HOST, port=Config.DB_PORT, database=Config.DB_NAME, user=Config.DB_USER, - password=Config.DB_PASSWORD + password=Config.DB_PASSWORD, sslmode="disable" ) return conn except Exception as e: @@ -25,6 +58,23 @@ def get_db(): return None +def put_db(conn): + """归还连接到连接池""" + if conn is None: + return + global _pool + if _pool is not None: + try: + _pool.putconn(conn) + return + except Exception: + pass + try: + conn.close() + except Exception: + pass + + def login_required(f): """登录验证装饰器""" @functools.wraps(f) @@ -60,7 +110,7 @@ def db_get_available_cash(user_id): result = cur.fetchone() return float(result['available_cash'] or 0) if result else 0 finally: - conn.close() + put_db(conn) def db_update_available_cash(user_id, amount): @@ -78,7 +128,7 @@ def db_update_available_cash(user_id, amount): conn.rollback() return False, str(e) finally: - conn.close() + put_db(conn) # ========== 用户操作 ========== @@ -111,7 +161,7 @@ def create_user(email, password): conn.rollback() return None, str(e) finally: - conn.close() + put_db(conn) def verify_user(email, password): @@ -131,7 +181,7 @@ def verify_user(email, password): return {'id': user['id'], 'username': user.get('email') or user['username']}, None finally: - conn.close() + put_db(conn) def change_user_password(user_id, old_password, new_password): @@ -158,7 +208,7 @@ def change_user_password(user_id, old_password, new_password): except Exception as e: return False, str(e) finally: - conn.close() + put_db(conn) # ========== 交易记录操作(数据库版) ========== @@ -181,7 +231,7 @@ def db_get_trades(user_id): """, (user_id,)) return cur.fetchall() finally: - conn.close() + put_db(conn) def db_get_trade(user_id, trade_id): @@ -199,7 +249,7 @@ def db_get_trade(user_id, trade_id): """, (trade_id, user_id)) return cur.fetchone() finally: - conn.close() + put_db(conn) def db_add_trade(user_id, data): @@ -239,7 +289,7 @@ def db_add_trade(user_id, data): conn.rollback() return None, str(e) finally: - conn.close() + put_db(conn) def db_update_trade(user_id, trade_id, data): @@ -289,7 +339,7 @@ def db_update_trade(user_id, trade_id, data): conn.rollback() return None, str(e) finally: - conn.close() + put_db(conn) def db_delete_trade(user_id, trade_id): @@ -304,7 +354,7 @@ def db_delete_trade(user_id, trade_id): conn.commit() return cur.rowcount > 0 finally: - conn.close() + put_db(conn) # ========== 关注列表操作(数据库版) ========== @@ -325,7 +375,7 @@ def db_get_watchlist(user_id): """, (user_id,)) return cur.fetchall() finally: - conn.close() + put_db(conn) def db_add_to_watchlist(user_id, code, name): @@ -348,7 +398,7 @@ def db_add_to_watchlist(user_id, code, name): conn.rollback() return None, str(e) finally: - conn.close() + put_db(conn) def db_remove_from_watchlist(user_id, code): @@ -363,7 +413,7 @@ def db_remove_from_watchlist(user_id, code): conn.commit() return db_get_watchlist(user_id) finally: - conn.close() + put_db(conn) # ========== 分析缓存操作(数据库版) ========== @@ -389,7 +439,7 @@ def db_get_alerts_cache(user_id): } return {'alerts': [], 'lastUpdate': None} finally: - conn.close() + put_db(conn) def db_save_alerts_cache(user_id, alerts): @@ -415,7 +465,7 @@ def db_save_alerts_cache(user_id, alerts): print(f"保存分析缓存失败: {e}") return False finally: - conn.close() + put_db(conn) # ========== 基本面数据操作(数据库版) ========== @@ -438,7 +488,7 @@ def db_get_fundamental(code): """, (code, date.today())) return cur.fetchone() finally: - conn.close() + put_db(conn) def db_save_fundamental(code, data): @@ -497,7 +547,7 @@ def db_save_fundamental(code, data): print(f"保存基本面数据失败: {e}") return False finally: - conn.close() + put_db(conn) # ========== 资金流向历史数据操作(数据库版) ========== @@ -526,7 +576,7 @@ def db_get_fund_flow_history(code): return [dict(row) for row in rows], latest_date finally: - conn.close() + put_db(conn) def db_save_fund_flow_history(code, records): @@ -584,4 +634,4 @@ def db_save_fund_flow_history(code, records): print(f"保存资金流向历史失败: {e}") return False finally: - conn.close() + put_db(conn) diff --git a/stock-html/gunicorn.conf.py b/stock-html/gunicorn.conf.py new file mode 100644 index 0000000..e02b39f --- /dev/null +++ b/stock-html/gunicorn.conf.py @@ -0,0 +1,15 @@ +"""Gunicorn 生产环境配置""" +import multiprocessing + +bind = "0.0.0.0:3333" +workers = min(multiprocessing.cpu_count() * 2 + 1, 4) +worker_class = "gthread" +threads = 4 +timeout = 120 +keepalive = 5 +max_requests = 1000 +max_requests_jitter = 50 +accesslog = "-" +errorlog = "-" +loglevel = "info" +preload_app = False