sycommon-python-lib 0.1.54__tar.gz → 0.1.55a0__tar.gz

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 (65) hide show
  1. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/PKG-INFO +2 -1
  2. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/pyproject.toml +2 -1
  3. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/services.py +26 -16
  4. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/synacos/feign.py +6 -4
  5. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/synacos/nacos_service.py +3 -0
  6. sycommon_python_lib-0.1.55a0/src/sycommon/tools/snowflake.py +221 -0
  7. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon_python_lib.egg-info/PKG-INFO +2 -1
  8. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon_python_lib.egg-info/requires.txt +1 -0
  9. sycommon_python_lib-0.1.54/src/sycommon/tools/snowflake.py +0 -33
  10. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/README.md +0 -0
  11. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/setup.cfg +0 -0
  12. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/command/cli.py +0 -0
  13. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/__init__.py +0 -0
  14. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/config/Config.py +0 -0
  15. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/config/DatabaseConfig.py +0 -0
  16. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/config/EmbeddingConfig.py +0 -0
  17. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/config/LLMConfig.py +0 -0
  18. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/config/MQConfig.py +0 -0
  19. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/config/RerankerConfig.py +0 -0
  20. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/config/__init__.py +0 -0
  21. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/database/base_db_service.py +0 -0
  22. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/database/database_service.py +0 -0
  23. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/health/__init__.py +0 -0
  24. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/health/health_check.py +0 -0
  25. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/health/metrics.py +0 -0
  26. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/health/ping.py +0 -0
  27. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/logging/__init__.py +0 -0
  28. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/logging/kafka_log.py +0 -0
  29. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/logging/logger_wrapper.py +0 -0
  30. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/logging/sql_logger.py +0 -0
  31. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/middleware/__init__.py +0 -0
  32. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/middleware/context.py +0 -0
  33. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/middleware/cors.py +0 -0
  34. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/middleware/docs.py +0 -0
  35. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/middleware/exception.py +0 -0
  36. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/middleware/middleware.py +0 -0
  37. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/middleware/monitor_memory.py +0 -0
  38. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/middleware/mq.py +0 -0
  39. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/middleware/timeout.py +0 -0
  40. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/middleware/traceid.py +0 -0
  41. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/models/__init__.py +0 -0
  42. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/models/base_http.py +0 -0
  43. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/models/log.py +0 -0
  44. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/models/mqlistener_config.py +0 -0
  45. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/models/mqmsg_model.py +0 -0
  46. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/models/mqsend_config.py +0 -0
  47. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/models/sso_user.py +0 -0
  48. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/rabbitmq/rabbitmq_client.py +0 -0
  49. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/rabbitmq/rabbitmq_pool.py +0 -0
  50. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/rabbitmq/rabbitmq_service.py +0 -0
  51. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/sse/__init__.py +0 -0
  52. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/sse/event.py +0 -0
  53. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/sse/sse.py +0 -0
  54. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/synacos/__init__.py +0 -0
  55. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/synacos/example.py +0 -0
  56. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/synacos/example2.py +0 -0
  57. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/synacos/feign_client.py +0 -0
  58. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/synacos/param.py +0 -0
  59. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/tools/__init__.py +0 -0
  60. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/tools/docs.py +0 -0
  61. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon/tools/timing.py +0 -0
  62. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon_python_lib.egg-info/SOURCES.txt +0 -0
  63. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon_python_lib.egg-info/dependency_links.txt +0 -0
  64. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon_python_lib.egg-info/entry_points.txt +0 -0
  65. {sycommon_python_lib-0.1.54 → sycommon_python_lib-0.1.55a0}/src/sycommon_python_lib.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sycommon-python-lib
3
- Version: 0.1.54
3
+ Version: 0.1.55a0
4
4
  Summary: Add your description here
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -12,6 +12,7 @@ Requires-Dist: kafka-python>=2.2.16
12
12
  Requires-Dist: loguru>=0.7.3
13
13
  Requires-Dist: mysql-connector-python>=9.5.0
14
14
  Requires-Dist: nacos-sdk-python<3.0,>=2.0.9
15
+ Requires-Dist: netifaces>=0.11.0
15
16
  Requires-Dist: pydantic>=2.12.4
16
17
  Requires-Dist: python-dotenv>=1.2.1
17
18
  Requires-Dist: pyyaml>=6.0.3
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sycommon-python-lib"
3
- version = "0.1.54"
3
+ version = "0.1.55-alpha"
4
4
  description = "Add your description here"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -13,6 +13,7 @@ dependencies = [
13
13
  "loguru>=0.7.3",
14
14
  "mysql-connector-python>=9.5.0",
15
15
  "nacos-sdk-python>=2.0.9,<3.0",
16
+ "netifaces>=0.11.0",
16
17
  "pydantic>=2.12.4",
17
18
  "python-dotenv>=1.2.1",
18
19
  "pyyaml>=6.0.3",
@@ -92,17 +92,22 @@ class Services(metaclass=SingletonMeta):
92
92
  async def combined_lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
93
93
  # 1. 执行Services自身的初始化
94
94
  instance = cls(config, app)
95
- has_listeners = bool(
95
+
96
+ # 明确判断是否有有效的监听器/发送器配置
97
+ has_valid_listeners = bool(
96
98
  rabbitmq_listeners and len(rabbitmq_listeners) > 0)
97
- has_senders = bool(rabbitmq_senders and len(rabbitmq_senders) > 0)
99
+ has_valid_senders = bool(
100
+ rabbitmq_senders and len(rabbitmq_senders) > 0)
98
101
 
99
102
  try:
100
- await instance._setup_mq_async(
101
- rabbitmq_listeners=rabbitmq_listeners,
102
- rabbitmq_senders=rabbitmq_senders,
103
- has_listeners=has_listeners,
104
- has_senders=has_senders
105
- )
103
+ # 只有存在监听器或发送器时才初始化RabbitMQService
104
+ if has_valid_listeners or has_valid_senders:
105
+ await instance._setup_mq_async(
106
+ rabbitmq_listeners=rabbitmq_listeners if has_valid_listeners else None,
107
+ rabbitmq_senders=rabbitmq_senders if has_valid_senders else None,
108
+ has_listeners=has_valid_listeners,
109
+ has_senders=has_valid_senders
110
+ )
106
111
  cls._initialized = True
107
112
  logging.info("Services初始化完成")
108
113
  except Exception as e:
@@ -145,7 +150,12 @@ class Services(metaclass=SingletonMeta):
145
150
  has_senders: bool = False,
146
151
  ):
147
152
  """异步设置MQ相关服务(适配单通道RabbitMQService)"""
148
- # 初始化RabbitMQ服务,传递状态
153
+ # ========== 只有需要使用MQ时才初始化 ==========
154
+ if not (has_listeners or has_senders):
155
+ logging.info("无RabbitMQ监听器/发送器配置,跳过RabbitMQService初始化")
156
+ return
157
+
158
+ # 仅当有监听器或发送器时,才执行RabbitMQService初始化
149
159
  RabbitMQService.init(self._config, has_listeners, has_senders)
150
160
 
151
161
  # 优化:等待连接池“存在且初始化完成”(避免提前执行后续逻辑)
@@ -156,25 +166,25 @@ class Services(metaclass=SingletonMeta):
156
166
  logging.info("等待RabbitMQ连接池初始化...")
157
167
  await asyncio.sleep(0.5)
158
168
 
159
- # 设置发送器,传递是否有监听器的标志
160
- if rabbitmq_senders:
169
+ # ========== 保留原有严格的发送器/监听器初始化判断 ==========
170
+ # 只有配置了发送器才执行发送器初始化
171
+ if has_senders and rabbitmq_senders:
161
172
  # 判断是否有监听器,如果有遍历监听器列表,队列名一样将prefetch_count属性设置到发送器对象中
162
- if rabbitmq_listeners:
173
+ if has_listeners and rabbitmq_listeners:
163
174
  for sender in rabbitmq_senders:
164
175
  for listener in rabbitmq_listeners:
165
176
  if sender.queue_name == listener.queue_name:
166
177
  sender.prefetch_count = listener.prefetch_count
167
178
  await self._setup_senders_async(rabbitmq_senders, has_listeners)
168
179
 
169
- # 设置监听器,传递是否有发送器的标志
170
- if rabbitmq_listeners:
180
+ # 只有配置了监听器才执行监听器初始化
181
+ if has_listeners and rabbitmq_listeners:
171
182
  await self._setup_listeners_async(rabbitmq_listeners, has_senders)
172
183
 
173
184
  # 验证初始化结果
174
185
  if has_listeners:
175
186
  # 异步获取客户端数量(适配新的RabbitMQService)
176
- listener_count = len(
177
- RabbitMQService._consumer_tasks)
187
+ listener_count = len(RabbitMQService._consumer_tasks)
178
188
  logging.info(f"监听器初始化完成,共启动 {listener_count} 个消费者")
179
189
  if listener_count == 0:
180
190
  logging.warning("未成功初始化任何监听器,请检查配置或MQ服务状态")
@@ -135,13 +135,15 @@ async def _handle_feign_response(response, service_name: str, api_path: str):
135
135
  """
136
136
  try:
137
137
  status_code = response.status
138
- content_type = response.headers.get('Content-Type', '').lower()
138
+ content_type = response.headers.get('Content-Type', '')
139
+ content_type = content_type.lower() if content_type else ''
140
+
139
141
  response_body = None
140
142
 
141
143
  if status_code == 200:
142
- if 'application/json' in content_type:
144
+ if content_type and 'application/json' in content_type:
143
145
  response_body = await response.json()
144
- elif 'text/' in content_type:
146
+ elif content_type and 'text/' in content_type:
145
147
  # 文本类型(text/plain、text/html等):按文本读取
146
148
  try:
147
149
  response_body = await response.text(encoding='utf-8')
@@ -158,7 +160,7 @@ async def _handle_feign_response(response, service_name: str, api_path: str):
158
160
  else:
159
161
  # 非200状态:统一读取响应体(兼容文本/二进制错误信息)
160
162
  try:
161
- if 'application/json' in content_type:
163
+ if content_type and 'application/json' in content_type:
162
164
  response_body = await response.json()
163
165
  else:
164
166
  response_body = await response.text(encoding='utf-8', errors='ignore')
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  import threading
2
3
  import json
3
4
  from typing import Callable, Dict, List, Optional
@@ -14,6 +15,8 @@ import random
14
15
  from sycommon.config.Config import SingletonMeta
15
16
  from sycommon.logging.kafka_log import SYLogger
16
17
 
18
+ logging.getLogger("nacos.client").setLevel(logging.WARNING)
19
+
17
20
 
18
21
  class NacosService(metaclass=SingletonMeta):
19
22
  def __init__(self, config):
@@ -0,0 +1,221 @@
1
+ import time
2
+ import threading
3
+ import socket
4
+ import hashlib
5
+ import random
6
+ from typing import Optional
7
+ from os import environ
8
+ import netifaces
9
+
10
+
11
+ class Snowflake:
12
+ """雪花算法生成器(无公网依赖,适配内网环境)"""
13
+ START_TIMESTAMP = 1388534400000 # 2014-01-01 00:00:00
14
+ SEQUENCE_BITS = 12
15
+ MACHINE_ID_BITS = 10
16
+ MAX_MACHINE_ID = (1 << MACHINE_ID_BITS) - 1 # 0~1023
17
+ MAX_SEQUENCE = (1 << SEQUENCE_BITS) - 1
18
+ MACHINE_ID_SHIFT = SEQUENCE_BITS
19
+ TIMESTAMP_SHIFT = SEQUENCE_BITS + MACHINE_ID_BITS
20
+
21
+ # 类级别的单例实例
22
+ _instance = None
23
+ _instance_lock = threading.Lock()
24
+
25
+ def __init__(self, machine_id: Optional[int] = None):
26
+ """
27
+ 初始化:优先使用传入的machine_id,否则自动从K8s环境获取
28
+ :param machine_id: 手动指定机器ID(None则自动计算)
29
+ """
30
+ # 自动计算K8s环境下的machine_id
31
+ if machine_id is None:
32
+ machine_id = self._get_k8s_machine_id()
33
+
34
+ if not (0 <= machine_id <= self.MAX_MACHINE_ID):
35
+ raise ValueError(f"机器ID必须在0~{self.MAX_MACHINE_ID}之间")
36
+
37
+ self.machine_id = machine_id
38
+ self.last_timestamp = -1
39
+ self.sequence = 0
40
+ self.lock = threading.Lock()
41
+
42
+ def _get_k8s_machine_id(self) -> int:
43
+ """
44
+ 从K8s环境自动计算唯一machine_id(无公网依赖,多层兜底):
45
+ 优先级:POD_NAME > POD_IP > 容器内网IP(网卡读取) > 容器主机名 > 随机数(最终兜底)
46
+ """
47
+ # 1. 优先读取K8s内置的POD_NAME(默认注入,优先级最高)
48
+ pod_name = environ.get("POD_NAME")
49
+ if pod_name:
50
+ return self._hash_to_machine_id(pod_name)
51
+
52
+ # 2. 读取POD_IP(手动配置downwardAPI后必存在)
53
+ pod_ip = environ.get("POD_IP")
54
+ if pod_ip:
55
+ return self._hash_to_machine_id(pod_ip)
56
+
57
+ # 3. 兜底1:读取本机网卡获取内网IP(无公网依赖)
58
+ try:
59
+ local_ip = self._get_local_internal_ip()
60
+ if local_ip:
61
+ return self._hash_to_machine_id(local_ip)
62
+ else:
63
+ # logger.warning("读取网卡信息成功,但未找到非回环内网IP")
64
+ pass
65
+ except Exception as e:
66
+ # logger.warning(f"读取本机网卡IP失败: {e},尝试使用主机名")
67
+ pass
68
+
69
+ # 4. 兜底2:获取容器主机名(K8s中默认等于Pod名称,保证唯一)
70
+ hostname = socket.gethostname()
71
+ if hostname:
72
+ # logger.info(
73
+ # f"未读取到POD_NAME/POD_IP/内网IP,使用主机名: {hostname}生成machine_id")
74
+ return self._hash_to_machine_id(hostname)
75
+
76
+ # 5. 最终兜底:生成随机数(仅极端情况使用,日志告警)
77
+ random_id = random.randint(0, self.MAX_MACHINE_ID)
78
+ # logger.warning(f"所有方式均失败,使用随机数生成machine_id: {random_id}(可能重复!)")
79
+ return random_id
80
+
81
+ def _get_local_internal_ip(self) -> Optional[str]:
82
+ """
83
+ 读取本机网卡信息,获取非回环的内网IP(无公网依赖)
84
+ :return: 内网IP字符串,失败返回None
85
+ """
86
+ try:
87
+ # 遍历所有网卡
88
+ for interface in netifaces.interfaces():
89
+ # 获取网卡的IP地址信息
90
+ addrs = netifaces.ifaddresses(interface)
91
+ # 只取IPv4地址
92
+ if netifaces.AF_INET in addrs:
93
+ for addr in addrs[netifaces.AF_INET]:
94
+ ip = addr.get('addr')
95
+ # 过滤回环地址(127.0.0.1)和docker虚拟地址(172.17.0.0/16可选过滤)
96
+ if ip and not ip.startswith('127.'):
97
+ # 可选:过滤docker0的默认地址段(根据实际内网段调整)
98
+ # if not ip.startswith('172.17.'):
99
+ return ip
100
+ return None
101
+ except ImportError:
102
+ # 若未安装netifaces,降级为socket方式(仅尝试本地解析,无公网连接)
103
+ # logger.warning("未安装netifaces库,尝试降级方式获取IP")
104
+ return self._get_local_ip_fallback()
105
+
106
+ def _get_local_ip_fallback(self) -> Optional[str]:
107
+ """
108
+ 降级方案:不连接公网,仅通过本地socket获取IP(兼容无netifaces的场景)
109
+ """
110
+ try:
111
+ # 创建socket但不连接任何地址,仅绑定到本地
112
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
113
+ # 绑定到0.0.0.0:0(仅用于获取本机IP,不发送数据)
114
+ s.bind(('', 0))
115
+ local_ip = s.getsockname()[0]
116
+ s.close()
117
+ # 过滤回环地址
118
+ if not local_ip.startswith('127.'):
119
+ return local_ip
120
+ return None
121
+ except Exception:
122
+ return None
123
+
124
+ def _hash_to_machine_id(self, text: str) -> int:
125
+ """将字符串哈希后取模,得到0~1023的machine_id(保证分布均匀)"""
126
+ hash_bytes = hashlib.md5(text.encode("utf-8")).digest()
127
+ hash_int = int.from_bytes(hash_bytes[:4], byteorder="big")
128
+ return hash_int % self.MAX_MACHINE_ID
129
+
130
+ def _get_current_timestamp(self) -> int:
131
+ return int(time.time() * 1000)
132
+
133
+ def _wait_next_millisecond(self, current_timestamp: int) -> int:
134
+ while current_timestamp <= self.last_timestamp:
135
+ current_timestamp = self._get_current_timestamp()
136
+ return current_timestamp
137
+
138
+ def generate_id(self) -> int:
139
+ with self.lock:
140
+ current_timestamp = self._get_current_timestamp()
141
+
142
+ if current_timestamp < self.last_timestamp:
143
+ raise RuntimeError(
144
+ f"时钟回拨检测:当前时间戳({current_timestamp}) < 上一次时间戳({self.last_timestamp})"
145
+ )
146
+
147
+ if current_timestamp == self.last_timestamp:
148
+ self.sequence = (self.sequence + 1) & self.MAX_SEQUENCE
149
+ if self.sequence == 0:
150
+ current_timestamp = self._wait_next_millisecond(
151
+ current_timestamp)
152
+ else:
153
+ self.sequence = 0
154
+
155
+ self.last_timestamp = current_timestamp
156
+
157
+ snowflake_id = (
158
+ ((current_timestamp - self.START_TIMESTAMP) << self.TIMESTAMP_SHIFT)
159
+ | (self.machine_id << self.MACHINE_ID_SHIFT)
160
+ | self.sequence
161
+ )
162
+
163
+ return snowflake_id
164
+
165
+ @staticmethod
166
+ def parse_id(snowflake_id: int) -> dict:
167
+ from datetime import datetime
168
+ sequence = snowflake_id & Snowflake.MAX_SEQUENCE
169
+ machine_id = (snowflake_id >>
170
+ Snowflake.MACHINE_ID_SHIFT) & Snowflake.MAX_MACHINE_ID
171
+ timestamp = (snowflake_id >> Snowflake.TIMESTAMP_SHIFT) + \
172
+ Snowflake.START_TIMESTAMP
173
+ generate_time = datetime.fromtimestamp(
174
+ timestamp / 1000).strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
175
+
176
+ return {
177
+ "snowflake_id": snowflake_id,
178
+ "generate_time": generate_time,
179
+ "machine_id": machine_id,
180
+ "sequence": sequence
181
+ }
182
+
183
+ @classmethod
184
+ def next_id(cls) -> str:
185
+ """
186
+ 生成雪花ID(单例模式,避免重复创建实例)
187
+ :return: 雪花ID字符串
188
+ """
189
+ # 单例模式创建实例
190
+ if cls._instance is None:
191
+ with cls._instance_lock:
192
+ if cls._instance is None:
193
+ cls._instance = cls()
194
+ # 生成ID并转为字符串返回
195
+ return str(cls._instance.generate_id())
196
+
197
+
198
+ if __name__ == "__main__":
199
+ # 生成1000个ID并验证
200
+ id_set = set() # 用于检测重复ID
201
+ _MAX_JAVA_LONG = 9223372036854775807
202
+
203
+ for i in range(1000):
204
+ id_str = Snowflake.next_id()
205
+ id_num = int(id_str)
206
+
207
+ # 验证ID不超过Java long最大值
208
+ assert id_num <= _MAX_JAVA_LONG, f"ID超过Java long最大值: {id_num}"
209
+
210
+ # 验证ID不重复
211
+ assert id_str not in id_set, f"重复生成ID: {id_str}"
212
+ id_set.add(id_str)
213
+
214
+ # 每100个ID打印一次解析结果
215
+ if i % 100 == 0:
216
+ parse_result = Snowflake.parse_id(id_num)
217
+ print(f"生成ID: {id_str}")
218
+ print(f"解析结果: {parse_result}")
219
+ print("-" * 50)
220
+
221
+ print(f"成功生成{len(id_set)}个唯一雪花ID,验证通过!")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sycommon-python-lib
3
- Version: 0.1.54
3
+ Version: 0.1.55a0
4
4
  Summary: Add your description here
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -12,6 +12,7 @@ Requires-Dist: kafka-python>=2.2.16
12
12
  Requires-Dist: loguru>=0.7.3
13
13
  Requires-Dist: mysql-connector-python>=9.5.0
14
14
  Requires-Dist: nacos-sdk-python<3.0,>=2.0.9
15
+ Requires-Dist: netifaces>=0.11.0
15
16
  Requires-Dist: pydantic>=2.12.4
16
17
  Requires-Dist: python-dotenv>=1.2.1
17
18
  Requires-Dist: pyyaml>=6.0.3
@@ -6,6 +6,7 @@ kafka-python>=2.2.16
6
6
  loguru>=0.7.3
7
7
  mysql-connector-python>=9.5.0
8
8
  nacos-sdk-python<3.0,>=2.0.9
9
+ netifaces>=0.11.0
9
10
  pydantic>=2.12.4
10
11
  python-dotenv>=1.2.1
11
12
  pyyaml>=6.0.3
@@ -1,33 +0,0 @@
1
- import uuid
2
-
3
-
4
- class Snowflake:
5
- """基于UUID生成兼容Java long类型的唯一ID"""
6
- _MAX_JAVA_LONG = 9223372036854775807 # Java long最大值(18位)
7
-
8
- @staticmethod
9
- def next_id() -> str:
10
- """生成不超过Java long最大值的唯一ID字符串(18位以内)"""
11
- while True:
12
- # 生成UUID并转换为整数
13
- uuid_int = int(uuid.uuid4().hex, 16)
14
-
15
- # 取低63位,并强制限制不超过Java long最大值
16
- id_num = uuid_int & ((1 << 63) - 1) # 取低63位
17
- id_num = min(id_num, Snowflake._MAX_JAVA_LONG) # 强制限制最大值
18
-
19
- # 转换为字符串并验证长度(确保18位以内)
20
- id_str = str(id_num)
21
- if len(id_str) <= 18:
22
- return id_str
23
-
24
-
25
- # 使用示例
26
- if __name__ == "__main__":
27
- # 生成1000个ID并验证
28
- for _ in range(1000):
29
- id_str = Snowflake.next_id()
30
- id_num = int(id_str)
31
- print(f"ID: {id_str} (长度: {len(id_str)})")
32
- assert len(id_str) <= 18, f"ID长度超过18位: {id_str}"
33
- assert id_num <= Snowflake._MAX_JAVA_LONG, f"ID超过Java long最大值: {id_num}"