infomankit 0.3.23__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. infoman/__init__.py +1 -0
  2. infoman/cli/README.md +378 -0
  3. infoman/cli/__init__.py +7 -0
  4. infoman/cli/commands/__init__.py +3 -0
  5. infoman/cli/commands/init.py +312 -0
  6. infoman/cli/scaffold.py +634 -0
  7. infoman/cli/templates/Makefile.template +132 -0
  8. infoman/cli/templates/app/__init__.py.template +3 -0
  9. infoman/cli/templates/app/app.py.template +4 -0
  10. infoman/cli/templates/app/models_base.py.template +18 -0
  11. infoman/cli/templates/app/models_entity_init.py.template +11 -0
  12. infoman/cli/templates/app/models_schemas_init.py.template +11 -0
  13. infoman/cli/templates/app/repository_init.py.template +11 -0
  14. infoman/cli/templates/app/routers_init.py.template +15 -0
  15. infoman/cli/templates/app/services_init.py.template +11 -0
  16. infoman/cli/templates/app/static_index.html.template +39 -0
  17. infoman/cli/templates/app/static_main.js.template +31 -0
  18. infoman/cli/templates/app/static_style.css.template +111 -0
  19. infoman/cli/templates/app/utils_init.py.template +11 -0
  20. infoman/cli/templates/config/.env.dev.template +43 -0
  21. infoman/cli/templates/config/.env.prod.template +43 -0
  22. infoman/cli/templates/config/README.md.template +28 -0
  23. infoman/cli/templates/docker/.dockerignore.template +60 -0
  24. infoman/cli/templates/docker/Dockerfile.template +47 -0
  25. infoman/cli/templates/docker/README.md.template +240 -0
  26. infoman/cli/templates/docker/docker-compose.yml.template +81 -0
  27. infoman/cli/templates/docker/mysql_custom.cnf.template +42 -0
  28. infoman/cli/templates/docker/mysql_init.sql.template +15 -0
  29. infoman/cli/templates/project/.env.example.template +1 -0
  30. infoman/cli/templates/project/.gitignore.template +60 -0
  31. infoman/cli/templates/project/Makefile.template +38 -0
  32. infoman/cli/templates/project/README.md.template +137 -0
  33. infoman/cli/templates/project/deploy.sh.template +97 -0
  34. infoman/cli/templates/project/main.py.template +10 -0
  35. infoman/cli/templates/project/manage.sh.template +97 -0
  36. infoman/cli/templates/project/pyproject.toml.template +47 -0
  37. infoman/cli/templates/project/service.sh.template +203 -0
  38. infoman/config/__init__.py +25 -0
  39. infoman/config/base.py +67 -0
  40. infoman/config/db_cache.py +237 -0
  41. infoman/config/db_relation.py +181 -0
  42. infoman/config/db_vector.py +39 -0
  43. infoman/config/jwt.py +16 -0
  44. infoman/config/llm.py +16 -0
  45. infoman/config/log.py +627 -0
  46. infoman/config/mq.py +26 -0
  47. infoman/config/settings.py +65 -0
  48. infoman/llm/__init__.py +0 -0
  49. infoman/llm/llm.py +297 -0
  50. infoman/logger/__init__.py +57 -0
  51. infoman/logger/context.py +191 -0
  52. infoman/logger/core.py +358 -0
  53. infoman/logger/filters.py +157 -0
  54. infoman/logger/formatters.py +138 -0
  55. infoman/logger/handlers.py +276 -0
  56. infoman/logger/metrics.py +160 -0
  57. infoman/performance/README.md +583 -0
  58. infoman/performance/__init__.py +19 -0
  59. infoman/performance/cli.py +215 -0
  60. infoman/performance/config.py +166 -0
  61. infoman/performance/reporter.py +519 -0
  62. infoman/performance/runner.py +303 -0
  63. infoman/performance/standards.py +222 -0
  64. infoman/service/__init__.py +8 -0
  65. infoman/service/app.py +67 -0
  66. infoman/service/core/__init__.py +0 -0
  67. infoman/service/core/auth.py +105 -0
  68. infoman/service/core/lifespan.py +132 -0
  69. infoman/service/core/monitor.py +57 -0
  70. infoman/service/core/response.py +37 -0
  71. infoman/service/exception/__init__.py +7 -0
  72. infoman/service/exception/error.py +274 -0
  73. infoman/service/exception/exception.py +25 -0
  74. infoman/service/exception/handler.py +238 -0
  75. infoman/service/infrastructure/__init__.py +8 -0
  76. infoman/service/infrastructure/base.py +212 -0
  77. infoman/service/infrastructure/db_cache/__init__.py +8 -0
  78. infoman/service/infrastructure/db_cache/manager.py +194 -0
  79. infoman/service/infrastructure/db_relation/__init__.py +41 -0
  80. infoman/service/infrastructure/db_relation/manager.py +300 -0
  81. infoman/service/infrastructure/db_relation/manager_pro.py +408 -0
  82. infoman/service/infrastructure/db_relation/mysql.py +52 -0
  83. infoman/service/infrastructure/db_relation/pgsql.py +54 -0
  84. infoman/service/infrastructure/db_relation/sqllite.py +25 -0
  85. infoman/service/infrastructure/db_vector/__init__.py +40 -0
  86. infoman/service/infrastructure/db_vector/manager.py +201 -0
  87. infoman/service/infrastructure/db_vector/qdrant.py +322 -0
  88. infoman/service/infrastructure/mq/__init__.py +15 -0
  89. infoman/service/infrastructure/mq/manager.py +178 -0
  90. infoman/service/infrastructure/mq/nats/__init__.py +0 -0
  91. infoman/service/infrastructure/mq/nats/nats_client.py +57 -0
  92. infoman/service/infrastructure/mq/nats/nats_event_router.py +25 -0
  93. infoman/service/launch.py +284 -0
  94. infoman/service/middleware/__init__.py +7 -0
  95. infoman/service/middleware/base.py +41 -0
  96. infoman/service/middleware/logging.py +51 -0
  97. infoman/service/middleware/rate_limit.py +301 -0
  98. infoman/service/middleware/request_id.py +21 -0
  99. infoman/service/middleware/white_list.py +24 -0
  100. infoman/service/models/__init__.py +8 -0
  101. infoman/service/models/base.py +441 -0
  102. infoman/service/models/type/embed.py +70 -0
  103. infoman/service/routers/__init__.py +18 -0
  104. infoman/service/routers/health_router.py +311 -0
  105. infoman/service/routers/monitor_router.py +44 -0
  106. infoman/service/utils/__init__.py +8 -0
  107. infoman/service/utils/cache/__init__.py +0 -0
  108. infoman/service/utils/cache/cache.py +192 -0
  109. infoman/service/utils/module_loader.py +10 -0
  110. infoman/service/utils/parse.py +10 -0
  111. infoman/service/utils/resolver/__init__.py +8 -0
  112. infoman/service/utils/resolver/base.py +47 -0
  113. infoman/service/utils/resolver/resp.py +102 -0
  114. infoman/service/vector/__init__.py +20 -0
  115. infoman/service/vector/base.py +56 -0
  116. infoman/service/vector/qdrant.py +125 -0
  117. infoman/service/vector/service.py +67 -0
  118. infoman/utils/__init__.py +2 -0
  119. infoman/utils/decorators/__init__.py +8 -0
  120. infoman/utils/decorators/cache.py +137 -0
  121. infoman/utils/decorators/retry.py +99 -0
  122. infoman/utils/decorators/safe_execute.py +99 -0
  123. infoman/utils/decorators/timing.py +99 -0
  124. infoman/utils/encryption/__init__.py +8 -0
  125. infoman/utils/encryption/aes.py +66 -0
  126. infoman/utils/encryption/ecc.py +108 -0
  127. infoman/utils/encryption/rsa.py +112 -0
  128. infoman/utils/file/__init__.py +0 -0
  129. infoman/utils/file/handler.py +22 -0
  130. infoman/utils/hash/__init__.py +0 -0
  131. infoman/utils/hash/hash.py +61 -0
  132. infoman/utils/http/__init__.py +8 -0
  133. infoman/utils/http/client.py +62 -0
  134. infoman/utils/http/info.py +94 -0
  135. infoman/utils/http/result.py +19 -0
  136. infoman/utils/notification/__init__.py +8 -0
  137. infoman/utils/notification/feishu.py +35 -0
  138. infoman/utils/text/__init__.py +8 -0
  139. infoman/utils/text/extractor.py +111 -0
  140. infomankit-0.3.23.dist-info/METADATA +632 -0
  141. infomankit-0.3.23.dist-info/RECORD +143 -0
  142. infomankit-0.3.23.dist-info/WHEEL +4 -0
  143. infomankit-0.3.23.dist-info/entry_points.txt +5 -0
@@ -0,0 +1,300 @@
1
+ # database/manager.py
2
+ """
3
+ 多数据库管理器
4
+
5
+ 功能:
6
+ - 支持多个数据库同时连接
7
+ - 每个数据库独立配置
8
+ - 统一的健康检查
9
+ - 优雅关闭
10
+ """
11
+
12
+ from typing import Dict, Optional, List, Any
13
+ from fastapi import FastAPI
14
+ from tortoise import Tortoise
15
+ from loguru import logger
16
+
17
+ from infoman.config import settings, DatabaseConfig
18
+ from infoman.service.infrastructure.db_relation.mysql import MySQLBackend
19
+ from infoman.service.infrastructure.db_relation.sqllite import SQLiteBackend
20
+ from infoman.service.infrastructure.db_relation.pgsql import PostgreSQLBackend
21
+
22
+
23
+ BACKENDS = {
24
+ "mysql": MySQLBackend,
25
+ "postgresql": PostgreSQLBackend,
26
+ "sqlite": SQLiteBackend,
27
+ }
28
+
29
+
30
+ class DatabaseManager:
31
+ """数据库管理器"""
32
+
33
+ def __init__(self):
34
+ self.connections: Dict[str, DatabaseConfig] = {}
35
+ self.initialized = False
36
+
37
+ @property
38
+ def is_available(self) -> bool:
39
+ """是否有可用的数据库连接"""
40
+ return self.initialized and len(self.connections) > 0
41
+
42
+ @property
43
+ def client(self):
44
+ """
45
+ 获取默认客户端(Tortoise connections)
46
+
47
+ Returns:
48
+ Tortoise connections 对象
49
+ """
50
+ if not self.initialized:
51
+ return None
52
+
53
+ from tortoise import connections
54
+ return connections
55
+
56
+ def gen_tortoise_config(self) -> Optional[dict]:
57
+ enabled_dbs = settings.enabled_databases
58
+
59
+ if not enabled_dbs:
60
+ logger.warning("⚠️ 没有启用任何数据库")
61
+ return None
62
+
63
+ # ========== 连接配置 ==========
64
+ connections = {}
65
+
66
+ for conn_name, db_config in enabled_dbs.items():
67
+ backend_class = BACKENDS.get(db_config.type)
68
+
69
+ if not backend_class:
70
+ logger.error(f"❌ 不支持的数据库类型: {db_config.type}")
71
+ continue
72
+
73
+ connections[conn_name] = {
74
+ "engine": backend_class.get_engine(),
75
+ "credentials": backend_class.get_credentials(db_config),
76
+ }
77
+
78
+ self.connections[conn_name] = db_config
79
+
80
+ # ========== 应用配置(模型分组)==========
81
+ apps = {}
82
+
83
+ for conn_name, db_config in enabled_dbs.items():
84
+ if db_config.models:
85
+ app_name = f"{conn_name}_models"
86
+ apps[app_name] = {
87
+ "models": db_config.models,
88
+ "default_connection": conn_name,
89
+ }
90
+
91
+ if not apps and connections:
92
+ return None
93
+
94
+ config = {
95
+ "connections": connections,
96
+ "apps": apps,
97
+ "use_tz": settings.DB_USE_TZ,
98
+ "timezone": settings.DB_TIMEZONE,
99
+ }
100
+ return config
101
+
102
+ async def startup(self, app: Optional[FastAPI] = None) -> bool:
103
+ if self.initialized:
104
+ logger.warning("⚠️ DatabaseManager 已初始化,跳过重复初始化")
105
+ return True
106
+
107
+ config = self.gen_tortoise_config()
108
+
109
+ if not config:
110
+ logger.info("⏭️ 数据库未配置,跳过初始化")
111
+ return False
112
+
113
+ try:
114
+ logger.info("🚀 初始化数据库...")
115
+ for conn_name, db_config in self.connections.items():
116
+ logger.info(
117
+ f" - [{conn_name}] {db_config.type.upper()}: "
118
+ f"{db_config.user}@{db_config.host}:{db_config.port}/{db_config.database}"
119
+ )
120
+
121
+ await Tortoise.init(config=config)
122
+
123
+ if app:
124
+ app.state.db_client = self.client
125
+ logger.debug("✅ 数据库客户端已挂载到 app.state")
126
+ self.initialized = True
127
+
128
+ logger.success(
129
+ f"✅ 数据库连接成功({len(self.connections)} 个)\n"
130
+ f" 连接名: {list(self.connections.keys())}"
131
+ )
132
+ else:
133
+ logger.info(f"无app实例,数据库跳过初始化")
134
+
135
+ return True
136
+ except Exception as e:
137
+ logger.error(f"❌ 数据库连接失败: {e}")
138
+ return False
139
+
140
+ async def register(self, app: FastAPI) -> bool:
141
+ return await self.startup(app)
142
+
143
+ async def health_check(self, conn_name: Optional[str] = None) -> Dict:
144
+ if not settings.enabled_databases:
145
+ return {
146
+ "status": "not_configured",
147
+ "name": "database",
148
+ "details": {"enabled": False}
149
+ }
150
+
151
+ if not self.initialized:
152
+ return {
153
+ "status": "unhealthy",
154
+ "name": "database",
155
+ "details": {"error": "未初始化"}
156
+ }
157
+
158
+ # 检查单个连接
159
+ if conn_name:
160
+ result = await self._check_single_connection(conn_name)
161
+ return {
162
+ "status": result.get("status", "unhealthy"),
163
+ "name": f"database_{conn_name}",
164
+ "details": result
165
+ }
166
+
167
+ # 检查所有连接
168
+ results = {}
169
+ for name in self.connections.keys():
170
+ results[name] = await self._check_single_connection(name)
171
+
172
+ # 汇总状态
173
+ all_healthy = all(r.get("status") == "healthy" for r in results.values())
174
+
175
+ return {
176
+ "status": "healthy" if all_healthy else "unhealthy",
177
+ "name": "database",
178
+ "details": {
179
+ "connections": results,
180
+ "count": len(results)
181
+ }
182
+ }
183
+
184
+ async def check_health(self, conn_name: Optional[str] = None) -> Dict:
185
+ return await self.health_check(conn_name)
186
+
187
+ async def _check_single_connection(self, conn_name: str) -> Dict:
188
+ try:
189
+ from tortoise import connections
190
+
191
+ conn = connections.get(conn_name)
192
+ db_config = self.connections.get(conn_name)
193
+
194
+ if not conn or not db_config:
195
+ return {
196
+ "status": "not_found",
197
+ "error": f"连接 '{conn_name}' 不存在",
198
+ }
199
+
200
+ # 执行健康检查查询
201
+ await conn.execute_query("SELECT 1")
202
+
203
+ # 获取连接池状态
204
+ pool_status = {
205
+ "size": conn._pool.size() if hasattr(conn, "_pool") else "N/A",
206
+ "free": conn._pool.freesize() if hasattr(conn, "_pool") else "N/A",
207
+ }
208
+
209
+ return {
210
+ "status": "healthy",
211
+ "type": db_config.type,
212
+ "database": db_config.database,
213
+ "pool": pool_status,
214
+ }
215
+
216
+ except Exception as e:
217
+ logger.error(f"❌ 连接 '{conn_name}' 健康检查失败: {e}")
218
+ return {
219
+ "status": "unhealthy",
220
+ "error": str(e),
221
+ }
222
+
223
+ async def shutdown(self):
224
+ if not self.initialized:
225
+ return
226
+
227
+ try:
228
+ logger.info("⏹️ 关闭数据库连接...")
229
+
230
+ for conn_name in self.connections.keys():
231
+ logger.info(f" - 关闭连接: {conn_name}")
232
+
233
+ await Tortoise.close_connections()
234
+
235
+ self.initialized = False
236
+
237
+ logger.success("✅ 所有数据库连接已关闭")
238
+
239
+ except Exception as e:
240
+ logger.error(f"❌ 关闭数据库连接失败: {e}")
241
+
242
+ async def close(self):
243
+ """关闭所有数据库连接(兼容旧接口)"""
244
+ await self.shutdown()
245
+
246
+ async def get_stats(self) -> Dict[str, Any]:
247
+ if not self.is_available:
248
+ return {}
249
+
250
+ stats = {
251
+ "connections_count": len(self.connections),
252
+ "connections": {}
253
+ }
254
+
255
+ # 获取每个连接的详细信息
256
+ for conn_name, db_config in self.connections.items():
257
+ conn_stat = await self._check_single_connection(conn_name)
258
+ stats["connections"][conn_name] = {
259
+ "type": db_config.type,
260
+ "database": db_config.database,
261
+ "host": db_config.host,
262
+ "port": db_config.port,
263
+ "status": conn_stat.get("status"),
264
+ "pool": conn_stat.get("pool", {})
265
+ }
266
+
267
+ return stats
268
+
269
+ def get_connection_names(self) -> List[str]:
270
+ """获取所有连接名称"""
271
+ return list(self.connections.keys())
272
+
273
+ def has_connection(self, conn_name: str) -> bool:
274
+ """检查连接是否存在"""
275
+ return conn_name in self.connections
276
+
277
+
278
+ # 全局单例
279
+ db_manager = DatabaseManager()
280
+
281
+
282
+ # =================================================================
283
+ # 便捷函数
284
+ # =================================================================
285
+
286
+
287
+ async def register_databases(app: FastAPI) -> bool:
288
+ return await db_manager.register(app)
289
+
290
+
291
+ async def check_databases_health(conn_name: Optional[str] = None) -> Dict:
292
+ return await db_manager.check_health(conn_name)
293
+
294
+
295
+ async def close_databases():
296
+ await db_manager.close()
297
+
298
+
299
+ def get_connection_names() -> List[str]:
300
+ return db_manager.get_connection_names()
@@ -0,0 +1,408 @@
1
+ """
2
+ SQLAlchemy 专业版数据库管理器
3
+
4
+ Version: 1.0.0
5
+ Author: Maxwell
6
+ """
7
+
8
+ from typing import Dict, Optional, Any
9
+ from fastapi import FastAPI
10
+ from loguru import logger
11
+
12
+ from infoman.config import settings, DatabaseConfig
13
+
14
+ # ==================== SQLAlchemy 导入 ====================
15
+
16
+ try:
17
+ from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
18
+ from sqlalchemy import text
19
+ from sqlalchemy.pool import NullPool, AsyncAdaptedQueuePool
20
+
21
+ _SQLALCHEMY_AVAILABLE = True
22
+ except ImportError:
23
+ _SQLALCHEMY_AVAILABLE = False
24
+ raise ImportError(
25
+ "SQLAlchemy 未安装,请运行: pip install sqlalchemy[asyncio] asyncmy asyncpg aiosqlite"
26
+ )
27
+
28
+
29
+ # ==================== SQLAlchemy 后端实现 ====================
30
+
31
+ class SQLAlchemyMySQLBackend:
32
+ """SQLAlchemy MySQL 后端"""
33
+
34
+ @staticmethod
35
+ def get_url(config: DatabaseConfig) -> str:
36
+ """生成 MySQL 连接 URL"""
37
+ return (
38
+ f"mysql+asyncmy://{config.user}:{config.password}@"
39
+ f"{config.host}:{config.port}/{config.database}"
40
+ f"?charset={config.charset}"
41
+ )
42
+
43
+
44
+ class SQLAlchemyPostgreSQLBackend:
45
+ """SQLAlchemy PostgreSQL 后端"""
46
+
47
+ @staticmethod
48
+ def get_url(config: DatabaseConfig) -> str:
49
+ """生成 PostgreSQL 连接 URL"""
50
+ return (
51
+ f"postgresql+asyncpg://{config.user}:{config.password}@"
52
+ f"{config.host}:{config.port}/{config.database}"
53
+ )
54
+
55
+
56
+ class SQLAlchemySQLiteBackend:
57
+ """SQLAlchemy SQLite 后端"""
58
+
59
+ @staticmethod
60
+ def get_url(config: DatabaseConfig) -> str:
61
+ """生成 SQLite 连接 URL"""
62
+ return f"sqlite+aiosqlite:///{config.database}"
63
+
64
+
65
+ SQLALCHEMY_BACKENDS = {
66
+ "mysql": SQLAlchemyMySQLBackend,
67
+ "postgresql": SQLAlchemyPostgreSQLBackend,
68
+ "sqlite": SQLAlchemySQLiteBackend,
69
+ }
70
+
71
+
72
+ # ==================== 专业版数据库管理器 ====================
73
+
74
+ class ProDatabaseManager:
75
+ """
76
+ 专业版数据库管理器 - 仅支持 SQLAlchemy
77
+
78
+ 功能:
79
+ - 支持多个数据库同时连接
80
+ - 支持 MySQL、PostgreSQL、SQLite
81
+ - 统一的健康检查
82
+ - 优雅关闭
83
+ - 连接池管理
84
+
85
+ 使用示例:
86
+ >>> manager = ProDatabaseManager()
87
+ >>> await manager.startup(app)
88
+ >>>
89
+ >>> # 获取 session maker
90
+ >>> session_maker = manager.get_session_maker("default")
91
+ >>> async with session_maker() as session:
92
+ >>> result = await session.execute(text("SELECT 1"))
93
+ >>>
94
+ >>> # 获取 engine
95
+ >>> engine = manager.get_engine("default")
96
+ """
97
+
98
+ def __init__(self):
99
+ # SQLAlchemy 引擎和会话
100
+ self.engines: Dict[str, Any] = {}
101
+ self.session_makers: Dict[str, Any] = {}
102
+ self.configs: Dict[str, DatabaseConfig] = {}
103
+ self.initialized = False
104
+
105
+ @property
106
+ def is_available(self) -> bool:
107
+ """是否有可用的数据库连接"""
108
+ return self.initialized and len(self.engines) > 0
109
+
110
+ def get_session_maker(self, name: str = "default"):
111
+ if not self.initialized:
112
+ raise RuntimeError("DatabaseManager 未初始化")
113
+
114
+ session_maker = self.session_makers.get(name)
115
+ if not session_maker:
116
+ raise RuntimeError(f"连接 '{name}' 不存在")
117
+
118
+ return session_maker
119
+
120
+ def get_engine(self, name: str = "default"):
121
+ """
122
+ 获取 SQLAlchemy Engine
123
+
124
+ Args:
125
+ name: 连接名称
126
+
127
+ Returns:
128
+ AsyncEngine 实例
129
+
130
+ Raises:
131
+ RuntimeError: 未初始化或连接不存在
132
+ """
133
+ if not self.initialized:
134
+ raise RuntimeError("DatabaseManager 未初始化")
135
+
136
+ engine = self.engines.get(name)
137
+ if not engine:
138
+ raise RuntimeError(f"连接 '{name}' 不存在")
139
+
140
+ return engine
141
+
142
+ def _get_pool_class(self, db_config: DatabaseConfig):
143
+ """根据配置获取连接池类"""
144
+ # SQLite 使用 NullPool(单线程)
145
+ if db_config.type == "sqlite":
146
+ return NullPool
147
+ # 其他数据库使用 AsyncAdaptedQueuePool(异步引擎专用)
148
+ return AsyncAdaptedQueuePool
149
+
150
+ async def _init_connection(
151
+ self,
152
+ conn_name: str,
153
+ db_config: DatabaseConfig
154
+ ):
155
+ """初始化单个数据库连接"""
156
+ backend_class = SQLALCHEMY_BACKENDS.get(db_config.type)
157
+
158
+ if not backend_class:
159
+ logger.error(f"❌ 不支持的数据库类型: {db_config.type}")
160
+ return
161
+
162
+ url = backend_class.get_url(db_config)
163
+
164
+ logger.info(
165
+ f" - [{conn_name}] {db_config.type.upper()}: "
166
+ f"{db_config.user}@{db_config.host}:{db_config.port}/{db_config.database}"
167
+ )
168
+
169
+ # 获取连接池类
170
+ pool_class = self._get_pool_class(db_config)
171
+
172
+ # 创建引擎
173
+ engine_kwargs = {
174
+ "url": url,
175
+ "echo": db_config.echo,
176
+ "pool_pre_ping": True, # 健康检查
177
+ "poolclass": pool_class,
178
+ }
179
+
180
+ # 非 NullPool 才设置连接池参数
181
+ if pool_class != NullPool:
182
+ engine_kwargs.update({
183
+ "pool_size": db_config.pool_max_size,
184
+ "max_overflow": db_config.pool_max_size,
185
+ "pool_recycle": db_config.pool_recycle,
186
+ })
187
+
188
+ engine = create_async_engine(**engine_kwargs)
189
+
190
+ # 创建 session maker
191
+ session_maker = async_sessionmaker(
192
+ engine,
193
+ class_=AsyncSession,
194
+ expire_on_commit=False,
195
+ )
196
+
197
+ self.engines[conn_name] = engine
198
+ self.session_makers[conn_name] = session_maker
199
+ self.configs[conn_name] = db_config
200
+
201
+ async def startup(self, app: Optional[FastAPI] = None) -> bool:
202
+ if self.initialized:
203
+ logger.warning("⚠️ DatabaseManager 已初始化,跳过重复初始化")
204
+ return True
205
+
206
+ enabled_dbs = settings.enabled_databases
207
+
208
+ if not enabled_dbs:
209
+ logger.info("⏭️ 数据库未配置,跳过初始化")
210
+ return False
211
+
212
+ try:
213
+ logger.info("🚀 初始化 SQLAlchemy 数据库管理器...")
214
+
215
+ # 初始化所有连接
216
+ for conn_name, db_config in enabled_dbs.items():
217
+ await self._init_connection(conn_name, db_config)
218
+
219
+ if not self.engines:
220
+ logger.warning("⚠️ 没有成功初始化任何数据库连接")
221
+ return False
222
+
223
+ # 挂载到 app.state
224
+ if app:
225
+ app.state.db_engines = self.engines
226
+ app.state.db_sessions = self.session_makers
227
+ logger.debug("✅ 数据库引擎已挂载到 app.state")
228
+
229
+ self.initialized = True
230
+ logger.success(
231
+ f"✅ SQLAlchemy 连接成功({len(self.engines)} 个)\n"
232
+ f" 连接名: {list(self.engines.keys())}"
233
+ )
234
+
235
+ return True
236
+
237
+ except Exception as e:
238
+ logger.error(f"❌ 数据库连接失败: {e}")
239
+ raise
240
+
241
+ async def register(self, app: FastAPI) -> bool:
242
+ """注册到 FastAPI 应用"""
243
+ return await self.startup(app)
244
+
245
+ async def health_check(self, conn_name: Optional[str] = None) -> Dict:
246
+ if not self.is_available:
247
+ return {
248
+ "status": "not_configured",
249
+ "name": "database",
250
+ "details": {"enabled": False}
251
+ }
252
+
253
+ results = {}
254
+
255
+ # 检查所有连接或指定连接
256
+ for name in self.engines.keys():
257
+ if conn_name is None or conn_name == name:
258
+ results[name] = await self._check_connection(name)
259
+
260
+ # 汇总状态
261
+ all_healthy = all(r.get("status") == "healthy" for r in results.values())
262
+
263
+ return {
264
+ "status": "healthy" if all_healthy else "unhealthy",
265
+ "name": "database",
266
+ "details": {
267
+ "connections": results,
268
+ "count": len(results)
269
+ }
270
+ }
271
+
272
+ async def _check_connection(self, conn_name: str) -> Dict:
273
+ """检查单个连接"""
274
+ try:
275
+ engine = self.engines.get(conn_name)
276
+ session_maker = self.session_makers.get(conn_name)
277
+ db_config = self.configs.get(conn_name)
278
+
279
+ if not engine or not session_maker or not db_config:
280
+ return {
281
+ "status": "not_found",
282
+ "error": f"连接 '{conn_name}' 不存在",
283
+ }
284
+
285
+ # 执行健康检查查询
286
+ async with session_maker() as session:
287
+ result = await session.execute(text("SELECT 1"))
288
+ result.scalar()
289
+
290
+ # 获取连接池状态
291
+ pool = engine.pool
292
+ pool_status = {
293
+ "size": pool.size(),
294
+ "checked_in": pool.checkedin(),
295
+ "checked_out": pool.checkedout(),
296
+ "overflow": pool.overflow(),
297
+ } if hasattr(pool, 'size') else {"type": "NullPool"}
298
+
299
+ return {
300
+ "status": "healthy",
301
+ "type": db_config.type,
302
+ "database": db_config.database,
303
+ "pool": pool_status,
304
+ }
305
+
306
+ except Exception as e:
307
+ logger.error(f"❌ 连接 '{conn_name}' 健康检查失败: {e}")
308
+ return {
309
+ "status": "unhealthy",
310
+ "error": str(e),
311
+ }
312
+
313
+ async def shutdown(self):
314
+ """关闭所有数据库连接"""
315
+ if not self.initialized:
316
+ return
317
+
318
+ try:
319
+ logger.info("⏹️ 关闭数据库连接...")
320
+
321
+ for conn_name, engine in self.engines.items():
322
+ logger.info(f" - 关闭引擎: {conn_name}")
323
+ await engine.dispose()
324
+
325
+ self.engines.clear()
326
+ self.session_makers.clear()
327
+ self.configs.clear()
328
+ self.initialized = False
329
+
330
+ logger.success("✅ 所有数据库连接已关闭")
331
+
332
+ except Exception as e:
333
+ logger.error(f"❌ 关闭数据库连接失败: {e}")
334
+
335
+ async def close(self):
336
+ """关闭所有数据库连接(兼容旧接口)"""
337
+ await self.shutdown()
338
+
339
+
340
+ # ==================== 全局单例 ====================
341
+
342
+ db_manager = ProDatabaseManager()
343
+
344
+
345
+ # ==================== 便捷函数 ====================
346
+
347
+ async def register_databases(app: FastAPI) -> bool:
348
+ """注册数据库到 FastAPI 应用"""
349
+ return await db_manager.register(app)
350
+
351
+
352
+ async def check_databases_health(conn_name: Optional[str] = None) -> Dict:
353
+ """检查数据库健康状态"""
354
+ return await db_manager.health_check(conn_name)
355
+
356
+
357
+ async def close_databases():
358
+ """关闭所有数据库连接"""
359
+ await db_manager.close()
360
+
361
+
362
+ def get_connection_names() -> list[str]:
363
+ """获取所有连接名称"""
364
+ return list(db_manager.engines.keys())
365
+
366
+
367
+ # ==================== 依赖注入辅助函数 ====================
368
+
369
+ async def get_db_session(conn_name: str = "myql"):
370
+ """
371
+ FastAPI 依赖注入:获取数据库会话
372
+
373
+ 使用示例:
374
+ >>> @app.get("/users")
375
+ >>> async def get_users(session: AsyncSession = Depends(get_db_session)):
376
+ >>> result = await session.execute(text("SELECT * FROM users"))
377
+ >>> return result.fetchall()
378
+
379
+ Raises:
380
+ RuntimeError: 数据库未初始化或连接不存在
381
+ """
382
+ # 检查数据库管理器是否已初始化
383
+ if not db_manager.initialized:
384
+ raise RuntimeError(
385
+ "数据库管理器未初始化。请确保:\n"
386
+ "1. 在 FastAPI 应用中使用了 lifespan\n"
387
+ "2. 或在应用启动时调用了 await db_manager.startup(app)\n"
388
+ "3. settings.enabled_databases 已正确配置"
389
+ )
390
+
391
+ # 检查连接是否存在
392
+ if conn_name not in db_manager.session_makers:
393
+ available = list(db_manager.session_makers.keys())
394
+ raise RuntimeError(
395
+ f"数据库连接 '{conn_name}' 不存在。\n"
396
+ f"可用的连接: {available}"
397
+ )
398
+
399
+ session_maker = db_manager.session_makers[conn_name]
400
+ async with session_maker() as session:
401
+ try:
402
+ yield session
403
+ await session.commit()
404
+ except Exception:
405
+ await session.rollback()
406
+ raise
407
+ finally:
408
+ await session.close()