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,216 @@
|
|
|
1
|
+
"""``WebSocketSession`` 会话抽象与全局注册表(对齐 Spring ``WebSocketSession`` +
|
|
2
|
+
``WebSocketHandlerRegistry``)。
|
|
3
|
+
|
|
4
|
+
``WebSocketSession`` 包装 Starlette ``WebSocket``,提供:
|
|
5
|
+
- 唯一 ``id``(uuid4)
|
|
6
|
+
- ``attributes`` 字典(用户态附加数据,对齐 Spring ``attributes``)
|
|
7
|
+
- ``send_text`` / ``send_json`` / ``send_bytes`` / ``receive_text`` / ``receive_json`` / ``close``
|
|
8
|
+
- ``is_open`` / ``is_closed`` 状态
|
|
9
|
+
- ``user`` 属性(可选,关联鉴权用户)
|
|
10
|
+
|
|
11
|
+
``WebSocketSessionRegistry`` 线程安全注册表,支持:
|
|
12
|
+
- ``register`` / ``unregister`` / ``get`` / ``all``
|
|
13
|
+
- ``send_to_user(user, message)`` 定向推送
|
|
14
|
+
- ``broadcast(message)`` 广播
|
|
15
|
+
- ``close_all(code, reason)`` 关闭所有会话(优雅退出)
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import asyncio
|
|
20
|
+
import logging
|
|
21
|
+
import threading
|
|
22
|
+
import uuid
|
|
23
|
+
from typing import Any, Dict, Iterable, List, Optional
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger("Spring.WebSocket.Session")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class WebSocketSession:
|
|
29
|
+
"""WebSocket 会话抽象,包装 Starlette ``WebSocket``。
|
|
30
|
+
|
|
31
|
+
每个会话有唯一 ``id``;``attributes`` 用于在生命周期钩子间传递用户态数据。
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, websocket, user: Optional[str] = None):
|
|
35
|
+
# 延迟导入以避免顶层依赖 FastAPI/Starlette(仅类型注解需要)
|
|
36
|
+
self._ws = websocket
|
|
37
|
+
self._id: str = uuid.uuid4().hex
|
|
38
|
+
self._attributes: Dict[str, Any] = {}
|
|
39
|
+
self._user: Optional[str] = user
|
|
40
|
+
self._closed: bool = False
|
|
41
|
+
self._lock = threading.Lock()
|
|
42
|
+
|
|
43
|
+
# ==================== 属性 ====================
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def id(self) -> str:
|
|
47
|
+
return self._id
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def attributes(self) -> Dict[str, Any]:
|
|
51
|
+
return self._attributes
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def user(self) -> Optional[str]:
|
|
55
|
+
return self._user
|
|
56
|
+
|
|
57
|
+
@user.setter
|
|
58
|
+
def user(self, value: Optional[str]) -> None:
|
|
59
|
+
self._user = value
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def is_open(self) -> bool:
|
|
63
|
+
return not self._closed and self._ws is not None
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def is_closed(self) -> bool:
|
|
67
|
+
return self._closed
|
|
68
|
+
|
|
69
|
+
# ==================== 接收 ====================
|
|
70
|
+
|
|
71
|
+
async def receive_text(self) -> str:
|
|
72
|
+
return await self._ws.receive_text()
|
|
73
|
+
|
|
74
|
+
async def receive_bytes(self) -> bytes:
|
|
75
|
+
return await self._ws.receive_bytes()
|
|
76
|
+
|
|
77
|
+
async def receive_json(self) -> Any:
|
|
78
|
+
return await self._ws.receive_json()
|
|
79
|
+
|
|
80
|
+
# ==================== 发送 ====================
|
|
81
|
+
|
|
82
|
+
async def send_text(self, message: str) -> None:
|
|
83
|
+
if self._closed:
|
|
84
|
+
logger.debug("send_text on closed session %s, ignored", self._id)
|
|
85
|
+
return
|
|
86
|
+
await self._ws.send_text(message)
|
|
87
|
+
|
|
88
|
+
async def send_bytes(self, data: bytes) -> None:
|
|
89
|
+
if self._closed:
|
|
90
|
+
return
|
|
91
|
+
await self._ws.send_bytes(data)
|
|
92
|
+
|
|
93
|
+
async def send_json(self, data: Any) -> None:
|
|
94
|
+
"""发送 JSON 消息。Starlette ``send_json`` 内部用 ``json.dumps``。"""
|
|
95
|
+
if self._closed:
|
|
96
|
+
return
|
|
97
|
+
await self._ws.send_json(data)
|
|
98
|
+
|
|
99
|
+
# ==================== 关闭 ====================
|
|
100
|
+
|
|
101
|
+
async def close(self, code: int = 1000, reason: str = "") -> None:
|
|
102
|
+
with self._lock:
|
|
103
|
+
if self._closed:
|
|
104
|
+
return
|
|
105
|
+
self._closed = True
|
|
106
|
+
try:
|
|
107
|
+
await self._ws.close(code=code, reason=reason)
|
|
108
|
+
except Exception as exc:
|
|
109
|
+
logger.debug("close session %s failed: %s", self._id, exc)
|
|
110
|
+
|
|
111
|
+
def mark_closed(self) -> None:
|
|
112
|
+
"""标记会话已关闭(不主动发 close 帧,用于异常分支)。"""
|
|
113
|
+
with self._lock:
|
|
114
|
+
self._closed = True
|
|
115
|
+
|
|
116
|
+
def __repr__(self) -> str:
|
|
117
|
+
return f"WebSocketSession(id={self._id!r}, user={self._user!r}, open={self.is_open})"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# ==================== 全局会话注册表 ====================
|
|
121
|
+
|
|
122
|
+
class WebSocketSessionRegistry:
|
|
123
|
+
"""线程安全的 WebSocket 会话注册表。
|
|
124
|
+
|
|
125
|
+
- ``register(session)`` 注册会话
|
|
126
|
+
- ``unregister(session_id)`` 注销会话
|
|
127
|
+
- ``get(session_id)`` 按 id 取会话
|
|
128
|
+
- ``all()`` 返回所有会话列表(拷贝)
|
|
129
|
+
- ``send_to_user(user, ...)`` 定向推送
|
|
130
|
+
- ``broadcast(...)`` 广播
|
|
131
|
+
- ``close_all(...)`` 关闭所有会话
|
|
132
|
+
|
|
133
|
+
推送方法自动跳过已关闭的会话;推送是 ``async`` 的,需要事件循环驱动。
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
def __init__(self):
|
|
137
|
+
self._sessions: Dict[str, WebSocketSession] = {}
|
|
138
|
+
self._lock = threading.RLock()
|
|
139
|
+
|
|
140
|
+
def register(self, session: WebSocketSession) -> None:
|
|
141
|
+
with self._lock:
|
|
142
|
+
self._sessions[session.id] = session
|
|
143
|
+
|
|
144
|
+
def unregister(self, session_id: str) -> Optional[WebSocketSession]:
|
|
145
|
+
with self._lock:
|
|
146
|
+
return self._sessions.pop(session_id, None)
|
|
147
|
+
|
|
148
|
+
def get(self, session_id: str) -> Optional[WebSocketSession]:
|
|
149
|
+
with self._lock:
|
|
150
|
+
return self._sessions.get(session_id)
|
|
151
|
+
|
|
152
|
+
def all(self) -> List[WebSocketSession]:
|
|
153
|
+
with self._lock:
|
|
154
|
+
return list(self._sessions.values())
|
|
155
|
+
|
|
156
|
+
def count(self) -> int:
|
|
157
|
+
with self._lock:
|
|
158
|
+
return len(self._sessions)
|
|
159
|
+
|
|
160
|
+
def clear(self) -> None:
|
|
161
|
+
with self._lock:
|
|
162
|
+
self._sessions.clear()
|
|
163
|
+
|
|
164
|
+
async def send_to_user(self, user: str, message: Any, as_json: bool = True) -> int:
|
|
165
|
+
"""向指定用户的所有会话推送消息;返回成功推送的会话数。"""
|
|
166
|
+
sent = 0
|
|
167
|
+
for session in self.all():
|
|
168
|
+
if session.user != user or not session.is_open:
|
|
169
|
+
continue
|
|
170
|
+
try:
|
|
171
|
+
if as_json:
|
|
172
|
+
await session.send_json(message)
|
|
173
|
+
else:
|
|
174
|
+
await session.send_text(message if isinstance(message, str) else str(message))
|
|
175
|
+
sent += 1
|
|
176
|
+
except Exception as exc:
|
|
177
|
+
logger.warning("send_to_user failed for session %s: %s", session.id, exc)
|
|
178
|
+
return sent
|
|
179
|
+
|
|
180
|
+
async def broadcast(self, message: Any, as_json: bool = True,
|
|
181
|
+
exclude: Optional[Iterable[str]] = None) -> int:
|
|
182
|
+
"""向所有会话广播;``exclude`` 是要排除的 session_id 列表。返回推送数。"""
|
|
183
|
+
excluded = set(exclude or [])
|
|
184
|
+
sent = 0
|
|
185
|
+
for session in self.all():
|
|
186
|
+
if session.id in excluded or not session.is_open:
|
|
187
|
+
continue
|
|
188
|
+
try:
|
|
189
|
+
if as_json:
|
|
190
|
+
await session.send_json(message)
|
|
191
|
+
else:
|
|
192
|
+
await session.send_text(message if isinstance(message, str) else str(message))
|
|
193
|
+
sent += 1
|
|
194
|
+
except Exception as exc:
|
|
195
|
+
logger.warning("broadcast failed for session %s: %s", session.id, exc)
|
|
196
|
+
return sent
|
|
197
|
+
|
|
198
|
+
async def close_all(self, code: int = 1001, reason: str = "server shutdown") -> None:
|
|
199
|
+
"""关闭所有会话(优雅退出)。"""
|
|
200
|
+
for session in self.all():
|
|
201
|
+
try:
|
|
202
|
+
await session.close(code=code, reason=reason)
|
|
203
|
+
except Exception as exc:
|
|
204
|
+
logger.debug("close_all session %s failed: %s", session.id, exc)
|
|
205
|
+
self.clear()
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
# 全局单例(对齐 Spring ``WebSocketHandlerRegistry`` 默认实现)
|
|
209
|
+
global_session_registry = WebSocketSessionRegistry()
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
__all__ = [
|
|
213
|
+
"WebSocketSession",
|
|
214
|
+
"WebSocketSessionRegistry",
|
|
215
|
+
"global_session_registry",
|
|
216
|
+
]
|