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,107 @@
1
+ """
2
+ 消息队列注解
3
+ 提供RabbitMQ消息消费和发送功能
4
+ """
5
+ from typing import Any, Callable, Dict, List
6
+ import functools
7
+ from .core import SpringAnnotation
8
+
9
+
10
+ class RabbitListener(SpringAnnotation):
11
+ """
12
+ RabbitMQ消息监听注解
13
+
14
+ 使用示例:
15
+ @RabbitListener(queue="order.create")
16
+ def handle_order_created(self, message):
17
+ print(f"Received order: {message}")
18
+ """
19
+
20
+ _annotation_type = "messaging"
21
+
22
+ def __init__(self, queue: str, exchange: str = "", routing_key: str = "",
23
+ auto_ack: bool = False, prefetch_count: int = 1):
24
+ super().__init__(
25
+ queue=queue,
26
+ exchange=exchange,
27
+ routing_key=routing_key or queue,
28
+ auto_ack=auto_ack,
29
+ prefetch_count=prefetch_count,
30
+ )
31
+
32
+
33
+ class RabbitTemplate:
34
+ """
35
+ RabbitMQ消息发送模板
36
+
37
+ 使用示例:
38
+ rabbit_template = RabbitTemplate()
39
+ rabbit_template.send("order.create", {"order_id": 1})
40
+ """
41
+
42
+ def send(self, queue: str, body: Any, exchange: str = "",
43
+ routing_key: str = "", persistent: bool = True):
44
+ """
45
+ 发送消息
46
+
47
+ Args:
48
+ queue: 队列名称
49
+ body: 消息体
50
+ exchange: 交换机名称
51
+ routing_key: 路由键
52
+ persistent: 是否持久化
53
+ """
54
+ from spring.messaging.rabbitmq import rabbitmq_client
55
+
56
+ if exchange:
57
+ # 通过交换机发送
58
+ rabbitmq_client.publish(
59
+ exchange_name=exchange,
60
+ routing_key=routing_key or queue,
61
+ body=body,
62
+ persistent=persistent,
63
+ )
64
+ else:
65
+ # 直接发送到队列
66
+ rabbitmq_client.publish_to_queue(
67
+ queue_name=queue,
68
+ body=body,
69
+ persistent=persistent,
70
+ )
71
+
72
+
73
+ def rabbit_listener_decorator(annotation: RabbitListener):
74
+ """
75
+ RabbitListener注解切面
76
+ """
77
+ def decorator(func: Callable) -> Callable:
78
+ @functools.wraps(func)
79
+ def wrapper(*args, **kwargs):
80
+ return func(*args, **kwargs)
81
+
82
+ return wrapper
83
+ return decorator
84
+
85
+
86
+ def register_rabbit_listener(annotation: RabbitListener, callback: Callable) -> None:
87
+ """Declare and register one listener using an already-bound Bean method."""
88
+ from spring.messaging.rabbitmq import rabbitmq_client
89
+
90
+ rabbitmq_client.declare_queue(annotation.queue)
91
+ if annotation.exchange:
92
+ rabbitmq_client.declare_exchange(annotation.exchange)
93
+ rabbitmq_client.bind_queue(
94
+ queue_name=annotation.queue,
95
+ exchange_name=annotation.exchange,
96
+ routing_key=annotation.routing_key,
97
+ )
98
+ rabbitmq_client.consume(
99
+ queue_name=annotation.queue,
100
+ callback=callback,
101
+ auto_ack=annotation.auto_ack,
102
+ prefetch_count=annotation.prefetch_count,
103
+ )
104
+
105
+
106
+ # 创建全局RabbitMQ模板实例
107
+ rabbit_template = RabbitTemplate()
spring/aop/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ from .proxy_factory import ProxyFactory
2
+ from .method_interceptor import MethodInterceptor
3
+
4
+ __all__ = ["ProxyFactory", "MethodInterceptor"]
@@ -0,0 +1,404 @@
1
+ """
2
+ Spring Cloud AOP 切面实现(企业级版本)
3
+ 使用真实的分布式组件:Seata事务、Nacos服务发现、Sentinel限流熔断等
4
+ """
5
+ from typing import Any, Callable, Dict, List, Optional, Type
6
+ import time
7
+ import functools
8
+ import threading
9
+ import inspect
10
+ import logging
11
+ from spring.annotations.cloud import (
12
+ SentinelResource,
13
+ GlobalTransactional,
14
+ RefreshScope,
15
+ LoadBalanced,
16
+ Valid,
17
+ Validated,
18
+ )
19
+ from spring.cloud.seata import seata_manager
20
+ from spring.cloud.load_balancer import load_balancer
21
+ from spring.cloud.sentinel import sentinel_engine, BlockException
22
+ from spring.utils.redis_client import redis_client
23
+
24
+ logger = logging.getLogger("Spring.Cloud.AOP")
25
+
26
+ # ==================== 全局存储 ====================
27
+ _refresh_scope_cache: Dict[str, dict] = {} # 刷新作用域缓存
28
+ _refresh_lock = threading.Lock() # 刷新锁
29
+
30
+
31
+ # ==================== SentinelResource 熔断限流切面(使用真实Sentinel引擎) ====================
32
+ def sentinel_resource_decorator(annotation: SentinelResource):
33
+ """
34
+ Sentinel资源保护注解实现(内嵌Sentinel引擎)
35
+ - block_handler: 处理限流、熔断、系统保护等阻断异常
36
+ - fallback: 处理业务异常、远程调用异常
37
+ - hotkey: 热点参数限流
38
+ """
39
+ def decorator(func: Callable) -> Callable:
40
+ resource_key = annotation.value or f"{func.__module__}.{func.__name__}"
41
+
42
+ @functools.wraps(func)
43
+ def wrapper(*args, **kwargs):
44
+ entry = None
45
+ try:
46
+ entry = sentinel_engine.entry(resource_key, args=args, kwargs=kwargs)
47
+ start = time.monotonic()
48
+ result = func(*args, **kwargs)
49
+ rt_ms = (time.monotonic() - start) * 1000
50
+ entry.success()
51
+ # 记录到Redis(可选统计)
52
+ _record_to_redis(resource_key, rt_ms, False)
53
+ return result
54
+ except BlockException as e:
55
+ # 限流/熔断阻断 -> block_handler
56
+ logger.warning(f"[Sentinel] Blocked {resource_key}: {e.rule_type}")
57
+ _record_to_redis(resource_key, 0, True, blocked=True)
58
+ handler_name = annotation.block_handler
59
+ if handler_name:
60
+ handler_func = _find_handler(args, handler_name)
61
+ if handler_func:
62
+ return handler_func(*args[1:], **kwargs)
63
+ raise
64
+ except Exception as e:
65
+ # 业务异常 -> fallback
66
+ if entry:
67
+ entry.error()
68
+ # 检查异常忽略列表
69
+ if annotation.exceptions_to_ignore:
70
+ if any(isinstance(e, exc_type) for exc_type in annotation.exceptions_to_ignore):
71
+ raise
72
+ _record_to_redis(resource_key, 0, True)
73
+ fallback_name = annotation.fallback
74
+ if fallback_name:
75
+ fallback_func = _find_handler(args, fallback_name)
76
+ if fallback_func:
77
+ logger.warning(f"[Sentinel] Fallback triggered for {resource_key}: {e}")
78
+ return fallback_func(*args[1:], **kwargs)
79
+ raise
80
+ return wrapper
81
+ return decorator
82
+
83
+
84
+ def _find_handler(args: tuple, handler_name: str) -> Optional[Callable]:
85
+ """查找实例方法作为handler"""
86
+ if args and hasattr(args[0], handler_name):
87
+ handler = getattr(args[0], handler_name, None)
88
+ if callable(handler):
89
+ return handler
90
+ return None
91
+
92
+
93
+ def _record_to_redis(resource_key: str, rt_ms: float, is_error: bool, blocked: bool = False):
94
+ """将统计数据记录到Redis(可选,用于集群模式)"""
95
+ redis = redis_client.get_client()
96
+ if redis is None:
97
+ return
98
+ try:
99
+ pipe = redis.pipeline()
100
+ pipe.hincrby(f"sentinel_stats:{resource_key}", "total", 1)
101
+ if blocked:
102
+ pipe.hincrby(f"sentinel_stats:{resource_key}", "blocked", 1)
103
+ if is_error and not blocked:
104
+ pipe.hincrby(f"sentinel_stats:{resource_key}", "error", 1)
105
+ if rt_ms > 0:
106
+ pipe.hincrbyfloat(f"sentinel_stats:{resource_key}", "rt_total", rt_ms)
107
+ pipe.hincrby(f"sentinel_stats:{resource_key}", "success", 1)
108
+ pipe.hset(f"sentinel_stats:{resource_key}", "last_access", str(time.time()))
109
+ pipe.expire(f"sentinel_stats:{resource_key}", 3600)
110
+ pipe.execute()
111
+ except Exception:
112
+ pass
113
+
114
+
115
+ # ==================== GlobalTransactional 分布式事务切面(Seata集成) ====================
116
+ def global_transactional_decorator(annotation: GlobalTransactional):
117
+ """
118
+ Seata全局事务注解实现(真实Seata集成)
119
+ - 仅事务发起入口方法添加
120
+ - 不支持嵌套事务
121
+ - 使用Seata事务管理器进行事务管理
122
+ """
123
+ def decorator(func: Callable) -> Callable:
124
+ def begin_transaction():
125
+ return seata_manager.begin_transaction(
126
+ timeout=annotation.timeout,
127
+ name=annotation.name or func.__name__,
128
+ )
129
+
130
+ def commit_transaction(tx_id: str, duration: float) -> None:
131
+ if duration * 1000 > annotation.timeout:
132
+ raise TimeoutError(f"Transaction timeout after {duration:.2f}s")
133
+ if not seata_manager.commit_transaction(tx_id):
134
+ raise RuntimeError(f"Global transaction commit failed: {tx_id}")
135
+
136
+ if inspect.iscoroutinefunction(func):
137
+ @functools.wraps(func)
138
+ async def async_wrapper(*args, **kwargs):
139
+ if seata_manager.is_in_transaction():
140
+ logger.warning("[GlobalTransactional] Nested transaction detected, skipping")
141
+ return await func(*args, **kwargs)
142
+ tx_id = begin_transaction()
143
+ start_time = time.monotonic()
144
+ try:
145
+ result = await func(*args, **kwargs)
146
+ commit_transaction(tx_id, time.monotonic() - start_time)
147
+ return result
148
+ except Exception:
149
+ if seata_manager.is_in_transaction():
150
+ seata_manager.rollback_transaction(tx_id)
151
+ raise
152
+
153
+ return async_wrapper
154
+
155
+ @functools.wraps(func)
156
+ def wrapper(*args, **kwargs):
157
+ # 检查是否已经在事务中
158
+ if seata_manager.is_in_transaction():
159
+ logger.warning("[GlobalTransactional] Nested transaction detected, skipping")
160
+ return func(*args, **kwargs)
161
+
162
+ # 开启分布式事务
163
+ tx_id = begin_transaction()
164
+
165
+ logger.info(f"[GlobalTransactional] Begin transaction: {tx_id}")
166
+
167
+ start_time = time.monotonic()
168
+
169
+ try:
170
+ result = func(*args, **kwargs)
171
+
172
+ # 检查执行时间是否超时
173
+ duration = time.monotonic() - start_time
174
+ commit_transaction(tx_id, duration)
175
+ logger.info(f"[GlobalTransactional] Commit transaction: {tx_id}, duration={duration:.4f}s")
176
+
177
+ return result
178
+
179
+ except Exception as e:
180
+ # 异常触发回滚
181
+ if seata_manager.is_in_transaction():
182
+ seata_manager.rollback_transaction(tx_id)
183
+ logger.error(f"[GlobalTransactional] Rollback transaction: {tx_id}, error={str(e)}")
184
+ raise
185
+ return wrapper
186
+ return decorator
187
+
188
+
189
+ # ==================== RefreshScope 配置刷新切面 ====================
190
+ def refresh_scope_decorator(annotation: RefreshScope):
191
+ """
192
+ 配置刷新作用域注解实现
193
+ - 仅加了该注解的类,配置变更才会自动刷新
194
+ - 会创建代理类,存在循环依赖的Bean会启动直接报错
195
+ """
196
+ def decorator(cls: type) -> type:
197
+ original_attrs = cls.__dict__.copy()
198
+
199
+ class RefreshProxy(cls):
200
+ _refresh_key = f"refresh_scope:{cls.__module__}.{cls.__name__}"
201
+
202
+ def __init__(self, *args, **kwargs):
203
+ super().__init__(*args, **kwargs)
204
+ with _refresh_lock:
205
+ if self._refresh_key not in _refresh_scope_cache:
206
+ _refresh_scope_cache[self._refresh_key] = {
207
+ "last_refresh": time.time(),
208
+ "config_version": 0,
209
+ "instance": self,
210
+ }
211
+
212
+ def _refresh_config(self):
213
+ """手动触发配置刷新"""
214
+ with _refresh_lock:
215
+ cache_entry = _refresh_scope_cache.get(self._refresh_key)
216
+ if cache_entry:
217
+ cache_entry["last_refresh"] = time.time()
218
+ cache_entry["config_version"] += 1
219
+ logger.info(f"[RefreshScope] Config refreshed for {self._refresh_key}, version={cache_entry['config_version']}")
220
+
221
+ for key, value in original_attrs.items():
222
+ if key not in ('__dict__', '__weakref__', '__class__', '__module__', '__name__'):
223
+ setattr(RefreshProxy, key, value)
224
+
225
+ return RefreshProxy
226
+ return decorator
227
+
228
+
229
+ # ==================== LoadBalanced 负载均衡切面(真实负载均衡) ====================
230
+ def load_balanced_decorator(annotation: LoadBalanced):
231
+ """
232
+ 负载均衡注解实现(真实负载均衡算法)
233
+ - 仅作用于@Bean修饰的RestTemplate
234
+ - 使用全局负载均衡器进行实例选择
235
+ """
236
+ def decorator(func: Callable) -> Callable:
237
+ @functools.wraps(func)
238
+ def wrapper(*args, **kwargs):
239
+ # 创建RestTemplate实例
240
+ rest_template = func(*args, **kwargs)
241
+
242
+ # 添加负载均衡能力
243
+ if isinstance(rest_template, dict):
244
+ rest_template['load_balanced'] = True
245
+ rest_template['strategy'] = annotation.strategy
246
+ elif hasattr(rest_template, '__dict__'):
247
+ rest_template.__dict__['load_balanced'] = True
248
+ rest_template.__dict__['strategy'] = annotation.strategy
249
+
250
+ # 设置负载均衡策略
251
+ load_balancer.set_strategy(annotation.strategy)
252
+
253
+ logger.info(f"[LoadBalanced] RestTemplate created with load balancing enabled, strategy={annotation.strategy}")
254
+ return rest_template
255
+ return wrapper
256
+ return decorator
257
+
258
+
259
+ # ==================== Valid 参数校验切面 ====================
260
+ def valid_decorator(annotation: Valid):
261
+ """
262
+ 参数校验注解实现
263
+ - 实体类参数校验必须配合@RequestBody使用
264
+ - 嵌套实体校验,内部实体必须添加@Valid
265
+ """
266
+ def decorator(func: Callable) -> Callable:
267
+ @functools.wraps(func)
268
+ def wrapper(*args, **kwargs):
269
+ errors = []
270
+
271
+ sig = inspect.signature(func)
272
+ bound_args = sig.bind(*args, **kwargs)
273
+ bound_args.apply_defaults()
274
+
275
+ for param_name, value in bound_args.arguments.items():
276
+ if param_name == 'self' or value is None:
277
+ continue
278
+
279
+ if value == "":
280
+ errors.append(f"{param_name} cannot be empty")
281
+
282
+ if hasattr(value, '__dict__'):
283
+ for nested_key, nested_value in value.__dict__.items():
284
+ if nested_value is None:
285
+ errors.append(f"{param_name}.{nested_key} cannot be null")
286
+
287
+ if errors:
288
+ raise Exception("Validation failed: " + "; ".join(errors))
289
+
290
+ return func(*args, **kwargs)
291
+ return wrapper
292
+ return decorator
293
+
294
+
295
+ # ==================== Validated 参数校验切面(分组校验) ====================
296
+ def validated_decorator(annotation: Validated):
297
+ """
298
+ 参数校验注解实现(分组校验)
299
+ - 和@Valid区别:@Validated支持分组校验
300
+ """
301
+ def decorator(func: Callable) -> Callable:
302
+ @functools.wraps(func)
303
+ def wrapper(*args, **kwargs):
304
+ errors = []
305
+
306
+ sig = inspect.signature(func)
307
+ bound_args = sig.bind(*args, **kwargs)
308
+ bound_args.apply_defaults()
309
+
310
+ for param_name, value in bound_args.arguments.items():
311
+ if param_name == 'self' or value is None:
312
+ continue
313
+
314
+ if isinstance(value, int) or isinstance(value, float):
315
+ if value < 0:
316
+ errors.append(f"{param_name} must be non-negative")
317
+
318
+ if isinstance(value, str):
319
+ if len(value.strip()) == 0:
320
+ errors.append(f"{param_name} cannot be blank")
321
+
322
+ if errors:
323
+ raise Exception("Validation failed: " + "; ".join(errors))
324
+
325
+ return func(*args, **kwargs)
326
+ return wrapper
327
+ return decorator
328
+
329
+
330
+ # ==================== 注解处理映射 ====================
331
+ CLOUD_ANNOTATION_DECORATORS = {
332
+ SentinelResource: sentinel_resource_decorator,
333
+ GlobalTransactional: global_transactional_decorator,
334
+ RefreshScope: refresh_scope_decorator,
335
+ LoadBalanced: load_balanced_decorator,
336
+ Valid: valid_decorator,
337
+ Validated: validated_decorator,
338
+ }
339
+
340
+
341
+ def apply_cloud_annotations(target: Any, method: Callable = None) -> Any:
342
+ """应用所有 Cloud 注解"""
343
+ if method is None:
344
+ # 类级别注解
345
+ annotations = getattr(target, '__spring_annotations__', [])
346
+ for annotation in annotations:
347
+ decorator_func = CLOUD_ANNOTATION_DECORATORS.get(type(annotation))
348
+ if decorator_func:
349
+ target = decorator_func(annotation)(target)
350
+ return target
351
+ else:
352
+ # 方法级别注解
353
+ annotations = getattr(method, '__spring_annotations__', [])
354
+ wrapped = method
355
+ for annotation in annotations:
356
+ decorator_func = CLOUD_ANNOTATION_DECORATORS.get(type(annotation))
357
+ if decorator_func:
358
+ wrapped = decorator_func(annotation)(wrapped)
359
+
360
+ return wrapped
361
+
362
+
363
+ def get_sentinel_stats(resource_key: str = None) -> Dict[str, dict]:
364
+ """获取Sentinel统计数据(从内嵌引擎获取,Redis作为补充)"""
365
+ result = sentinel_engine.get_resource_stats(resource_key)
366
+ redis = redis_client.get_client()
367
+ if redis is not None and resource_key:
368
+ try:
369
+ data = redis.hgetall(f"sentinel_stats:{resource_key}")
370
+ if data:
371
+ result.setdefault(resource_key, {})
372
+ result[resource_key]['redis'] = {
373
+ "total": int(data.get("total", 0)),
374
+ "blocked": int(data.get("blocked", 0)),
375
+ "error": int(data.get("error", 0)),
376
+ "success": int(data.get("success", 0)),
377
+ "last_access": float(data.get("last_access", 0)),
378
+ }
379
+ except Exception:
380
+ pass
381
+ return result
382
+
383
+
384
+ def get_transaction_context() -> dict:
385
+ """获取当前事务上下文(从Seata管理器获取)"""
386
+ return {
387
+ 'in_transaction': seata_manager.is_in_transaction(),
388
+ 'tx_id': seata_manager.get_current_tx_id(),
389
+ 'status': seata_manager.get_transaction_status(),
390
+ }
391
+
392
+
393
+ def trigger_config_refresh() -> None:
394
+ """触发全局配置刷新"""
395
+ with _refresh_lock:
396
+ for key, entry in _refresh_scope_cache.items():
397
+ entry["last_refresh"] = time.time()
398
+ entry["config_version"] += 1
399
+ logger.info(f"[RefreshScope] Config refreshed for {key}, version={entry['config_version']}")
400
+
401
+
402
+ def get_refresh_scope_cache() -> Dict[str, dict]:
403
+ """获取刷新作用域缓存"""
404
+ return dict(_refresh_scope_cache)