zlwu vor 4 Monaten
Ursprung
Commit
544324a047

+ 11 - 0
backend/.env.example

@@ -1,6 +1,11 @@
 # 数据库连接配置示例
 # DATABASE_URL 格式:mysql+pymysql://user:password@host:port/dbname
 DATABASE_URL=mysql+pymysql://root:password@localhost:3306/sanqiang
+# SQLAlchemy 连接池(生产 WORKERS=1 建议:DB_POOL_SIZE=20 DB_MAX_OVERFLOW=30,合计 50)
+# DB_POOL_SIZE=5
+# DB_MAX_OVERFLOW=10
+# DB_POOL_TIMEOUT=30
+# DB_POOL_RECYCLE=3600
 
 # JWT配置示例
 # SECRET_KEY=your-secret-key-change-in-production
@@ -25,6 +30,12 @@ DATABASE_URL=mysql+pymysql://root:password@localhost:3306/sanqiang
 # REDIS_TIMEOUT_SEC=30
 # REDIS_SSL_ENABLED=false
 
+# API 加密 nonce 写 Redis 超时(秒,默认 5;过短易误报 redis unavailable)
+# API_ENCRYPT_REDIS_OP_TIMEOUT_SEC=5
+
+# 全量 API 请求日志(高并发生产建议 false,每请求额外占 1 条 DB 连接)
+# API_REQUEST_LOG_ENABLED=false
+
 # MinIO 配置示例
 # STORAGE_TYPE=minio
 # MINIO_ENDPOINT=127.0.0.1:9000

+ 5 - 1
backend/app/core/api_crypto_middleware.py

@@ -81,7 +81,11 @@ async def _mark_nonce_once(user_id: str, nonce: str) -> bool:
     client = _get_redis_client()
     if client is not None:
         try:
-            ok = await asyncio.wait_for(client.set(key, "1", ex=ttl_sec, nx=True), timeout=2.0)
+            redis_op_timeout = max(1.0, float(settings.API_ENCRYPT_REDIS_OP_TIMEOUT_SEC))
+            ok = await asyncio.wait_for(
+                client.set(key, "1", ex=ttl_sec, nx=True),
+                timeout=redis_op_timeout,
+            )
             return bool(ok)
         except (asyncio.TimeoutError, redis_exceptions.RedisError):
             logger.warning("api crypto nonce redis unavailable, fallback to local memory")

+ 7 - 0
backend/app/core/config.py

@@ -14,6 +14,11 @@ class Settings(BaseSettings):
 
     # 数据库配置
     DATABASE_URL: str = "mysql+pymysql://root:password@localhost:3306/sanqiang"
+    # SQLAlchemy 连接池(生产 WORKERS=1 建议 pool_size=10、max_overflow=20)
+    DB_POOL_SIZE: int = 5
+    DB_MAX_OVERFLOW: int = 10
+    DB_POOL_TIMEOUT: int = 30
+    DB_POOL_RECYCLE: int = 3600
 
     # JWT配置
     SECRET_KEY: str = "your-secret-key-change-in-production-please-use-random-string"
@@ -123,6 +128,8 @@ class Settings(BaseSettings):
     API_ENCRYPT_FIXED_SECRET: str = ""
     API_ENCRYPT_TS_SKEW_SEC: int = 300
     API_ENCRYPT_NONCE_TTL_SEC: int = 300
+    # nonce 写入 Redis 的操作超时(秒);过短易在并发/首连时误报 redis unavailable
+    API_ENCRYPT_REDIS_OP_TIMEOUT_SEC: float = 5.0
     API_ENCRYPT_EXCLUDE_PATHS: str = (
         "/,"
         "/health,"

+ 4 - 1
backend/app/core/database.py

@@ -7,7 +7,10 @@ from app.core.config import settings
 engine = create_engine(
     settings.DATABASE_URL,
     pool_pre_ping=True,
-    pool_recycle=3600,
+    pool_size=settings.DB_POOL_SIZE,
+    max_overflow=settings.DB_MAX_OVERFLOW,
+    pool_timeout=settings.DB_POOL_TIMEOUT,
+    pool_recycle=settings.DB_POOL_RECYCLE,
     echo=settings.DEBUG,
 )
 

+ 10 - 0
backend/app/main.py

@@ -134,10 +134,20 @@ _api_crypto_boot_msg = (
     f"fixed_secret={_mask_secret(settings.API_ENCRYPT_FIXED_SECRET)} "
     f"ts_skew_sec={settings.API_ENCRYPT_TS_SKEW_SEC} "
     f"nonce_ttl_sec={settings.API_ENCRYPT_NONCE_TTL_SEC} "
+    f"redis_op_timeout_sec={settings.API_ENCRYPT_REDIS_OP_TIMEOUT_SEC} "
     f"exclude_count={len([x for x in (settings.API_ENCRYPT_EXCLUDE_PATHS or '').split(',') if x.strip()])}"
 )
+_db_pool_boot_msg = (
+    "DB_POOL_BOOT "
+    f"pool_size={settings.DB_POOL_SIZE} "
+    f"max_overflow={settings.DB_MAX_OVERFLOW} "
+    f"pool_timeout={settings.DB_POOL_TIMEOUT} "
+    f"max_connections={settings.DB_POOL_SIZE + settings.DB_MAX_OVERFLOW}"
+)
 print(_api_crypto_boot_msg)
 logger.info(_api_crypto_boot_msg)
+print(_db_pool_boot_msg)
+logger.info(_db_pool_boot_msg)
 
 
 @app.get("/")