2025-12-10 20:52:44 +08:00
|
|
|
|
"""
|
|
|
|
|
|
数据库连接和初始化
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
import time
|
2026-01-30 03:10:21 +08:00
|
|
|
|
from collections.abc import Generator
|
2026-02-02 03:16:52 +08:00
|
|
|
|
from contextlib import contextmanager
|
2026-02-01 17:28:00 +08:00
|
|
|
|
from typing import Any, cast
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
|
|
from sqlalchemy import create_engine, event
|
|
|
|
|
|
from sqlalchemy.engine import Engine
|
|
|
|
|
|
from sqlalchemy.orm import Session, sessionmaker
|
2026-01-15 17:03:19 +08:00
|
|
|
|
from sqlalchemy.pool import QueuePool
|
2026-02-01 17:28:00 +08:00
|
|
|
|
from starlette.requests import Request
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
|
|
from src.core.logger import logger
|
|
|
|
|
|
|
2026-02-01 17:28:00 +08:00
|
|
|
|
from ..config import config
|
|
|
|
|
|
from ..models.database import Base, SystemConfig, User, UserRole
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
|
|
# 延迟初始化的数据库引擎和会话工厂
|
2026-01-30 03:10:21 +08:00
|
|
|
|
_engine: Engine | None = None
|
|
|
|
|
|
_SessionLocal: sessionmaker[Session] | None = None
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
|
|
# 连接池监控
|
|
|
|
|
|
_last_pool_warning: float = 0.0
|
|
|
|
|
|
POOL_WARNING_INTERVAL = 60 # 每60秒最多警告一次
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-01-15 17:03:19 +08:00
|
|
|
|
def _setup_pool_monitoring(engine: Engine) -> None:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""设置连接池监控事件"""
|
|
|
|
|
|
|
|
|
|
|
|
@event.listens_for(engine, "connect")
|
2026-01-15 17:03:19 +08:00
|
|
|
|
def receive_connect(dbapi_conn: Any, connection_record: Any) -> None:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""连接创建时的监控"""
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
@event.listens_for(engine, "checkout")
|
2026-01-15 17:03:19 +08:00
|
|
|
|
def receive_checkout(dbapi_conn: Any, connection_record: Any, connection_proxy: Any) -> None:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""从连接池检出连接时的监控"""
|
|
|
|
|
|
global _last_pool_warning
|
|
|
|
|
|
|
2026-01-15 17:03:19 +08:00
|
|
|
|
pool = cast(QueuePool, engine.pool)
|
2025-12-10 20:52:44 +08:00
|
|
|
|
# 获取连接池状态
|
|
|
|
|
|
checked_out = pool.checkedout()
|
|
|
|
|
|
pool_size = pool.size()
|
|
|
|
|
|
overflow = pool.overflow()
|
|
|
|
|
|
max_capacity = config.db_pool_size + config.db_max_overflow
|
|
|
|
|
|
|
|
|
|
|
|
# 计算使用率
|
|
|
|
|
|
usage_rate = (checked_out / max_capacity) * 100 if max_capacity > 0 else 0
|
|
|
|
|
|
|
|
|
|
|
|
# 如果使用率超过阈值,发出警告
|
|
|
|
|
|
if usage_rate >= config.db_pool_warn_threshold:
|
|
|
|
|
|
current_time = time.time()
|
|
|
|
|
|
# 避免频繁警告
|
|
|
|
|
|
if current_time - _last_pool_warning > POOL_WARNING_INTERVAL:
|
|
|
|
|
|
_last_pool_warning = current_time
|
|
|
|
|
|
logger.warning(
|
|
|
|
|
|
f"数据库连接池使用率过高: checked_out={checked_out}, "
|
|
|
|
|
|
f"pool_size={pool_size}, overflow={overflow}, "
|
|
|
|
|
|
f"max_capacity={max_capacity}, usage_rate={usage_rate:.1f}%, "
|
|
|
|
|
|
f"threshold={config.db_pool_warn_threshold}%"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-01-15 17:03:19 +08:00
|
|
|
|
def get_pool_status() -> dict[str, Any]:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""获取连接池状态"""
|
|
|
|
|
|
engine = _ensure_engine()
|
2026-01-15 17:03:19 +08:00
|
|
|
|
pool = cast(QueuePool, engine.pool)
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"checked_out": pool.checkedout(),
|
|
|
|
|
|
"pool_size": pool.size(),
|
|
|
|
|
|
"overflow": pool.overflow(),
|
|
|
|
|
|
"max_capacity": config.db_pool_size + config.db_max_overflow,
|
|
|
|
|
|
"pool_timeout": config.db_pool_timeout,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-01-15 17:03:19 +08:00
|
|
|
|
def log_pool_status() -> None:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""记录连接池状态到日志(用于监控)"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
status = get_pool_status()
|
|
|
|
|
|
usage_rate = (
|
|
|
|
|
|
(status["checked_out"] / status["max_capacity"] * 100)
|
|
|
|
|
|
if status["max_capacity"] > 0
|
|
|
|
|
|
else 0
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
f"数据库连接池状态: checked_out={status['checked_out']}, "
|
|
|
|
|
|
f"pool_size={status['pool_size']}, overflow={status['overflow']}, "
|
|
|
|
|
|
f"max_capacity={status['max_capacity']}, usage_rate={usage_rate:.1f}%"
|
|
|
|
|
|
)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"获取连接池状态失败: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _ensure_engine() -> Engine:
|
|
|
|
|
|
"""
|
|
|
|
|
|
确保数据库引擎已创建(延迟加载)
|
|
|
|
|
|
|
|
|
|
|
|
这允许测试和 CLI 工具在导入模块时不会立即连接数据库
|
|
|
|
|
|
"""
|
|
|
|
|
|
global _engine, _SessionLocal
|
|
|
|
|
|
|
|
|
|
|
|
if _engine is not None:
|
|
|
|
|
|
return _engine
|
|
|
|
|
|
|
|
|
|
|
|
# 获取数据库配置
|
|
|
|
|
|
DATABASE_URL = config.database_url
|
|
|
|
|
|
|
|
|
|
|
|
# 验证数据库类型(生产环境要求 PostgreSQL,但允许测试环境使用其他数据库)
|
|
|
|
|
|
is_production = config.environment == "production"
|
|
|
|
|
|
if is_production and not DATABASE_URL.startswith("postgresql://"):
|
|
|
|
|
|
raise ValueError("生产环境只支持 PostgreSQL 数据库,请配置正确的 DATABASE_URL")
|
|
|
|
|
|
|
|
|
|
|
|
# 创建引擎
|
|
|
|
|
|
_engine = create_engine(
|
|
|
|
|
|
DATABASE_URL,
|
|
|
|
|
|
poolclass=QueuePool, # 使用队列连接池
|
|
|
|
|
|
pool_size=config.db_pool_size, # 连接池大小
|
|
|
|
|
|
max_overflow=config.db_max_overflow, # 最大溢出连接数
|
|
|
|
|
|
pool_timeout=config.db_pool_timeout, # 连接超时(秒)
|
|
|
|
|
|
pool_recycle=config.db_pool_recycle, # 连接回收时间(秒)
|
|
|
|
|
|
pool_pre_ping=True, # 检查连接活性
|
|
|
|
|
|
echo=False, # 关闭SQL日志输出(太冗长)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 设置连接池监控
|
|
|
|
|
|
_setup_pool_monitoring(_engine)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建会话工厂
|
|
|
|
|
|
_SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=_engine)
|
|
|
|
|
|
|
|
|
|
|
|
_log_pool_capacity()
|
|
|
|
|
|
|
2026-02-01 17:28:00 +08:00
|
|
|
|
logger.debug(
|
|
|
|
|
|
f"数据库引擎已初始化: {DATABASE_URL.split('@')[-1] if '@' in DATABASE_URL else 'local'}"
|
|
|
|
|
|
)
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
|
|
return _engine
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-01-15 17:03:19 +08:00
|
|
|
|
def _log_pool_capacity() -> None:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
theoretical = config.db_pool_size + config.db_max_overflow
|
|
|
|
|
|
workers = max(1, config.worker_processes)
|
|
|
|
|
|
total_estimated = theoretical * workers
|
2025-12-18 00:35:46 +08:00
|
|
|
|
safe_limit = config.pg_max_connections - config.pg_reserved_connections
|
|
|
|
|
|
logger.info(
|
2025-12-18 19:07:20 +08:00
|
|
|
|
"数据库连接池配置: pool_size={}, max_overflow={}, workers={}, total_estimated={}, safe_limit={}",
|
2025-12-18 00:35:46 +08:00
|
|
|
|
config.db_pool_size,
|
|
|
|
|
|
config.db_max_overflow,
|
|
|
|
|
|
workers,
|
|
|
|
|
|
total_estimated,
|
|
|
|
|
|
safe_limit,
|
|
|
|
|
|
)
|
|
|
|
|
|
if total_estimated > safe_limit:
|
|
|
|
|
|
logger.warning(
|
2025-12-18 19:07:20 +08:00
|
|
|
|
"数据库连接池总需求可能超过 PostgreSQL 限制: {} > {} (pg_max_connections - reserved),"
|
2025-12-18 00:35:46 +08:00
|
|
|
|
"建议调整 DB_POOL_SIZE/DB_MAX_OVERFLOW 或减少 worker 数",
|
|
|
|
|
|
total_estimated,
|
|
|
|
|
|
safe_limit,
|
|
|
|
|
|
)
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-01-30 03:10:21 +08:00
|
|
|
|
def get_db(request: Request = None) -> Generator[Session]: # type: ignore[assignment]
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""获取数据库会话
|
|
|
|
|
|
|
2025-12-18 01:59:40 +08:00
|
|
|
|
事务策略说明
|
|
|
|
|
|
============
|
|
|
|
|
|
本项目采用**混合事务管理**策略:
|
|
|
|
|
|
|
|
|
|
|
|
1. **LLM 请求路径**:
|
|
|
|
|
|
- 由 PluginMiddleware 统一管理事务
|
|
|
|
|
|
- Service 层使用 db.flush() 使更改可见,但不提交
|
|
|
|
|
|
- 请求结束时由中间件统一 commit 或 rollback
|
|
|
|
|
|
- 例外:UsageService.record_usage() 会显式 commit,因为使用记录需要立即持久化
|
|
|
|
|
|
|
|
|
|
|
|
2. **管理后台 API**:
|
|
|
|
|
|
- 路由层显式调用 db.commit()
|
2025-12-18 19:07:20 +08:00
|
|
|
|
- 提交后设置 request.state.tx_committed_by_route = True
|
|
|
|
|
|
- 中间件看到此标志后跳过 commit,只负责 close
|
2025-12-18 01:59:40 +08:00
|
|
|
|
|
|
|
|
|
|
3. **后台任务/调度器**:
|
|
|
|
|
|
- 使用独立 Session(通过 create_session() 或 next(get_db()))
|
|
|
|
|
|
- 自行管理事务生命周期
|
|
|
|
|
|
|
|
|
|
|
|
使用方式
|
|
|
|
|
|
========
|
|
|
|
|
|
- FastAPI 请求:通过 Depends(get_db) 注入,支持中间件管理的 session 复用
|
|
|
|
|
|
- 非请求上下文:直接调用 get_db(),退化为独立 session 模式
|
|
|
|
|
|
|
2025-12-18 19:07:20 +08:00
|
|
|
|
路由层提交事务示例
|
|
|
|
|
|
==================
|
|
|
|
|
|
```python
|
|
|
|
|
|
@router.post("/example")
|
|
|
|
|
|
async def example(request: Request, db: Session = Depends(get_db)):
|
|
|
|
|
|
# ... 业务逻辑 ...
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
request.state.tx_committed_by_route = True # 告知中间件已提交
|
|
|
|
|
|
return {"message": "success"}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2025-12-18 01:59:40 +08:00
|
|
|
|
注意事项
|
|
|
|
|
|
========
|
|
|
|
|
|
- 本函数不自动提交事务
|
|
|
|
|
|
- 异常时会自动回滚
|
|
|
|
|
|
- 中间件管理模式下,session 关闭由中间件负责
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""
|
2025-12-18 00:35:46 +08:00
|
|
|
|
# FastAPI 请求上下文:优先复用中间件绑定的 request.state.db
|
|
|
|
|
|
if request is not None:
|
|
|
|
|
|
existing_db = getattr(getattr(request, "state", None), "db", None)
|
|
|
|
|
|
if isinstance(existing_db, Session):
|
|
|
|
|
|
yield existing_db
|
|
|
|
|
|
return
|
|
|
|
|
|
|
2025-12-10 20:52:44 +08:00
|
|
|
|
# 确保引擎已初始化
|
|
|
|
|
|
_ensure_engine()
|
2026-01-15 17:03:19 +08:00
|
|
|
|
assert _SessionLocal is not None
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
|
|
db = _SessionLocal()
|
2025-12-18 00:35:46 +08:00
|
|
|
|
|
|
|
|
|
|
# 如果中间件声明会统一管理会话生命周期,则把 session 绑定到 request.state,
|
|
|
|
|
|
# 并由中间件负责 commit/rollback/close(这里不关闭,避免流式响应提前释放会话)。
|
|
|
|
|
|
managed_by_middleware = bool(
|
|
|
|
|
|
request is not None
|
|
|
|
|
|
and hasattr(request, "state")
|
|
|
|
|
|
and getattr(request.state, "db_managed_by_middleware", False)
|
|
|
|
|
|
)
|
|
|
|
|
|
if managed_by_middleware:
|
|
|
|
|
|
request.state.db = db
|
|
|
|
|
|
db.info["managed_by_middleware"] = True
|
|
|
|
|
|
|
2025-12-10 20:52:44 +08:00
|
|
|
|
try:
|
|
|
|
|
|
yield db
|
|
|
|
|
|
# 不再自动 commit,由业务代码显式管理事务
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
try:
|
|
|
|
|
|
db.rollback() # 失败时回滚未提交的事务
|
|
|
|
|
|
except Exception as rollback_error:
|
|
|
|
|
|
# 记录回滚错误(可能是 commit 正在进行中)
|
|
|
|
|
|
logger.debug(f"回滚事务时出错(可忽略): {rollback_error}")
|
|
|
|
|
|
raise
|
|
|
|
|
|
finally:
|
2025-12-18 00:35:46 +08:00
|
|
|
|
if not managed_by_middleware:
|
|
|
|
|
|
try:
|
|
|
|
|
|
db.close() # 确保连接返回池
|
|
|
|
|
|
except Exception as close_error:
|
|
|
|
|
|
# 记录关闭错误(如 IllegalStateChangeError)
|
|
|
|
|
|
# 连接池会处理连接的回收
|
|
|
|
|
|
logger.debug(f"关闭数据库连接时出错(可忽略): {close_error}")
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_session() -> Session:
|
|
|
|
|
|
"""
|
|
|
|
|
|
创建一个新的数据库会话
|
|
|
|
|
|
|
|
|
|
|
|
注意:调用者必须负责关闭会话
|
|
|
|
|
|
推荐在 with 语句中使用或手动调用 session.close()
|
|
|
|
|
|
|
|
|
|
|
|
示例:
|
|
|
|
|
|
db = create_session()
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 使用 db
|
|
|
|
|
|
finally:
|
|
|
|
|
|
db.close()
|
|
|
|
|
|
"""
|
|
|
|
|
|
_ensure_engine()
|
2026-01-15 17:03:19 +08:00
|
|
|
|
assert _SessionLocal is not None
|
2025-12-10 20:52:44 +08:00
|
|
|
|
return _SessionLocal()
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-02-02 03:16:52 +08:00
|
|
|
|
@contextmanager
|
|
|
|
|
|
def get_db_context() -> Generator[Session, None, None]:
|
|
|
|
|
|
"""
|
|
|
|
|
|
获取数据库会话的上下文管理器
|
|
|
|
|
|
|
|
|
|
|
|
自动管理会话生命周期:创建、提交/回滚、关闭
|
|
|
|
|
|
|
|
|
|
|
|
示例:
|
|
|
|
|
|
with get_db_context() as db:
|
|
|
|
|
|
user = db.query(User).first()
|
|
|
|
|
|
# 事务在 with 块结束时自动提交或回滚
|
|
|
|
|
|
"""
|
|
|
|
|
|
_ensure_engine()
|
|
|
|
|
|
assert _SessionLocal is not None
|
|
|
|
|
|
db = _SessionLocal()
|
|
|
|
|
|
try:
|
|
|
|
|
|
yield db
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
db.rollback()
|
|
|
|
|
|
raise
|
|
|
|
|
|
finally:
|
|
|
|
|
|
db.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-12-10 20:52:44 +08:00
|
|
|
|
def get_db_url() -> str:
|
|
|
|
|
|
"""返回当前配置的数据库连接字符串(供脚本/测试使用)。"""
|
|
|
|
|
|
return config.database_url
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-01-15 17:03:19 +08:00
|
|
|
|
def init_db() -> None:
|
2025-12-16 17:28:40 +08:00
|
|
|
|
"""初始化数据库
|
|
|
|
|
|
|
|
|
|
|
|
注意:数据库表结构由 Alembic 管理,部署时请运行 ./migrate.sh
|
|
|
|
|
|
"""
|
2025-12-30 14:47:35 +08:00
|
|
|
|
import sys
|
2026-02-01 17:28:00 +08:00
|
|
|
|
|
2025-12-30 14:47:35 +08:00
|
|
|
|
from sqlalchemy.exc import OperationalError
|
|
|
|
|
|
|
2025-12-10 20:52:44 +08:00
|
|
|
|
logger.info("初始化数据库...")
|
|
|
|
|
|
|
|
|
|
|
|
# 确保引擎已创建
|
2025-12-16 17:28:40 +08:00
|
|
|
|
_ensure_engine()
|
2026-01-15 17:03:19 +08:00
|
|
|
|
assert _SessionLocal is not None
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
2025-12-16 17:28:40 +08:00
|
|
|
|
# 数据库表结构由 Alembic 迁移管理
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
|
|
db = _SessionLocal()
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 创建管理员账户(如果环境变量中配置了)
|
|
|
|
|
|
init_admin_user(db)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加默认模型配置
|
|
|
|
|
|
init_default_models(db)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加系统配置
|
|
|
|
|
|
init_system_configs(db)
|
|
|
|
|
|
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
logger.info("数据库初始化完成")
|
|
|
|
|
|
|
2025-12-30 14:47:35 +08:00
|
|
|
|
except OperationalError as e:
|
|
|
|
|
|
db.rollback()
|
|
|
|
|
|
# 提取数据库连接信息用于提示
|
|
|
|
|
|
db_url = config.database_url
|
|
|
|
|
|
# 隐藏密码,只显示 host:port/database
|
|
|
|
|
|
if "@" in db_url:
|
|
|
|
|
|
db_info = db_url.split("@")[-1]
|
|
|
|
|
|
else:
|
|
|
|
|
|
db_info = db_url
|
|
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
|
|
|
|
|
|
|
# 直接打印到 stderr,确保消息显示
|
|
|
|
|
|
print("", file=sys.stderr)
|
|
|
|
|
|
print("=" * 60, file=sys.stderr)
|
|
|
|
|
|
print("数据库连接失败", file=sys.stderr)
|
|
|
|
|
|
print("=" * 60, file=sys.stderr)
|
|
|
|
|
|
print("", file=sys.stderr)
|
|
|
|
|
|
print(f"无法连接到数据库: {db_info}", file=sys.stderr)
|
|
|
|
|
|
print("", file=sys.stderr)
|
|
|
|
|
|
print("请检查以下事项:", file=sys.stderr)
|
|
|
|
|
|
print(" 1. PostgreSQL 服务是否正在运行", file=sys.stderr)
|
|
|
|
|
|
print(" 2. 数据库连接配置是否正确 (DATABASE_URL)", file=sys.stderr)
|
|
|
|
|
|
print(" 3. 数据库用户名和密码是否正确", file=sys.stderr)
|
|
|
|
|
|
print("", file=sys.stderr)
|
|
|
|
|
|
print("如果使用 Docker,请先运行:", file=sys.stderr)
|
2026-01-04 22:42:58 +08:00
|
|
|
|
print(" docker compose -f docker-compose.build.yml up -d postgres redis", file=sys.stderr)
|
2025-12-30 14:47:35 +08:00
|
|
|
|
print("", file=sys.stderr)
|
|
|
|
|
|
print("=" * 60, file=sys.stderr)
|
|
|
|
|
|
# 使用 os._exit 直接退出,避免 uvicorn 捕获并打印堆栈
|
|
|
|
|
|
os._exit(1)
|
|
|
|
|
|
|
2025-12-10 20:52:44 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"数据库初始化失败: {e}")
|
|
|
|
|
|
db.rollback()
|
|
|
|
|
|
raise
|
|
|
|
|
|
finally:
|
|
|
|
|
|
db.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-01-15 17:03:19 +08:00
|
|
|
|
def init_admin_user(db: Session) -> None:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""从环境变量创建管理员账户"""
|
|
|
|
|
|
# 检查是否使用默认凭据
|
|
|
|
|
|
if config.admin_email == "admin@localhost" and config.admin_password == "admin123":
|
|
|
|
|
|
logger.warning("使用默认管理员账户配置,建议修改为安全的凭据")
|
|
|
|
|
|
|
2026-02-06 16:37:06 +08:00
|
|
|
|
# 检查是否已存在管理员(优先按角色判断,避免修改用户名/邮箱后重复创建)
|
|
|
|
|
|
existing_admin = db.query(User).filter(User.role == UserRole.ADMIN).first()
|
|
|
|
|
|
if existing_admin:
|
|
|
|
|
|
logger.info(f"管理员账户已存在: {existing_admin.email}")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 再检查配置的邮箱或用户名是否已被普通用户占用
|
|
|
|
|
|
existing_user = (
|
2025-12-10 20:52:44 +08:00
|
|
|
|
db.query(User)
|
|
|
|
|
|
.filter((User.email == config.admin_email) | (User.username == config.admin_username))
|
|
|
|
|
|
.first()
|
|
|
|
|
|
)
|
2026-02-06 16:37:06 +08:00
|
|
|
|
if existing_user:
|
|
|
|
|
|
logger.warning(
|
|
|
|
|
|
f"配置的管理员邮箱/用户名已被占用: {existing_user.email} ({existing_user.username})"
|
|
|
|
|
|
)
|
2025-12-10 20:52:44 +08:00
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 创建管理员账户
|
|
|
|
|
|
admin = User(
|
|
|
|
|
|
email=config.admin_email,
|
2026-01-19 03:19:17 +08:00
|
|
|
|
email_verified=True,
|
2025-12-10 20:52:44 +08:00
|
|
|
|
username=config.admin_username,
|
|
|
|
|
|
role=UserRole.ADMIN,
|
|
|
|
|
|
is_active=True,
|
|
|
|
|
|
)
|
|
|
|
|
|
admin.set_password(config.admin_password)
|
|
|
|
|
|
|
|
|
|
|
|
db.add(admin)
|
2025-12-18 00:35:46 +08:00
|
|
|
|
db.flush() # 分配ID,但不提交事务(由外层 init_db 统一 commit)
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
2026-03-08 00:05:48 +08:00
|
|
|
|
from src.services.wallet import WalletService
|
|
|
|
|
|
|
|
|
|
|
|
WalletService.initialize_user_wallet(
|
|
|
|
|
|
db,
|
|
|
|
|
|
user=admin,
|
|
|
|
|
|
initial_gift_usd=0,
|
|
|
|
|
|
unlimited=True,
|
|
|
|
|
|
description="系统管理员初始化钱包",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-12-10 20:52:44 +08:00
|
|
|
|
logger.info(f"创建管理员账户成功: {admin.email} ({admin.username})")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"创建管理员账户失败: {e}")
|
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-01-15 17:03:19 +08:00
|
|
|
|
def init_default_models(db: Session) -> None:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""初始化默认模型配置"""
|
|
|
|
|
|
|
|
|
|
|
|
# 注意:作为中转代理服务,不再预设模型配置
|
2025-12-15 14:30:53 +08:00
|
|
|
|
# 模型配置应该通过 GlobalModel 和 Model 表动态管理
|
2025-12-10 20:52:44 +08:00
|
|
|
|
# 这个函数保留用于未来可能的默认模型初始化
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-01-15 17:03:19 +08:00
|
|
|
|
def init_system_configs(db: Session) -> None:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""初始化系统配置"""
|
2026-01-15 17:03:19 +08:00
|
|
|
|
configs: list[dict[str, Any]] = [
|
2026-03-08 00:05:48 +08:00
|
|
|
|
{
|
|
|
|
|
|
"key": "default_user_initial_gift_usd",
|
|
|
|
|
|
"value": 10.0,
|
|
|
|
|
|
"description": "新用户默认初始赠款(美元)",
|
|
|
|
|
|
},
|
2025-12-10 20:52:44 +08:00
|
|
|
|
{"key": "rate_limit_per_minute", "value": 60, "description": "每分钟请求限制"},
|
|
|
|
|
|
{"key": "enable_registration", "value": False, "description": "是否开放用户注册"},
|
|
|
|
|
|
{"key": "require_email_verification", "value": False, "description": "是否需要邮箱验证"},
|
|
|
|
|
|
{"key": "api_key_expire_days", "value": 365, "description": "API密钥过期天数"},
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
for config_data in configs:
|
|
|
|
|
|
existing = db.query(SystemConfig).filter_by(key=config_data["key"]).first()
|
|
|
|
|
|
if not existing:
|
2026-01-15 17:03:19 +08:00
|
|
|
|
row = SystemConfig()
|
|
|
|
|
|
row.key = config_data["key"]
|
|
|
|
|
|
row.value = config_data["value"]
|
|
|
|
|
|
row.description = config_data["description"]
|
|
|
|
|
|
db.add(row)
|
2025-12-10 20:52:44 +08:00
|
|
|
|
logger.info(f"添加系统配置: {config_data['key']}")
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-01-15 17:03:19 +08:00
|
|
|
|
def reset_db() -> None:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""重置数据库(仅用于开发)"""
|
|
|
|
|
|
logger.warning("重置数据库...")
|
|
|
|
|
|
|
|
|
|
|
|
# 确保引擎已创建
|
|
|
|
|
|
engine = _ensure_engine()
|
|
|
|
|
|
|
|
|
|
|
|
Base.metadata.drop_all(bind=engine)
|
|
|
|
|
|
init_db()
|