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,1015 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Spring AOP 切面实现(企业级版本)
|
|
3
|
+
使用 Redis 持久化存储,支持真正的分布式锁、限流、熔断等功能
|
|
4
|
+
"""
|
|
5
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
6
|
+
import asyncio
|
|
7
|
+
import time
|
|
8
|
+
import functools
|
|
9
|
+
import threading
|
|
10
|
+
import hashlib
|
|
11
|
+
import secrets
|
|
12
|
+
import re
|
|
13
|
+
import logging
|
|
14
|
+
import inspect
|
|
15
|
+
from spring.annotations.core import (
|
|
16
|
+
RateLimit,
|
|
17
|
+
CircuitBreaker,
|
|
18
|
+
Idempotent,
|
|
19
|
+
AuditLog,
|
|
20
|
+
FeatureToggle,
|
|
21
|
+
Lock,
|
|
22
|
+
Metrics,
|
|
23
|
+
Synchronized,
|
|
24
|
+
Validate,
|
|
25
|
+
Trace,
|
|
26
|
+
Retryable,
|
|
27
|
+
)
|
|
28
|
+
from spring.utils.redis_client import redis_client
|
|
29
|
+
|
|
30
|
+
# Bean Validation 方法级切面(@BeanValidate)—— 纯标准库模块,无可选依赖
|
|
31
|
+
from spring.validation.aop import (
|
|
32
|
+
BeanValidate as _BeanValidate,
|
|
33
|
+
bean_validate_decorator as _bean_validate_decorator,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger("Spring.AOP")
|
|
37
|
+
|
|
38
|
+
# ==================== 本地缓存(读写分离,提升性能) ====================
|
|
39
|
+
_rate_limit_local_cache: Dict[str, List[float]] = {}
|
|
40
|
+
_circuit_breaker_local_cache: Dict[str, dict] = {}
|
|
41
|
+
_idempotent_local_cache: Dict[str, Any] = {}
|
|
42
|
+
_idempotent_expire_times: Dict[str, float] = {}
|
|
43
|
+
_metrics_local_cache: Dict[str, dict] = {}
|
|
44
|
+
_trace_context = threading.local()
|
|
45
|
+
|
|
46
|
+
# 本地缓存刷新间隔(秒)
|
|
47
|
+
_LOCAL_CACHE_TTL = 5
|
|
48
|
+
|
|
49
|
+
# 分段锁,按 key 的哈希值分片,减少锁竞争
|
|
50
|
+
_NUM_SEGMENTS = 32
|
|
51
|
+
_segment_locks = [threading.Lock() for _ in range(_NUM_SEGMENTS)]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _get_segment_lock(key: str) -> threading.Lock:
|
|
55
|
+
"""根据 key 获取对应的分段锁"""
|
|
56
|
+
if isinstance(key, str):
|
|
57
|
+
return _segment_locks[hash(key) % _NUM_SEGMENTS]
|
|
58
|
+
return _segment_locks[0]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _resolve_dynamic_key(key_template: str, func: Callable, args: tuple, kwargs: dict) -> str:
|
|
62
|
+
"""解析动态键模板,如 "user_id" 或 "stock_{product_id}" """
|
|
63
|
+
if not key_template:
|
|
64
|
+
return key_template
|
|
65
|
+
|
|
66
|
+
# 获取函数签名,将位置参数转换为命名参数
|
|
67
|
+
sig = inspect.signature(func)
|
|
68
|
+
try:
|
|
69
|
+
bound_args = sig.bind(*args, **kwargs)
|
|
70
|
+
bound_args.apply_defaults()
|
|
71
|
+
all_params = bound_args.arguments
|
|
72
|
+
except (ValueError, TypeError):
|
|
73
|
+
all_params = kwargs
|
|
74
|
+
|
|
75
|
+
# 方式1:{param_name} 格式的占位符
|
|
76
|
+
if '{' in key_template and '}' in key_template:
|
|
77
|
+
try:
|
|
78
|
+
return key_template.format(**all_params)
|
|
79
|
+
except (KeyError, IndexError):
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
# 方式2:直接是参数名
|
|
83
|
+
if key_template in all_params:
|
|
84
|
+
return str(all_params[key_template])
|
|
85
|
+
|
|
86
|
+
# 都不匹配,返回原始模板
|
|
87
|
+
return key_template
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ==================== RateLimit 限流切面(Redis持久化) ====================
|
|
91
|
+
def rate_limit_decorator(annotation: RateLimit):
|
|
92
|
+
def decorator(func: Callable) -> Callable:
|
|
93
|
+
@functools.wraps(func)
|
|
94
|
+
def wrapper(*args, **kwargs):
|
|
95
|
+
base_key = annotation.key or f"{func.__module__}.{func.__name__}"
|
|
96
|
+
# 解析动态key
|
|
97
|
+
key = _resolve_dynamic_key(base_key, func, args, kwargs)
|
|
98
|
+
if key == base_key and annotation.key:
|
|
99
|
+
key = f"rate_limit:{annotation.key}:{key}"
|
|
100
|
+
else:
|
|
101
|
+
key = f"rate_limit:{key}"
|
|
102
|
+
|
|
103
|
+
now = time.time()
|
|
104
|
+
time_window = annotation.time_window
|
|
105
|
+
max_requests = annotation.max_requests
|
|
106
|
+
|
|
107
|
+
# 尝试使用 Redis 进行限流
|
|
108
|
+
redis = redis_client.get_client()
|
|
109
|
+
if redis is not None:
|
|
110
|
+
try:
|
|
111
|
+
# 使用 Redis sorted set 实现滑动窗口限流
|
|
112
|
+
# 移除过期的请求记录
|
|
113
|
+
redis.zremrangebyscore(key, 0, now - time_window)
|
|
114
|
+
# 获取当前窗口内的请求数
|
|
115
|
+
current_count = redis.zcard(key)
|
|
116
|
+
|
|
117
|
+
if current_count >= max_requests:
|
|
118
|
+
raise Exception(f"Rate limit exceeded: {max_requests} requests per {time_window}s")
|
|
119
|
+
|
|
120
|
+
# 添加当前请求时间戳
|
|
121
|
+
redis.zadd(key, {str(now): now})
|
|
122
|
+
# 设置过期时间,避免内存泄漏
|
|
123
|
+
redis.expire(key, time_window)
|
|
124
|
+
except Exception as e:
|
|
125
|
+
logger.warning(f"Redis rate limit failed, falling back to local: {e}")
|
|
126
|
+
# Redis 失败时回退到本地缓存
|
|
127
|
+
return _rate_limit_local(func, args, kwargs, key, now, time_window, max_requests)
|
|
128
|
+
else:
|
|
129
|
+
# Redis 不可用时使用本地缓存
|
|
130
|
+
return _rate_limit_local(func, args, kwargs, key, now, time_window, max_requests)
|
|
131
|
+
|
|
132
|
+
return func(*args, **kwargs)
|
|
133
|
+
|
|
134
|
+
def _rate_limit_local(func, args, kwargs, key, now, time_window, max_requests):
|
|
135
|
+
"""本地限流实现(回退方案)"""
|
|
136
|
+
with _get_segment_lock(key):
|
|
137
|
+
if key not in _rate_limit_local_cache:
|
|
138
|
+
_rate_limit_local_cache[key] = []
|
|
139
|
+
|
|
140
|
+
# 清理过期的请求记录
|
|
141
|
+
_rate_limit_local_cache[key] = [
|
|
142
|
+
t for t in _rate_limit_local_cache[key]
|
|
143
|
+
if now - t < time_window
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
current_count = len(_rate_limit_local_cache[key])
|
|
147
|
+
|
|
148
|
+
if current_count >= max_requests:
|
|
149
|
+
raise Exception(f"Rate limit exceeded: {max_requests} requests per {time_window}s")
|
|
150
|
+
|
|
151
|
+
_rate_limit_local_cache[key].append(now)
|
|
152
|
+
|
|
153
|
+
return func(*args, **kwargs)
|
|
154
|
+
|
|
155
|
+
return wrapper
|
|
156
|
+
return decorator
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# ==================== CircuitBreaker 熔断切面(Redis持久化) ====================
|
|
160
|
+
def circuit_breaker_decorator(annotation: CircuitBreaker):
|
|
161
|
+
def decorator(func: Callable) -> Callable:
|
|
162
|
+
@functools.wraps(func)
|
|
163
|
+
def wrapper(*args, **kwargs):
|
|
164
|
+
key = f"circuit_breaker:{func.__module__}.{func.__name__}"
|
|
165
|
+
|
|
166
|
+
# 尝试使用 Redis 获取熔断状态
|
|
167
|
+
redis = redis_client.get_client()
|
|
168
|
+
if redis is not None:
|
|
169
|
+
try:
|
|
170
|
+
return _circuit_breaker_redis(func, args, kwargs, key, annotation, redis)
|
|
171
|
+
except Exception as e:
|
|
172
|
+
logger.warning(f"Redis circuit breaker failed, falling back to local: {e}")
|
|
173
|
+
return _circuit_breaker_local(func, args, kwargs, key, annotation)
|
|
174
|
+
else:
|
|
175
|
+
return _circuit_breaker_local(func, args, kwargs, key, annotation)
|
|
176
|
+
|
|
177
|
+
def _circuit_breaker_redis(func, args, kwargs, key, annotation, redis):
|
|
178
|
+
"""Redis 熔断实现"""
|
|
179
|
+
# 获取熔断状态
|
|
180
|
+
state_data = redis.hgetall(key)
|
|
181
|
+
|
|
182
|
+
if not state_data:
|
|
183
|
+
# 初始化状态
|
|
184
|
+
state_data = {
|
|
185
|
+
"failures": "0",
|
|
186
|
+
"last_failure": "0",
|
|
187
|
+
"state": "CLOSED",
|
|
188
|
+
}
|
|
189
|
+
redis.hset(key, mapping=state_data)
|
|
190
|
+
|
|
191
|
+
state = state_data.get("state", "CLOSED")
|
|
192
|
+
failures = int(state_data.get("failures", "0"))
|
|
193
|
+
last_failure = float(state_data.get("last_failure", "0"))
|
|
194
|
+
now = time.time()
|
|
195
|
+
|
|
196
|
+
# 判断是否需要熔断
|
|
197
|
+
if state == "OPEN":
|
|
198
|
+
if now - last_failure > annotation.recovery_timeout:
|
|
199
|
+
# 进入半开状态
|
|
200
|
+
redis.hset(key, "state", "HALF_OPEN", "failures", "0")
|
|
201
|
+
else:
|
|
202
|
+
# 熔断中,直接返回
|
|
203
|
+
if annotation.fallback_method:
|
|
204
|
+
fallback = getattr(args[0], annotation.fallback_method, None) if args else None
|
|
205
|
+
if fallback and callable(fallback):
|
|
206
|
+
return fallback(*args[1:], **kwargs)
|
|
207
|
+
raise Exception(f"Circuit breaker is open for {key}")
|
|
208
|
+
|
|
209
|
+
try:
|
|
210
|
+
result = func(*args, **kwargs)
|
|
211
|
+
|
|
212
|
+
# 成功,重置状态
|
|
213
|
+
redis.hset(key, "failures", "0", "state", "CLOSED")
|
|
214
|
+
|
|
215
|
+
return result
|
|
216
|
+
except Exception as e:
|
|
217
|
+
# 失败,增加失败计数
|
|
218
|
+
new_failures = failures + 1
|
|
219
|
+
redis.hset(key, "failures", str(new_failures), "last_failure", str(time.time()))
|
|
220
|
+
|
|
221
|
+
if new_failures >= annotation.failure_threshold:
|
|
222
|
+
redis.hset(key, "state", "OPEN")
|
|
223
|
+
logger.warning(f"Circuit breaker opened for {key}")
|
|
224
|
+
|
|
225
|
+
raise
|
|
226
|
+
|
|
227
|
+
def _circuit_breaker_local(func, args, kwargs, key, annotation):
|
|
228
|
+
"""本地熔断实现(回退方案)"""
|
|
229
|
+
with _get_segment_lock(key):
|
|
230
|
+
if key not in _circuit_breaker_local_cache:
|
|
231
|
+
_circuit_breaker_local_cache[key] = {
|
|
232
|
+
"failures": 0,
|
|
233
|
+
"last_failure": 0,
|
|
234
|
+
"state": "CLOSED",
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
state = _circuit_breaker_local_cache[key]["state"]
|
|
238
|
+
failures = _circuit_breaker_local_cache[key]["failures"]
|
|
239
|
+
last_failure = _circuit_breaker_local_cache[key]["last_failure"]
|
|
240
|
+
now = time.time()
|
|
241
|
+
|
|
242
|
+
if state == "OPEN":
|
|
243
|
+
if now - last_failure > annotation.recovery_timeout:
|
|
244
|
+
_circuit_breaker_local_cache[key]["state"] = "HALF_OPEN"
|
|
245
|
+
_circuit_breaker_local_cache[key]["failures"] = 0
|
|
246
|
+
else:
|
|
247
|
+
if annotation.fallback_method:
|
|
248
|
+
fallback = getattr(args[0], annotation.fallback_method, None) if args else None
|
|
249
|
+
if fallback and callable(fallback):
|
|
250
|
+
return fallback(*args[1:], **kwargs)
|
|
251
|
+
raise Exception(f"Circuit breaker is open for {key}")
|
|
252
|
+
|
|
253
|
+
try:
|
|
254
|
+
result = func(*args, **kwargs)
|
|
255
|
+
|
|
256
|
+
with _get_segment_lock(key):
|
|
257
|
+
if key in _circuit_breaker_local_cache:
|
|
258
|
+
_circuit_breaker_local_cache[key]["failures"] = 0
|
|
259
|
+
_circuit_breaker_local_cache[key]["state"] = "CLOSED"
|
|
260
|
+
|
|
261
|
+
return result
|
|
262
|
+
except Exception as e:
|
|
263
|
+
with _get_segment_lock(key):
|
|
264
|
+
if key in _circuit_breaker_local_cache:
|
|
265
|
+
_circuit_breaker_local_cache[key]["failures"] += 1
|
|
266
|
+
_circuit_breaker_local_cache[key]["last_failure"] = time.time()
|
|
267
|
+
|
|
268
|
+
if _circuit_breaker_local_cache[key]["failures"] >= annotation.failure_threshold:
|
|
269
|
+
_circuit_breaker_local_cache[key]["state"] = "OPEN"
|
|
270
|
+
logger.warning(f"Circuit breaker opened for {key}")
|
|
271
|
+
|
|
272
|
+
raise
|
|
273
|
+
|
|
274
|
+
return wrapper
|
|
275
|
+
return decorator
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
# ==================== Idempotent 幂等性切面(Redis持久化) ====================
|
|
279
|
+
def idempotent_decorator(annotation: Idempotent):
|
|
280
|
+
def decorator(func: Callable) -> Callable:
|
|
281
|
+
@functools.wraps(func)
|
|
282
|
+
def wrapper(*args, **kwargs):
|
|
283
|
+
# 生成幂等键
|
|
284
|
+
if annotation.key:
|
|
285
|
+
param_value = _resolve_dynamic_key(annotation.key, func, args, kwargs)
|
|
286
|
+
key = f"idempotent:{annotation.prefix}:{param_value}"
|
|
287
|
+
else:
|
|
288
|
+
params_hash = hashlib.sha256(f"{args}{kwargs}".encode()).hexdigest()
|
|
289
|
+
key = f"idempotent:{annotation.prefix}:{params_hash}"
|
|
290
|
+
|
|
291
|
+
now = time.time()
|
|
292
|
+
|
|
293
|
+
# 尝试使用 Redis 实现幂等性
|
|
294
|
+
redis = redis_client.get_client()
|
|
295
|
+
if redis is not None:
|
|
296
|
+
try:
|
|
297
|
+
return _idempotent_redis(func, args, kwargs, key, annotation, now, redis)
|
|
298
|
+
except Exception as e:
|
|
299
|
+
logger.warning(f"Redis idempotent failed, falling back to local: {e}")
|
|
300
|
+
return _idempotent_local(func, args, kwargs, key, annotation, now)
|
|
301
|
+
else:
|
|
302
|
+
return _idempotent_local(func, args, kwargs, key, annotation, now)
|
|
303
|
+
|
|
304
|
+
def _idempotent_redis(func, args, kwargs, key, annotation, now, redis):
|
|
305
|
+
"""Redis 幂等性实现"""
|
|
306
|
+
# 使用 Lua 脚本保证原子性
|
|
307
|
+
script = """
|
|
308
|
+
local key = KEYS[1]
|
|
309
|
+
local result_key = key .. ":result"
|
|
310
|
+
local expire_key = key .. ":expire"
|
|
311
|
+
|
|
312
|
+
-- 检查是否已有缓存结果
|
|
313
|
+
local stored_result = redis.call("get", result_key)
|
|
314
|
+
local expire_time = tonumber(redis.call("get", expire_key) or "0")
|
|
315
|
+
|
|
316
|
+
if stored_result and expire_time > tonumber(ARGV[1]) then
|
|
317
|
+
return stored_result
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
-- 标记正在处理(设置一个短暂的锁)
|
|
321
|
+
local lock_set = redis.call("set", key .. ":processing", "1", "nx", "ex", 5)
|
|
322
|
+
if not lock_set then
|
|
323
|
+
-- 正在处理中,等待一小会儿
|
|
324
|
+
return "PROCESSING"
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
-- 返回 nil 表示需要执行
|
|
328
|
+
return nil
|
|
329
|
+
"""
|
|
330
|
+
|
|
331
|
+
result = redis.eval(script, 1, key, now)
|
|
332
|
+
|
|
333
|
+
if result == "PROCESSING":
|
|
334
|
+
# 正在处理中,等待结果
|
|
335
|
+
wait_end = time.time() + 5
|
|
336
|
+
while time.time() < wait_end:
|
|
337
|
+
stored_result = redis.get(f"{key}:result")
|
|
338
|
+
expire_time = float(redis.get(f"{key}:expire") or "0")
|
|
339
|
+
if stored_result and expire_time > time.time():
|
|
340
|
+
try:
|
|
341
|
+
import json
|
|
342
|
+
return json.loads(stored_result)
|
|
343
|
+
except:
|
|
344
|
+
return stored_result
|
|
345
|
+
time.sleep(0.05)
|
|
346
|
+
raise Exception("Idempotent operation timeout")
|
|
347
|
+
|
|
348
|
+
if result is not None:
|
|
349
|
+
# 有缓存结果,直接返回
|
|
350
|
+
try:
|
|
351
|
+
import json
|
|
352
|
+
return json.loads(result)
|
|
353
|
+
except:
|
|
354
|
+
return result
|
|
355
|
+
|
|
356
|
+
# 需要执行
|
|
357
|
+
try:
|
|
358
|
+
result = func(*args, **kwargs)
|
|
359
|
+
|
|
360
|
+
# 缓存结果
|
|
361
|
+
try:
|
|
362
|
+
import json
|
|
363
|
+
result_str = json.dumps(result)
|
|
364
|
+
except:
|
|
365
|
+
result_str = str(result)
|
|
366
|
+
|
|
367
|
+
redis.set(f"{key}:result", result_str, ex=annotation.expire)
|
|
368
|
+
redis.set(f"{key}:expire", time.time() + annotation.expire, ex=annotation.expire)
|
|
369
|
+
|
|
370
|
+
return result
|
|
371
|
+
except:
|
|
372
|
+
# 清理处理标记
|
|
373
|
+
redis.delete(f"{key}:processing")
|
|
374
|
+
raise
|
|
375
|
+
|
|
376
|
+
def _idempotent_local(func, args, kwargs, key, annotation, now):
|
|
377
|
+
"""本地幂等性实现(回退方案)"""
|
|
378
|
+
with _get_segment_lock(key):
|
|
379
|
+
# 清理过期条目
|
|
380
|
+
expired_keys = [k for k, t in _idempotent_expire_times.items() if now > t]
|
|
381
|
+
for k in expired_keys:
|
|
382
|
+
_idempotent_local_cache.pop(k, None)
|
|
383
|
+
_idempotent_expire_times.pop(k, None)
|
|
384
|
+
|
|
385
|
+
if key in _idempotent_local_cache:
|
|
386
|
+
stored_value = _idempotent_local_cache[key]
|
|
387
|
+
expire_time = _idempotent_expire_times.get(key, 0)
|
|
388
|
+
|
|
389
|
+
if stored_value is not None and expire_time > now:
|
|
390
|
+
return stored_value
|
|
391
|
+
|
|
392
|
+
# 标记正在处理
|
|
393
|
+
_idempotent_local_cache[key] = None
|
|
394
|
+
_idempotent_expire_times[key] = now + annotation.expire
|
|
395
|
+
|
|
396
|
+
try:
|
|
397
|
+
result = func(*args, **kwargs)
|
|
398
|
+
|
|
399
|
+
with _get_segment_lock(key):
|
|
400
|
+
_idempotent_local_cache[key] = result
|
|
401
|
+
_idempotent_expire_times[key] = time.time() + annotation.expire
|
|
402
|
+
|
|
403
|
+
return result
|
|
404
|
+
except:
|
|
405
|
+
with _get_segment_lock(key):
|
|
406
|
+
_idempotent_local_cache.pop(key, None)
|
|
407
|
+
_idempotent_expire_times.pop(key, None)
|
|
408
|
+
raise
|
|
409
|
+
|
|
410
|
+
return wrapper
|
|
411
|
+
return decorator
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
# ==================== AuditLog 审计日志切面 ====================
|
|
415
|
+
def audit_log_decorator(annotation: AuditLog):
|
|
416
|
+
def decorator(func: Callable) -> Callable:
|
|
417
|
+
@functools.wraps(func)
|
|
418
|
+
def wrapper(*args, **kwargs):
|
|
419
|
+
start_time = time.time()
|
|
420
|
+
result = None
|
|
421
|
+
exception = None
|
|
422
|
+
|
|
423
|
+
try:
|
|
424
|
+
result = func(*args, **kwargs)
|
|
425
|
+
return result
|
|
426
|
+
except Exception as e:
|
|
427
|
+
exception = e
|
|
428
|
+
raise
|
|
429
|
+
finally:
|
|
430
|
+
end_time = time.time()
|
|
431
|
+
execution_time = end_time - start_time
|
|
432
|
+
|
|
433
|
+
# 格式化详情(支持位置参数和命名参数)
|
|
434
|
+
detail = annotation.detail
|
|
435
|
+
if detail:
|
|
436
|
+
try:
|
|
437
|
+
sig = inspect.signature(func)
|
|
438
|
+
bound_args = sig.bind(*args, **kwargs)
|
|
439
|
+
bound_args.apply_defaults()
|
|
440
|
+
all_params = dict(bound_args.arguments)
|
|
441
|
+
all_params.pop('self', None)
|
|
442
|
+
detail = detail.format(**all_params)
|
|
443
|
+
except:
|
|
444
|
+
pass
|
|
445
|
+
|
|
446
|
+
log_msg = (
|
|
447
|
+
f"[AuditLog] Action={annotation.action}, "
|
|
448
|
+
f"Target={annotation.target}, "
|
|
449
|
+
f"Detail={detail}, "
|
|
450
|
+
f"Method={func.__name__}, "
|
|
451
|
+
f"Status={'SUCCESS' if exception is None else 'FAILED'}, "
|
|
452
|
+
f"Duration={execution_time:.4f}s"
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
log_level = getattr(logger, annotation.level.lower(), logger.info)
|
|
456
|
+
log_level(log_msg)
|
|
457
|
+
return wrapper
|
|
458
|
+
return decorator
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
# ==================== FeatureToggle 功能开关注解 ====================
|
|
462
|
+
def feature_toggle_decorator(annotation: FeatureToggle):
|
|
463
|
+
def decorator(func: Callable) -> Callable:
|
|
464
|
+
@functools.wraps(func)
|
|
465
|
+
def wrapper(*args, **kwargs):
|
|
466
|
+
import os
|
|
467
|
+
|
|
468
|
+
# 优先从 Redis 获取开关状态
|
|
469
|
+
redis = redis_client.get_client()
|
|
470
|
+
toggle_value = None
|
|
471
|
+
|
|
472
|
+
if redis is not None:
|
|
473
|
+
try:
|
|
474
|
+
toggle_value = redis.get(f"feature:{annotation.name}")
|
|
475
|
+
except:
|
|
476
|
+
pass
|
|
477
|
+
|
|
478
|
+
# 如果 Redis 没有,从环境变量获取
|
|
479
|
+
if toggle_value is None:
|
|
480
|
+
toggle_value = os.getenv(f"FEATURE_{annotation.name.upper()}", str(annotation.default))
|
|
481
|
+
|
|
482
|
+
enabled = toggle_value.lower() in ('true', '1', 'yes', 'enabled')
|
|
483
|
+
|
|
484
|
+
if not enabled:
|
|
485
|
+
raise Exception(f"Feature '{annotation.name}' is not enabled")
|
|
486
|
+
|
|
487
|
+
return func(*args, **kwargs)
|
|
488
|
+
return wrapper
|
|
489
|
+
return decorator
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
# ==================== Lock 分布式锁切面(Redis实现) ====================
|
|
493
|
+
def lock_decorator(annotation: Lock):
|
|
494
|
+
def decorator(func: Callable) -> Callable:
|
|
495
|
+
@functools.wraps(func)
|
|
496
|
+
def wrapper(*args, **kwargs):
|
|
497
|
+
# 生成锁键
|
|
498
|
+
if annotation.key:
|
|
499
|
+
resolved_key = _resolve_dynamic_key(annotation.key, func, args, kwargs)
|
|
500
|
+
lock_key = f"{annotation.prefix}:{resolved_key}"
|
|
501
|
+
else:
|
|
502
|
+
lock_key = f"{annotation.prefix}:{func.__module__}.{func.__name__}"
|
|
503
|
+
|
|
504
|
+
# 尝试使用 Redis 分布式锁
|
|
505
|
+
redis = redis_client.get_client()
|
|
506
|
+
if redis is not None:
|
|
507
|
+
try:
|
|
508
|
+
return _lock_redis(func, args, kwargs, lock_key, annotation, redis)
|
|
509
|
+
except Exception as e:
|
|
510
|
+
logger.warning(f"Redis lock failed, falling back to local: {e}")
|
|
511
|
+
return _lock_local(func, args, kwargs, lock_key, annotation)
|
|
512
|
+
else:
|
|
513
|
+
return _lock_local(func, args, kwargs, lock_key, annotation)
|
|
514
|
+
|
|
515
|
+
def _lock_redis(func, args, kwargs, lock_key, annotation, redis):
|
|
516
|
+
"""Redis 分布式锁实现"""
|
|
517
|
+
lock_id = redis_client.acquire_lock(lock_key, timeout=annotation.expire, wait_timeout=annotation.wait_timeout)
|
|
518
|
+
|
|
519
|
+
if lock_id is None:
|
|
520
|
+
raise Exception(f"Could not acquire lock for {lock_key}")
|
|
521
|
+
|
|
522
|
+
try:
|
|
523
|
+
return func(*args, **kwargs)
|
|
524
|
+
finally:
|
|
525
|
+
redis_client.release_lock(lock_key, lock_id)
|
|
526
|
+
|
|
527
|
+
def _lock_local(func, args, kwargs, lock_key, annotation):
|
|
528
|
+
"""本地锁实现(回退方案)"""
|
|
529
|
+
# 使用分段锁
|
|
530
|
+
with _get_segment_lock(lock_key):
|
|
531
|
+
return func(*args, **kwargs)
|
|
532
|
+
|
|
533
|
+
return wrapper
|
|
534
|
+
return decorator
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
# ==================== Metrics 指标监控切面(Redis持久化) ====================
|
|
538
|
+
def metrics_decorator(annotation: Metrics):
|
|
539
|
+
def decorator(func: Callable) -> Callable:
|
|
540
|
+
@functools.wraps(func)
|
|
541
|
+
def wrapper(*args, **kwargs):
|
|
542
|
+
name = annotation.name or f"{func.__module__}.{func.__name__}"
|
|
543
|
+
key = f"metrics:{name}"
|
|
544
|
+
|
|
545
|
+
start_time = time.time()
|
|
546
|
+
result = None
|
|
547
|
+
has_error = False
|
|
548
|
+
|
|
549
|
+
try:
|
|
550
|
+
result = func(*args, **kwargs)
|
|
551
|
+
return result
|
|
552
|
+
except Exception as e:
|
|
553
|
+
has_error = True
|
|
554
|
+
raise
|
|
555
|
+
finally:
|
|
556
|
+
duration = time.time() - start_time
|
|
557
|
+
|
|
558
|
+
# 更新本地缓存
|
|
559
|
+
with _get_segment_lock(key):
|
|
560
|
+
if name not in _metrics_local_cache:
|
|
561
|
+
_metrics_local_cache[name] = {
|
|
562
|
+
"count": 0,
|
|
563
|
+
"total_time": 0,
|
|
564
|
+
"errors": 0,
|
|
565
|
+
"min_time": float('inf'),
|
|
566
|
+
"max_time": float('-inf'),
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
_metrics_local_cache[name]["count"] += 1
|
|
570
|
+
_metrics_local_cache[name]["total_time"] += duration
|
|
571
|
+
if has_error:
|
|
572
|
+
_metrics_local_cache[name]["errors"] += 1
|
|
573
|
+
_metrics_local_cache[name]["min_time"] = min(_metrics_local_cache[name]["min_time"], duration)
|
|
574
|
+
_metrics_local_cache[name]["max_time"] = max(_metrics_local_cache[name]["max_time"], duration)
|
|
575
|
+
|
|
576
|
+
# 每 100 次调用同步到 Redis
|
|
577
|
+
with _get_segment_lock(key):
|
|
578
|
+
if _metrics_local_cache[name]["count"] % 100 == 0:
|
|
579
|
+
avg_time = _metrics_local_cache[name]["total_time"] / _metrics_local_cache[name]["count"]
|
|
580
|
+
logger.info(
|
|
581
|
+
f"[Metrics] {name} - "
|
|
582
|
+
f"Count={_metrics_local_cache[name]['count']}, "
|
|
583
|
+
f"AvgTime={avg_time:.4f}s, "
|
|
584
|
+
f"Min={_metrics_local_cache[name]['min_time']:.4f}s, "
|
|
585
|
+
f"Max={_metrics_local_cache[name]['max_time']:.4f}s, "
|
|
586
|
+
f"Errors={_metrics_local_cache[name]['errors']}"
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
# 同步到 Redis
|
|
590
|
+
redis = redis_client.get_client()
|
|
591
|
+
if redis is not None:
|
|
592
|
+
try:
|
|
593
|
+
# 使用 Hash 存储指标
|
|
594
|
+
metrics_data = {
|
|
595
|
+
"count": str(_metrics_local_cache[name]["count"]),
|
|
596
|
+
"total_time": str(_metrics_local_cache[name]["total_time"]),
|
|
597
|
+
"errors": str(_metrics_local_cache[name]["errors"]),
|
|
598
|
+
"min_time": str(_metrics_local_cache[name]["min_time"]),
|
|
599
|
+
"max_time": str(_metrics_local_cache[name]["max_time"]),
|
|
600
|
+
"last_update": str(time.time()),
|
|
601
|
+
}
|
|
602
|
+
redis.hset(key, mapping=metrics_data)
|
|
603
|
+
except Exception as e:
|
|
604
|
+
logger.warning(f"Redis metrics sync failed: {e}")
|
|
605
|
+
|
|
606
|
+
return wrapper
|
|
607
|
+
return decorator
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
# ==================== Synchronized 方法同步切面 ====================
|
|
611
|
+
def synchronized_decorator(annotation: Synchronized):
|
|
612
|
+
def decorator(func: Callable) -> Callable:
|
|
613
|
+
@functools.wraps(func)
|
|
614
|
+
def wrapper(*args, **kwargs):
|
|
615
|
+
lock_name = annotation.lock_name or f"{func.__module__}.{func.__name__}"
|
|
616
|
+
|
|
617
|
+
# 使用分段锁
|
|
618
|
+
with _get_segment_lock(lock_name):
|
|
619
|
+
return func(*args, **kwargs)
|
|
620
|
+
|
|
621
|
+
return wrapper
|
|
622
|
+
return decorator
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
# ==================== Validate 参数校验切面 ====================
|
|
626
|
+
def validate_decorator(annotation: Validate):
|
|
627
|
+
def decorator(func: Callable) -> Callable:
|
|
628
|
+
@functools.wraps(func)
|
|
629
|
+
def wrapper(*args, **kwargs):
|
|
630
|
+
errors = []
|
|
631
|
+
|
|
632
|
+
sig = inspect.signature(func)
|
|
633
|
+
bound_args = sig.bind(*args, **kwargs)
|
|
634
|
+
bound_args.apply_defaults()
|
|
635
|
+
|
|
636
|
+
for param_name, value in bound_args.arguments.items():
|
|
637
|
+
if param_name == 'self' or value is None:
|
|
638
|
+
continue
|
|
639
|
+
|
|
640
|
+
if annotation.field is not None and param_name != annotation.field:
|
|
641
|
+
continue
|
|
642
|
+
|
|
643
|
+
if annotation.min_length is not None and len(str(value)) < annotation.min_length:
|
|
644
|
+
errors.append(f"{param_name} length must be at least {annotation.min_length}")
|
|
645
|
+
|
|
646
|
+
if annotation.max_length is not None and len(str(value)) > annotation.max_length:
|
|
647
|
+
errors.append(f"{param_name} length must be at most {annotation.max_length}")
|
|
648
|
+
|
|
649
|
+
try:
|
|
650
|
+
num_value = float(value)
|
|
651
|
+
if annotation.min is not None and num_value < annotation.min:
|
|
652
|
+
errors.append(f"{param_name} must be at least {annotation.min}")
|
|
653
|
+
if annotation.max is not None and num_value > annotation.max:
|
|
654
|
+
errors.append(f"{param_name} must be at most {annotation.max}")
|
|
655
|
+
except (ValueError, TypeError):
|
|
656
|
+
pass
|
|
657
|
+
|
|
658
|
+
if annotation.regex is not None:
|
|
659
|
+
if not re.match(annotation.regex, str(value)):
|
|
660
|
+
errors.append(f"{param_name} does not match pattern")
|
|
661
|
+
|
|
662
|
+
if errors:
|
|
663
|
+
message = annotation.message or "; ".join(errors)
|
|
664
|
+
raise Exception(message)
|
|
665
|
+
|
|
666
|
+
return func(*args, **kwargs)
|
|
667
|
+
return wrapper
|
|
668
|
+
return decorator
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
# ==================== Trace 分布式追踪切面 ====================
|
|
672
|
+
def trace_decorator(annotation: Trace):
|
|
673
|
+
def decorator(func: Callable) -> Callable:
|
|
674
|
+
@functools.wraps(func)
|
|
675
|
+
def wrapper(*args, **kwargs):
|
|
676
|
+
# 获取或生成 trace_id
|
|
677
|
+
trace_id = getattr(_trace_context, 'trace_id', None)
|
|
678
|
+
if not trace_id:
|
|
679
|
+
trace_id = secrets.token_hex(16)
|
|
680
|
+
|
|
681
|
+
_trace_context.trace_id = trace_id
|
|
682
|
+
|
|
683
|
+
span_name = annotation.span_name or func.__name__
|
|
684
|
+
|
|
685
|
+
logger.info(f"[Trace] Start span={span_name}, trace_id={trace_id}")
|
|
686
|
+
|
|
687
|
+
start_time = time.time()
|
|
688
|
+
|
|
689
|
+
try:
|
|
690
|
+
result = func(*args, **kwargs)
|
|
691
|
+
duration = time.time() - start_time
|
|
692
|
+
logger.info(f"[Trace] End span={span_name}, trace_id={trace_id}, duration={duration:.4f}s")
|
|
693
|
+
return result
|
|
694
|
+
except Exception as e:
|
|
695
|
+
duration = time.time() - start_time
|
|
696
|
+
logger.error(f"[Trace] Error span={span_name}, trace_id={trace_id}, duration={duration:.4f}s, error={str(e)}")
|
|
697
|
+
raise
|
|
698
|
+
return wrapper
|
|
699
|
+
return decorator
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
# ==================== Retryable 重试切面 ====================
|
|
703
|
+
def _retryable_decorator(annotation: Retryable):
|
|
704
|
+
"""
|
|
705
|
+
@Retryable重试切面实现(与Spring Annotation兼容)
|
|
706
|
+
|
|
707
|
+
支持:
|
|
708
|
+
- 指定重试的异常类型
|
|
709
|
+
- 指定不重试的异常类型
|
|
710
|
+
- 最大重试次数
|
|
711
|
+
- 退避策略(固定延迟/指数退避)
|
|
712
|
+
- 随机因子
|
|
713
|
+
- 恢复方法(recover)
|
|
714
|
+
"""
|
|
715
|
+
def decorator(func: Callable) -> Callable:
|
|
716
|
+
if inspect.iscoroutinefunction(func):
|
|
717
|
+
@functools.wraps(func)
|
|
718
|
+
async def async_wrapper(*args, **kwargs):
|
|
719
|
+
last_exception = None
|
|
720
|
+
|
|
721
|
+
for retry_count in range(1, annotation.max_retries + 1):
|
|
722
|
+
try:
|
|
723
|
+
return await func(*args, **kwargs)
|
|
724
|
+
except Exception as exc:
|
|
725
|
+
last_exception = exc
|
|
726
|
+
if isinstance(exc, annotation.exclude):
|
|
727
|
+
raise
|
|
728
|
+
if not isinstance(exc, annotation.value):
|
|
729
|
+
raise
|
|
730
|
+
if retry_count >= annotation.max_retries:
|
|
731
|
+
break
|
|
732
|
+
|
|
733
|
+
delay = _calculate_retry_backoff(
|
|
734
|
+
annotation.backoff, retry_count
|
|
735
|
+
)
|
|
736
|
+
logger.info(
|
|
737
|
+
f"[Retry] Retrying {func.__name__} "
|
|
738
|
+
f"(attempt {retry_count}/{annotation.max_retries - 1}), "
|
|
739
|
+
f"exception: {type(exc).__name__}, delay: {delay:.2f}ms"
|
|
740
|
+
)
|
|
741
|
+
await asyncio.sleep(delay / 1000.0)
|
|
742
|
+
|
|
743
|
+
if last_exception and annotation.recover and args:
|
|
744
|
+
recover_func = getattr(args[0], annotation.recover, None)
|
|
745
|
+
if recover_func and callable(recover_func):
|
|
746
|
+
result = recover_func(*args[1:], **kwargs)
|
|
747
|
+
if inspect.isawaitable(result):
|
|
748
|
+
return await result
|
|
749
|
+
return result
|
|
750
|
+
|
|
751
|
+
if last_exception:
|
|
752
|
+
raise last_exception
|
|
753
|
+
|
|
754
|
+
return async_wrapper
|
|
755
|
+
|
|
756
|
+
@functools.wraps(func)
|
|
757
|
+
def wrapper(*args, **kwargs):
|
|
758
|
+
max_retries = annotation.max_retries
|
|
759
|
+
exceptions_to_retry = annotation.value
|
|
760
|
+
exceptions_to_exclude = annotation.exclude
|
|
761
|
+
backoff = annotation.backoff
|
|
762
|
+
recover_method = annotation.recover
|
|
763
|
+
|
|
764
|
+
last_exception = None
|
|
765
|
+
retry_count = 0
|
|
766
|
+
|
|
767
|
+
while retry_count < max_retries:
|
|
768
|
+
try:
|
|
769
|
+
return func(*args, **kwargs)
|
|
770
|
+
except Exception as e:
|
|
771
|
+
last_exception = e
|
|
772
|
+
retry_count += 1
|
|
773
|
+
|
|
774
|
+
# 检查是否是需要排除的异常
|
|
775
|
+
if isinstance(e, exceptions_to_exclude):
|
|
776
|
+
logger.info(f"[Retry] Exception {type(e).__name__} excluded from retry, re-raising")
|
|
777
|
+
raise
|
|
778
|
+
|
|
779
|
+
# 检查是否是需要重试的异常
|
|
780
|
+
if not isinstance(e, exceptions_to_retry):
|
|
781
|
+
logger.info(f"[Retry] Exception {type(e).__name__} not in retry list, re-raising")
|
|
782
|
+
raise
|
|
783
|
+
|
|
784
|
+
# 判断是否需要继续重试
|
|
785
|
+
if retry_count >= max_retries:
|
|
786
|
+
logger.warning(f"[Retry] Max retries ({max_retries}) exceeded for {func.__name__}: {str(e)}")
|
|
787
|
+
break
|
|
788
|
+
|
|
789
|
+
# 计算退避时间
|
|
790
|
+
delay = _calculate_retry_backoff(backoff, retry_count)
|
|
791
|
+
|
|
792
|
+
logger.info(
|
|
793
|
+
f"[Retry] Retrying {func.__name__} (attempt {retry_count}/{max_retries-1}), "
|
|
794
|
+
f"exception: {type(e).__name__}, delay: {delay:.2f}ms"
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
# 等待
|
|
798
|
+
time.sleep(delay / 1000.0)
|
|
799
|
+
|
|
800
|
+
# 重试失败,尝试调用恢复方法
|
|
801
|
+
if last_exception and recover_method:
|
|
802
|
+
logger.info(f"[Retry] Calling recover method '{recover_method}' for {func.__name__}")
|
|
803
|
+
try:
|
|
804
|
+
# 尝试从实例中获取恢复方法
|
|
805
|
+
if args:
|
|
806
|
+
recover_func = getattr(args[0], recover_method, None)
|
|
807
|
+
else:
|
|
808
|
+
recover_func = None
|
|
809
|
+
|
|
810
|
+
if recover_func and callable(recover_func):
|
|
811
|
+
# 移除self参数(如果存在)
|
|
812
|
+
if args:
|
|
813
|
+
return recover_func(*args[1:], **kwargs)
|
|
814
|
+
else:
|
|
815
|
+
return recover_func(**kwargs)
|
|
816
|
+
except Exception as recover_e:
|
|
817
|
+
logger.error(f"[Retry] Recover method '{recover_method}' failed: {recover_e}")
|
|
818
|
+
|
|
819
|
+
# 所有重试都失败,抛出最后一个异常
|
|
820
|
+
if last_exception:
|
|
821
|
+
raise last_exception
|
|
822
|
+
|
|
823
|
+
return wrapper
|
|
824
|
+
return decorator
|
|
825
|
+
|
|
826
|
+
|
|
827
|
+
def _calculate_retry_backoff(backoff, attempt: int) -> float:
|
|
828
|
+
"""
|
|
829
|
+
计算退避时间
|
|
830
|
+
|
|
831
|
+
参数:
|
|
832
|
+
backoff: 退避配置
|
|
833
|
+
attempt: 当前重试次数(从1开始)
|
|
834
|
+
|
|
835
|
+
返回:
|
|
836
|
+
退避时间(毫秒)
|
|
837
|
+
"""
|
|
838
|
+
import random
|
|
839
|
+
|
|
840
|
+
if backoff is None:
|
|
841
|
+
return 1000.0
|
|
842
|
+
|
|
843
|
+
# 获取退避参数
|
|
844
|
+
delay = getattr(backoff, 'delay', 1000)
|
|
845
|
+
max_delay = getattr(backoff, 'max_delay', 10000)
|
|
846
|
+
multiplier = getattr(backoff, 'multiplier', 2.0)
|
|
847
|
+
random_factor = getattr(backoff, 'random_factor', 0.1)
|
|
848
|
+
|
|
849
|
+
# 指数退避:delay * (multiplier ^ (attempt - 1))
|
|
850
|
+
calculated_delay = delay * (multiplier ** (attempt - 1))
|
|
851
|
+
|
|
852
|
+
# 应用随机因子
|
|
853
|
+
if random_factor > 0:
|
|
854
|
+
random_delta = calculated_delay * random_factor
|
|
855
|
+
calculated_delay = calculated_delay + random.uniform(-random_delta, random_delta)
|
|
856
|
+
|
|
857
|
+
# 确保不超过最大延迟
|
|
858
|
+
calculated_delay = min(calculated_delay, max_delay)
|
|
859
|
+
|
|
860
|
+
# 确保不小于0
|
|
861
|
+
calculated_delay = max(calculated_delay, 0)
|
|
862
|
+
|
|
863
|
+
return calculated_delay
|
|
864
|
+
|
|
865
|
+
|
|
866
|
+
# ==================== 注解处理映射 ====================
|
|
867
|
+
ANNOTATION_DECORATORS = {
|
|
868
|
+
RateLimit: rate_limit_decorator,
|
|
869
|
+
CircuitBreaker: circuit_breaker_decorator,
|
|
870
|
+
Idempotent: idempotent_decorator,
|
|
871
|
+
AuditLog: audit_log_decorator,
|
|
872
|
+
FeatureToggle: feature_toggle_decorator,
|
|
873
|
+
Lock: lock_decorator,
|
|
874
|
+
Metrics: metrics_decorator,
|
|
875
|
+
Synchronized: synchronized_decorator,
|
|
876
|
+
Validate: validate_decorator,
|
|
877
|
+
Trace: trace_decorator,
|
|
878
|
+
Retryable: _retryable_decorator,
|
|
879
|
+
# Bean Validation 方法级切面:受管 Bean 方法调用前校验参数对象
|
|
880
|
+
_BeanValidate: _bean_validate_decorator,
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
|
|
884
|
+
def apply_annotations(target: Any, method: Callable) -> Callable:
|
|
885
|
+
"""应用所有自定义注解"""
|
|
886
|
+
annotations = getattr(method, '__spring_annotations__', [])
|
|
887
|
+
|
|
888
|
+
wrapped = method
|
|
889
|
+
remaining_annotations = []
|
|
890
|
+
|
|
891
|
+
for annotation in annotations:
|
|
892
|
+
decorator_func = ANNOTATION_DECORATORS.get(type(annotation))
|
|
893
|
+
if decorator_func:
|
|
894
|
+
wrapped = decorator_func(annotation)(wrapped)
|
|
895
|
+
else:
|
|
896
|
+
remaining_annotations.append(annotation)
|
|
897
|
+
|
|
898
|
+
if remaining_annotations:
|
|
899
|
+
setattr(wrapped, '__spring_annotations__', remaining_annotations)
|
|
900
|
+
|
|
901
|
+
return wrapped
|
|
902
|
+
|
|
903
|
+
|
|
904
|
+
def get_metrics() -> Dict[str, dict]:
|
|
905
|
+
"""获取所有指标数据"""
|
|
906
|
+
# 优先从 Redis 获取
|
|
907
|
+
redis = redis_client.get_client()
|
|
908
|
+
if redis is not None:
|
|
909
|
+
try:
|
|
910
|
+
# 扫描所有 metrics 键
|
|
911
|
+
keys = redis.keys("metrics:*")
|
|
912
|
+
result = {}
|
|
913
|
+
for key in keys:
|
|
914
|
+
data = redis.hgetall(key)
|
|
915
|
+
if data:
|
|
916
|
+
name = key.replace("metrics:", "")
|
|
917
|
+
result[name] = {
|
|
918
|
+
"count": int(data.get("count", "0")),
|
|
919
|
+
"total_time": float(data.get("total_time", "0")),
|
|
920
|
+
"errors": int(data.get("errors", "0")),
|
|
921
|
+
"min_time": float(data.get("min_time", float('inf'))),
|
|
922
|
+
"max_time": float(data.get("max_time", float('-inf'))),
|
|
923
|
+
}
|
|
924
|
+
return result
|
|
925
|
+
except:
|
|
926
|
+
pass
|
|
927
|
+
|
|
928
|
+
# 回退到本地缓存
|
|
929
|
+
return dict(_metrics_local_cache)
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
def reset_circuit_breaker(key: str) -> None:
|
|
933
|
+
"""重置熔断器状态"""
|
|
934
|
+
# 重置 Redis 中的状态
|
|
935
|
+
redis = redis_client.get_client()
|
|
936
|
+
if redis is not None:
|
|
937
|
+
try:
|
|
938
|
+
redis_key = f"circuit_breaker:{key}" if not key.startswith("circuit_breaker:") else key
|
|
939
|
+
redis.hset(redis_key, mapping={
|
|
940
|
+
"failures": "0",
|
|
941
|
+
"last_failure": "0",
|
|
942
|
+
"state": "CLOSED",
|
|
943
|
+
})
|
|
944
|
+
except:
|
|
945
|
+
pass
|
|
946
|
+
|
|
947
|
+
# 重置本地缓存
|
|
948
|
+
local_key = f"circuit_breaker:{key}" if not key.startswith("circuit_breaker:") else key
|
|
949
|
+
if local_key in _circuit_breaker_local_cache:
|
|
950
|
+
_circuit_breaker_local_cache[local_key] = {
|
|
951
|
+
"failures": 0,
|
|
952
|
+
"last_failure": 0,
|
|
953
|
+
"state": "CLOSED",
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
def clear_idempotent_cache(key: str = None) -> None:
|
|
958
|
+
"""清理幂等性缓存"""
|
|
959
|
+
redis = redis_client.get_client()
|
|
960
|
+
|
|
961
|
+
if key:
|
|
962
|
+
# 清理指定键
|
|
963
|
+
if redis is not None:
|
|
964
|
+
try:
|
|
965
|
+
redis_key = f"idempotent:{key}" if not key.startswith("idempotent:") else key
|
|
966
|
+
redis.delete(redis_key)
|
|
967
|
+
redis.delete(f"{redis_key}:result")
|
|
968
|
+
redis.delete(f"{redis_key}:expire")
|
|
969
|
+
redis.delete(f"{redis_key}:processing")
|
|
970
|
+
except:
|
|
971
|
+
pass
|
|
972
|
+
|
|
973
|
+
local_key = f"idempotent:{key}" if not key.startswith("idempotent:") else key
|
|
974
|
+
_idempotent_local_cache.pop(local_key, None)
|
|
975
|
+
_idempotent_expire_times.pop(local_key, None)
|
|
976
|
+
else:
|
|
977
|
+
# 清理所有键
|
|
978
|
+
if redis is not None:
|
|
979
|
+
try:
|
|
980
|
+
keys = redis.keys("idempotent:*")
|
|
981
|
+
if keys:
|
|
982
|
+
redis.delete(*keys)
|
|
983
|
+
except:
|
|
984
|
+
pass
|
|
985
|
+
|
|
986
|
+
_idempotent_local_cache.clear()
|
|
987
|
+
_idempotent_expire_times.clear()
|
|
988
|
+
|
|
989
|
+
|
|
990
|
+
def enable_feature(name: str) -> None:
|
|
991
|
+
"""启用功能"""
|
|
992
|
+
import os
|
|
993
|
+
os.environ[f"FEATURE_{name.upper()}"] = "true"
|
|
994
|
+
|
|
995
|
+
# 同步到 Redis
|
|
996
|
+
redis = redis_client.get_client()
|
|
997
|
+
if redis is not None:
|
|
998
|
+
try:
|
|
999
|
+
redis.set(f"feature:{name}", "true")
|
|
1000
|
+
except:
|
|
1001
|
+
pass
|
|
1002
|
+
|
|
1003
|
+
|
|
1004
|
+
def disable_feature(name: str) -> None:
|
|
1005
|
+
"""禁用功能"""
|
|
1006
|
+
import os
|
|
1007
|
+
os.environ[f"FEATURE_{name.upper()}"] = "false"
|
|
1008
|
+
|
|
1009
|
+
# 同步到 Redis
|
|
1010
|
+
redis = redis_client.get_client()
|
|
1011
|
+
if redis is not None:
|
|
1012
|
+
try:
|
|
1013
|
+
redis.set(f"feature:{name}", "false")
|
|
1014
|
+
except:
|
|
1015
|
+
pass
|