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,12 @@
1
+ """
2
+ 重试机制模块
3
+ 提供重试注解和重试策略
4
+ """
5
+ from .retry_annotations import Retryable, Backoff
6
+ from .retry_decorator import retryable_decorator
7
+
8
+ __all__ = [
9
+ 'Retryable',
10
+ 'Backoff',
11
+ 'retryable_decorator',
12
+ ]
@@ -0,0 +1,71 @@
1
+ """
2
+ 重试注解定义
3
+ """
4
+ from typing import Type, Tuple, Optional
5
+
6
+
7
+ class Backoff:
8
+ """
9
+ 退避策略配置
10
+
11
+ 参数:
12
+ delay: 初始延迟时间(毫秒),默认1000
13
+ max_delay: 最大延迟时间(毫秒),默认10000
14
+ multiplier: 延迟倍增因子,默认2.0(指数退避)
15
+ random_factor: 随机因子(0.0-1.0),默认0.1
16
+ """
17
+
18
+ def __init__(
19
+ self,
20
+ delay: int = 1000,
21
+ max_delay: int = 10000,
22
+ multiplier: float = 2.0,
23
+ random_factor: float = 0.1
24
+ ):
25
+ self.delay = delay
26
+ self.max_delay = max_delay
27
+ self.multiplier = multiplier
28
+ self.random_factor = random_factor
29
+
30
+
31
+ class Retryable:
32
+ """
33
+ 重试注解
34
+
35
+ 参数:
36
+ value: 要重试的异常类型(或异常类型元组)
37
+ max_retries: 最大重试次数,默认3(包含首次调用)
38
+ backoff: 退避策略配置
39
+ exclude: 不重试的异常类型(或异常类型元组)
40
+ recover: 恢复方法名(当重试失败时调用)
41
+ """
42
+
43
+ def __init__(
44
+ self,
45
+ value: Optional[Tuple[Type[Exception], ...]] = None,
46
+ max_retries: int = 3,
47
+ backoff: Optional[object] = None,
48
+ exclude: Optional[Tuple[Type[Exception], ...]] = None,
49
+ recover: str = "",
50
+ max_attempts: Optional[int] = None,
51
+ ):
52
+ if max_attempts is not None:
53
+ if max_retries != 3 and max_retries != max_attempts:
54
+ raise ValueError("max_retries 与 max_attempts 不能设置为不同值")
55
+ max_retries = max_attempts
56
+ if max_retries <= 0:
57
+ raise ValueError("max_retries 必须大于0")
58
+ if isinstance(backoff, (int, float)):
59
+ if backoff < 0:
60
+ raise ValueError("backoff 延迟不能小于0")
61
+ backoff = Backoff(
62
+ delay=int(backoff),
63
+ max_delay=int(backoff),
64
+ multiplier=1.0,
65
+ random_factor=0.0,
66
+ )
67
+ self.value = value or (Exception,)
68
+ self.max_retries = max_retries
69
+ self.backoff = backoff or Backoff()
70
+ self.exclude = exclude or ()
71
+ self.recover = recover
@@ -0,0 +1,155 @@
1
+ """
2
+ 重试切面实现
3
+ """
4
+ import time
5
+ import random
6
+ import logging
7
+ import functools
8
+ from typing import Callable, Tuple, Type
9
+
10
+ logger = logging.getLogger("Spring.Retry")
11
+
12
+
13
+ def retryable_decorator(annotation):
14
+ """
15
+ @Retryable注解切面实现
16
+
17
+ 支持:
18
+ - 指定重试的异常类型
19
+ - 指定不重试的异常类型
20
+ - 最大重试次数
21
+ - 退避策略(固定延迟/指数退避)
22
+ - 随机因子
23
+ - 恢复方法(recover)
24
+ """
25
+ def decorator(func: Callable) -> Callable:
26
+ @functools.wraps(func)
27
+ def wrapper(*args, **kwargs):
28
+ max_retries = annotation.max_retries
29
+ exceptions_to_retry = annotation.value
30
+ exceptions_to_exclude = annotation.exclude
31
+ backoff = annotation.backoff
32
+ recover_method = annotation.recover
33
+
34
+ last_exception = None
35
+ retry_count = 0
36
+
37
+ while retry_count < max_retries:
38
+ try:
39
+ return func(*args, **kwargs)
40
+ except Exception as e:
41
+ last_exception = e
42
+ retry_count += 1
43
+
44
+ # 检查是否是需要排除的异常
45
+ if isinstance(e, exceptions_to_exclude):
46
+ logger.info(f"[Retry] Exception {type(e).__name__} excluded from retry, re-raising")
47
+ raise
48
+
49
+ # 检查是否是需要重试的异常
50
+ if not isinstance(e, exceptions_to_retry):
51
+ logger.info(f"[Retry] Exception {type(e).__name__} not in retry list, re-raising")
52
+ raise
53
+
54
+ # 判断是否需要继续重试
55
+ if retry_count >= max_retries:
56
+ logger.warning(f"[Retry] Max retries ({max_retries}) exceeded for {func.__name__}: {str(e)}")
57
+ break
58
+
59
+ # 计算退避时间
60
+ delay = _calculate_backoff(backoff, retry_count)
61
+
62
+ logger.info(
63
+ f"[Retry] Retrying {func.__name__} (attempt {retry_count}/{max_retries-1}), "
64
+ f"exception: {type(e).__name__}, delay: {delay:.2f}ms"
65
+ )
66
+
67
+ # 等待
68
+ time.sleep(delay / 1000.0)
69
+
70
+ # 重试失败,尝试调用恢复方法
71
+ if last_exception and recover_method:
72
+ logger.info(f"[Retry] Calling recover method '{recover_method}' for {func.__name__}")
73
+ try:
74
+ # 尝试从实例中获取恢复方法
75
+ if args:
76
+ recover_func = getattr(args[0], recover_method, None)
77
+ else:
78
+ recover_func = None
79
+
80
+ if recover_func and callable(recover_func):
81
+ # 移除self参数(如果存在)
82
+ if args:
83
+ return recover_func(*args[1:], **kwargs)
84
+ else:
85
+ return recover_func(**kwargs)
86
+ except Exception as recover_e:
87
+ logger.error(f"[Retry] Recover method '{recover_method}' failed: {recover_e}")
88
+
89
+ # 所有重试都失败,抛出最后一个异常
90
+ if last_exception:
91
+ raise last_exception
92
+
93
+ return wrapper
94
+ return decorator
95
+
96
+
97
+ def _calculate_backoff(backoff, attempt: int) -> float:
98
+ """
99
+ 计算退避时间
100
+
101
+ 参数:
102
+ backoff: 退避配置
103
+ attempt: 当前重试次数(从1开始)
104
+
105
+ 返回:
106
+ 退避时间(毫秒)
107
+ """
108
+ # 指数退避:delay * (multiplier ^ (attempt - 1))
109
+ delay = backoff.delay * (backoff.multiplier ** (attempt - 1))
110
+
111
+ # 应用随机因子
112
+ if backoff.random_factor > 0:
113
+ random_delta = delay * backoff.random_factor
114
+ delay = delay + random.uniform(-random_delta, random_delta)
115
+
116
+ # 确保不超过最大延迟
117
+ delay = min(delay, backoff.max_delay)
118
+
119
+ # 确保不小于0
120
+ delay = max(delay, 0)
121
+
122
+ return delay
123
+
124
+
125
+ def retry(
126
+ max_retries: int = 3,
127
+ delay: int = 1000,
128
+ max_delay: int = 10000,
129
+ multiplier: float = 2.0,
130
+ exceptions: Tuple[Type[Exception], ...] = (Exception,)
131
+ ) -> Callable:
132
+ """
133
+ 便捷的重试装饰器
134
+
135
+ 参数:
136
+ max_retries: 最大重试次数
137
+ delay: 初始延迟(毫秒)
138
+ max_delay: 最大延迟(毫秒)
139
+ multiplier: 延迟倍增因子
140
+ exceptions: 需要重试的异常类型
141
+
142
+ 使用示例:
143
+ @retry(max_retries=5, delay=500)
144
+ def fetch_data():
145
+ pass
146
+ """
147
+ from spring.retry.retry_annotations import Retryable, Backoff
148
+
149
+ annotation = Retryable(
150
+ value=exceptions,
151
+ max_retries=max_retries,
152
+ backoff=Backoff(delay=delay, max_delay=max_delay, multiplier=multiplier)
153
+ )
154
+
155
+ return retryable_decorator(annotation)
@@ -0,0 +1,3 @@
1
+ from .scheduler import Scheduler
2
+
3
+ __all__ = ["Scheduler"]
@@ -0,0 +1,389 @@
1
+ from typing import Dict, Any, Callable, Optional
2
+ import asyncio
3
+ import time
4
+ import logging
5
+ import threading
6
+
7
+
8
+ class Scheduler:
9
+ def __init__(self):
10
+ # 存储任务信息: {task_id: {'task': asyncio.Task, 'loop': asyncio.AbstractEventLoop}}
11
+ self._tasks: Dict[str, dict] = {}
12
+ self._lock = threading.RLock()
13
+ self._logger = logging.getLogger("Spring.Scheduler")
14
+
15
+ def schedule(self, task_id: str, func: Callable, **kwargs) -> None:
16
+ fixed_rate = kwargs.get('fixed_rate')
17
+ fixed_delay = kwargs.get('fixed_delay')
18
+ cron = kwargs.get('cron')
19
+ initial_delay = kwargs.get('initial_delay', 0)
20
+
21
+ try:
22
+ loop = asyncio.get_event_loop()
23
+ if not loop.is_running():
24
+ # 如果有事件循环但未运行,创建新线程运行
25
+ def run_loop():
26
+ loop = asyncio.new_event_loop()
27
+ asyncio.set_event_loop(loop)
28
+ task = None
29
+ if fixed_rate:
30
+ task = loop.create_task(self._schedule_fixed_rate(task_id, func, fixed_rate, initial_delay))
31
+ elif fixed_delay:
32
+ task = loop.create_task(self._schedule_fixed_delay(task_id, func, fixed_delay, initial_delay))
33
+ elif cron:
34
+ task = loop.create_task(self._schedule_cron(task_id, func, cron, initial_delay))
35
+
36
+ if task:
37
+ with self._lock:
38
+ self._tasks[task_id] = {'task': task, 'loop': loop}
39
+
40
+ loop.run_forever()
41
+
42
+ thread = threading.Thread(target=run_loop, daemon=True)
43
+ thread.start()
44
+ return
45
+ except RuntimeError:
46
+ # 没有事件循环,创建新线程运行
47
+ def run_loop():
48
+ loop = asyncio.new_event_loop()
49
+ asyncio.set_event_loop(loop)
50
+ task = None
51
+ if fixed_rate:
52
+ task = loop.create_task(self._schedule_fixed_rate(task_id, func, fixed_rate, initial_delay))
53
+ elif fixed_delay:
54
+ task = loop.create_task(self._schedule_fixed_delay(task_id, func, fixed_delay, initial_delay))
55
+ elif cron:
56
+ task = loop.create_task(self._schedule_cron(task_id, func, cron, initial_delay))
57
+
58
+ if task:
59
+ with self._lock:
60
+ self._tasks[task_id] = {'task': task, 'loop': loop}
61
+
62
+ loop.run_forever()
63
+
64
+ thread = threading.Thread(target=run_loop, daemon=True)
65
+ thread.start()
66
+ return
67
+
68
+ # 有运行中的事件循环
69
+ loop = asyncio.get_event_loop()
70
+ task = None
71
+ if fixed_rate:
72
+ task = asyncio.create_task(self._schedule_fixed_rate(task_id, func, fixed_rate, initial_delay))
73
+ elif fixed_delay:
74
+ task = asyncio.create_task(self._schedule_fixed_delay(task_id, func, fixed_delay, initial_delay))
75
+ elif cron:
76
+ task = asyncio.create_task(self._schedule_cron(task_id, func, cron, initial_delay))
77
+ else:
78
+ self._logger.warning(f"No scheduling type specified for task: {task_id}")
79
+
80
+ if task:
81
+ with self._lock:
82
+ self._tasks[task_id] = {'task': task, 'loop': loop}
83
+
84
+ async def _schedule_fixed_rate(self, task_id: str, func: Callable, rate_ms: int, initial_delay: int) -> None:
85
+ await asyncio.sleep(initial_delay / 1000)
86
+
87
+ while True:
88
+ # 检查任务是否已被取消
89
+ with self._lock:
90
+ task_info = self._tasks.get(task_id)
91
+ if task_info is None or task_info['task'].done():
92
+ break
93
+
94
+ start_time = time.time()
95
+ try:
96
+ if asyncio.iscoroutinefunction(func):
97
+ await func()
98
+ else:
99
+ func()
100
+ except Exception as e:
101
+ self._logger.error(f"Scheduled task {task_id} failed: {str(e)}")
102
+
103
+ elapsed_ms = (time.time() - start_time) * 1000
104
+ sleep_time = max(0, rate_ms - elapsed_ms) / 1000
105
+ await asyncio.sleep(sleep_time)
106
+
107
+ async def _schedule_fixed_delay(self, task_id: str, func: Callable, delay_ms: int, initial_delay: int) -> None:
108
+ await asyncio.sleep(initial_delay / 1000)
109
+
110
+ while True:
111
+ # 检查任务是否已被取消
112
+ with self._lock:
113
+ task_info = self._tasks.get(task_id)
114
+ if task_info is None or task_info['task'].done():
115
+ break
116
+
117
+ try:
118
+ if asyncio.iscoroutinefunction(func):
119
+ await func()
120
+ else:
121
+ func()
122
+ except Exception as e:
123
+ self._logger.error(f"Scheduled task {task_id} failed: {str(e)}")
124
+
125
+ await asyncio.sleep(delay_ms / 1000)
126
+
127
+ async def _schedule_cron(self, task_id: str, func: Callable, cron_expr: str, initial_delay: int) -> None:
128
+ await asyncio.sleep(initial_delay / 1000)
129
+
130
+ while True:
131
+ # 检查任务是否已被取消
132
+ with self._lock:
133
+ task_info = self._tasks.get(task_id)
134
+ if task_info is None or task_info['task'].done():
135
+ break
136
+
137
+ try:
138
+ if asyncio.iscoroutinefunction(func):
139
+ await func()
140
+ else:
141
+ func()
142
+ except Exception as e:
143
+ self._logger.error(f"Scheduled task {task_id} failed: {str(e)}")
144
+
145
+ await asyncio.sleep(self._parse_cron(cron_expr))
146
+
147
+ def _parse_cron(self, cron_expr: str) -> float:
148
+ import datetime
149
+
150
+ parts = cron_expr.split()
151
+
152
+ if len(parts) == 5:
153
+ second = '*'
154
+ minute, hour, day, month, weekday = parts
155
+ elif len(parts) == 6:
156
+ second, minute, hour, day, month, weekday = parts
157
+ else:
158
+ self._logger.warning(f"Invalid cron expression: {cron_expr}")
159
+ return 60.0
160
+
161
+ try:
162
+ now = datetime.datetime.now()
163
+ next_run = now + datetime.timedelta(seconds=1)
164
+
165
+ # 解析所有字段的可能值
166
+ seconds = self._parse_field(second, 0, 59)
167
+ minutes = self._parse_field(minute, 0, 59)
168
+ hours = self._parse_field(hour, 0, 23)
169
+ days = self._parse_field(day, 1, 31)
170
+ months = self._parse_field(month, 1, 12)
171
+
172
+ # 获取当前月份的最大天数
173
+ def get_max_day(year, m):
174
+ if m == 2:
175
+ if year % 4 == 0 and (year % 100 != 0 or year % 400 == 0):
176
+ return 29
177
+ return 28
178
+ if m in [4, 6, 9, 11]:
179
+ return 30
180
+ return 31
181
+
182
+ # 从当前时间开始查找下一个匹配时间
183
+ while True:
184
+ # 检查月份
185
+ if next_run.month not in months:
186
+ # 跳转到下一个匹配月份的第一天
187
+ next_month_found = False
188
+ for m in months:
189
+ if m > next_run.month:
190
+ next_run = next_run.replace(month=m, day=1, hour=0, minute=0, second=0)
191
+ next_month_found = True
192
+ break
193
+ if not next_month_found:
194
+ # 下一年的第一个匹配月份
195
+ if months:
196
+ next_run = next_run.replace(year=next_run.year + 1, month=months[0], day=1, hour=0, minute=0, second=0)
197
+ else:
198
+ return 60.0
199
+ continue
200
+
201
+ # 检查日期
202
+ max_day = get_max_day(next_run.year, next_run.month)
203
+ valid_days = [d for d in days if d <= max_day]
204
+
205
+ if next_run.day not in valid_days:
206
+ # 跳转到下一个匹配日期
207
+ next_day_found = False
208
+ for d in valid_days:
209
+ if d > next_run.day:
210
+ next_run = next_run.replace(day=d, hour=0, minute=0, second=0)
211
+ next_day_found = True
212
+ break
213
+ if not next_day_found:
214
+ # 跳转到下一个月
215
+ next_month_found = False
216
+ for m in months:
217
+ if m > next_run.month:
218
+ next_run = next_run.replace(month=m, day=1, hour=0, minute=0, second=0)
219
+ next_month_found = True
220
+ break
221
+ if not next_month_found:
222
+ if months:
223
+ next_run = next_run.replace(year=next_run.year + 1, month=months[0], day=1, hour=0, minute=0, second=0)
224
+ else:
225
+ return 60.0
226
+ continue
227
+
228
+ # 检查星期
229
+ if weekday != '*' and weekday != '?':
230
+ if not self._matches_weekday(next_run.weekday(), weekday):
231
+ # 跳到下一天
232
+ next_run = next_run + datetime.timedelta(days=1)
233
+ next_run = next_run.replace(hour=0, minute=0, second=0)
234
+ continue
235
+
236
+ # 检查小时
237
+ if next_run.hour not in hours:
238
+ # 跳转到下一个匹配小时
239
+ next_hour_found = False
240
+ for h in hours:
241
+ if h > next_run.hour:
242
+ next_run = next_run.replace(hour=h, minute=0, second=0)
243
+ next_hour_found = True
244
+ break
245
+ if not next_hour_found:
246
+ # 跳转到下一天
247
+ next_run = next_run + datetime.timedelta(days=1)
248
+ next_run = next_run.replace(hour=0, minute=0, second=0)
249
+ continue
250
+
251
+ # 检查分钟
252
+ if next_run.minute not in minutes:
253
+ # 跳转到下一个匹配分钟
254
+ next_minute_found = False
255
+ for m in minutes:
256
+ if m > next_run.minute:
257
+ next_run = next_run.replace(minute=m, second=0)
258
+ next_minute_found = True
259
+ break
260
+ if not next_minute_found:
261
+ # 跳转到下一小时
262
+ next_run = next_run + datetime.timedelta(hours=1)
263
+ next_run = next_run.replace(minute=0, second=0)
264
+ continue
265
+
266
+ # 检查秒
267
+ if next_run.second in seconds:
268
+ # 找到匹配时间
269
+ delta = (next_run - now).total_seconds()
270
+ return max(0, delta)
271
+
272
+ # 跳转到下一个匹配秒
273
+ next_second_found = False
274
+ for s in seconds:
275
+ if s > next_run.second:
276
+ next_run = next_run.replace(second=s)
277
+ next_second_found = True
278
+ break
279
+ if not next_second_found:
280
+ # 跳转到下一分钟
281
+ next_run = next_run + datetime.timedelta(minutes=1)
282
+ next_run = next_run.replace(second=0)
283
+
284
+ # 防止无限循环
285
+ if (next_run - now).total_seconds() > 365 * 24 * 3600:
286
+ return 60.0
287
+
288
+ except Exception as e:
289
+ self._logger.error(f"Failed to parse cron expression '{cron_expr}': {str(e)}")
290
+ return 60.0
291
+
292
+ def _parse_field(self, expr: str, min_val: int, max_val: int) -> list:
293
+ """解析 cron 字段表达式,返回所有可能的取值"""
294
+ if expr == '*' or expr == '?':
295
+ return list(range(min_val, max_val + 1))
296
+
297
+ result = []
298
+ for part in expr.split(','):
299
+ if '/' in part:
300
+ # 处理 step 表达式,如 */5 或 0/5
301
+ step_parts = part.split('/')
302
+ if len(step_parts) == 2:
303
+ start_str, step_str = step_parts
304
+ start = int(start_str) if start_str != '*' else min_val
305
+ step = int(step_str)
306
+ for val in range(start, max_val + 1, step):
307
+ if val >= min_val:
308
+ result.append(val)
309
+ elif '-' in part:
310
+ # 处理范围表达式,如 1-5
311
+ range_parts = part.split('-')
312
+ if len(range_parts) == 2:
313
+ start = int(range_parts[0])
314
+ end = int(range_parts[1])
315
+ for val in range(start, end + 1):
316
+ if min_val <= val <= max_val:
317
+ result.append(val)
318
+ elif part.isdigit():
319
+ # 处理单个值
320
+ val = int(part)
321
+ if min_val <= val <= max_val:
322
+ result.append(val)
323
+
324
+ return sorted(set(result))
325
+
326
+ def _matches_field(self, value: int, expr: str, min_val: int, max_val: int) -> bool:
327
+ if expr == '*' or expr == '?':
328
+ return True
329
+
330
+ # 处理 */5 或 0/5 格式(从起始值开始每隔step执行)
331
+ if '/' in expr:
332
+ parts = expr.split('/')
333
+ if len(parts) == 2:
334
+ start = int(parts[0]) if parts[0] != '*' else min_val
335
+ step = int(parts[1])
336
+ return (value - start) % step == 0 and value >= start
337
+
338
+ if expr.isdigit():
339
+ return value == int(expr)
340
+
341
+ if '-' in expr:
342
+ parts = expr.split('-')
343
+ if len(parts) == 2:
344
+ return min_val <= int(parts[0]) <= value <= int(parts[1]) <= max_val
345
+
346
+ return False
347
+
348
+ def _matches_weekday(self, value: int, expr: str) -> bool:
349
+ if expr == '*' or expr == '?':
350
+ return True
351
+
352
+ if expr.isdigit():
353
+ day = int(expr)
354
+ if day == 7:
355
+ day = 0
356
+ return value == day
357
+
358
+ return False
359
+
360
+ def stop(self, task_id: str) -> None:
361
+ with self._lock:
362
+ if task_id not in self._tasks:
363
+ return
364
+
365
+ task_info = self._tasks[task_id]
366
+ task = task_info['task']
367
+ loop = task_info['loop']
368
+
369
+ if not task.done():
370
+ try:
371
+ # 使用 call_soon_threadsafe 安全地跨线程取消任务
372
+ if loop.is_running():
373
+ loop.call_soon_threadsafe(task.cancel)
374
+ else:
375
+ task.cancel()
376
+ except Exception as e:
377
+ self._logger.error(f"Failed to cancel task {task_id}: {str(e)}")
378
+
379
+ del self._tasks[task_id]
380
+ self._logger.info(f"Scheduled task {task_id} stopped")
381
+
382
+ def stop_all(self) -> None:
383
+ with self._lock:
384
+ task_ids = list(self._tasks.keys())
385
+
386
+ for task_id in task_ids:
387
+ self.stop(task_id)
388
+
389
+ self._logger.info(f"All {len(task_ids)} scheduled tasks stopped")
@@ -0,0 +1,39 @@
1
+ """
2
+ Spring Security 模块
3
+ 提供认证授权、JWT支持、密钥管理、重放防护等企业级安全功能
4
+ """
5
+ from .security_context import SecurityContext, SecurityContextHolder
6
+ from .security_aop import (
7
+ pre_authorize_decorator,
8
+ secured_decorator,
9
+ authenticate_decorator,
10
+ )
11
+
12
+ # 可选导入:JWT工具(需要pyjwt)
13
+ try:
14
+ from .jwt_utils import JwtUtils
15
+ except ImportError:
16
+ JwtUtils = None
17
+
18
+ # 密钥管理器
19
+ from .secret_manager import SecretManager, is_sensitive_key, mask_secret, resolve_secret_config
20
+
21
+ # 重放攻击防护
22
+ from .replay_protection import ReplayProtection, NonceCache, RedisNonceCache, create_replay_protection
23
+
24
+ __all__ = [
25
+ 'JwtUtils',
26
+ 'SecurityContext',
27
+ 'SecurityContextHolder',
28
+ 'pre_authorize_decorator',
29
+ 'secured_decorator',
30
+ 'authenticate_decorator',
31
+ 'SecretManager',
32
+ 'is_sensitive_key',
33
+ 'mask_secret',
34
+ 'resolve_secret_config',
35
+ 'ReplayProtection',
36
+ 'NonceCache',
37
+ 'RedisNonceCache',
38
+ 'create_replay_protection',
39
+ ]