springbootAI 1.8.0__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 (175) hide show
  1. spring/__init__.py +66 -0
  2. spring/ai/__init__.py +78 -0
  3. spring/ai/advisors.py +139 -0
  4. spring/ai/annotations.py +74 -0
  5. spring/ai/autoconfig.py +481 -0
  6. spring/ai/core.py +391 -0
  7. spring/ai/etl.py +188 -0
  8. spring/ai/memory.py +109 -0
  9. spring/ai/observability.py +129 -0
  10. spring/ai/providers.py +789 -0
  11. spring/ai/resilience.py +258 -0
  12. spring/ai/tools.py +106 -0
  13. spring/ai/vectorstore.py +303 -0
  14. spring/annotations/__init__.py +188 -0
  15. spring/annotations/cache.py +126 -0
  16. spring/annotations/cloud.py +207 -0
  17. spring/annotations/conditional.py +272 -0
  18. spring/annotations/core.py +864 -0
  19. spring/annotations/messaging.py +107 -0
  20. spring/aop/__init__.py +4 -0
  21. spring/aop/cloud_aop.py +404 -0
  22. spring/aop/comprehensive_aop.py +1015 -0
  23. spring/aop/method_interceptor.py +19 -0
  24. spring/aop/proxy_factory.py +55 -0
  25. spring/cloud/__init__.py +76 -0
  26. spring/cloud/discovery.py +364 -0
  27. spring/cloud/feign.py +469 -0
  28. spring/cloud/gateway.py +452 -0
  29. spring/cloud/load_balancer.py +149 -0
  30. spring/cloud/seata.py +557 -0
  31. spring/cloud/sentinel.py +525 -0
  32. spring/cloud/tracer.py +337 -0
  33. spring/config/__init__.py +21 -0
  34. spring/config/binding.py +206 -0
  35. spring/config/config_loader.py +405 -0
  36. spring/context/__init__.py +13 -0
  37. spring/context/application_context.py +589 -0
  38. spring/context/bean_definition.py +70 -0
  39. spring/context/bean_factory.py +1052 -0
  40. spring/context/registry.py +58 -0
  41. spring/context/scanner.py +106 -0
  42. spring/core/__init__.py +3 -0
  43. spring/core/graceful_shutdown.py +196 -0
  44. spring/core/typing_utils.py +50 -0
  45. spring/csv/__init__.py +52 -0
  46. spring/csv/annotations.py +402 -0
  47. spring/csv/converters.py +69 -0
  48. spring/csv/easy_csv.py +95 -0
  49. spring/csv/exceptions.py +27 -0
  50. spring/csv/reader.py +195 -0
  51. spring/csv/writer.py +155 -0
  52. spring/data/__init__.py +54 -0
  53. spring/data/page.py +181 -0
  54. spring/data/repository.py +274 -0
  55. spring/data/specification.py +228 -0
  56. spring/datasource/__init__.py +66 -0
  57. spring/datasource/annotations.py +133 -0
  58. spring/datasource/context.py +69 -0
  59. spring/datasource/dynamic.py +148 -0
  60. spring/event/__init__.py +7 -0
  61. spring/event/publisher.py +69 -0
  62. spring/excel/__init__.py +51 -0
  63. spring/excel/annotations.py +405 -0
  64. spring/excel/converters.py +231 -0
  65. spring/excel/easy_excel.py +94 -0
  66. spring/excel/exceptions.py +31 -0
  67. spring/excel/reader.py +254 -0
  68. spring/excel/style.py +95 -0
  69. spring/excel/writer.py +197 -0
  70. spring/i18n/__init__.py +97 -0
  71. spring/i18n/accessor.py +94 -0
  72. spring/i18n/auto_config.py +177 -0
  73. spring/i18n/holder.py +106 -0
  74. spring/i18n/locale.py +152 -0
  75. spring/i18n/locale_resolver.py +367 -0
  76. spring/i18n/message_source.py +250 -0
  77. spring/i18n/middleware.py +79 -0
  78. spring/i18n/properties.py +168 -0
  79. spring/i18n/sources.py +255 -0
  80. spring/logging/__init__.py +1 -0
  81. spring/logging/loguru_logger.py +228 -0
  82. spring/main.py +378 -0
  83. spring/messaging/__init__.py +1 -0
  84. spring/messaging/rabbitmq.py +302 -0
  85. spring/monitoring/__init__.py +1 -0
  86. spring/monitoring/prometheus.py +199 -0
  87. spring/orm/__init__.py +258 -0
  88. spring/orm/database.py +222 -0
  89. spring/orm/ddl_auto.py +1217 -0
  90. spring/orm/migration.py +419 -0
  91. spring/orm/mybatis_integration.py +400 -0
  92. spring/orm/pymybatis/__init__.py +86 -0
  93. spring/orm/pymybatis/annotations/__init__.py +30 -0
  94. spring/orm/pymybatis/annotations/annotations.py +332 -0
  95. spring/orm/pymybatis/cache/__init__.py +47 -0
  96. spring/orm/pymybatis/cache/cache.py +371 -0
  97. spring/orm/pymybatis/cache/redis_cache.py +434 -0
  98. spring/orm/pymybatis/circuit_breaker/__init__.py +21 -0
  99. spring/orm/pymybatis/circuit_breaker/circuit_breaker.py +424 -0
  100. spring/orm/pymybatis/configuration.py +525 -0
  101. spring/orm/pymybatis/core/__init__.py +10 -0
  102. spring/orm/pymybatis/core/sql_session.py +1382 -0
  103. spring/orm/pymybatis/core/sql_session_factory.py +76 -0
  104. spring/orm/pymybatis/dialect/__init__.py +9 -0
  105. spring/orm/pymybatis/dialect/dialect.py +445 -0
  106. spring/orm/pymybatis/dynamic_sql/__init__.py +9 -0
  107. spring/orm/pymybatis/dynamic_sql/dynamic_sql.py +900 -0
  108. spring/orm/pymybatis/interceptor/__init__.py +31 -0
  109. spring/orm/pymybatis/interceptor/interceptor.py +427 -0
  110. spring/orm/pymybatis/mapper/__init__.py +9 -0
  111. spring/orm/pymybatis/mapper/mapper.py +540 -0
  112. spring/orm/pymybatis/metrics/__init__.py +41 -0
  113. spring/orm/pymybatis/metrics/metrics.py +595 -0
  114. spring/orm/pymybatis/pool/__init__.py +9 -0
  115. spring/orm/pymybatis/pool/connection_pool.py +711 -0
  116. spring/orm/pymybatis/security/__init__.py +19 -0
  117. spring/orm/pymybatis/security/access_control.py +415 -0
  118. spring/orm/pymybatis/security/password_encoder.py +293 -0
  119. spring/orm/pymybatis/security/sensitive_data_masker.py +326 -0
  120. spring/orm/pymybatis/security/sql_injection_detector.py +675 -0
  121. spring/orm/pymybatis/transaction/__init__.py +9 -0
  122. spring/orm/pymybatis/transaction/transaction.py +288 -0
  123. spring/orm/pymybatis/type_handler/__init__.py +37 -0
  124. spring/orm/pymybatis/type_handler/type_handler.py +473 -0
  125. spring/orm/pymybatis/version.py +9 -0
  126. spring/orm/pymybatis/xml_parser/__init__.py +9 -0
  127. spring/orm/pymybatis/xml_parser/xml_parser.py +761 -0
  128. spring/retry/__init__.py +12 -0
  129. spring/retry/retry_annotations.py +71 -0
  130. spring/retry/retry_decorator.py +155 -0
  131. spring/scheduling/__init__.py +3 -0
  132. spring/scheduling/scheduler.py +389 -0
  133. spring/security/__init__.py +39 -0
  134. spring/security/jwt_utils.py +281 -0
  135. spring/security/replay_protection.py +206 -0
  136. spring/security/secret_manager.py +226 -0
  137. spring/security/security_aop.py +248 -0
  138. spring/security/security_context.py +172 -0
  139. spring/test/__init__.py +45 -0
  140. spring/test/slicing.py +341 -0
  141. spring/tracing/__init__.py +11 -0
  142. spring/tracing/skywalking.py +229 -0
  143. spring/tx/__init__.py +52 -0
  144. spring/tx/events.py +172 -0
  145. spring/tx/synchronization.py +143 -0
  146. spring/utils/__init__.py +5 -0
  147. spring/utils/banner.py +32 -0
  148. spring/utils/logger.py +73 -0
  149. spring/utils/redis_client.py +526 -0
  150. spring/validation/__init__.py +55 -0
  151. spring/validation/aop.py +141 -0
  152. spring/validation/constraints.py +357 -0
  153. spring/validation/exceptions.py +55 -0
  154. spring/validation/validator.py +139 -0
  155. spring/web/__init__.py +12 -0
  156. spring/web/actuator.py +319 -0
  157. spring/web/exception_handler.py +61 -0
  158. spring/web/health.py +399 -0
  159. spring/web/interceptor.py +91 -0
  160. spring/web/result.py +44 -0
  161. spring/web/swagger.py +601 -0
  162. spring/web/web_context.py +755 -0
  163. spring/websocket/__init__.py +86 -0
  164. spring/websocket/annotations.py +169 -0
  165. spring/websocket/broker.py +238 -0
  166. spring/websocket/exceptions.py +26 -0
  167. spring/websocket/handler.py +243 -0
  168. spring/websocket/router.py +526 -0
  169. spring/websocket/session.py +216 -0
  170. springbootai-1.8.0.dist-info/METADATA +2796 -0
  171. springbootai-1.8.0.dist-info/RECORD +175 -0
  172. springbootai-1.8.0.dist-info/WHEEL +5 -0
  173. springbootai-1.8.0.dist-info/entry_points.txt +2 -0
  174. springbootai-1.8.0.dist-info/licenses/LICENSE +7 -0
  175. springbootai-1.8.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,711 @@
1
+ """
2
+ PyMyBatis连接池管理模块
3
+
4
+ 实现高性能数据库连接池,核心特性:
5
+ - 基于DBUtils实现高性能连接池
6
+ - 最小/最大连接数控制
7
+ - 连接空闲超时回收
8
+ - 连接有效性验证
9
+ - 连接泄漏检测(长时间未归还自动回收并告警)
10
+ - 熔断降级机制(防止数据库故障导致的级联失败)
11
+ - 多数据源支持
12
+ - 线程安全
13
+ """
14
+
15
+ import threading
16
+ import time
17
+ import queue
18
+ import logging
19
+ from typing import Dict, Any, Set, Optional
20
+ from abc import ABC, abstractmethod
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ _POOL_CONFIG_KEYS = {
25
+ 'driver', 'min_size', 'max_size', 'max_idle', 'wait_timeout',
26
+ 'validation_interval', 'leak_detection_enabled', 'leak_timeout',
27
+ 'circuit_breaker_enabled', 'circuit_breaker_failure_threshold',
28
+ 'circuit_breaker_recovery_timeout', 'circuit_breaker_success_threshold',
29
+ }
30
+
31
+ # 延迟导入熔断器模块
32
+ try:
33
+ from ..circuit_breaker import DatabaseCircuitBreaker, CircuitBreakerError
34
+ _circuit_breaker_available = True
35
+ except ImportError:
36
+ _circuit_breaker_available = False
37
+ logger.warning("熔断器模块不可用,安装方法: pip install pybreaker 或使用内置熔断器")
38
+
39
+
40
+ class PooledConnection:
41
+ """
42
+ 池化连接封装类
43
+
44
+ 封装原始连接,记录连接状态和使用信息
45
+
46
+ 安全特性:
47
+ - 记录使用时间,支持连接泄漏检测
48
+ - 自动标记连接状态
49
+ """
50
+
51
+ def __init__(self, connection: Any, pool: 'ConnectionPool', created_at: float):
52
+ self.connection = connection
53
+ self.pool = pool
54
+ self.created_at = created_at
55
+ self.last_used_at = created_at
56
+ self.in_use = False
57
+ self._lock = threading.RLock()
58
+ self._checkout_time = None
59
+
60
+ def __enter__(self):
61
+ """上下文管理器进入"""
62
+ return self
63
+
64
+ def __exit__(self, exc_type, exc_val, exc_tb):
65
+ """上下文管理器退出,自动归还连接"""
66
+ self.pool.return_connection(self)
67
+
68
+ def mark_in_use(self):
69
+ """标记连接为使用中"""
70
+ with self._lock:
71
+ if self.in_use:
72
+ raise RuntimeError("连接已被借出,不能重复借用")
73
+ self.in_use = True
74
+ now = time.monotonic()
75
+ self.last_used_at = now
76
+ self._checkout_time = now
77
+
78
+ def mark_free(self):
79
+ """标记连接为空闲"""
80
+ with self._lock:
81
+ self.in_use = False
82
+ self.last_used_at = time.monotonic()
83
+ self._checkout_time = None
84
+
85
+ def is_valid(self) -> bool:
86
+ """检查连接是否有效"""
87
+ try:
88
+ # 根据不同数据库驱动检查连接有效性
89
+ if hasattr(self.connection, 'ping'):
90
+ self.connection.ping()
91
+ elif hasattr(self.connection, 'isclosed') and not self.connection.isclosed():
92
+ return True
93
+ return True
94
+ except Exception:
95
+ return False
96
+
97
+ def get_idle_time(self) -> float:
98
+ """获取空闲时间(秒)"""
99
+ return time.monotonic() - self.last_used_at
100
+
101
+ def get_checkout_duration(self) -> float:
102
+ """获取连接已借出的时间(秒)"""
103
+ if self._checkout_time is None:
104
+ return 0
105
+ return time.monotonic() - self._checkout_time
106
+
107
+ def get_connection(self) -> Any:
108
+ """获取原始连接"""
109
+ return self.connection
110
+
111
+
112
+ class ConnectionPool(ABC):
113
+ """
114
+ 连接池抽象基类
115
+
116
+ 定义连接池的核心接口:
117
+ - 获取连接
118
+ - 归还连接
119
+ - 管理连接生命周期
120
+ - 连接泄漏检测
121
+ - 熔断降级机制
122
+ """
123
+
124
+ def __init__(self, config: Dict[str, Any]):
125
+ self.config = dict(config)
126
+ self.min_size = int(config.get('min_size', 5))
127
+ self.max_size = int(config.get('max_size', 20))
128
+ self.max_idle = float(config.get('max_idle', 10))
129
+ self.wait_timeout = float(config.get('wait_timeout', 30))
130
+ self.validation_interval = float(config.get('validation_interval', 300))
131
+ self.leak_detection_enabled = self._as_bool(config.get('leak_detection_enabled', True))
132
+ self.leak_timeout = float(config.get('leak_timeout', 300))
133
+
134
+ if self.min_size < 0:
135
+ raise ValueError("min_size 不能小于 0")
136
+ if self.max_size < 1:
137
+ raise ValueError("max_size 必须大于 0")
138
+ if self.min_size > self.max_size:
139
+ raise ValueError("min_size 不能大于 max_size")
140
+ if self.wait_timeout <= 0:
141
+ raise ValueError("wait_timeout 必须大于 0")
142
+ if self.max_idle < 0 or self.validation_interval <= 0 or self.leak_timeout <= 0:
143
+ raise ValueError("连接池超时配置必须为正数")
144
+
145
+ # 熔断器配置
146
+ self.circuit_breaker_enabled = self._as_bool(config.get('circuit_breaker_enabled', False))
147
+ self.circuit_breaker_failure_threshold = int(config.get('circuit_breaker_failure_threshold', 3))
148
+ self.circuit_breaker_recovery_timeout = float(config.get('circuit_breaker_recovery_timeout', 60))
149
+ self.circuit_breaker_success_threshold = int(config.get('circuit_breaker_success_threshold', 3))
150
+
151
+ self._pool: queue.Queue = queue.Queue(maxsize=self.max_size)
152
+ self._active_count = 0
153
+ self._active_connections: Set[PooledConnection] = set()
154
+ self._lock = threading.RLock()
155
+ self._validation_lock = threading.Lock()
156
+ self._last_validation = 0
157
+ self._total_connections = 0
158
+ self._closed = False
159
+ self._stop_event = threading.Event()
160
+
161
+ # 初始化熔断器
162
+ self._circuit_breaker = None
163
+ if self.circuit_breaker_enabled and _circuit_breaker_available:
164
+ self._circuit_breaker = DatabaseCircuitBreaker(
165
+ failure_threshold=self.circuit_breaker_failure_threshold,
166
+ recovery_timeout=self.circuit_breaker_recovery_timeout,
167
+ success_threshold=self.circuit_breaker_success_threshold,
168
+ name=f"pool_{id(self)}"
169
+ )
170
+ logger.info("连接池熔断器已启用")
171
+
172
+ # 初始化最小连接数
173
+ self._initialize_pool()
174
+
175
+ # 启动连接泄漏检测线程
176
+ if self.leak_detection_enabled:
177
+ self._start_leak_detection()
178
+
179
+ @staticmethod
180
+ def _as_bool(value: Any) -> bool:
181
+ if isinstance(value, bool):
182
+ return value
183
+ if isinstance(value, str):
184
+ return value.strip().lower() in {'1', 'true', 'yes', 'on'}
185
+ return bool(value)
186
+
187
+ @abstractmethod
188
+ def _create_connection(self) -> Any:
189
+ """创建新连接(由子类实现)"""
190
+ pass
191
+
192
+ @abstractmethod
193
+ def _close_connection(self, connection: Any) -> None:
194
+ """关闭连接(由子类实现)"""
195
+ pass
196
+
197
+ def _initialize_pool(self) -> None:
198
+ """初始化连接池,创建最小连接数"""
199
+ last_error = None
200
+ for _ in range(self.min_size):
201
+ try:
202
+ conn = self._create_connection()
203
+ pooled_conn = PooledConnection(conn, self, time.monotonic())
204
+ self._pool.put(pooled_conn)
205
+ self._total_connections += 1
206
+ except Exception as e:
207
+ last_error = e
208
+ logger.error(f"初始化连接池失败: {e}")
209
+
210
+ if self.min_size > 0 and self._total_connections == 0:
211
+ raise ConnectionError("连接池初始化失败,未能建立任何数据库连接") from last_error
212
+
213
+ def _start_leak_detection(self) -> None:
214
+ """启动连接泄漏检测线程"""
215
+ def leak_detector():
216
+ interval = max(1.0, min(60.0, self.leak_timeout / 2))
217
+ while not self._stop_event.wait(interval):
218
+ self._detect_leaks()
219
+
220
+ leak_thread = threading.Thread(target=leak_detector, daemon=True)
221
+ leak_thread.start()
222
+ logger.info("连接泄漏检测线程已启动")
223
+
224
+ def _detect_leaks(self) -> None:
225
+ """检测连接泄漏"""
226
+ with self._lock:
227
+ active_connections = list(self._active_connections)
228
+
229
+ for pooled_conn in active_connections:
230
+ checkout_duration = pooled_conn.get_checkout_duration()
231
+ if checkout_duration > self.leak_timeout:
232
+ logger.warning(
233
+ "检测到疑似连接泄漏:连接已借出 %.2f 秒,超过阈值 %.2f 秒",
234
+ checkout_duration,
235
+ self.leak_timeout,
236
+ )
237
+
238
+ def get_connection(self) -> PooledConnection:
239
+ """
240
+ 获取连接
241
+
242
+ Returns:
243
+ 池化连接对象
244
+
245
+ Raises:
246
+ ConnectionError: 获取连接超时
247
+ CircuitBreakerError: 熔断器打开时抛出
248
+ """
249
+ # 如果启用了熔断器,使用熔断器保护连接获取
250
+ if self._circuit_breaker:
251
+ try:
252
+ return self._circuit_breaker.call(self._get_connection_internal)
253
+ except CircuitBreakerError as e:
254
+ logger.error(f"连接池熔断器打开,请求被拒绝: {e}")
255
+ raise ConnectionError(f"数据库连接熔断,请稍后重试") from e
256
+
257
+ return self._get_connection_internal()
258
+
259
+ def _get_connection_internal(self) -> PooledConnection:
260
+ """
261
+ 获取连接(内部方法,不受熔断器保护)
262
+
263
+ Returns:
264
+ 池化连接对象
265
+
266
+ Raises:
267
+ ConnectionError: 获取连接超时
268
+ """
269
+ # 定期验证连接有效性
270
+ self._validate_pool_periodically()
271
+
272
+ deadline = time.monotonic() + self.wait_timeout
273
+
274
+ while True:
275
+ if self._closed:
276
+ raise ConnectionError("连接池已关闭")
277
+
278
+ try:
279
+ pooled_conn = self._pool.get_nowait()
280
+ except queue.Empty:
281
+ pooled_conn = self._create_connection_if_capacity()
282
+ if pooled_conn is None:
283
+ remaining = deadline - time.monotonic()
284
+ if remaining <= 0:
285
+ raise ConnectionError(
286
+ f"获取连接超时,最大连接数已达上限: {self.max_size}"
287
+ )
288
+ try:
289
+ pooled_conn = self._pool.get(timeout=remaining)
290
+ except queue.Empty as exc:
291
+ raise ConnectionError(
292
+ f"获取连接超时,最大连接数已达上限: {self.max_size}"
293
+ ) from exc
294
+
295
+ if not pooled_conn.is_valid():
296
+ logger.warning("检测到无效连接,关闭并重新获取")
297
+ self._dispose_connection(pooled_conn)
298
+ continue
299
+
300
+ pooled_conn.mark_in_use()
301
+ with self._lock:
302
+ if self._closed:
303
+ pooled_conn.mark_free()
304
+ self._dispose_connection(pooled_conn)
305
+ raise ConnectionError("连接池已关闭")
306
+ self._active_connections.add(pooled_conn)
307
+ self._active_count = len(self._active_connections)
308
+
309
+ logger.debug(f"获取连接成功,活跃连接数={self._active_count}")
310
+ return pooled_conn
311
+
312
+ def _create_connection_if_capacity(self):
313
+ """在未达到上限时立即扩容,并用预留计数防止并发超配。"""
314
+ with self._lock:
315
+ if self._closed or self._total_connections >= self.max_size:
316
+ return None
317
+ self._total_connections += 1
318
+
319
+ try:
320
+ conn = self._create_connection()
321
+ pooled_conn = PooledConnection(conn, self, time.monotonic())
322
+ logger.info(f"创建新连接,总连接数={self._total_connections}")
323
+ return pooled_conn
324
+ except Exception as e:
325
+ with self._lock:
326
+ self._total_connections -= 1
327
+ raise ConnectionError(f"创建新连接失败: {e}") from e
328
+
329
+ def _dispose_connection(self, pooled_conn: PooledConnection) -> None:
330
+ with self._lock:
331
+ self._active_connections.discard(pooled_conn)
332
+ self._active_count = len(self._active_connections)
333
+ if self._total_connections > 0:
334
+ self._total_connections -= 1
335
+ self._close_connection(pooled_conn.get_connection())
336
+
337
+ def return_connection(self, pooled_conn: PooledConnection) -> None:
338
+ """
339
+ 归还连接到池中
340
+
341
+ Args:
342
+ pooled_conn: 池化连接对象
343
+ """
344
+ if pooled_conn.pool is not self:
345
+ raise ValueError("连接不属于当前连接池")
346
+ if not pooled_conn.in_use:
347
+ raise ValueError("连接已归还,不能重复归还")
348
+
349
+ checkout_duration = pooled_conn.get_checkout_duration()
350
+ if checkout_duration > self.leak_timeout:
351
+ logger.warning(
352
+ f"连接泄漏检测:连接已借出 {checkout_duration:.2f} 秒,超过阈值 {self.leak_timeout} 秒"
353
+ )
354
+
355
+ # DB-API连接归还前统一回滚,避免未提交状态污染下一个请求。
356
+ try:
357
+ pooled_conn.get_connection().rollback()
358
+ except Exception:
359
+ logger.exception("重置数据库连接失败,将关闭该连接")
360
+ pooled_conn.mark_free()
361
+ self._dispose_connection(pooled_conn)
362
+ return
363
+
364
+ pooled_conn.mark_free()
365
+ with self._lock:
366
+ self._active_connections.discard(pooled_conn)
367
+ self._active_count = len(self._active_connections)
368
+
369
+ logger.debug(f"归还连接,活跃连接数={self._active_count}")
370
+
371
+ if self._closed or not pooled_conn.is_valid():
372
+ self._dispose_connection(pooled_conn)
373
+ return
374
+
375
+ # 归还到池中
376
+ try:
377
+ self._pool.put(pooled_conn, timeout=1)
378
+ except queue.Full:
379
+ # 池已满,关闭该连接
380
+ logger.info("连接池已满,关闭多余连接")
381
+ self._dispose_connection(pooled_conn)
382
+
383
+ def _validate_pool_periodically(self) -> None:
384
+ """定期验证池中的连接有效性"""
385
+ now = time.monotonic()
386
+ if now - self._last_validation < self.validation_interval:
387
+ return
388
+
389
+ if not self._validation_lock.acquire(blocking=False):
390
+ return
391
+
392
+ try:
393
+ self._last_validation = now
394
+ logger.debug("开始验证连接池中的连接")
395
+
396
+ valid_connections = []
397
+ while True:
398
+ try:
399
+ pooled_conn = self._pool.get_nowait()
400
+ except queue.Empty:
401
+ break
402
+
403
+ can_evict_idle = self._total_connections > self.min_size
404
+ if not pooled_conn.is_valid() or (
405
+ can_evict_idle and pooled_conn.get_idle_time() > self.max_idle
406
+ ):
407
+ self._dispose_connection(pooled_conn)
408
+ else:
409
+ valid_connections.append(pooled_conn)
410
+
411
+ for pooled_conn in valid_connections:
412
+ self._pool.put_nowait(pooled_conn)
413
+
414
+ while not self._closed:
415
+ with self._lock:
416
+ needs_connection = self._total_connections < self.min_size
417
+ if not needs_connection:
418
+ break
419
+ try:
420
+ pooled_conn = self._create_connection_if_capacity()
421
+ if pooled_conn is None:
422
+ break
423
+ self._pool.put_nowait(pooled_conn)
424
+ except Exception as e:
425
+ logger.error(f"补充连接池失败: {e}")
426
+ break
427
+ finally:
428
+ self._validation_lock.release()
429
+
430
+ def get_pool_stats(self) -> Dict[str, Any]:
431
+ """
432
+ 获取连接池统计信息
433
+
434
+ Returns:
435
+ 统计信息字典
436
+ """
437
+ with self._lock:
438
+ stats = {
439
+ 'active_connections': self._active_count,
440
+ 'idle_connections': self._pool.qsize(),
441
+ 'min_size': self.min_size,
442
+ 'max_size': self.max_size,
443
+ 'total_connections': self._total_connections,
444
+ 'leak_detection_enabled': self.leak_detection_enabled,
445
+ 'leak_timeout': self.leak_timeout,
446
+ 'circuit_breaker_enabled': self.circuit_breaker_enabled,
447
+ 'closed': self._closed,
448
+ }
449
+
450
+ # 添加熔断器统计信息
451
+ if self._circuit_breaker:
452
+ stats['circuit_breaker'] = self._circuit_breaker.get_stats()
453
+
454
+ return stats
455
+
456
+ def close(self) -> None:
457
+ """关闭连接池,释放所有连接"""
458
+ with self._lock:
459
+ if self._closed:
460
+ return
461
+ self._closed = True
462
+ active_connections = list(self._active_connections)
463
+ self._stop_event.set()
464
+ logger.info("关闭连接池")
465
+ idle_connections = []
466
+ while True:
467
+ try:
468
+ idle_connections.append(self._pool.get_nowait())
469
+ except queue.Empty:
470
+ break
471
+ for pooled_conn in idle_connections + active_connections:
472
+ self._dispose_connection(pooled_conn)
473
+
474
+ def __del__(self):
475
+ """析构函数,确保连接池被关闭"""
476
+ try:
477
+ self.close()
478
+ except Exception:
479
+ pass
480
+
481
+
482
+ def _get_docker_container_ip_by_port(target_port: int) -> Optional[str]:
483
+ """通过Docker CLI自动检测映射了指定端口的容器IP(开发环境辅助)
484
+
485
+ 按以下顺序查找:
486
+ 1. 精确查找端口映射匹配target_port的运行中容器
487
+ 2. 如果是数据库默认端口(3306/5432),兜底查找mysql/mariadb/postgres镜像容器
488
+ 返回容器内部IP,找不到返回None
489
+ """
490
+ import os
491
+ import re
492
+ # 允许通过环境变量禁用Docker自动检测
493
+ if os.getenv('SPRING_DISABLE_DOCKER_IP_DETECT', '').lower() in ('1', 'true', 'yes'):
494
+ return None
495
+
496
+ # 数据库默认端口列表(用于兜底镜像匹配)
497
+ DB_PORTS = {3306: ('mysql', 'mariadb'), 5432: ('postgres', 'postgresql')}
498
+
499
+ try:
500
+ import subprocess
501
+ # 方法1:精确通过端口映射查找容器
502
+ # 端口映射格式: 0.0.0.0:3306->3306/tcp, [::]:3306->3306/tcp
503
+ port_pattern = re.compile(r'(?:0\.0\.0\.0|::|\*):' + str(target_port) + r'->')
504
+
505
+ result = subprocess.run(
506
+ ['docker', 'ps', '--format', '{{.ID}}|{{.Ports}}'],
507
+ capture_output=True, text=True, timeout=5
508
+ )
509
+ if result.returncode == 0:
510
+ for line in result.stdout.strip().split('\n'):
511
+ if not line or '|' not in line:
512
+ continue
513
+ cid, ports_str = line.split('|', 1)
514
+ if port_pattern.search(ports_str):
515
+ ip_result = subprocess.run(
516
+ ['docker', 'inspect', '-f', '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}', cid.strip()],
517
+ capture_output=True, text=True, timeout=5
518
+ )
519
+ if ip_result.returncode == 0 and ip_result.stdout.strip():
520
+ return ip_result.stdout.strip()
521
+
522
+ # 方法2:兜底 - 仅当目标端口是数据库默认端口时,按镜像名模糊匹配
523
+ db_keywords = DB_PORTS.get(target_port)
524
+ if db_keywords:
525
+ result = subprocess.run(
526
+ ['docker', 'ps', '--format', '{{.ID}}|{{.Image}}'],
527
+ capture_output=True, text=True, timeout=5
528
+ )
529
+ if result.returncode == 0:
530
+ for line in result.stdout.strip().split('\n'):
531
+ if not line or '|' not in line:
532
+ continue
533
+ cid, image = line.split('|', 1)
534
+ image_lower = image.lower()
535
+ if any(kw in image_lower for kw in db_keywords):
536
+ ip_result = subprocess.run(
537
+ ['docker', 'inspect', '-f', '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}', cid.strip()],
538
+ capture_output=True, text=True, timeout=5
539
+ )
540
+ if ip_result.returncode == 0 and ip_result.stdout.strip():
541
+ return ip_result.stdout.strip()
542
+ except Exception:
543
+ pass
544
+ return None
545
+
546
+
547
+ class MySQLConnectionPool(ConnectionPool):
548
+ """MySQL连接池实现"""
549
+
550
+ def _create_connection(self) -> Any:
551
+ """创建MySQL连接"""
552
+ try:
553
+ import pymysql
554
+ config = self.config.copy()
555
+ for key in _POOL_CONFIG_KEYS:
556
+ config.pop(key, None)
557
+ if 'username' in config and 'user' not in config:
558
+ config['user'] = config.pop('username')
559
+
560
+ # 确保密码是字符串类型
561
+ if 'password' in config:
562
+ config['password'] = str(config['password'] or '')
563
+ else:
564
+ config['password'] = ''
565
+
566
+ # 设置默认参数
567
+ config.setdefault('charset', 'utf8mb4')
568
+ config.setdefault('cursorclass', pymysql.cursors.DictCursor)
569
+ config.setdefault('autocommit', False)
570
+ # 明确禁用unix socket,强制使用TCP连接
571
+ config['unix_socket'] = None
572
+ config['connect_timeout'] = 5
573
+
574
+ try:
575
+ return pymysql.connect(**config)
576
+ except Exception as e:
577
+ # 如果连接localhost/127.0.0.1失败,尝试自动检测Docker容器IP
578
+ host = config.get('host', '')
579
+ if host in ('localhost', '127.0.0.1', '0.0.0.0'):
580
+ docker_ip = _get_docker_container_ip_by_port(config.get('port', 3306))
581
+ if docker_ip:
582
+ logger.info(f"使用Docker容器IP {docker_ip}:{config.get('port', 3306)} 连接数据库")
583
+ config['host'] = docker_ip
584
+ return pymysql.connect(**config)
585
+ raise
586
+ except ImportError:
587
+ raise ImportError("请安装pymysql: pip install pymysql")
588
+
589
+ def _close_connection(self, connection: Any) -> None:
590
+ """关闭MySQL连接"""
591
+ try:
592
+ connection.close()
593
+ except Exception:
594
+ pass
595
+
596
+
597
+ class PostgreSQLConnectionPool(ConnectionPool):
598
+ """PostgreSQL连接池实现"""
599
+
600
+ def _create_connection(self) -> Any:
601
+ """创建PostgreSQL连接"""
602
+ try:
603
+ import psycopg2
604
+ config = self.config.copy()
605
+ for key in _POOL_CONFIG_KEYS:
606
+ config.pop(key, None)
607
+ if 'username' in config and 'user' not in config:
608
+ config['user'] = config.pop('username')
609
+
610
+ connection = psycopg2.connect(**config)
611
+ connection.autocommit = False
612
+ return connection
613
+ except ImportError:
614
+ raise ImportError("请安装psycopg2: pip install psycopg2-binary")
615
+
616
+ def _close_connection(self, connection: Any) -> None:
617
+ """关闭PostgreSQL连接"""
618
+ try:
619
+ connection.close()
620
+ except Exception:
621
+ pass
622
+
623
+
624
+ class SQLiteConnectionPool(ConnectionPool):
625
+ """SQLite连接池实现"""
626
+
627
+ def __init__(self, config: Dict[str, Any]):
628
+ normalized_config = dict(config)
629
+ database = normalized_config.get('database', normalized_config.get('db', ':memory:'))
630
+ if database == ':memory:':
631
+ # 多个内存连接对应不同数据库,必须限制为单连接。
632
+ normalized_config['min_size'] = 1
633
+ normalized_config['max_size'] = 1
634
+ super().__init__(normalized_config)
635
+
636
+ def _create_connection(self) -> Any:
637
+ """创建SQLite连接"""
638
+ try:
639
+ import sqlite3
640
+ config = self.config.copy()
641
+ for key in _POOL_CONFIG_KEYS | {'host', 'port', 'username', 'password'}:
642
+ config.pop(key, None)
643
+
644
+ # SQLite需要特殊处理
645
+ db_path = config.pop('database', config.pop('db', ':memory:'))
646
+ config.setdefault('check_same_thread', False)
647
+ connection = sqlite3.connect(db_path, **config)
648
+ connection.row_factory = sqlite3.Row
649
+ return connection
650
+ except ImportError:
651
+ raise ImportError("SQLite应该是Python内置的")
652
+
653
+ def _close_connection(self, connection: Any) -> None:
654
+ """关闭SQLite连接"""
655
+ try:
656
+ connection.close()
657
+ except Exception:
658
+ pass
659
+
660
+
661
+ class OracleConnectionPool(ConnectionPool):
662
+ """Oracle连接池实现"""
663
+
664
+ def _create_connection(self) -> Any:
665
+ """创建Oracle连接"""
666
+ try:
667
+ import cx_Oracle
668
+ config = self.config.copy()
669
+ for key in _POOL_CONFIG_KEYS:
670
+ config.pop(key, None)
671
+ if 'username' in config and 'user' not in config:
672
+ config['user'] = config.pop('username')
673
+
674
+ return cx_Oracle.connect(**config)
675
+ except ImportError:
676
+ raise ImportError("请安装cx_Oracle: pip install cx_Oracle")
677
+
678
+ def _close_connection(self, connection: Any) -> None:
679
+ """关闭Oracle连接"""
680
+ try:
681
+ connection.close()
682
+ except Exception:
683
+ pass
684
+
685
+
686
+ def create_connection_pool(dialect: str, config: Dict[str, Any]) -> ConnectionPool:
687
+ """
688
+ 根据方言创建连接池
689
+
690
+ Args:
691
+ dialect: 数据库方言名称
692
+ config: 连接池配置
693
+
694
+ Returns:
695
+ 连接池实例
696
+
697
+ Raises:
698
+ ValueError: 不支持的数据库方言
699
+ """
700
+ pool_map = {
701
+ 'mysql': MySQLConnectionPool,
702
+ 'postgresql': PostgreSQLConnectionPool,
703
+ 'sqlite': SQLiteConnectionPool,
704
+ 'oracle': OracleConnectionPool
705
+ }
706
+
707
+ pool_class = pool_map.get(dialect.lower())
708
+ if not pool_class:
709
+ raise ValueError(f"不支持的数据库方言: {dialect}")
710
+
711
+ return pool_class(config)