sycommon-python-lib 0.1.56b5__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 (38) hide show
  1. sycommon/config/Config.py +24 -3
  2. sycommon/config/LangfuseConfig.py +15 -0
  3. sycommon/config/SentryConfig.py +13 -0
  4. sycommon/llm/embedding.py +78 -23
  5. sycommon/llm/get_llm.py +9 -218
  6. sycommon/llm/struct_token.py +192 -0
  7. sycommon/llm/sy_langfuse.py +103 -0
  8. sycommon/llm/usage_token.py +117 -0
  9. sycommon/logging/kafka_log.py +187 -433
  10. sycommon/middleware/exception.py +10 -16
  11. sycommon/middleware/timeout.py +2 -1
  12. sycommon/middleware/traceid.py +81 -76
  13. sycommon/notice/uvicorn_monitor.py +32 -27
  14. sycommon/rabbitmq/rabbitmq_client.py +228 -243
  15. sycommon/rabbitmq/rabbitmq_pool.py +201 -123
  16. sycommon/rabbitmq/rabbitmq_service.py +25 -843
  17. sycommon/rabbitmq/rabbitmq_service_client_manager.py +211 -0
  18. sycommon/rabbitmq/rabbitmq_service_connection_monitor.py +73 -0
  19. sycommon/rabbitmq/rabbitmq_service_consumer_manager.py +285 -0
  20. sycommon/rabbitmq/rabbitmq_service_core.py +117 -0
  21. sycommon/rabbitmq/rabbitmq_service_producer_manager.py +238 -0
  22. sycommon/sentry/__init__.py +0 -0
  23. sycommon/sentry/sy_sentry.py +35 -0
  24. sycommon/services.py +122 -96
  25. sycommon/synacos/nacos_client_base.py +119 -0
  26. sycommon/synacos/nacos_config_manager.py +107 -0
  27. sycommon/synacos/nacos_heartbeat_manager.py +144 -0
  28. sycommon/synacos/nacos_service.py +63 -783
  29. sycommon/synacos/nacos_service_discovery.py +157 -0
  30. sycommon/synacos/nacos_service_registration.py +270 -0
  31. sycommon/tools/env.py +62 -0
  32. sycommon/tools/merge_headers.py +20 -0
  33. sycommon/tools/snowflake.py +101 -153
  34. {sycommon_python_lib-0.1.56b5.dist-info → sycommon_python_lib-0.1.57b1.dist-info}/METADATA +10 -8
  35. {sycommon_python_lib-0.1.56b5.dist-info → sycommon_python_lib-0.1.57b1.dist-info}/RECORD +38 -20
  36. {sycommon_python_lib-0.1.56b5.dist-info → sycommon_python_lib-0.1.57b1.dist-info}/WHEEL +0 -0
  37. {sycommon_python_lib-0.1.56b5.dist-info → sycommon_python_lib-0.1.57b1.dist-info}/entry_points.txt +0 -0
  38. {sycommon_python_lib-0.1.56b5.dist-info → sycommon_python_lib-0.1.57b1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,157 @@
1
+ import threading
2
+ from typing import List, Dict
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 NacosServiceDiscovery:
9
+ """Nacos服务发现类 - 负责服务实例发现和轮询"""
10
+
11
+ def __init__(self, client_base: NacosClientBase):
12
+ self.client_base = client_base
13
+
14
+ # 轮询管理
15
+ self._round_robin_index = 0
16
+ self._round_robin_lock = threading.Lock()
17
+
18
+ # 连接监控配置
19
+ self.connection_check_interval = self.client_base.nacos_config.get(
20
+ 'connectionCheckInterval', 30)
21
+ self._monitor_thread_started = False
22
+ self._monitor_thread_lock = threading.Lock()
23
+
24
+ def discover_services(self, service_name: str, group: str = "DEFAULT_GROUP", version: str = None) -> List[Dict]:
25
+ """发现服务实例列表 (与Java格式兼容)"""
26
+ if not self.client_base.ensure_client_connected():
27
+ return []
28
+
29
+ return self.get_service_instances(service_name, group, version)
30
+
31
+ def get_service_instances(self, service_name: str, group: str = "DEFAULT_GROUP", target_version: str = None) -> List[Dict]:
32
+ """
33
+ 获取服务实例列表,并按照以下优先级规则筛选:
34
+ 1. 相同版本号的实例
35
+ 2. 无版本号的实例
36
+ 3. 所有实例中轮询
37
+ """
38
+ try:
39
+ namespace_id = self.client_base.nacos_config['namespaceId']
40
+ instances = self.client_base.nacos_client.list_naming_instance(
41
+ service_name,
42
+ namespace_id=namespace_id,
43
+ group_name=group,
44
+ healthy_only=True,
45
+ )
46
+
47
+ if not instances or 'hosts' not in instances:
48
+ SYLogger.info(f"nacos:未发现 {service_name} 的服务实例")
49
+ return []
50
+
51
+ all_instances = instances.get('hosts', [])
52
+ all_instances = [
53
+ instance for instance in all_instances
54
+ if instance.get('enabled', True)
55
+ ]
56
+ SYLogger.info(
57
+ f"nacos:共发现 {len(all_instances)} 个 {service_name} 服务实例")
58
+
59
+ version_to_use = target_version
60
+
61
+ if version_to_use:
62
+ same_version_instances = [
63
+ instance for instance in all_instances
64
+ if instance.get('metadata', {}).get('version') == version_to_use
65
+ ]
66
+
67
+ if same_version_instances:
68
+ SYLogger.info(
69
+ f"nacos:筛选出 {len(same_version_instances)} 个与当前版本({version_to_use})匹配的实例")
70
+ return same_version_instances
71
+
72
+ no_version_instances = [
73
+ instance for instance in all_instances
74
+ if 'version' not in instance.get('metadata', {})
75
+ ]
76
+
77
+ if no_version_instances:
78
+ SYLogger.info(
79
+ f"nacos:未找到相同版本({version_to_use})的实例,筛选出 {len(no_version_instances)} 个无版本号的实例")
80
+ return no_version_instances
81
+ else:
82
+ no_version_instances = [
83
+ instance for instance in all_instances
84
+ if 'version' not in instance.get('metadata', {})
85
+ ]
86
+
87
+ if no_version_instances:
88
+ # 从通用实例中轮询
89
+ with self._round_robin_lock:
90
+ selected_index = self._round_robin_index % len(
91
+ no_version_instances)
92
+ self._round_robin_index = (
93
+ selected_index + 1) % len(no_version_instances)
94
+
95
+ SYLogger.info(
96
+ f"nacos:无版本请求,从 {len(no_version_instances)} 个通用实例中选择")
97
+ return [no_version_instances[selected_index]]
98
+
99
+ SYLogger.info(
100
+ f"nacos:使用轮询方式从 {len(all_instances)} 个实例中选择")
101
+
102
+ with self._round_robin_lock:
103
+ selected_index = self._round_robin_index % len(all_instances)
104
+ self._round_robin_index = (
105
+ selected_index + 1) % len(all_instances)
106
+
107
+ return [all_instances[selected_index]]
108
+
109
+ except Exception as e:
110
+ SYLogger.error(f"nacos:服务发现失败: {service_name}: {str(e)}")
111
+ return []
112
+
113
+ def monitor_connection(self, registration: NacosServiceRegistration):
114
+ """连接监控线程"""
115
+ with self._monitor_thread_lock:
116
+ if self.client_base._shutdown_event.is_set() or self._monitor_thread_started:
117
+ SYLogger.warning("nacos:监控线程已启动/已关闭,拒绝重复启动")
118
+ return
119
+ self._monitor_thread_started = True
120
+
121
+ check_interval = self.connection_check_interval
122
+ SYLogger.info(
123
+ f"nacos:连接监控线程启动 - 线程ID: {threading.current_thread().ident}")
124
+
125
+ while not self.client_base._shutdown_event.is_set():
126
+ try:
127
+ if not self.client_base.is_connected:
128
+ SYLogger.warning("nacos:客户端未连接,尝试重新初始化")
129
+ self.client_base.ensure_client_connected()
130
+ else:
131
+ current_registered = registration.check_service_registered()
132
+
133
+ if current_registered != registration.registered:
134
+ registration.registered = current_registered
135
+ if not current_registered:
136
+ SYLogger.warning("nacos:服务实例未注册,触发单次重新注册")
137
+ retry_thread = threading.Thread(
138
+ target=registration.register,
139
+ args=(True,),
140
+ daemon=True,
141
+ name="NacosSingleRetryThread"
142
+ )
143
+ retry_thread.start()
144
+ else:
145
+ SYLogger.info("nacos:服务实例已注册,触发单次验证")
146
+ registration.verify_registration()
147
+
148
+ self.client_base._shutdown_event.wait(check_interval)
149
+
150
+ except Exception as e:
151
+ SYLogger.error(f"nacos:连接监控异常: {str(e)}")
152
+ self.client_base._shutdown_event.wait(5)
153
+
154
+ with self._monitor_thread_lock:
155
+ self._monitor_thread_started = False
156
+ SYLogger.info(
157
+ f"nacos:连接监控线程退出 - 线程ID: {threading.current_thread().ident}")
@@ -0,0 +1,270 @@
1
+ import threading
2
+ import time
3
+ import atexit
4
+ from sycommon.logging.kafka_log import SYLogger
5
+ from sycommon.synacos.nacos_client_base import NacosClientBase
6
+
7
+
8
+ class NacosServiceRegistration:
9
+ """Nacos服务注册类 - 负责服务注册、注销和状态验证"""
10
+
11
+ def __init__(self, client_base: NacosClientBase, service_name: str, real_ip: str, port: int, version: str):
12
+ self.client_base = client_base
13
+ self.service_name = service_name
14
+ self.real_ip = real_ip
15
+ self.port = port
16
+ self.version = version
17
+
18
+ # 注册配置
19
+ self.register_retry_interval = self.client_base.nacos_config.get(
20
+ 'registerRetryInterval', 15)
21
+ self.long_term_retry_delay = self.client_base.nacos_config.get(
22
+ 'longTermRetryDelay', 30)
23
+ self.max_long_term_retries = self.client_base.nacos_config.get(
24
+ 'maxLongTermRetries', -1)
25
+
26
+ # 验证配置
27
+ self.registration_verify_count = self.client_base.nacos_config.get(
28
+ 'registrationVerifyCount', 1)
29
+ self.registration_verify_interval = self.client_base.nacos_config.get(
30
+ 'registrationVerifyInterval', 1)
31
+ self.registration_post_delay = self.client_base.nacos_config.get(
32
+ 'registrationPostDelay', 3)
33
+
34
+ # 状态管理
35
+ self.registered = False
36
+ self._long_term_retry_count = 0
37
+ self._verify_lock = threading.Lock()
38
+ self._last_verify_time = 0
39
+ self._atexit_registered = False
40
+
41
+ def _cleanup_stale_instance(self):
42
+ """清理可能存在的残留实例"""
43
+ if not self.client_base.is_connected:
44
+ return
45
+
46
+ try:
47
+ self.client_base.nacos_client.remove_naming_instance(
48
+ service_name=self.service_name,
49
+ ip=self.real_ip,
50
+ port=int(self.port),
51
+ cluster_name="DEFAULT"
52
+ )
53
+ SYLogger.warning(f"nacos:清理残留实例: {self.real_ip}:{self.port}")
54
+ except Exception as e:
55
+ SYLogger.error(f"nacos:清理残留实例异常: {e}")
56
+
57
+ def check_service_registered(self) -> bool:
58
+ """检查服务是否已注册(基于实例列表)"""
59
+ if not self.client_base.enable_register_nacos:
60
+ return True
61
+
62
+ if not self.client_base.ensure_client_connected():
63
+ return False
64
+
65
+ try:
66
+ namespace_id = self.client_base.nacos_config['namespaceId']
67
+ instances = self.client_base.nacos_client.list_naming_instance(
68
+ service_name=self.service_name,
69
+ namespace_id=namespace_id,
70
+ group_name="DEFAULT_GROUP",
71
+ healthy_only=True,
72
+ )
73
+
74
+ found = False
75
+ for instance in instances.get('hosts', []):
76
+ if (instance.get('ip') == self.real_ip and
77
+ instance.get('port') == int(self.port)):
78
+ SYLogger.info(f"nacos:找到已注册实例: {self.real_ip}:{self.port}")
79
+ found = True
80
+ break
81
+
82
+ if not found:
83
+ SYLogger.warning(f"nacos:未找到注册实例: {self.real_ip}:{self.port}")
84
+
85
+ self.registered = found
86
+ return found
87
+ except Exception as e:
88
+ SYLogger.error(f"nacos:检查服务注册状态失败: {e}")
89
+ return False
90
+
91
+ def verify_registration(self) -> bool:
92
+ """多次验证服务是否成功注册(加锁防止重复执行)"""
93
+ if self._verify_lock.locked():
94
+ SYLogger.warning("nacos:注册验证已在执行中,跳过重复调用")
95
+ return self.registered
96
+
97
+ with self._verify_lock:
98
+ current_time = time.time()
99
+ if current_time - self._last_verify_time < 15: # 15秒冷却
100
+ return True
101
+
102
+ success_count = 0
103
+ verify_count = self.registration_verify_count
104
+ SYLogger.info(
105
+ f"nacos:开始验证服务注册状态,共验证 {verify_count} 次,间隔 {self.registration_verify_interval} 秒")
106
+
107
+ self._last_verify_time = current_time
108
+
109
+ for i in range(verify_count):
110
+ if self.check_service_registered():
111
+ success_count += 1
112
+
113
+ if i < verify_count - 1:
114
+ self.client_base._shutdown_event.wait(
115
+ self.registration_verify_interval)
116
+ if self.client_base._shutdown_event.is_set():
117
+ SYLogger.warning("nacos:应用正在关闭,终止注册验证")
118
+ break
119
+
120
+ pass_threshold = verify_count / 2
121
+ result = success_count >= pass_threshold
122
+
123
+ if result:
124
+ SYLogger.info(
125
+ f"nacos:服务注册验证成功,{success_count}/{verify_count} 次验证通过")
126
+ else:
127
+ SYLogger.error(
128
+ f"nacos:服务注册验证失败,仅 {success_count}/{verify_count} 次验证通过")
129
+
130
+ return result
131
+
132
+ def register(self, force: bool = False) -> bool:
133
+ """注册服务到Nacos"""
134
+ if self.registered and not force and self.check_service_registered():
135
+ return True
136
+
137
+ if self.registered and not force:
138
+ self.registered = False
139
+ SYLogger.warning("nacos:本地状态显示已注册,但Nacos中未找到服务实例,准备重新注册")
140
+
141
+ metadata = {
142
+ "ignore-metrics": "true",
143
+ }
144
+ if self.version:
145
+ metadata["version"] = self.version
146
+
147
+ for attempt in range(self.client_base.max_retries):
148
+ if not self.client_base.ensure_client_connected():
149
+ return False
150
+
151
+ try:
152
+ self.client_base.nacos_client.add_naming_instance(
153
+ service_name=self.service_name,
154
+ ip=self.real_ip,
155
+ port=int(self.port),
156
+ metadata=metadata,
157
+ cluster_name="DEFAULT",
158
+ healthy=True,
159
+ ephemeral=True,
160
+ heartbeat_interval=15 # 心跳间隔默认15秒
161
+ )
162
+ SYLogger.info(
163
+ f"nacos:服务 {self.service_name} 注册请求已发送: {self.real_ip}:{self.port}")
164
+
165
+ if not self._atexit_registered:
166
+ atexit.register(self.deregister_service)
167
+ self._atexit_registered = True
168
+
169
+ return True
170
+ except Exception as e:
171
+ if "signal only works in main thread" in str(e):
172
+ return True
173
+ elif attempt < self.client_base.max_retries - 1:
174
+ SYLogger.warning(
175
+ f"nacos:服务注册失败 (尝试 {attempt+1}/{self.client_base.max_retries}): {e}")
176
+ time.sleep(self.client_base.retry_delay)
177
+ else:
178
+ SYLogger.error(f"nacos:服务注册失败,已达到最大重试次数: {e}")
179
+ return False
180
+
181
+ def register_with_retry(self) -> bool:
182
+ """带重试机制的服务注册(基于实例列表检查)"""
183
+ retry_count = 0
184
+ last_error = None
185
+ self.registered = False
186
+ # 首次注册尝试标记
187
+ first_attempt = True
188
+
189
+ while (not self.registered) and (self.max_long_term_retries < 0 or retry_count < self.max_long_term_retries):
190
+ if self.registered:
191
+ return True
192
+
193
+ try:
194
+ register_success = self.register(force=True)
195
+ if not register_success:
196
+ raise RuntimeError("nacos:服务注册请求失败")
197
+
198
+ SYLogger.info(
199
+ f"nacos:服务注册请求已发送,{'首次启动信任注册,跳过阻塞验证' if first_attempt else f'延迟 {self.registration_post_delay} 秒后开始验证'}")
200
+
201
+ # 核心逻辑:首次注册跳过阻塞验证,非首次按原逻辑
202
+ if first_attempt:
203
+ # 首次注册:直接标记成功,不阻塞
204
+ self.registered = True
205
+ self.client_base._client_initialized = True
206
+ self.client_base._shutdown_event.set()
207
+ self.client_base._shutdown_event.clear()
208
+ self._long_term_retry_count = 0
209
+
210
+ SYLogger.info(f"nacos:首次启动信任注册成功: {self.service_name}")
211
+ first_attempt = False # 标记为非首次
212
+ return True
213
+ else:
214
+ # 非首次/重试:保留原有阻塞验证逻辑
215
+ time.sleep(self.registration_post_delay)
216
+ registered = self.verify_registration()
217
+ self.registered = registered
218
+
219
+ if self.registered:
220
+ self.client_base._client_initialized = True
221
+ self.client_base._shutdown_event.set()
222
+ self.client_base._shutdown_event.clear()
223
+ self._long_term_retry_count = 0
224
+
225
+ SYLogger.info(
226
+ f"nacos:服务注册成功并通过验证: {self.service_name}")
227
+ return True
228
+ else:
229
+ raise RuntimeError("nacos:服务注册验证失败")
230
+
231
+ except Exception as e:
232
+ last_error = str(e)
233
+ retry_count += 1
234
+ first_attempt = False # 失败后标记为非首次
235
+ delay = min(self.register_retry_interval,
236
+ self.client_base.max_retry_delay)
237
+
238
+ SYLogger.warning(
239
+ f"nacos:服务注册尝试 {retry_count} 失败: {last_error},{delay}秒后重试")
240
+ time.sleep(delay)
241
+
242
+ if self.registered:
243
+ return True
244
+
245
+ if last_error:
246
+ SYLogger.error(f"nacos:服务注册失败,最终错误: {last_error}")
247
+ else:
248
+ SYLogger.error(f"nacos:服务注册失败,已达到最大重试次数: {self.service_name}")
249
+
250
+ return False
251
+
252
+ def deregister_service(self):
253
+ """从Nacos注销服务"""
254
+ if not self.registered or not self.client_base.is_connected:
255
+ return
256
+
257
+ SYLogger.info("nacos:正在注销服务...")
258
+ try:
259
+ self.client_base.nacos_client.remove_naming_instance(
260
+ service_name=self.service_name,
261
+ ip=self.real_ip,
262
+ port=int(self.port),
263
+ cluster_name="DEFAULT"
264
+ )
265
+ self.registered = False
266
+ SYLogger.info(f"nacos:服务 {self.service_name} 已注销")
267
+ except Exception as e:
268
+ SYLogger.error(f"nacos:注销服务时发生错误: {e}")
269
+ finally:
270
+ self.client_base._shutdown_event.set()
sycommon/tools/env.py ADDED
@@ -0,0 +1,62 @@
1
+ import os
2
+
3
+
4
+ def _normalize_env_key(key: str) -> str:
5
+ """
6
+ 环境变量名标准化:
7
+ 1. 转小写
8
+ 2. 中划线(-)和下划线(_)统一替换为下划线(_)
9
+ :param key: 原始环境变量名
10
+ :return: 标准化后的key
11
+ """
12
+ return key.lower().replace('-', '_')
13
+
14
+
15
+ def check_env_flag(target_keys: list, default: str = 'false') -> bool:
16
+ """
17
+ 检查环境变量是否为"true"(自动兼容:大小写、中划线/下划线)
18
+ :param target_keys: 目标变量名列表(如 ['REGISTER-NACOS'],无需传双key)
19
+ :param default: 默认值(未找到变量时使用)
20
+ :return: 布尔值
21
+ """
22
+ # 1. 标准化目标key(小写+统一下划线)
23
+ target_keys_normalized = [_normalize_env_key(k) for k in target_keys]
24
+
25
+ # 2. 遍历所有环境变量,标准化后匹配
26
+ for env_key, env_val in os.environ.items():
27
+ env_key_normalized = _normalize_env_key(env_key)
28
+ if env_key_normalized in target_keys_normalized:
29
+ # 3. 值去空格 + 转小写 判断
30
+ return env_val.strip().lower() == 'true'
31
+
32
+ # 4. 未找到变量时,判断默认值
33
+ return default.strip().lower() == 'true'
34
+
35
+
36
+ def get_env_var(key: str, default='', case_insensitive: bool = True) -> str:
37
+ """
38
+ 获取环境变量值(自动兼容:大小写、中划线/下划线)
39
+ :param key: 目标环境变量名(如 'REGISTER-NACOS'/'version')
40
+ :param default: 无匹配时的默认值,默认空字符串
41
+ :param case_insensitive: 是否忽略变量名大小写(默认True,建议保持)
42
+ :return: 匹配到的环境变量值 / 默认值
43
+ """
44
+ if case_insensitive:
45
+ # 标准化目标key(小写+统一下划线)
46
+ target_key_normalized = _normalize_env_key(key)
47
+
48
+ # 遍历环境变量,标准化后匹配
49
+ for env_key, env_val in os.environ.items():
50
+ env_key_normalized = _normalize_env_key(env_key)
51
+ if env_key_normalized == target_key_normalized:
52
+ return env_val
53
+ return default
54
+ else:
55
+ # 不忽略大小写时,仅自动兼容中划线/下划线
56
+ target_key_1 = key # 原始key
57
+ target_key_2 = key.replace(
58
+ '-', '_') if '-' in key else key.replace('_', '-') # 替换格式的key
59
+ val = os.getenv(target_key_1)
60
+ if val is not None:
61
+ return val
62
+ return os.getenv(target_key_2, default)
@@ -95,3 +95,23 @@ def merge_headers(
95
95
  processed_headers[key_lower] = value
96
96
 
97
97
  return processed_headers
98
+
99
+
100
+ def get_header_value(headers: list, target_key: str, default=None):
101
+ """
102
+ 从列表中查找指定 header 的值
103
+
104
+ Args:
105
+ headers: header 列表,例如 [('Content-Type', 'application/json'), ...]
106
+ target_key: 要查找的 key
107
+ default: 如果没找到返回的默认值
108
+ """
109
+ if not headers:
110
+ return default
111
+
112
+ for item in headers:
113
+ # 兼容 list 和 tuple,确保长度为2
114
+ if isinstance(item, (list, tuple)) and len(item) == 2 and item[0] == target_key:
115
+ return item[1]
116
+
117
+ return default