sycommon-python-lib 0.1.32__py3-none-any.whl → 0.1.56b5__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 (30) hide show
  1. sycommon/config/Config.py +6 -2
  2. sycommon/config/RerankerConfig.py +1 -0
  3. sycommon/database/async_base_db_service.py +36 -0
  4. sycommon/database/async_database_service.py +96 -0
  5. sycommon/llm/__init__.py +0 -0
  6. sycommon/llm/embedding.py +149 -0
  7. sycommon/llm/get_llm.py +246 -0
  8. sycommon/llm/llm_logger.py +126 -0
  9. sycommon/llm/llm_tokens.py +119 -0
  10. sycommon/logging/async_sql_logger.py +65 -0
  11. sycommon/logging/kafka_log.py +21 -9
  12. sycommon/logging/logger_levels.py +23 -0
  13. sycommon/middleware/context.py +2 -0
  14. sycommon/middleware/traceid.py +155 -32
  15. sycommon/notice/__init__.py +0 -0
  16. sycommon/notice/uvicorn_monitor.py +195 -0
  17. sycommon/rabbitmq/rabbitmq_client.py +385 -626
  18. sycommon/rabbitmq/rabbitmq_pool.py +287 -71
  19. sycommon/rabbitmq/rabbitmq_service.py +345 -191
  20. sycommon/services.py +104 -64
  21. sycommon/synacos/feign.py +71 -18
  22. sycommon/synacos/feign_client.py +26 -8
  23. sycommon/synacos/nacos_service.py +91 -54
  24. sycommon/tools/merge_headers.py +97 -0
  25. sycommon/tools/snowflake.py +290 -23
  26. {sycommon_python_lib-0.1.32.dist-info → sycommon_python_lib-0.1.56b5.dist-info}/METADATA +17 -12
  27. {sycommon_python_lib-0.1.32.dist-info → sycommon_python_lib-0.1.56b5.dist-info}/RECORD +30 -18
  28. {sycommon_python_lib-0.1.32.dist-info → sycommon_python_lib-0.1.56b5.dist-info}/WHEEL +0 -0
  29. {sycommon_python_lib-0.1.32.dist-info → sycommon_python_lib-0.1.56b5.dist-info}/entry_points.txt +0 -0
  30. {sycommon_python_lib-0.1.32.dist-info → sycommon_python_lib-0.1.56b5.dist-info}/top_level.txt +0 -0
sycommon/services.py CHANGED
@@ -7,6 +7,7 @@ from fastapi import FastAPI, applications
7
7
  from pydantic import BaseModel
8
8
  from typing import Any, Callable, Dict, List, Tuple, Union, Optional, AsyncGenerator
9
9
  from sycommon.config.Config import SingletonMeta
10
+ from sycommon.logging.logger_levels import setup_logger_levels
10
11
  from sycommon.models.mqlistener_config import RabbitMQListenerConfig
11
12
  from sycommon.models.mqsend_config import RabbitMQSendConfig
12
13
  from sycommon.rabbitmq.rabbitmq_service import RabbitMQService
@@ -18,10 +19,13 @@ class Services(metaclass=SingletonMeta):
18
19
  _config: Optional[dict] = None
19
20
  _initialized: bool = False
20
21
  _registered_senders: List[str] = []
21
- _mq_tasks: List[asyncio.Task] = []
22
22
  _instance: Optional['Services'] = None
23
23
  _app: Optional[FastAPI] = None
24
24
  _user_lifespan: Optional[Callable] = None
25
+ _shutdown_lock: asyncio.Lock = asyncio.Lock()
26
+
27
+ # 用于存储待执行的异步数据库初始化任务
28
+ _pending_async_db_setup: List[Tuple[Callable, str]] = []
25
29
 
26
30
  def __init__(self, config: dict, app: FastAPI):
27
31
  if not Services._config:
@@ -48,28 +52,32 @@ class Services(metaclass=SingletonMeta):
48
52
  nacos_service: Optional[Callable[[dict], None]] = None,
49
53
  logging_service: Optional[Callable[[dict], None]] = None,
50
54
  database_service: Optional[Union[
51
- Tuple[Callable[[dict, str], None], str],
52
- List[Tuple[Callable[[dict, str], None], str]]
55
+ Tuple[Callable, str],
56
+ List[Tuple[Callable, str]]
53
57
  ]] = None,
54
58
  rabbitmq_listeners: Optional[List[RabbitMQListenerConfig]] = None,
55
59
  rabbitmq_senders: Optional[List[RabbitMQSendConfig]] = None
56
60
  ) -> FastAPI:
57
61
  load_dotenv()
58
- # 保存应用实例和配置
62
+ setup_logger_levels()
59
63
  cls._app = app
60
64
  cls._config = config
61
65
  cls._user_lifespan = app.router.lifespan_context
62
- # 设置文档
66
+
63
67
  applications.get_swagger_ui_html = custom_swagger_ui_html
64
68
  applications.get_redoc_html = custom_redoc_html
65
- # 设置app.state host, port
69
+
66
70
  if not cls._config:
67
71
  config = yaml.safe_load(open('app.yaml', 'r', encoding='utf-8'))
68
72
  cls._config = config
69
- app.host = cls._config.get('Host')
70
- app.post = cls._config.get('Port')
71
73
 
72
- # 立即配置非异步服务(在应用启动前)
74
+ app.state.config = {
75
+ "host": cls._config.get('Host', '0.0.0.0'),
76
+ "port": cls._config.get('Port', 8080),
77
+ "workers": cls._config.get('Workers', 1),
78
+ "h11_max_incomplete_event_size": cls._config.get('H11MaxIncompleteEventSize', 1024 * 1024 * 10)
79
+ }
80
+
73
81
  if middleware:
74
82
  middleware(app, config)
75
83
 
@@ -79,25 +87,62 @@ class Services(metaclass=SingletonMeta):
79
87
  if logging_service:
80
88
  logging_service(config)
81
89
 
90
+ # ========== 处理数据库服务 ==========
91
+ # 清空之前的待执行列表(防止热重载时重复)
92
+ cls._pending_async_db_setup = []
93
+
82
94
  if database_service:
83
- cls._setup_database_static(database_service, config)
95
+ # 解析配置并区分同步/异步
96
+ items = [database_service] if isinstance(
97
+ database_service, tuple) else database_service
98
+ for item in items:
99
+ db_setup_func, db_name = item
100
+ if asyncio.iscoroutinefunction(db_setup_func):
101
+ # 如果是异步函数,加入待执行列表
102
+ logging.info(f"检测到异步数据库服务: {db_name},将在应用启动时初始化")
103
+ cls._pending_async_db_setup.append(item)
104
+ else:
105
+ # 如果是同步函数,立即执行
106
+ logging.info(f"执行同步数据库服务: {db_name}")
107
+ try:
108
+ db_setup_func(config, db_name)
109
+ except Exception as e:
110
+ logging.error(
111
+ f"同步数据库服务 {db_name} 初始化失败: {e}", exc_info=True)
112
+ raise
84
113
 
85
114
  # 创建组合生命周期管理器
86
115
  @asynccontextmanager
87
116
  async def combined_lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
88
117
  # 1. 执行Services自身的初始化
89
118
  instance = cls(config, app)
90
- has_listeners = bool(
119
+
120
+ # ========== 执行挂起的异步数据库初始化 ==========
121
+ if cls._pending_async_db_setup:
122
+ logging.info("开始执行异步数据库初始化...")
123
+ for db_setup_func, db_name in cls._pending_async_db_setup:
124
+ try:
125
+ await db_setup_func(config, db_name)
126
+ logging.info(f"异步数据库服务 {db_name} 初始化成功")
127
+ except Exception as e:
128
+ logging.error(
129
+ f"异步数据库服务 {db_name} 初始化失败: {e}", exc_info=True)
130
+ raise
131
+
132
+ # ========== 初始化 MQ ==========
133
+ has_valid_listeners = bool(
91
134
  rabbitmq_listeners and len(rabbitmq_listeners) > 0)
92
- has_senders = bool(rabbitmq_senders and len(rabbitmq_senders) > 0)
135
+ has_valid_senders = bool(
136
+ rabbitmq_senders and len(rabbitmq_senders) > 0)
93
137
 
94
138
  try:
95
- await instance._setup_mq_async(
96
- rabbitmq_listeners=rabbitmq_listeners,
97
- rabbitmq_senders=rabbitmq_senders,
98
- has_listeners=has_listeners,
99
- has_senders=has_senders
100
- )
139
+ if has_valid_listeners or has_valid_senders:
140
+ await instance._setup_mq_async(
141
+ rabbitmq_listeners=rabbitmq_listeners if has_valid_listeners else None,
142
+ rabbitmq_senders=rabbitmq_senders if has_valid_senders else None,
143
+ has_listeners=has_valid_listeners,
144
+ has_senders=has_valid_senders
145
+ )
101
146
  cls._initialized = True
102
147
  logging.info("Services初始化完成")
103
148
  except Exception as e:
@@ -109,28 +154,18 @@ class Services(metaclass=SingletonMeta):
109
154
  # 2. 执行用户定义的生命周期
110
155
  if cls._user_lifespan:
111
156
  async with cls._user_lifespan(app):
112
- yield # 应用运行阶段
157
+ yield
113
158
  else:
114
- yield # 没有用户生命周期时直接 yield
159
+ yield
115
160
 
116
161
  # 3. 执行Services的关闭逻辑
117
162
  await cls.shutdown()
118
163
  logging.info("Services已关闭")
119
164
 
120
- # 设置组合生命周期
121
165
  app.router.lifespan_context = combined_lifespan
122
-
123
166
  return app
124
167
 
125
- @staticmethod
126
- def _setup_database_static(database_service, config):
127
- """静态方法:设置数据库服务"""
128
- if isinstance(database_service, tuple):
129
- db_setup, db_name = database_service
130
- db_setup(config, db_name)
131
- elif isinstance(database_service, list):
132
- for db_setup, db_name in database_service:
133
- db_setup(config, db_name)
168
+ # 移除了 _setup_database_static,因为逻辑已内联到 plugins 中
134
169
 
135
170
  async def _setup_mq_async(
136
171
  self,
@@ -140,39 +175,46 @@ class Services(metaclass=SingletonMeta):
140
175
  has_senders: bool = False,
141
176
  ):
142
177
  """异步设置MQ相关服务"""
143
- # 初始化RabbitMQ服务,传递状态
178
+ if not (has_listeners or has_senders):
179
+ logging.info("无RabbitMQ监听器/发送器配置,跳过RabbitMQService初始化")
180
+ return
181
+
144
182
  RabbitMQService.init(self._config, has_listeners, has_senders)
145
183
 
146
- # 设置发送器,传递是否有监听器的标志
147
- if rabbitmq_senders:
148
- # 判断是否有监听器,如果有遍历监听器列表,队列名一样将prefetch_count属性设置到发送器对象中
149
- if rabbitmq_listeners:
184
+ start_time = asyncio.get_event_loop().time()
185
+ while not (RabbitMQService._connection_pool and RabbitMQService._connection_pool._initialized) and not RabbitMQService._is_shutdown:
186
+ if asyncio.get_event_loop().time() - start_time > 30:
187
+ raise TimeoutError("RabbitMQ连接池初始化超时(30秒)")
188
+ logging.info("等待RabbitMQ连接池初始化...")
189
+ await asyncio.sleep(0.5)
190
+
191
+ if has_senders and rabbitmq_senders:
192
+ if has_listeners and rabbitmq_listeners:
150
193
  for sender in rabbitmq_senders:
151
194
  for listener in rabbitmq_listeners:
152
195
  if sender.queue_name == listener.queue_name:
153
196
  sender.prefetch_count = listener.prefetch_count
154
197
  await self._setup_senders_async(rabbitmq_senders, has_listeners)
155
198
 
156
- # 设置监听器,传递是否有发送器的标志
157
- if rabbitmq_listeners:
199
+ if has_listeners and rabbitmq_listeners:
158
200
  await self._setup_listeners_async(rabbitmq_listeners, has_senders)
159
201
 
160
- # 验证初始化结果
161
202
  if has_listeners:
162
- listener_count = len(RabbitMQService._clients)
203
+ listener_count = len(RabbitMQService._consumer_tasks)
163
204
  logging.info(f"监听器初始化完成,共启动 {listener_count} 个消费者")
164
205
  if listener_count == 0:
165
- logging.warning("未成功初始化任何监听器,请检查配置")
206
+ logging.warning("未成功初始化任何监听器,请检查配置或MQ服务状态")
166
207
 
167
208
  async def _setup_senders_async(self, rabbitmq_senders, has_listeners: bool):
209
+ """设置发送器"""
168
210
  Services._registered_senders = [
169
211
  sender.queue_name for sender in rabbitmq_senders]
170
-
171
- # 将是否有监听器的信息传递给RabbitMQService
172
212
  await RabbitMQService.setup_senders(rabbitmq_senders, has_listeners)
213
+ Services._registered_senders = RabbitMQService._sender_client_names
173
214
  logging.info(f"已注册的RabbitMQ发送器: {Services._registered_senders}")
174
215
 
175
216
  async def _setup_listeners_async(self, rabbitmq_listeners, has_senders: bool):
217
+ """设置监听器"""
176
218
  await RabbitMQService.setup_listeners(rabbitmq_listeners, has_senders)
177
219
 
178
220
  @classmethod
@@ -183,11 +225,15 @@ class Services(metaclass=SingletonMeta):
183
225
  max_retries: int = 3,
184
226
  retry_delay: float = 1.0, **kwargs
185
227
  ) -> None:
186
- """发送消息,添加重试机制"""
228
+ """发送消息"""
187
229
  if not cls._initialized or not cls._loop:
188
230
  logging.error("Services not properly initialized!")
189
231
  raise ValueError("服务未正确初始化")
190
232
 
233
+ if RabbitMQService._is_shutdown:
234
+ logging.error("RabbitMQService已关闭,无法发送消息")
235
+ raise RuntimeError("RabbitMQ服务已关闭")
236
+
191
237
  for attempt in range(max_retries):
192
238
  try:
193
239
  if queue_name not in cls._registered_senders:
@@ -195,11 +241,11 @@ class Services(metaclass=SingletonMeta):
195
241
  if queue_name not in cls._registered_senders:
196
242
  raise ValueError(f"发送器 {queue_name} 未注册")
197
243
 
198
- sender = RabbitMQService.get_sender(queue_name)
244
+ sender = await RabbitMQService.get_sender(queue_name)
199
245
  if not sender:
200
- raise ValueError(f"发送器 '{queue_name}' 不存在")
246
+ raise ValueError(f"发送器 '{queue_name}' 不存在或连接无效")
201
247
 
202
- await RabbitMQService.send_message(data, queue_name, ** kwargs)
248
+ await RabbitMQService.send_message(data, queue_name, **kwargs)
203
249
  logging.info(f"消息发送成功(尝试 {attempt+1}/{max_retries})")
204
250
  return
205
251
 
@@ -208,24 +254,18 @@ class Services(metaclass=SingletonMeta):
208
254
  logging.error(
209
255
  f"消息发送失败(已尝试 {max_retries} 次): {str(e)}", exc_info=True)
210
256
  raise
211
-
212
257
  logging.warning(
213
- f"消息发送失败(尝试 {attempt+1}/{max_retries}): {str(e)},"
214
- f"{retry_delay}秒后重试..."
215
- )
258
+ f"消息发送失败(尝试 {attempt+1}/{max_retries}): {str(e)},{retry_delay}秒后重试...")
216
259
  await asyncio.sleep(retry_delay)
217
260
 
218
- @staticmethod
219
- async def shutdown():
261
+ @classmethod
262
+ async def shutdown(cls):
220
263
  """关闭所有服务"""
221
- # 取消所有MQ任务
222
- for task in Services._mq_tasks:
223
- if not task.done():
224
- task.cancel()
225
- try:
226
- await task
227
- except asyncio.CancelledError:
228
- pass
229
-
230
- # 关闭RabbitMQ服务
231
- await RabbitMQService.shutdown()
264
+ async with cls._shutdown_lock:
265
+ if RabbitMQService._is_shutdown:
266
+ logging.info("RabbitMQService已关闭,无需重复操作")
267
+ return
268
+ await RabbitMQService.shutdown()
269
+ cls._initialized = False
270
+ cls._registered_senders.clear()
271
+ logging.info("所有服务已关闭")
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')
@@ -97,7 +106,7 @@ async def feign(service_name, api_path, method='GET', params=None, headers=None,
97
106
  data=data,
98
107
  timeout=timeout
99
108
  ) as response:
100
- return await _handle_feign_response(response)
109
+ return await _handle_feign_response(response, service_name, api_path)
101
110
  else:
102
111
  # 普通JSON请求
103
112
  async with session.request(
@@ -108,30 +117,74 @@ async def feign(service_name, api_path, method='GET', params=None, headers=None,
108
117
  json=body,
109
118
  timeout=timeout
110
119
  ) as response:
111
- return await _handle_feign_response(response)
120
+ return await _handle_feign_response(response, service_name, api_path)
112
121
  except aiohttp.ClientError as e:
113
122
  SYLogger.error(
114
- f"nacos:请求服务接口时出错ClientError path: {api_path} error:{e}")
123
+ f"nacos:请求服务接口时出错ClientError server: {service_name} path: {api_path} error:{e}")
115
124
  return None
116
125
  except Exception as e:
117
126
  import traceback
118
127
  SYLogger.error(
119
- f"nacos:请求服务接口时出错 path: {api_path} error:{traceback.format_exc()}")
128
+ f"nacos:请求服务接口时出错 server: {service_name} path: {api_path} error:{traceback.format_exc()}")
120
129
  return None
121
130
  finally:
122
131
  await session.close()
123
132
 
124
133
 
125
- async def _handle_feign_response(response):
126
- """处理Feign请求的响应"""
127
- if response.status == 200:
128
- content_type = response.headers.get('Content-Type')
129
- if 'application/json' in content_type:
130
- return await response.json()
134
+ async def _handle_feign_response(response, service_name: str, api_path: str):
135
+ """
136
+ 处理Feign请求的响应,统一返回格式
137
+ 调整逻辑:先判断状态码,再处理内容
138
+ - 200状态:优先识别JSON/文本,其他均按文件流(二进制)处理
139
+ - 非200状态:统一返回错误字典
140
+ """
141
+ try:
142
+ status_code = response.status
143
+ content_type = response.headers.get('Content-Type', '')
144
+ content_type = content_type.lower() if content_type else ''
145
+
146
+ response_body = None
147
+
148
+ if status_code == 200:
149
+ if content_type and 'application/json' in content_type:
150
+ response_body = await response.json()
151
+ elif content_type and 'text/' in content_type:
152
+ # 文本类型(text/plain、text/html等):按文本读取
153
+ try:
154
+ response_body = await response.text(encoding='utf-8')
155
+ except UnicodeDecodeError:
156
+ # 兼容中文编码(gbk)
157
+ response_body = await response.text(encoding='gbk')
158
+ else:
159
+ # 其他类型(PDF、图片、octet-stream等):按文件流(二进制)读取
160
+ binary_data = await response.read()
161
+ SYLogger.info(
162
+ f"按文件流处理响应,类型:{content_type},大小:{len(binary_data)/1024:.2f}KB")
163
+ return io.BytesIO(binary_data) # 返回BytesIO,支持read()
164
+ return response_body
131
165
  else:
132
- content = await response.read()
133
- return io.BytesIO(content)
134
- else:
135
- error_msg = await response.text()
136
- SYLogger.error(f"nacos:请求失败,状态码: {response.status},响应内容: {error_msg}")
166
+ # 非200状态:统一读取响应体(兼容文本/二进制错误信息)
167
+ try:
168
+ if content_type and 'application/json' in content_type:
169
+ response_body = await response.json()
170
+ else:
171
+ response_body = await response.text(encoding='utf-8', errors='ignore')
172
+ except Exception:
173
+ binary_data = await response.read()
174
+ response_body = f"非200状态,响应无法解码:{binary_data[:100].hex()} server: {service_name} path: {api_path}"
175
+
176
+ error_msg = f"请求失败,状态码: {status_code},响应内容: {str(response_body)[:500]} server: {service_name} path: {api_path}"
177
+ SYLogger.error(error_msg)
178
+ return {
179
+ "success": False,
180
+ "code": status_code,
181
+ "message": error_msg,
182
+ "data": response_body
183
+ }
184
+
185
+ except Exception as e:
186
+ import traceback
187
+ error_detail = f"处理响应异常: {str(e)}\n{traceback.format_exc()}"
188
+ SYLogger.error(
189
+ f"nacos:处理响应时出错: {error_detail} server: {service_name} path: {api_path}")
137
190
  return None
@@ -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
  }