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,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