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,19 @@
1
+ from typing import Callable, Any
2
+ from abc import ABC, abstractmethod
3
+
4
+
5
+ class MethodInterceptor(ABC):
6
+ @abstractmethod
7
+ def invoke(self, invocation: 'MethodInvocation') -> Any:
8
+ pass
9
+
10
+
11
+ class MethodInvocation:
12
+ def __init__(self, target: Any, method: Callable, args: tuple, kwargs: dict):
13
+ self.target = target
14
+ self.method = method
15
+ self.args = args
16
+ self.kwargs = kwargs
17
+
18
+ def proceed(self) -> Any:
19
+ return self.method(*self.args, **self.kwargs)
@@ -0,0 +1,55 @@
1
+ from typing import Type, Any, Dict, Callable, List
2
+ from spring.aop.method_interceptor import MethodInterceptor, MethodInvocation
3
+ import functools
4
+ import inspect
5
+
6
+
7
+ class ProxyFactory:
8
+ def __init__(self):
9
+ self._interceptors: Dict[str, List[MethodInterceptor]] = {}
10
+ self._cache_storage: Dict[str, Any] = {}
11
+
12
+ def add_interceptor(self, method_name: str, interceptor: MethodInterceptor) -> None:
13
+ if method_name not in self._interceptors:
14
+ self._interceptors[method_name] = []
15
+ self._interceptors[method_name].append(interceptor)
16
+
17
+ def create_proxy(self, target: Any, target_class: Type) -> Any:
18
+ for name, method in inspect.getmembers(target_class):
19
+ if not name.startswith('_') and inspect.isfunction(method):
20
+ interceptors = self._interceptors.get(name, [])
21
+ if interceptors:
22
+ wrapped_method = self._wrap_method(target, method, interceptors)
23
+ setattr(target, name, wrapped_method)
24
+ return target
25
+
26
+ def _wrap_method(self, target: Any, method: Callable, interceptors: List[MethodInterceptor]) -> Callable:
27
+ @functools.wraps(method)
28
+ def wrapper(*args, **kwargs):
29
+ invocation = MethodInvocation(target, method, args, kwargs)
30
+
31
+ def proceed():
32
+ return invocation.proceed()
33
+
34
+ invocation.proceed = proceed
35
+
36
+ result = invocation.proceed()
37
+
38
+ for interceptor in reversed(interceptors):
39
+ result = interceptor.invoke(invocation)
40
+
41
+ return result
42
+
43
+ return wrapper
44
+
45
+ def get_cache(self, key: str) -> Any:
46
+ return self._cache_storage.get(key)
47
+
48
+ def set_cache(self, key: str, value: Any) -> None:
49
+ self._cache_storage[key] = value
50
+
51
+ def clear_cache(self, key: str = None) -> None:
52
+ if key:
53
+ self._cache_storage.pop(key, None)
54
+ else:
55
+ self._cache_storage.clear()
@@ -0,0 +1,76 @@
1
+ """Spring Cloud 微服务组件包"""
2
+
3
+ from spring.cloud.discovery import NacosDiscoveryClient
4
+ try:
5
+ from spring.cloud.discovery import nacos_client as nacos_discovery_client
6
+ except ImportError:
7
+ nacos_discovery_client = None
8
+ from spring.cloud.load_balancer import LoadBalancer, load_balancer
9
+ from spring.cloud.feign import (
10
+ FeignClientProxy,
11
+ FeignClientFactory,
12
+ create_feign_client,
13
+ create_declared_feign_client,
14
+ )
15
+ from spring.cloud.seata import (
16
+ seata_manager,
17
+ init_seata,
18
+ SeataTransactionManager,
19
+ BranchStatus,
20
+ )
21
+ from spring.cloud.sentinel import (
22
+ sentinel_engine,
23
+ SentinelEngine,
24
+ FlowRule,
25
+ DegradeRule,
26
+ SystemRule,
27
+ HotParamRule,
28
+ BlockException,
29
+ sentinel_protect,
30
+ )
31
+ from spring.cloud.tracer import (
32
+ Tracer,
33
+ get_tracer,
34
+ trace_span,
35
+ SpanKind,
36
+ SpanStatus,
37
+ _build_traceparent,
38
+ _parse_traceparent,
39
+ )
40
+ from spring.cloud.gateway import (
41
+ GatewayRouter,
42
+ Route,
43
+ GatewayFilter,
44
+ AuthenticationFilter,
45
+ RateLimitFilter,
46
+ TracingFilter,
47
+ LoggingFilter,
48
+ get_gateway,
49
+ )
50
+
51
+ try:
52
+ from spring.messaging.rabbitmq import rabbitmq_client, RabbitMQClient
53
+ except ImportError:
54
+ rabbitmq_client = None
55
+ RabbitMQClient = None
56
+
57
+ __all__ = [
58
+ # 服务发现
59
+ 'nacos_discovery_client', 'NacosDiscoveryClient',
60
+ # 负载均衡
61
+ 'LoadBalancer', 'load_balancer',
62
+ # Feign
63
+ 'FeignClientProxy', 'FeignClientFactory', 'create_feign_client', 'create_declared_feign_client',
64
+ # 分布式事务
65
+ 'seata_manager', 'init_seata', 'SeataTransactionManager', 'BranchStatus',
66
+ # 熔断限流
67
+ 'sentinel_engine', 'SentinelEngine', 'FlowRule', 'DegradeRule',
68
+ 'SystemRule', 'HotParamRule', 'BlockException', 'sentinel_protect',
69
+ # 链路追踪
70
+ 'Tracer', 'get_tracer', 'trace_span', 'SpanKind', 'SpanStatus',
71
+ # 网关
72
+ 'GatewayRouter', 'Route', 'GatewayFilter',
73
+ 'AuthenticationFilter', 'RateLimitFilter', 'TracingFilter', 'LoggingFilter', 'get_gateway',
74
+ # 消息队列
75
+ 'rabbitmq_client', 'RabbitMQClient',
76
+ ]
@@ -0,0 +1,364 @@
1
+ """
2
+ 服务注册发现模块
3
+ 集成 Nacos 作为注册中心
4
+ """
5
+ import json
6
+ import logging
7
+ import time
8
+ from typing import Dict, List, Optional, Any
9
+ from urllib.request import urlopen
10
+
11
+ # 可选导入Nacos
12
+ try:
13
+ from nacos import NacosClient
14
+ except ImportError:
15
+ NacosClient = None
16
+
17
+ logger = logging.getLogger("Spring.Cloud.Discovery")
18
+
19
+
20
+ class NacosDiscoveryClient:
21
+ """Nacos服务注册发现客户端"""
22
+
23
+ _instance = None
24
+ _lock = __import__('threading').Lock()
25
+
26
+ def __new__(cls, *args, **kwargs):
27
+ if cls._instance is None:
28
+ with cls._lock:
29
+ if cls._instance is None:
30
+ cls._instance = super().__new__(cls)
31
+ return cls._instance
32
+
33
+ def __init__(self, server_addr: str = "localhost:8848", namespace: str = "", group: str = "DEFAULT_GROUP", username: str = "", password: str = ""):
34
+ if hasattr(self, '_initialized'):
35
+ if (
36
+ self.server_addr != server_addr
37
+ or self.namespace != namespace
38
+ or self.group != group
39
+ or self.username != username
40
+ or self.password != password
41
+ ):
42
+ self.server_addr = server_addr
43
+ self.namespace = namespace
44
+ self.group = group
45
+ self.username = username
46
+ self.password = password
47
+ self._client = None
48
+ self._ready = False
49
+ return
50
+ self.server_addr = server_addr
51
+ self.namespace = namespace
52
+ self.group = group
53
+ self.username = username
54
+ self.password = password
55
+ self._client: Optional[NacosClient] = None
56
+ self._ready = False
57
+ self._service_name: Optional[str] = None
58
+ self._ip: str = "127.0.0.1"
59
+ self._port: int = 8080
60
+ self._initialized = True
61
+
62
+ def connect(self) -> None:
63
+ """连接Nacos"""
64
+ if NacosClient is None:
65
+ logger.warning("Nacos SDK not installed, service discovery disabled")
66
+ self._client = None
67
+ self._ready = False
68
+ return
69
+
70
+ try:
71
+ # nacos-sdk-python 使用 server_addresses 作为第一个参数
72
+ # 开发环境如果没有配置认证,不传用户名密码也能连接
73
+ client_kwargs = {"namespace": self.namespace}
74
+
75
+ # 先尝试不带认证参数连接(开发环境常用)
76
+ try:
77
+ self._client = NacosClient(self.server_addr, **client_kwargs)
78
+ # 测试连接是否正常 - 发送一个测试服务注册
79
+ self._client.add_naming_instance(
80
+ service_name="_health_check",
81
+ ip="127.0.0.1",
82
+ port=0,
83
+ group_name=self.group,
84
+ ephemeral=True
85
+ )
86
+ # 立即注销测试服务
87
+ try:
88
+ self._client.remove_naming_instance(
89
+ service_name="_health_check",
90
+ ip="127.0.0.1",
91
+ port=0,
92
+ group_name=self.group
93
+ )
94
+ except Exception:
95
+ pass
96
+ except Exception as e1:
97
+ # 如果失败,尝试带认证参数
98
+ if self.username:
99
+ client_kwargs.update({
100
+ "username": self.username,
101
+ "password": self.password,
102
+ })
103
+ try:
104
+ self._client = NacosClient(self.server_addr, **client_kwargs)
105
+ except TypeError:
106
+ client_kwargs.pop("username", None)
107
+ client_kwargs.pop("password", None)
108
+ self._client = NacosClient(self.server_addr, **client_kwargs)
109
+ else:
110
+ raise
111
+ self._ready = True
112
+ logger.info(f"Connected to Nacos: {self.server_addr}")
113
+ except Exception as e:
114
+ logger.error(f"Failed to connect to Nacos: {e}")
115
+ self._client = None
116
+ self._ready = False
117
+
118
+ def is_healthy(self, timeout: float = 2.0) -> bool:
119
+ """Check the Nacos server liveness endpoint."""
120
+ if not self._ready or self._client is None:
121
+ return False
122
+
123
+ server = self.server_addr.split(",", 1)[0].strip().rstrip("/")
124
+ if not server.startswith(("http://", "https://")):
125
+ server = f"http://{server}"
126
+ health_url = f"{server}/nacos/v1/console/health/liveness"
127
+
128
+ try:
129
+ with urlopen(health_url, timeout=timeout) as response:
130
+ return 200 <= response.status < 300
131
+ except Exception as e:
132
+ logger.debug(f"Nacos health check failed: {e}")
133
+ return False
134
+
135
+ def register_service(self, service_name: str, ip: str, port: int, metadata: Dict[str, Any] = None) -> bool:
136
+ """
137
+ 注册服务到Nacos
138
+
139
+ Args:
140
+ service_name: 服务名称
141
+ ip: 服务IP
142
+ port: 服务端口
143
+ metadata: 元数据
144
+
145
+ Returns:
146
+ 是否成功
147
+ """
148
+ if self._client is None:
149
+ self.connect()
150
+
151
+ if self._client is None:
152
+ logger.warning("Nacos client not available, skipping registration")
153
+ return False
154
+
155
+ try:
156
+ self._service_name = service_name
157
+ self._ip = ip
158
+ self._port = port
159
+
160
+ self._client.add_naming_instance(
161
+ service_name=service_name,
162
+ ip=ip,
163
+ port=port,
164
+ metadata=metadata or {},
165
+ group_name=self.group
166
+ )
167
+
168
+ logger.info(f"Registered service: {service_name} at {ip}:{port}")
169
+ return True
170
+ except Exception as e:
171
+ logger.error(f"Failed to register service {service_name}: {e}")
172
+ return False
173
+
174
+ # Compatibility aliases used by the example CloudService API.
175
+ def register(self, service_name: str, ip: str, port: int,
176
+ metadata: Dict[str, Any] = None) -> bool:
177
+ return self.register_service(service_name, ip, port, metadata)
178
+
179
+ def get_services(self, page_no: int = 1, page_size: int = 100) -> List[str]:
180
+ """Return registered service names when supported by the SDK."""
181
+ if self._client is None:
182
+ self.connect()
183
+ if self._client is None:
184
+ return []
185
+
186
+ try:
187
+ list_services = getattr(self._client, "list_naming_services")
188
+ response = list_services(
189
+ page_no=page_no,
190
+ page_size=page_size,
191
+ group_name=self.group,
192
+ )
193
+ if isinstance(response, dict):
194
+ return response.get("doms") or response.get("serviceList") or []
195
+ return list(response or [])
196
+ except Exception as e:
197
+ logger.error(f"Failed to list Nacos services: {e}")
198
+ return []
199
+
200
+ def deregister_service(self, service_name: str, ip: str, port: int) -> bool:
201
+ """
202
+ 从Nacos注销服务
203
+
204
+ Args:
205
+ service_name: 服务名称
206
+ ip: 服务IP
207
+ port: 服务端口
208
+
209
+ Returns:
210
+ 是否成功
211
+ """
212
+ if self._client is None:
213
+ return False
214
+
215
+ try:
216
+ self._client.remove_naming_instance(
217
+ service_name=service_name,
218
+ ip=ip,
219
+ port=port,
220
+ group_name=self.group
221
+ )
222
+ logger.info(f"Deregistered service: {service_name} at {ip}:{port}")
223
+ return True
224
+ except Exception as e:
225
+ logger.error(f"Failed to deregister service {service_name}: {e}")
226
+ return False
227
+
228
+ def get_service_instances(self, service_name: str) -> List[Dict[str, Any]]:
229
+ """
230
+ 获取服务实例列表
231
+
232
+ Args:
233
+ service_name: 服务名称
234
+
235
+ Returns:
236
+ 实例列表
237
+ """
238
+ if self._client is None:
239
+ self.connect()
240
+
241
+ if self._client is None:
242
+ logger.warning("Nacos client not available, returning empty list")
243
+ return []
244
+
245
+ try:
246
+ # SDK方法名是 list_naming_instance(单数)
247
+ list_method = getattr(self._client, 'list_naming_instance', None)
248
+ if list_method is None:
249
+ list_method = getattr(self._client, 'list_naming_instances', None)
250
+
251
+ instances = list_method(
252
+ service_name=service_name,
253
+ group_name=self.group
254
+ )
255
+
256
+ result = []
257
+ if instances:
258
+ # 兼容不同SDK版本的返回格式
259
+ if isinstance(instances, dict):
260
+ hosts = instances.get('hosts', [])
261
+ elif isinstance(instances, (list, tuple)):
262
+ hosts = instances
263
+ else:
264
+ hosts = []
265
+
266
+ for instance in hosts:
267
+ if hasattr(instance, 'ip'):
268
+ # 对象格式
269
+ result.append({
270
+ 'ip': instance.ip,
271
+ 'port': instance.port,
272
+ 'weight': getattr(instance, 'weight', 1.0),
273
+ 'healthy': getattr(instance, 'healthy', True),
274
+ 'metadata': getattr(instance, 'metadata', {})
275
+ })
276
+ elif isinstance(instance, dict):
277
+ # 字典格式
278
+ result.append({
279
+ 'ip': instance.get('ip', ''),
280
+ 'port': instance.get('port', 0),
281
+ 'weight': instance.get('weight', 1.0),
282
+ 'healthy': instance.get('healthy', True),
283
+ 'metadata': instance.get('metadata', {})
284
+ })
285
+
286
+ return result
287
+ except Exception as e:
288
+ logger.error(f"Failed to get instances for {service_name}: {e}")
289
+ return []
290
+
291
+ def get_service_instance(self, service_name: str) -> Optional[Dict[str, Any]]:
292
+ """
293
+ 获取单个服务实例(用于负载均衡)
294
+
295
+ Args:
296
+ service_name: 服务名称
297
+
298
+ Returns:
299
+ 单个实例
300
+ """
301
+ instances = self.get_service_instances(service_name)
302
+ if not instances:
303
+ return None
304
+
305
+ # 过滤健康实例
306
+ healthy_instances = [i for i in instances if i.get('healthy', True)]
307
+ if not healthy_instances:
308
+ return None
309
+
310
+ # 使用轮询策略选择实例
311
+ return healthy_instances[0]
312
+
313
+ def subscribe(self, service_name: str, callback) -> bool:
314
+ """
315
+ 订阅服务变更
316
+
317
+ Args:
318
+ service_name: 服务名称
319
+ callback: 回调函数
320
+
321
+ Returns:
322
+ 是否成功
323
+ """
324
+ if self._client is None:
325
+ self.connect()
326
+
327
+ if self._client is None:
328
+ return False
329
+
330
+ try:
331
+ self._client.add_naming_listener(
332
+ service_name=service_name,
333
+ group_name=self.group,
334
+ cb=callback
335
+ )
336
+ logger.info(f"Subscribed to service: {service_name}")
337
+ return True
338
+ except Exception as e:
339
+ logger.error(f"Failed to subscribe to {service_name}: {e}")
340
+ return False
341
+
342
+
343
+ # 创建全局Nacos客户端实例
344
+ nacos_client = NacosDiscoveryClient()
345
+
346
+
347
+ def init_discovery(config: dict) -> None:
348
+ """
349
+ 初始化服务注册发现
350
+
351
+ Args:
352
+ config: 配置字典,包含server_addr, namespace, group等
353
+ """
354
+ # Reconfigure the singleton in place so modules that imported the client
355
+ # keep observing the active connection rather than a stale instance.
356
+ nacos_client.__init__(
357
+ server_addr=config.get('server_addr', 'localhost:8848'),
358
+ namespace=config.get('namespace', ''),
359
+ group=config.get('group', 'DEFAULT_GROUP'),
360
+ username=config.get('username', ''),
361
+ password=config.get('password', ''),
362
+ )
363
+ nacos_client.connect()
364
+ return nacos_client