sycommon-python-lib 0.1.46__py3-none-any.whl → 0.1.57b1__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 (51) hide show
  1. sycommon/config/Config.py +29 -4
  2. sycommon/config/LangfuseConfig.py +15 -0
  3. sycommon/config/RerankerConfig.py +1 -0
  4. sycommon/config/SentryConfig.py +13 -0
  5. sycommon/database/async_base_db_service.py +36 -0
  6. sycommon/database/async_database_service.py +96 -0
  7. sycommon/llm/__init__.py +0 -0
  8. sycommon/llm/embedding.py +204 -0
  9. sycommon/llm/get_llm.py +37 -0
  10. sycommon/llm/llm_logger.py +126 -0
  11. sycommon/llm/llm_tokens.py +119 -0
  12. sycommon/llm/struct_token.py +192 -0
  13. sycommon/llm/sy_langfuse.py +103 -0
  14. sycommon/llm/usage_token.py +117 -0
  15. sycommon/logging/async_sql_logger.py +65 -0
  16. sycommon/logging/kafka_log.py +200 -434
  17. sycommon/logging/logger_levels.py +23 -0
  18. sycommon/middleware/context.py +2 -0
  19. sycommon/middleware/exception.py +10 -16
  20. sycommon/middleware/timeout.py +2 -1
  21. sycommon/middleware/traceid.py +179 -51
  22. sycommon/notice/__init__.py +0 -0
  23. sycommon/notice/uvicorn_monitor.py +200 -0
  24. sycommon/rabbitmq/rabbitmq_client.py +267 -290
  25. sycommon/rabbitmq/rabbitmq_pool.py +277 -465
  26. sycommon/rabbitmq/rabbitmq_service.py +23 -891
  27. sycommon/rabbitmq/rabbitmq_service_client_manager.py +211 -0
  28. sycommon/rabbitmq/rabbitmq_service_connection_monitor.py +73 -0
  29. sycommon/rabbitmq/rabbitmq_service_consumer_manager.py +285 -0
  30. sycommon/rabbitmq/rabbitmq_service_core.py +117 -0
  31. sycommon/rabbitmq/rabbitmq_service_producer_manager.py +238 -0
  32. sycommon/sentry/__init__.py +0 -0
  33. sycommon/sentry/sy_sentry.py +35 -0
  34. sycommon/services.py +144 -115
  35. sycommon/synacos/feign.py +18 -7
  36. sycommon/synacos/feign_client.py +26 -8
  37. sycommon/synacos/nacos_client_base.py +119 -0
  38. sycommon/synacos/nacos_config_manager.py +107 -0
  39. sycommon/synacos/nacos_heartbeat_manager.py +144 -0
  40. sycommon/synacos/nacos_service.py +65 -769
  41. sycommon/synacos/nacos_service_discovery.py +157 -0
  42. sycommon/synacos/nacos_service_registration.py +270 -0
  43. sycommon/tools/env.py +62 -0
  44. sycommon/tools/merge_headers.py +117 -0
  45. sycommon/tools/snowflake.py +238 -23
  46. {sycommon_python_lib-0.1.46.dist-info → sycommon_python_lib-0.1.57b1.dist-info}/METADATA +18 -11
  47. sycommon_python_lib-0.1.57b1.dist-info/RECORD +89 -0
  48. sycommon_python_lib-0.1.46.dist-info/RECORD +0 -59
  49. {sycommon_python_lib-0.1.46.dist-info → sycommon_python_lib-0.1.57b1.dist-info}/WHEEL +0 -0
  50. {sycommon_python_lib-0.1.46.dist-info → sycommon_python_lib-0.1.57b1.dist-info}/entry_points.txt +0 -0
  51. {sycommon_python_lib-0.1.46.dist-info → sycommon_python_lib-0.1.57b1.dist-info}/top_level.txt +0 -0
sycommon/synacos/feign.py CHANGED
@@ -2,6 +2,9 @@ import io
2
2
  import os
3
3
  import time
4
4
 
5
+ from sycommon.tools.merge_headers import merge_headers
6
+ from sycommon.tools.snowflake import Snowflake
7
+
5
8
  import aiohttp
6
9
  from sycommon.logging.kafka_log import SYLogger
7
10
  from sycommon.synacos.nacos_service import NacosService
@@ -22,12 +25,18 @@ async def feign(service_name, api_path, method='GET', params=None, headers=None,
22
25
  try:
23
26
  # 初始化headers,确保是可修改的字典
24
27
  headers = headers.copy() if headers else {}
28
+ headers = merge_headers(SYLogger.get_headers(), headers)
29
+ if "x-traceId-header" not in headers:
30
+ headers["x-traceId-header"] = SYLogger.get_trace_id() or Snowflake.id
25
31
 
26
32
  # 处理JSON请求的Content-Type
27
33
  is_json_request = method.upper() in ["POST", "PUT", "PATCH"] and not (
28
34
  files or form_data or file_path)
29
- if is_json_request and "Content-Type" not in headers:
30
- headers["Content-Type"] = "application/json"
35
+ if is_json_request:
36
+ # 将headers的key全部转为小写,统一判断
37
+ headers_lower = {k.lower(): v for k, v in headers.items()}
38
+ if "content-type" not in headers_lower:
39
+ headers["Content-Type"] = "application/json"
31
40
 
32
41
  nacos_service = NacosService(None)
33
42
  version = headers.get('s-y-version')
@@ -43,7 +52,7 @@ async def feign(service_name, api_path, method='GET', params=None, headers=None,
43
52
  instance = instances[int(time.time()) % len(instances)]
44
53
 
45
54
  SYLogger.info(f"nacos:开始调用服务: {service_name}")
46
- SYLogger.info(f"nacos:请求头: {headers}")
55
+ # SYLogger.info(f"nacos:请求头: {headers}")
47
56
 
48
57
  ip = instance.get('ip')
49
58
  port = instance.get('port')
@@ -131,13 +140,15 @@ async def _handle_feign_response(response, service_name: str, api_path: str):
131
140
  """
132
141
  try:
133
142
  status_code = response.status
134
- content_type = response.headers.get('Content-Type', '').lower()
143
+ content_type = response.headers.get('Content-Type', '')
144
+ content_type = content_type.lower() if content_type else ''
145
+
135
146
  response_body = None
136
147
 
137
148
  if status_code == 200:
138
- if 'application/json' in content_type:
149
+ if content_type and 'application/json' in content_type:
139
150
  response_body = await response.json()
140
- elif 'text/' in content_type:
151
+ elif content_type and 'text/' in content_type:
141
152
  # 文本类型(text/plain、text/html等):按文本读取
142
153
  try:
143
154
  response_body = await response.text(encoding='utf-8')
@@ -154,7 +165,7 @@ async def _handle_feign_response(response, service_name: str, api_path: str):
154
165
  else:
155
166
  # 非200状态:统一读取响应体(兼容文本/二进制错误信息)
156
167
  try:
157
- if 'application/json' in content_type:
168
+ if content_type and 'application/json' in content_type:
158
169
  response_body = await response.json()
159
170
  else:
160
171
  response_body = await response.text(encoding='utf-8', errors='ignore')
@@ -5,6 +5,9 @@ import inspect
5
5
  from typing import Any, Dict, Optional, Literal, Type, TypeVar
6
6
  from urllib.parse import urljoin
7
7
 
8
+ from sycommon.tools.merge_headers import merge_headers
9
+ from sycommon.tools.snowflake import Snowflake
10
+
8
11
  import aiohttp
9
12
  from pydantic import BaseModel
10
13
  from sycommon.synacos.param import Body, Cookie, File, Form, Header, Param, Path, Query
@@ -26,6 +29,9 @@ def feign_client(
26
29
  default_headers: Optional[Dict[str, str]] = None
27
30
  ):
28
31
  default_headers = default_headers or {}
32
+ default_headers = {k.lower(): v for k, v in default_headers.items()}
33
+ default_headers = merge_headers(SYLogger.get_headers(), default_headers)
34
+ default_headers["x-traceId-header"] = SYLogger.get_trace_id() or Snowflake.id
29
35
 
30
36
  def decorator(cls):
31
37
  class FeignClient:
@@ -33,7 +39,8 @@ def feign_client(
33
39
  self.service_name = service_name
34
40
  self.path_prefix = path_prefix
35
41
  self.default_timeout = default_timeout
36
- self.default_headers = default_headers.copy()
42
+ self.default_headers = {
43
+ k.lower(): v for k, v in default_headers.copy().items()}
37
44
  self.nacos_manager: Optional[NacosService] = None
38
45
  self.session: Optional[aiohttp.ClientSession] = None
39
46
 
@@ -62,7 +69,8 @@ def feign_client(
62
69
  method = request_meta.get("method", "GET").upper()
63
70
  path = request_meta.get("path", "")
64
71
  is_upload = request_meta.get("is_upload", False)
65
- method_headers = request_meta.get("headers", {})
72
+ method_headers = {
73
+ k.lower(): v for k, v in request_meta.get("headers", {}).items()}
66
74
  timeout = request_meta.get(
67
75
  "timeout", self.default_timeout)
68
76
 
@@ -152,11 +160,16 @@ def feign_client(
152
160
  def _build_headers(self, param_meta: Dict[str, Param], bound_args: Dict[str, Any], method_headers: Dict[str, str]) -> Dict[str, str]:
153
161
  headers = self.default_headers.copy()
154
162
  headers.update(method_headers)
163
+ headers = merge_headers(SYLogger.get_headers(), headers)
164
+ headers["x-traceId-header"] = SYLogger.get_trace_id() or Snowflake.id
165
+
166
+ # 处理参数中的Header类型
155
167
  for name, meta in param_meta.items():
156
168
  if isinstance(meta, Header) and name in bound_args:
157
169
  value = bound_args[name]
158
170
  if value is not None:
159
- headers[meta.get_key(name)] = str(value)
171
+ header_key = meta.get_key(name).lower()
172
+ headers[header_key] = str(value)
160
173
  return headers
161
174
 
162
175
  def _replace_path_params(self, path: str, param_meta: Dict[str, Param], bound_args: Dict[str, Any]) -> str:
@@ -221,10 +234,14 @@ def feign_client(
221
234
  value) if not isinstance(value, dict) else value)
222
235
  return form_data
223
236
 
224
- # 处理表单提交(x-www-form-urlencoded
237
+ # 从headers中获取Content-Type(已小写key
225
238
  content_type = self.default_headers.get(
226
- "Content-Type") or method_headers.get("Content-Type", "")
227
- if "application/x-www-form-urlencoded" in content_type:
239
+ "content-type") or method_headers.get("content-type", "")
240
+ # 转为小写进行判断
241
+ content_type_lower = content_type.lower()
242
+
243
+ # 处理表单提交(x-www-form-urlencoded)
244
+ if "application/x-www-form-urlencoded" in content_type_lower:
228
245
  form_data = {}
229
246
  for name, value in bound_args.items():
230
247
  meta = param_meta.get(name)
@@ -270,7 +287,8 @@ def feign_client(
270
287
  """处理响应(支持 Pydantic 模型解析)"""
271
288
  status = response.status
272
289
  if 200 <= status < 300:
273
- content_type = response.headers.get("Content-Type", "")
290
+ content_type = response.headers.get(
291
+ "content-type", "").lower()
274
292
  if "application/json" in content_type:
275
293
  json_data = await response.json()
276
294
  # 若指定了 Pydantic 响应模型,自动解析
@@ -299,7 +317,7 @@ def feign_request(
299
317
  func._feign_meta = {
300
318
  "method": method.upper(),
301
319
  "path": path,
302
- "headers": headers.copy() if headers else {},
320
+ "headers": {k.lower(): v for k, v in headers.items()} if headers else {},
303
321
  "is_upload": False,
304
322
  "timeout": timeout
305
323
  }
@@ -0,0 +1,119 @@
1
+ import threading
2
+ import time
3
+ from typing import Optional
4
+ import nacos
5
+ from sycommon.logging.kafka_log import SYLogger
6
+
7
+
8
+ class NacosClientBase:
9
+ """Nacos客户端基础类 - 负责客户端初始化和连接管理"""
10
+
11
+ def __init__(self, nacos_config: dict, enable_register_nacos: bool):
12
+ self.nacos_config = nacos_config
13
+ self.enable_register_nacos = enable_register_nacos
14
+
15
+ # 客户端配置
16
+ self.max_retries = self.nacos_config.get('maxRetries', 5)
17
+ self.retry_delay = self.nacos_config.get('retryDelay', 5)
18
+ self.max_retry_delay = self.nacos_config.get('maxRetryDelay', 30)
19
+
20
+ # 状态管理
21
+ self._client_initialized = False
22
+ self._state_lock = threading.RLock()
23
+ self._shutdown_event = threading.Event()
24
+ self.nacos_client: Optional[nacos.NacosClient] = None
25
+
26
+ def _initialize_client(self) -> bool:
27
+ """初始化Nacos客户端(仅首次调用时执行)"""
28
+ if self._client_initialized:
29
+ return True
30
+
31
+ for attempt in range(self.max_retries):
32
+ try:
33
+ register_ip = self.nacos_config['registerIp']
34
+ namespace_id = self.nacos_config['namespaceId']
35
+ self.nacos_client = nacos.NacosClient(
36
+ server_addresses=register_ip,
37
+ namespace=namespace_id
38
+ )
39
+ SYLogger.info("nacos:客户端初始化成功")
40
+ self._client_initialized = True
41
+ return True
42
+ except Exception as e:
43
+ delay = min(self.retry_delay, self.max_retry_delay)
44
+ SYLogger.error(
45
+ f"nacos:客户端初始化失败 (尝试 {attempt+1}/{self.max_retries}): {e}")
46
+ time.sleep(delay)
47
+
48
+ SYLogger.warning("nacos:无法连接到 Nacos 服务器,已达到最大重试次数")
49
+ return False
50
+
51
+ def ensure_client_connected(self, retry_once: bool = False) -> bool:
52
+ """确保Nacos客户端已连接,返回连接状态"""
53
+ with self._state_lock:
54
+ if self._client_initialized:
55
+ return True
56
+
57
+ SYLogger.warning("nacos:客户端未初始化,尝试连接...")
58
+
59
+ max_attempts = 2 if retry_once else self.max_retries
60
+ attempt = 0
61
+
62
+ while attempt < max_attempts:
63
+ try:
64
+ register_ip = self.nacos_config['registerIp']
65
+ namespace_id = self.nacos_config['namespaceId']
66
+
67
+ self.nacos_client = nacos.NacosClient(
68
+ server_addresses=register_ip,
69
+ namespace=namespace_id
70
+ )
71
+
72
+ if self._verify_client_connection():
73
+ with self._state_lock:
74
+ self._client_initialized = True
75
+ SYLogger.info("nacos:客户端初始化成功")
76
+ return True
77
+ else:
78
+ raise ConnectionError("nacos:客户端初始化后无法验证连接")
79
+
80
+ except Exception as e:
81
+ attempt += 1
82
+ delay = min(self.retry_delay, self.max_retry_delay)
83
+ SYLogger.error(
84
+ f"nacos:客户端初始化失败 (尝试 {attempt}/{max_attempts}): {e}")
85
+ time.sleep(delay)
86
+
87
+ SYLogger.error("nacos:无法连接到 Nacos 服务器,已达到最大重试次数")
88
+ return False
89
+
90
+ def _verify_client_connection(self) -> bool:
91
+ """验证客户端是否真正连接成功"""
92
+ if not self.enable_register_nacos:
93
+ return True
94
+
95
+ try:
96
+ namespace_id = self.nacos_config['namespaceId']
97
+ self.nacos_client.list_naming_instance(
98
+ service_name="", # 空服务名仅用于验证连接
99
+ namespace_id=namespace_id,
100
+ group_name="DEFAULT_GROUP",
101
+ healthy_only=True
102
+ )
103
+ return True
104
+ except Exception as e:
105
+ SYLogger.warning(f"nacos:客户端连接验证失败: {e}")
106
+ return False
107
+
108
+ def reconnect_nacos_client(self) -> bool:
109
+ """重新连接Nacos客户端"""
110
+ SYLogger.warning("nacos:尝试重新连接Nacos客户端")
111
+ with self._state_lock:
112
+ self._client_initialized = False
113
+ return self.ensure_client_connected()
114
+
115
+ @property
116
+ def is_connected(self) -> bool:
117
+ """检查客户端是否已连接"""
118
+ with self._state_lock:
119
+ return self._client_initialized
@@ -0,0 +1,107 @@
1
+ import json
2
+ import threading
3
+ import time
4
+ from typing import Callable, Optional, Dict, List
5
+ from sycommon.synacos.nacos_client_base import NacosClientBase
6
+ import yaml
7
+ from sycommon.logging.kafka_log import SYLogger
8
+
9
+
10
+ class NacosConfigManager:
11
+ """Nacos配置管理类 - 负责配置读取、监听和更新"""
12
+
13
+ def __init__(self, client_base: NacosClientBase):
14
+ self.client_base = client_base
15
+
16
+ # 配置
17
+ self.config_watch_interval = self.client_base.nacos_config.get(
18
+ 'configWatchInterval', 30)
19
+
20
+ # 状态
21
+ self.share_configs: Dict = {}
22
+ self._config_listeners: Dict[str, Callable[[str], None]] = {}
23
+ self._config_cache: Dict[str, str] = {}
24
+ self._watch_thread: Optional[threading.Thread] = None
25
+
26
+ def read_configs(self, shared_configs: List[Dict]) -> dict:
27
+ """读取共享配置"""
28
+ configs = {}
29
+
30
+ for config in shared_configs:
31
+ data_id = config['dataId']
32
+ group = config['group']
33
+
34
+ for attempt in range(self.client_base.max_retries):
35
+ try:
36
+ if not self.client_base.ensure_client_connected():
37
+ self.client_base.reconnect_nacos_client()
38
+
39
+ content = self.client_base.nacos_client.get_config(
40
+ data_id, group)
41
+
42
+ try:
43
+ configs[data_id] = json.loads(content)
44
+ except json.JSONDecodeError:
45
+ try:
46
+ configs[data_id] = yaml.safe_load(content)
47
+ except yaml.YAMLError:
48
+ SYLogger.error(f"nacos:无法解析 {data_id} 的内容")
49
+ break
50
+ except Exception as e:
51
+ if attempt < self.client_base.max_retries - 1:
52
+ SYLogger.warning(
53
+ f"nacos:读取配置 {data_id} 失败 (尝试 {attempt+1}/{self.client_base.max_retries}): {e}")
54
+ time.sleep(self.client_base.retry_delay)
55
+ else:
56
+ SYLogger.error(
57
+ f"nacos:读取配置 {data_id} 失败,已达到最大重试次数: {e}")
58
+
59
+ self.share_configs = configs
60
+ return configs
61
+
62
+ def add_config_listener(self, data_id: str, callback: Callable[[str], None]):
63
+ """添加配置变更监听器"""
64
+ self._config_listeners[data_id] = callback
65
+ if config := self.get_config(data_id):
66
+ callback(config)
67
+
68
+ def get_config(self, data_id: str, group: str = "DEFAULT_GROUP") -> Optional[str]:
69
+ """获取配置内容"""
70
+ if not self.client_base.ensure_client_connected():
71
+ return None
72
+
73
+ try:
74
+ return self.client_base.nacos_client.get_config(data_id, group=group)
75
+ except Exception as e:
76
+ SYLogger.error(f"nacos:获取配置 {data_id} 失败: {str(e)}")
77
+ return None
78
+
79
+ def start_watch_configs(self):
80
+ """启动配置监视线程"""
81
+ self._watch_thread = threading.Thread(
82
+ target=self._watch_configs, daemon=True)
83
+ self._watch_thread.start()
84
+
85
+ def _watch_configs(self):
86
+ """配置监听线程"""
87
+ check_interval = self.config_watch_interval
88
+
89
+ while not self.client_base._shutdown_event.is_set():
90
+ try:
91
+ for data_id, callback in list(self._config_listeners.items()):
92
+ new_config = self.get_config(data_id)
93
+ if new_config and new_config != self._config_cache.get(data_id):
94
+ callback(new_config)
95
+ self._config_cache[data_id] = new_config
96
+ try:
97
+ self.share_configs[data_id] = json.loads(
98
+ new_config)
99
+ except json.JSONDecodeError:
100
+ try:
101
+ self.share_configs[data_id] = yaml.safe_load(
102
+ new_config)
103
+ except yaml.YAMLError:
104
+ SYLogger.error(f"nacos:无法解析 {data_id} 的内容")
105
+ except Exception as e:
106
+ SYLogger.error(f"nacos:配置监视线程异常: {str(e)}")
107
+ self.client_base._shutdown_event.wait(check_interval)
@@ -0,0 +1,144 @@
1
+ import threading
2
+ import time
3
+ from sycommon.logging.kafka_log import SYLogger
4
+ from sycommon.synacos.nacos_client_base import NacosClientBase
5
+ from sycommon.synacos.nacos_service_registration import NacosServiceRegistration
6
+
7
+
8
+ class NacosHeartbeatManager:
9
+ """Nacos心跳管理类 - 负责心跳发送和监控"""
10
+
11
+ def __init__(self, client_base: NacosClientBase, registration: NacosServiceRegistration, heartbeat_interval: int = 15):
12
+ self.client_base = client_base
13
+ self.registration = registration
14
+
15
+ # 心跳配置
16
+ self.heartbeat_interval = heartbeat_interval
17
+ self.heartbeat_timeout = 15
18
+ self.max_heartbeat_timeout = self.client_base.nacos_config.get(
19
+ 'maxHeartbeatTimeout', 30)
20
+
21
+ # 状态管理
22
+ self._heartbeat_lock = threading.Lock()
23
+ self._heartbeat_thread = None
24
+ self._last_heartbeat_time = 0
25
+ self._heartbeat_fail_count = 0
26
+
27
+ def start_heartbeat(self):
28
+ """启动心跳线程(确保单例)"""
29
+ with self._heartbeat_lock:
30
+ if self._heartbeat_thread is not None and self._heartbeat_thread.is_alive():
31
+ return
32
+
33
+ self._heartbeat_thread = None
34
+
35
+ self._heartbeat_thread = threading.Thread(
36
+ target=self._send_heartbeat_loop,
37
+ name="NacosHeartbeatThread",
38
+ daemon=True
39
+ )
40
+ self._heartbeat_thread.start()
41
+ SYLogger.info(
42
+ f"nacos:心跳线程启动,线程ID: {self._heartbeat_thread.ident},"
43
+ f"心跳间隔: {self.heartbeat_interval}秒,"
44
+ f"心跳超时: {self.heartbeat_timeout}秒"
45
+ )
46
+
47
+ def _send_heartbeat_loop(self):
48
+ """心跳发送循环"""
49
+ current_thread = threading.current_thread()
50
+ thread_ident = current_thread.ident
51
+ SYLogger.info(
52
+ f"nacos:心跳循环启动 - 线程ID: {thread_ident}, "
53
+ f"配置间隔: {self.heartbeat_interval}秒, "
54
+ f"超时时间: {self.heartbeat_timeout}秒"
55
+ )
56
+
57
+ consecutive_fail = 0
58
+
59
+ while not self.client_base._shutdown_event.is_set():
60
+ current_time = time.time()
61
+
62
+ try:
63
+ registered_status = self.registration.registered
64
+
65
+ if not registered_status:
66
+ SYLogger.warning(
67
+ f"nacos:服务未注册,跳过心跳 - 线程ID: {thread_ident}")
68
+ consecutive_fail = 0
69
+ else:
70
+ success = self.send_heartbeat()
71
+ if success:
72
+ consecutive_fail = 0
73
+ SYLogger.info(
74
+ f"nacos:心跳发送成功 - 时间: {current_time:.3f}, "
75
+ f"间隔: {self.heartbeat_interval}秒"
76
+ )
77
+ else:
78
+ consecutive_fail += 1
79
+ SYLogger.warning(
80
+ f"nacos:心跳发送失败 - 连续失败: {consecutive_fail}次"
81
+ )
82
+ if consecutive_fail >= 5:
83
+ SYLogger.error("nacos:心跳连续失败5次,尝试重连")
84
+ self.client_base.reconnect_nacos_client()
85
+ consecutive_fail = 0
86
+
87
+ except Exception as e:
88
+ consecutive_fail += 1
89
+ SYLogger.error(
90
+ f"nacos:心跳异常: {str(e)}, 连续失败: {consecutive_fail}次")
91
+
92
+ self.client_base._shutdown_event.wait(self.heartbeat_interval)
93
+
94
+ SYLogger.info(f"nacos:心跳循环已停止 - 线程ID: {thread_ident}")
95
+
96
+ def send_heartbeat(self) -> bool:
97
+ """发送心跳并添加超时控制"""
98
+ if not self.client_base.ensure_client_connected():
99
+ SYLogger.warning("nacos:客户端未连接,心跳发送失败")
100
+ return False
101
+
102
+ result_list = []
103
+
104
+ def heartbeat_task():
105
+ try:
106
+ result = self._send_heartbeat_internal()
107
+ result_list.append(result)
108
+ except Exception as e:
109
+ SYLogger.error(f"nacos:心跳任务执行异常: {e}")
110
+ result_list.append(False)
111
+
112
+ task_thread = threading.Thread(
113
+ target=heartbeat_task,
114
+ daemon=True,
115
+ name="NacosHeartbeatTaskThread"
116
+ )
117
+ task_thread.start()
118
+ task_thread.join(timeout=self.heartbeat_timeout)
119
+
120
+ if not result_list:
121
+ SYLogger.error(f"nacos:心跳发送超时({self.heartbeat_timeout}秒)")
122
+ self.client_base._client_initialized = False
123
+ return False
124
+
125
+ return result_list[0]
126
+
127
+ def _send_heartbeat_internal(self) -> bool:
128
+ """实际的心跳发送逻辑"""
129
+ result = self.client_base.nacos_client.send_heartbeat(
130
+ service_name=self.registration.service_name,
131
+ ip=self.registration.real_ip,
132
+ port=int(self.registration.port),
133
+ cluster_name="DEFAULT",
134
+ weight=1.0,
135
+ metadata={
136
+ "version": self.registration.version} if self.registration.version else None
137
+ )
138
+
139
+ if result and isinstance(result, dict) and result.get('lightBeatEnabled', False):
140
+ SYLogger.info(f"nacos:心跳发送成功,Nacos返回: {result}")
141
+ return True
142
+ else:
143
+ SYLogger.warning(f"nacos:心跳发送失败,Nacos返回: {result}")
144
+ return False