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.
- spring/__init__.py +66 -0
- spring/ai/__init__.py +78 -0
- spring/ai/advisors.py +139 -0
- spring/ai/annotations.py +74 -0
- spring/ai/autoconfig.py +481 -0
- spring/ai/core.py +391 -0
- spring/ai/etl.py +188 -0
- spring/ai/memory.py +109 -0
- spring/ai/observability.py +129 -0
- spring/ai/providers.py +789 -0
- spring/ai/resilience.py +258 -0
- spring/ai/tools.py +106 -0
- spring/ai/vectorstore.py +303 -0
- spring/annotations/__init__.py +188 -0
- spring/annotations/cache.py +126 -0
- spring/annotations/cloud.py +207 -0
- spring/annotations/conditional.py +272 -0
- spring/annotations/core.py +864 -0
- spring/annotations/messaging.py +107 -0
- spring/aop/__init__.py +4 -0
- spring/aop/cloud_aop.py +404 -0
- spring/aop/comprehensive_aop.py +1015 -0
- spring/aop/method_interceptor.py +19 -0
- spring/aop/proxy_factory.py +55 -0
- spring/cloud/__init__.py +76 -0
- spring/cloud/discovery.py +364 -0
- spring/cloud/feign.py +469 -0
- spring/cloud/gateway.py +452 -0
- spring/cloud/load_balancer.py +149 -0
- spring/cloud/seata.py +557 -0
- spring/cloud/sentinel.py +525 -0
- spring/cloud/tracer.py +337 -0
- spring/config/__init__.py +21 -0
- spring/config/binding.py +206 -0
- spring/config/config_loader.py +405 -0
- spring/context/__init__.py +13 -0
- spring/context/application_context.py +589 -0
- spring/context/bean_definition.py +70 -0
- spring/context/bean_factory.py +1052 -0
- spring/context/registry.py +58 -0
- spring/context/scanner.py +106 -0
- spring/core/__init__.py +3 -0
- spring/core/graceful_shutdown.py +196 -0
- spring/core/typing_utils.py +50 -0
- spring/csv/__init__.py +52 -0
- spring/csv/annotations.py +402 -0
- spring/csv/converters.py +69 -0
- spring/csv/easy_csv.py +95 -0
- spring/csv/exceptions.py +27 -0
- spring/csv/reader.py +195 -0
- spring/csv/writer.py +155 -0
- spring/data/__init__.py +54 -0
- spring/data/page.py +181 -0
- spring/data/repository.py +274 -0
- spring/data/specification.py +228 -0
- spring/datasource/__init__.py +66 -0
- spring/datasource/annotations.py +133 -0
- spring/datasource/context.py +69 -0
- spring/datasource/dynamic.py +148 -0
- spring/event/__init__.py +7 -0
- spring/event/publisher.py +69 -0
- spring/excel/__init__.py +51 -0
- spring/excel/annotations.py +405 -0
- spring/excel/converters.py +231 -0
- spring/excel/easy_excel.py +94 -0
- spring/excel/exceptions.py +31 -0
- spring/excel/reader.py +254 -0
- spring/excel/style.py +95 -0
- spring/excel/writer.py +197 -0
- spring/i18n/__init__.py +97 -0
- spring/i18n/accessor.py +94 -0
- spring/i18n/auto_config.py +177 -0
- spring/i18n/holder.py +106 -0
- spring/i18n/locale.py +152 -0
- spring/i18n/locale_resolver.py +367 -0
- spring/i18n/message_source.py +250 -0
- spring/i18n/middleware.py +79 -0
- spring/i18n/properties.py +168 -0
- spring/i18n/sources.py +255 -0
- spring/logging/__init__.py +1 -0
- spring/logging/loguru_logger.py +228 -0
- spring/main.py +378 -0
- spring/messaging/__init__.py +1 -0
- spring/messaging/rabbitmq.py +302 -0
- spring/monitoring/__init__.py +1 -0
- spring/monitoring/prometheus.py +199 -0
- spring/orm/__init__.py +258 -0
- spring/orm/database.py +222 -0
- spring/orm/ddl_auto.py +1217 -0
- spring/orm/migration.py +419 -0
- spring/orm/mybatis_integration.py +400 -0
- spring/orm/pymybatis/__init__.py +86 -0
- spring/orm/pymybatis/annotations/__init__.py +30 -0
- spring/orm/pymybatis/annotations/annotations.py +332 -0
- spring/orm/pymybatis/cache/__init__.py +47 -0
- spring/orm/pymybatis/cache/cache.py +371 -0
- spring/orm/pymybatis/cache/redis_cache.py +434 -0
- spring/orm/pymybatis/circuit_breaker/__init__.py +21 -0
- spring/orm/pymybatis/circuit_breaker/circuit_breaker.py +424 -0
- spring/orm/pymybatis/configuration.py +525 -0
- spring/orm/pymybatis/core/__init__.py +10 -0
- spring/orm/pymybatis/core/sql_session.py +1382 -0
- spring/orm/pymybatis/core/sql_session_factory.py +76 -0
- spring/orm/pymybatis/dialect/__init__.py +9 -0
- spring/orm/pymybatis/dialect/dialect.py +445 -0
- spring/orm/pymybatis/dynamic_sql/__init__.py +9 -0
- spring/orm/pymybatis/dynamic_sql/dynamic_sql.py +900 -0
- spring/orm/pymybatis/interceptor/__init__.py +31 -0
- spring/orm/pymybatis/interceptor/interceptor.py +427 -0
- spring/orm/pymybatis/mapper/__init__.py +9 -0
- spring/orm/pymybatis/mapper/mapper.py +540 -0
- spring/orm/pymybatis/metrics/__init__.py +41 -0
- spring/orm/pymybatis/metrics/metrics.py +595 -0
- spring/orm/pymybatis/pool/__init__.py +9 -0
- spring/orm/pymybatis/pool/connection_pool.py +711 -0
- spring/orm/pymybatis/security/__init__.py +19 -0
- spring/orm/pymybatis/security/access_control.py +415 -0
- spring/orm/pymybatis/security/password_encoder.py +293 -0
- spring/orm/pymybatis/security/sensitive_data_masker.py +326 -0
- spring/orm/pymybatis/security/sql_injection_detector.py +675 -0
- spring/orm/pymybatis/transaction/__init__.py +9 -0
- spring/orm/pymybatis/transaction/transaction.py +288 -0
- spring/orm/pymybatis/type_handler/__init__.py +37 -0
- spring/orm/pymybatis/type_handler/type_handler.py +473 -0
- spring/orm/pymybatis/version.py +9 -0
- spring/orm/pymybatis/xml_parser/__init__.py +9 -0
- spring/orm/pymybatis/xml_parser/xml_parser.py +761 -0
- spring/retry/__init__.py +12 -0
- spring/retry/retry_annotations.py +71 -0
- spring/retry/retry_decorator.py +155 -0
- spring/scheduling/__init__.py +3 -0
- spring/scheduling/scheduler.py +389 -0
- spring/security/__init__.py +39 -0
- spring/security/jwt_utils.py +281 -0
- spring/security/replay_protection.py +206 -0
- spring/security/secret_manager.py +226 -0
- spring/security/security_aop.py +248 -0
- spring/security/security_context.py +172 -0
- spring/test/__init__.py +45 -0
- spring/test/slicing.py +341 -0
- spring/tracing/__init__.py +11 -0
- spring/tracing/skywalking.py +229 -0
- spring/tx/__init__.py +52 -0
- spring/tx/events.py +172 -0
- spring/tx/synchronization.py +143 -0
- spring/utils/__init__.py +5 -0
- spring/utils/banner.py +32 -0
- spring/utils/logger.py +73 -0
- spring/utils/redis_client.py +526 -0
- spring/validation/__init__.py +55 -0
- spring/validation/aop.py +141 -0
- spring/validation/constraints.py +357 -0
- spring/validation/exceptions.py +55 -0
- spring/validation/validator.py +139 -0
- spring/web/__init__.py +12 -0
- spring/web/actuator.py +319 -0
- spring/web/exception_handler.py +61 -0
- spring/web/health.py +399 -0
- spring/web/interceptor.py +91 -0
- spring/web/result.py +44 -0
- spring/web/swagger.py +601 -0
- spring/web/web_context.py +755 -0
- spring/websocket/__init__.py +86 -0
- spring/websocket/annotations.py +169 -0
- spring/websocket/broker.py +238 -0
- spring/websocket/exceptions.py +26 -0
- spring/websocket/handler.py +243 -0
- spring/websocket/router.py +526 -0
- spring/websocket/session.py +216 -0
- springbootai-1.8.0.dist-info/METADATA +2796 -0
- springbootai-1.8.0.dist-info/RECORD +175 -0
- springbootai-1.8.0.dist-info/WHEEL +5 -0
- springbootai-1.8.0.dist-info/entry_points.txt +2 -0
- springbootai-1.8.0.dist-info/licenses/LICENSE +7 -0
- 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
spring/aop/cloud_aop.py
ADDED
|
@@ -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)
|