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