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,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()
|
spring/cloud/__init__.py
ADDED
|
@@ -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
|