sycommon-python-lib 0.1.27__py3-none-any.whl → 0.1.29__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.
@@ -470,12 +470,22 @@ class SYLogger:
470
470
  thread_info = SYLogger._get_execution_context()
471
471
 
472
472
  # 构建日志结构,添加线程/协程信息到threadName字段
473
- request_log = {
474
- "trace_id": str(trace_id) if trace_id else Snowflake.next_id(),
475
- "message": msg_str,
476
- "level": level,
477
- "threadName": thread_info
478
- }
473
+ request_log = {}
474
+ if level == "ERROR":
475
+ request_log = {
476
+ "trace_id": str(trace_id) if trace_id else Snowflake.next_id(),
477
+ "message": msg_str,
478
+ "traceback": traceback.format_exc(),
479
+ "level": level,
480
+ "threadName": thread_info
481
+ }
482
+ else:
483
+ request_log = {
484
+ "trace_id": str(trace_id) if trace_id else Snowflake.next_id(),
485
+ "message": msg_str,
486
+ "level": level,
487
+ "threadName": thread_info
488
+ }
479
489
 
480
490
  # 选择日志级别
481
491
  _log = ''
@@ -23,7 +23,6 @@ class SQLTraceLogger:
23
23
  else:
24
24
  return params
25
25
 
26
- # 监听 SQL 语句执行后事件(计算耗时并输出日志)
27
26
  @event.listens_for(Engine, "after_cursor_execute")
28
27
  def after_cursor_execute(
29
28
  conn, cursor, statement, parameters, context, executemany
@@ -44,7 +43,6 @@ class SQLTraceLogger:
44
43
  except Exception as e:
45
44
  SYLogger.error(f"SQL日志处理失败: {str(e)}")
46
45
 
47
- # 监听 SQL 执行开始事件
48
46
  @event.listens_for(Engine, "before_cursor_execute")
49
47
  def before_cursor_execute(
50
48
  conn, cursor, statement, parameters, context, executemany
@@ -608,6 +608,10 @@ class RabbitMQClient:
608
608
  if not self.message_handler or not self._is_consuming:
609
609
  logger.warning("未设置消息处理器或已停止消费")
610
610
  # await message.ack()
611
+ try:
612
+ await message.reject(requeue=True)
613
+ except Exception as e:
614
+ logger.error(f"拒绝消息失败: {e}")
611
615
  return
612
616
 
613
617
  message_id = message.message_id or str(id(message))
sycommon/synacos/feign.py CHANGED
@@ -94,20 +94,21 @@ from sycommon.synacos.nacos_service import NacosService
94
94
  # pass
95
95
  #
96
96
  # # ------------------------------
97
- # # 场景6: 文件上传 + 表单字段混合
97
+ # # 场景6: 多文件上传 + 表单字段混合
98
98
  # # 请求示例: POST /products/{product_id}/images (multipart/form-data)
99
+ # # 支持同时上传多个文件,共用字段名 "image_file"
99
100
  # # ------------------------------
100
- # @feign_upload(field_name="image_file") # 指定文件表单字段名
101
+ # @feign_upload(field_name="image_file") # 指定所有文件的表单字段名
101
102
  # @feign_request("POST", "/products/{product_id}/images")
102
103
  # async def upload_product_image(
103
104
  # self,
104
- # product_id: int, # Path参数
105
- # file_path: str, # 本地文件路径(会被转为文件上传)
106
- # image_type: str, # 表单字段(图片类型)
105
+ # product_id: int, # Path参数(URL路径中的占位符)
106
+ # file_paths: str | list[str], # 本地文件路径(单个路径字符串或多个路径列表)
107
+ # image_type: str, # 表单字段(图片类型,如"main"、"detail")
107
108
  # is_primary: bool = False, # 表单字段(是否主图)
108
- # remark: Optional[str] = None # 可选表单字段
109
+ # remark: Optional[str] = None # 可选表单字段(备注信息)
109
110
  # ) -> Dict[str, Any]:
110
- # """上传商品图片(文件+表单字段)"""
111
+ # """上传商品图片(支持多文件 + 表单字段混合)"""
111
112
  # pass
112
113
  #
113
114
  # # ------------------------------
@@ -203,17 +204,18 @@ from sycommon.synacos.nacos_service import NacosService
203
204
  # print(f"场景5 - 批量更新: 成功{batch_result.get('success_count')}个")
204
205
  #
205
206
  # # ------------------------------
206
- # # 调用场景6: 文件上传 + 表单字段
207
+ # # 调用场景6: 多文件上传 + 表单字段
208
+ # # 支持两种调用方式:单文件上传 / 多文件上传
207
209
  # # ------------------------------
208
210
  # if product_id:
209
- # upload_result = await ProductServiceClient().upload_product_image(
211
+ # single_upload_result = await ProductServiceClient().upload_product_image(
210
212
  # product_id=product_id,
211
- # file_path="/tmp/product_main.jpg", # 本地图片路径
213
+ # file_paths="/tmp/product_main.jpg", # 单个文件路径或多个路径[""]
212
214
  # image_type="main",
213
215
  # is_primary=True,
214
- # remark="商品主图"
216
+ # remark="商品主图(单文件)"
215
217
  # )
216
- # print(f"场景6 - 图片上传: URL={upload_result.get('image_url')}")
218
+ # print(f"场景6 - 单文件上传: 主图URL={single_upload_result.get('image_urls')[0]}")
217
219
  #
218
220
  # # ------------------------------
219
221
  # # 调用场景7: 多Path参数 + DELETE
@@ -392,16 +394,47 @@ def feign_request(method: str, path: str, headers: dict = None):
392
394
 
393
395
 
394
396
  def feign_upload(field_name: str = "file"):
395
- """处理文件上传的装饰器"""
397
+ """处理多文件上传的装饰器(支持同字段名多文件 + 表单字段混合)"""
396
398
  def decorator(func):
397
399
  async def wrapper(*args, **kwargs):
398
- file_path = kwargs.get('file_path')
399
- if not file_path or not os.path.exists(file_path):
400
- raise ValueError(f"文件路径不存在: {file_path}")
401
- with open(file_path, 'rb') as f:
402
- files = {field_name: (os.path.basename(file_path), f.read())}
403
- kwargs['files'] = files
404
- return await func(*args, **kwargs)
400
+ # 获取文件路径列表(支持单个文件路径字符串或多个文件路径列表)
401
+ file_paths = kwargs.get('file_paths')
402
+ if not file_paths:
403
+ raise ValueError("缺少文件路径参数: file_paths(可为单个路径字符串或列表)")
404
+
405
+ # 统一转为列表格式(兼容单个文件的情况)
406
+ if isinstance(file_paths, str):
407
+ file_paths = [file_paths]
408
+ if not isinstance(file_paths, list):
409
+ raise ValueError("file_paths 必须是字符串或列表")
410
+
411
+ # 验证所有文件是否存在
412
+ for path in file_paths:
413
+ if not os.path.exists(path):
414
+ raise FileNotFoundError(f"文件不存在: {path}")
415
+
416
+ # 构建 multipart/form-data 表单数据
417
+ form_data = aiohttp.FormData()
418
+
419
+ # 添加所有文件(共用同一个 field_name)
420
+ for file_path in file_paths:
421
+ with open(file_path, 'rb') as f:
422
+ form_data.add_field(
423
+ field_name, # 所有文件使用相同的表单字段名
424
+ f.read(),
425
+ filename=os.path.basename(file_path) # 保留原文件名
426
+ )
427
+
428
+ # 添加其他表单字段(从 kwargs 中提取非文件参数)
429
+ form_fields = {k: v for k, v in kwargs.items() if k !=
430
+ 'file_paths'}
431
+ for key, value in form_fields.items():
432
+ if value is not None:
433
+ form_data.add_field(key, str(value)) # 表单字段转为字符串
434
+
435
+ # 将构建好的表单数据传入原函数
436
+ kwargs['form_data'] = form_data
437
+ return await func(*args, **kwargs)
405
438
  return wrapper
406
439
  return decorator
407
440
 
@@ -457,8 +490,25 @@ async def feign(service_name, api_path, method='GET', params=None, headers=None,
457
490
  for key, value in form_data.items():
458
491
  data.add_field(key, value)
459
492
  if files:
460
- for field_name, (filename, content) in files.items():
461
- data.add_field(field_name, content, filename=filename)
493
+ # 兼容处理:同时支持字典(单文件)和列表(多文件)
494
+ if isinstance(files, dict):
495
+ # 处理原有字典格式(单文件)
496
+ # 字典格式:{field_name: (filename, content)}
497
+ for field_name, (filename, content) in files.items():
498
+ data.add_field(field_name, content,
499
+ filename=filename)
500
+ elif isinstance(files, list):
501
+ # 处理新列表格式(多文件)
502
+ # 列表格式:[(field_name, filename, content), ...]
503
+ for item in files:
504
+ if len(item) != 3:
505
+ raise ValueError(
506
+ f"列表元素格式错误,需为 (field_name, filename, content),实际为 {item}")
507
+ field_name, filename, content = item
508
+ data.add_field(field_name, content,
509
+ filename=filename)
510
+ else:
511
+ raise TypeError(f"files 参数必须是字典或列表,实际为 {type(files)}")
462
512
  if file_path:
463
513
  filename = os.path.basename(file_path)
464
514
  with open(file_path, 'rb') as f:
@@ -87,12 +87,18 @@ class NacosService(metaclass=SingletonMeta):
87
87
  # 心跳相关
88
88
  self._last_heartbeat_time = 0
89
89
  self._heartbeat_fail_count = 0
90
- self._heartbeat_lock = threading.Lock() # 控制心跳线程创建的锁
90
+ self._heartbeat_lock = threading.Lock()
91
91
  self._heartbeat_thread = None
92
92
 
93
93
  self.max_heartbeat_timeout = self.nacos_config.get(
94
- 'maxHeartbeatTimeout', 30) # 最大无心跳时间(秒)
95
- self._last_successful_heartbeat = time.time() # 上次成功心跳时间戳
94
+ 'maxHeartbeatTimeout', 30)
95
+ self._last_successful_heartbeat = time.time()
96
+ # 连接监控检查间隔(新增配置,默认30秒,避免硬编码)
97
+ self.connection_check_interval = self.nacos_config.get(
98
+ 'connectionCheckInterval', 30)
99
+ # 配置监视线程检查间隔(默认30秒)
100
+ self.config_watch_interval = self.nacos_config.get(
101
+ 'configWatchInterval', 30)
96
102
 
97
103
  # 启动配置监视线程
98
104
  self._watch_thread = threading.Thread(
@@ -586,10 +592,9 @@ class NacosService(metaclass=SingletonMeta):
586
592
 
587
593
  def monitor_connection(self):
588
594
  """优化的连接监控线程,缩短检查间隔"""
589
- # 缩短检查间隔(5秒一次)
590
- check_interval = self.nacos_config.get('checkInterval', 5)
591
- thread_start_time = time.time() # 线程启动时间
592
- check_counter = 0 # 检查计数器
595
+ check_interval = self.connection_check_interval
596
+ thread_start_time = time.time()
597
+ check_counter = 0
593
598
 
594
599
  while not self._shutdown_event.is_set():
595
600
  try:
@@ -739,7 +744,7 @@ class NacosService(metaclass=SingletonMeta):
739
744
 
740
745
  def _watch_configs(self):
741
746
  """配置监听线程"""
742
- check_interval = 5 # 固定检查间隔
747
+ check_interval = self.config_watch_interval
743
748
 
744
749
  while not self._shutdown_event.is_set():
745
750
  try:
@@ -750,7 +755,7 @@ class NacosService(metaclass=SingletonMeta):
750
755
  self._config_cache[data_id] = new_config
751
756
  except Exception as e:
752
757
  SYLogger.error(f"nacos:配置监视线程异常: {str(e)}")
753
- self._shutdown_event.wait(check_interval) # 检查间隔
758
+ self._shutdown_event.wait(check_interval)
754
759
 
755
760
  def discover_services(self, service_name: str, group: str = "DEFAULT_GROUP", version: str = None) -> List[Dict]:
756
761
  """发现服务实例列表 (与Java格式兼容)"""
@@ -1,24 +1,24 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sycommon-python-lib
3
- Version: 0.1.27
3
+ Version: 0.1.29
4
4
  Summary: Add your description here
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
7
7
  Requires-Dist: aio-pika>=9.5.7
8
- Requires-Dist: aiohttp>=3.12.15
8
+ Requires-Dist: aiohttp>=3.13.1
9
9
  Requires-Dist: decorator>=5.2.1
10
- Requires-Dist: fastapi>=0.118.0
10
+ Requires-Dist: fastapi>=0.120.0
11
11
  Requires-Dist: kafka-python>=2.2.15
12
12
  Requires-Dist: loguru>=0.7.3
13
- Requires-Dist: mysql-connector-python>=9.4.0
13
+ Requires-Dist: mysql-connector-python>=9.5.0
14
14
  Requires-Dist: nacos-sdk-python>=2.0.9
15
- Requires-Dist: pydantic>=2.11.9
16
- Requires-Dist: python-dotenv>=1.1.1
15
+ Requires-Dist: pydantic>=2.12.3
16
+ Requires-Dist: python-dotenv>=1.2.1
17
17
  Requires-Dist: pyyaml>=6.0.3
18
- Requires-Dist: sqlalchemy>=2.0.43
19
- Requires-Dist: starlette>=0.46.2
18
+ Requires-Dist: sqlalchemy>=2.0.44
19
+ Requires-Dist: starlette>=0.48.0
20
20
  Requires-Dist: uuid>=1.30
21
- Requires-Dist: uvicorn>=0.37.0
21
+ Requires-Dist: uvicorn>=0.38.0
22
22
 
23
23
  # sycommon-python-lib
24
24
 
@@ -15,9 +15,9 @@ sycommon/health/health_check.py,sha256=EhfbhspRpQiKJaxdtE-PzpKQO_ucaFKtQxIm16F5M
15
15
  sycommon/health/metrics.py,sha256=fHqO73JuhoZkNPR-xIlxieXiTCvttq-kG-tvxag1s1s,268
16
16
  sycommon/health/ping.py,sha256=FTlnIKk5y1mPfS1ZGOeT5IM_2udF5aqVLubEtuBp18M,250
17
17
  sycommon/logging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- sycommon/logging/kafka_log.py,sha256=3HjBg3WoDd3DmjtAlpyLcAVTKI-AHKrKlKmmPerq9tU,20751
18
+ sycommon/logging/kafka_log.py,sha256=qHq4sU42aGrTm9DWYKxGDp1pe9ENfYSbzI4iPDPNvJQ,21128
19
19
  sycommon/logging/logger_wrapper.py,sha256=TiHsrIIHiQMzXgXK12-0KIpU9GhwQJOoHslakzmq2zc,357
20
- sycommon/logging/sql_logger.py,sha256=s6mqGg3X1Srul8FQB-TJYGESigCrtnniuRq__Y6cFcY,2137
20
+ sycommon/logging/sql_logger.py,sha256=aEU3OGnI_51Tjyuuf4FpUi9KPTceFRuKAOyQbPzGhzM,2021
21
21
  sycommon/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
22
  sycommon/middleware/context.py,sha256=_5ghpv4u_yAvjkgM-XDx8OnO-YI1XtntHrXsHJHZkwo,88
23
23
  sycommon/middleware/cors.py,sha256=0B5d_ovD56wcH9TfktRs88Q09R9f8Xx5h5ALWYvE8Iw,600
@@ -35,21 +35,21 @@ sycommon/models/mqlistener_config.py,sha256=PPwhAVJ2AWvVAvNox_1t0fuBKTyRH3Ui9cuu
35
35
  sycommon/models/mqmsg_model.py,sha256=cxn0M5b0utQK6crMYmL-1waeGYHvK3AlGaRy23clqTE,277
36
36
  sycommon/models/mqsend_config.py,sha256=NQX9dc8PpuquMG36GCVhJe8omAW1KVXXqr6lSRU6D7I,268
37
37
  sycommon/models/sso_user.py,sha256=i1WAN6k5sPcPApQEdtjpWDy7VrzWLpOrOQewGLGoGIw,2702
38
- sycommon/rabbitmq/rabbitmq_client.py,sha256=Grjl_vDFe8fzyjw-bjOE_0keQwi-MQgB_GejCcBssTQ,27099
38
+ sycommon/rabbitmq/rabbitmq_client.py,sha256=kiQBMwLJW1sx9llxHUMXHHKXY5SJdafIHyVrEOu6OO4,27259
39
39
  sycommon/rabbitmq/rabbitmq_pool.py,sha256=_NMOO4CZy-I_anMqpzfYinz-8373_rg5FM9eqzdjGyU,3598
40
40
  sycommon/rabbitmq/rabbitmq_service.py,sha256=NWoMtRhvLjEsIX3sMgaSkrguE3gT5wLYj0u7laWZh8c,29997
41
41
  sycommon/sse/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
42
  sycommon/sse/event.py,sha256=k_rBJy23R7crtzQeetT0Q73D8o5-5p-eESGSs_BPOj0,2797
43
43
  sycommon/sse/sse.py,sha256=__CfWEcYxOxQ-HpLor4LTZ5hLWqw9-2X7CngqbVHsfw,10128
44
44
  sycommon/synacos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
- sycommon/synacos/feign.py,sha256=qALBl3YwVGvAzgx6tvwW84GptfS1u8WpapTRTygZROM,21282
46
- sycommon/synacos/nacos_service.py,sha256=kFGtOTKL6ZuFLebDK6gK6YNqdthhFW0aaXfG2eWzrzY,34493
45
+ sycommon/synacos/feign.py,sha256=g2Mt3Iyxrk8doQsuIRNYMoHi-TP-k6FO5SDMWAbyqTo,24208
46
+ sycommon/synacos/nacos_service.py,sha256=SO1s83Y8A5jyQNFhk7ZZ_BrGQyGZ8TXBKtzRYxI-uDQ,34661
47
47
  sycommon/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
48
  sycommon/tools/docs.py,sha256=OPj2ETheuWjXLyaXtaZPbwmJKfJaYXV5s4XMVAUNrms,1607
49
49
  sycommon/tools/snowflake.py,sha256=DdEj3T5r5OEvikp3puxqmmmz6BrggxomoSlnsRFb5dM,1174
50
50
  sycommon/tools/timing.py,sha256=OiiE7P07lRoMzX9kzb8sZU9cDb0zNnqIlY5pWqHcnkY,2064
51
- sycommon_python_lib-0.1.27.dist-info/METADATA,sha256=tgr4tP4xxA0OPHY7cRQzcRjMSHLN3bDlKYq_dCuoLRY,7038
52
- sycommon_python_lib-0.1.27.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
53
- sycommon_python_lib-0.1.27.dist-info/entry_points.txt,sha256=q_h2nbvhhmdnsOUZEIwpuoDjaNfBF9XqppDEmQn9d_A,46
54
- sycommon_python_lib-0.1.27.dist-info/top_level.txt,sha256=98CJ-cyM2WIKxLz-Pf0AitWLhJyrfXvyY8slwjTXNuc,17
55
- sycommon_python_lib-0.1.27.dist-info/RECORD,,
51
+ sycommon_python_lib-0.1.29.dist-info/METADATA,sha256=iun0vsoi2Xdi4hRDNxwAldSoOhVsM8mEdqNIoQ0Vwg8,7037
52
+ sycommon_python_lib-0.1.29.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
53
+ sycommon_python_lib-0.1.29.dist-info/entry_points.txt,sha256=q_h2nbvhhmdnsOUZEIwpuoDjaNfBF9XqppDEmQn9d_A,46
54
+ sycommon_python_lib-0.1.29.dist-info/top_level.txt,sha256=98CJ-cyM2WIKxLz-Pf0AitWLhJyrfXvyY8slwjTXNuc,17
55
+ sycommon_python_lib-0.1.29.dist-info/RECORD,,