sycommon-python-lib 0.1.10__py3-none-any.whl → 0.1.11__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.
Potentially problematic release.
This version of sycommon-python-lib might be problematic. Click here for more details.
- command/cli.py +167 -0
- sycommon/models/base_http.py +2 -2
- sycommon/rabbitmq/rabbitmq_client.py +557 -401
- sycommon/rabbitmq/rabbitmq_service.py +443 -280
- sycommon/services.py +11 -8
- sycommon/synacos/feign.py +338 -126
- {sycommon_python_lib-0.1.10.dist-info → sycommon_python_lib-0.1.11.dist-info}/METADATA +1 -1
- {sycommon_python_lib-0.1.10.dist-info → sycommon_python_lib-0.1.11.dist-info}/RECORD +11 -9
- sycommon_python_lib-0.1.11.dist-info/entry_points.txt +2 -0
- {sycommon_python_lib-0.1.10.dist-info → sycommon_python_lib-0.1.11.dist-info}/top_level.txt +1 -0
- {sycommon_python_lib-0.1.10.dist-info → sycommon_python_lib-0.1.11.dist-info}/WHEEL +0 -0
sycommon/services.py
CHANGED
|
@@ -129,11 +129,7 @@ class Services(metaclass=SingletonMeta):
|
|
|
129
129
|
has_listeners: bool = False,
|
|
130
130
|
has_senders: bool = False,
|
|
131
131
|
):
|
|
132
|
-
"""异步设置MQ
|
|
133
|
-
# 保存状态到类变量
|
|
134
|
-
Services._has_listeners = has_listeners
|
|
135
|
-
Services._has_senders = has_senders
|
|
136
|
-
|
|
132
|
+
"""异步设置MQ相关服务"""
|
|
137
133
|
# 初始化RabbitMQ服务,传递状态
|
|
138
134
|
RabbitMQService.init(self._config, has_listeners, has_senders)
|
|
139
135
|
|
|
@@ -145,6 +141,13 @@ class Services(metaclass=SingletonMeta):
|
|
|
145
141
|
if rabbitmq_listeners:
|
|
146
142
|
await self._setup_listeners_async(rabbitmq_listeners, has_senders)
|
|
147
143
|
|
|
144
|
+
# 验证初始化结果
|
|
145
|
+
if has_listeners:
|
|
146
|
+
listener_count = len(RabbitMQService._clients)
|
|
147
|
+
logging.info(f"监听器初始化完成,共启动 {listener_count} 个消费者")
|
|
148
|
+
if listener_count == 0:
|
|
149
|
+
logging.warning("未成功初始化任何监听器,请检查配置")
|
|
150
|
+
|
|
148
151
|
async def _setup_senders_async(self, rabbitmq_senders, has_listeners: bool):
|
|
149
152
|
Services._registered_senders = [
|
|
150
153
|
sender.queue_name for sender in rabbitmq_senders]
|
|
@@ -162,7 +165,7 @@ class Services(metaclass=SingletonMeta):
|
|
|
162
165
|
queue_name: str,
|
|
163
166
|
data: Union[str, Dict[str, Any], BaseModel, None],
|
|
164
167
|
max_retries: int = 3,
|
|
165
|
-
retry_delay: float = 1.0, **
|
|
168
|
+
retry_delay: float = 1.0, **kwargs
|
|
166
169
|
) -> None:
|
|
167
170
|
"""发送消息,添加重试机制"""
|
|
168
171
|
if not cls._initialized or not cls._loop:
|
|
@@ -172,7 +175,7 @@ class Services(metaclass=SingletonMeta):
|
|
|
172
175
|
for attempt in range(max_retries):
|
|
173
176
|
try:
|
|
174
177
|
if queue_name not in cls._registered_senders:
|
|
175
|
-
cls._registered_senders = RabbitMQService.
|
|
178
|
+
cls._registered_senders = RabbitMQService._sender_client_names
|
|
176
179
|
if queue_name not in cls._registered_senders:
|
|
177
180
|
raise ValueError(f"发送器 {queue_name} 未注册")
|
|
178
181
|
|
|
@@ -180,7 +183,7 @@ class Services(metaclass=SingletonMeta):
|
|
|
180
183
|
if not sender:
|
|
181
184
|
raise ValueError(f"发送器 '{queue_name}' 不存在")
|
|
182
185
|
|
|
183
|
-
await RabbitMQService.send_message(data, queue_name, **kwargs)
|
|
186
|
+
await RabbitMQService.send_message(data, queue_name, ** kwargs)
|
|
184
187
|
logging.info(f"消息发送成功(尝试 {attempt+1}/{max_retries})")
|
|
185
188
|
return
|
|
186
189
|
|
sycommon/synacos/feign.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import io
|
|
2
2
|
import os
|
|
3
3
|
import time
|
|
4
|
+
import inspect
|
|
4
5
|
from urllib.parse import urljoin
|
|
5
6
|
|
|
6
7
|
import aiohttp
|
|
@@ -14,163 +15,373 @@ from sycommon.synacos.nacos_service import NacosService
|
|
|
14
15
|
"""
|
|
15
16
|
|
|
16
17
|
# 示例Feign客户端接口
|
|
17
|
-
#
|
|
18
|
-
#
|
|
18
|
+
# 1. 定义完整的Feign客户端接口
|
|
19
|
+
# @feign_client(service_name="product-service", path_prefix="/api/v2")
|
|
20
|
+
# class ProductServiceClient:
|
|
21
|
+
# """商品服务Feign客户端示例,涵盖所有参数类型"""
|
|
19
22
|
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
+
# # ------------------------------
|
|
24
|
+
# # 场景1: Path参数 + Query参数
|
|
25
|
+
# # 请求示例: GET /products/{product_id}/reviews?status=APPROVED&page=1&size=10
|
|
26
|
+
# # ------------------------------
|
|
27
|
+
# @feign_request("GET", "/products/{product_id}/reviews")
|
|
28
|
+
# async def get_product_reviews(
|
|
29
|
+
# self,
|
|
30
|
+
# product_id: int, # Path参数 (URL路径中的占位符)
|
|
31
|
+
# status: Optional[str] = None, # Query参数 (URL查询字符串)
|
|
32
|
+
# page: int = 1, # Query参数 (默认值)
|
|
33
|
+
# size: int = 10 # Query参数 (默认值)
|
|
34
|
+
# ) -> Dict[str, Any]:
|
|
35
|
+
# """获取商品评价列表"""
|
|
23
36
|
# pass
|
|
24
37
|
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
38
|
+
# # ------------------------------
|
|
39
|
+
# # 场景2: 仅Query参数
|
|
40
|
+
# # 请求示例: GET /products?category=electronics&min_price=100&max_price=500&sort=price_asc
|
|
41
|
+
# # ------------------------------
|
|
42
|
+
# @feign_request("GET", "/products")
|
|
43
|
+
# async def search_products(
|
|
44
|
+
# self,
|
|
45
|
+
# category: str, # 必选Query参数
|
|
46
|
+
# min_price: Optional[float] = None, # 可选Query参数
|
|
47
|
+
# max_price: Optional[float] = None, # 可选Query参数
|
|
48
|
+
# sort: str = "created_desc" # 带默认值的Query参数
|
|
49
|
+
# ) -> Dict[str, Any]:
|
|
50
|
+
# """搜索商品(仅查询参数)"""
|
|
28
51
|
# pass
|
|
29
52
|
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
53
|
+
# # ------------------------------
|
|
54
|
+
# # 场景3: JSON Body参数 (POST)
|
|
55
|
+
# # 请求示例: POST /products (请求体为JSON)
|
|
56
|
+
# # ------------------------------
|
|
57
|
+
# @feign_request("POST", "/products", headers={"s-y-version": "2.1"})
|
|
58
|
+
# async def create_product(
|
|
59
|
+
# self,
|
|
60
|
+
# product_data: Dict[str, Any] # JSON请求体
|
|
61
|
+
# ) -> Dict[str, Any]:
|
|
62
|
+
# """创建商品(JSON请求体)"""
|
|
34
63
|
# pass
|
|
35
|
-
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
64
|
+
#
|
|
65
|
+
# # ------------------------------
|
|
66
|
+
# # 场景4: Path参数 + JSON Body (PUT)
|
|
67
|
+
# # 请求示例: PUT /products/{product_id} (请求体为JSON)
|
|
68
|
+
# # ------------------------------
|
|
69
|
+
# @feign_request("PUT", "/products/{product_id}")
|
|
70
|
+
# async def update_product(
|
|
71
|
+
# self,
|
|
72
|
+
# product_id: int, # Path参数
|
|
73
|
+
# update_data: Dict[str, Any] # JSON请求体
|
|
74
|
+
# ) -> Dict[str, Any]:
|
|
75
|
+
# """更新商品信息"""
|
|
76
|
+
# pass
|
|
77
|
+
#
|
|
78
|
+
# # ------------------------------
|
|
79
|
+
# # 场景5: FormData表单提交 (x-www-form-urlencoded)
|
|
80
|
+
# # 请求示例: POST /products/batch-status (表单字段)
|
|
81
|
+
# # ------------------------------
|
|
82
|
+
# @feign_request(
|
|
83
|
+
# "POST",
|
|
84
|
+
# "/products/batch-status",
|
|
85
|
+
# headers={"Content-Type": "application/x-www-form-urlencoded"}
|
|
86
|
+
# )
|
|
87
|
+
# async def batch_update_status(
|
|
88
|
+
# self,
|
|
89
|
+
# product_ids: str, # 表单字段 (多个ID用逗号分隔)
|
|
90
|
+
# status: str, # 表单字段 (目标状态)
|
|
91
|
+
# operator: str # 表单字段 (操作人)
|
|
92
|
+
# ) -> Dict[str, Any]:
|
|
93
|
+
# """批量更新商品状态(表单提交)"""
|
|
94
|
+
# pass
|
|
95
|
+
#
|
|
96
|
+
# # ------------------------------
|
|
97
|
+
# # 场景6: 文件上传 + 表单字段混合
|
|
98
|
+
# # 请求示例: POST /products/{product_id}/images (multipart/form-data)
|
|
99
|
+
# # ------------------------------
|
|
100
|
+
# @feign_upload(field_name="image_file") # 指定文件表单字段名
|
|
101
|
+
# @feign_request("POST", "/products/{product_id}/images")
|
|
102
|
+
# async def upload_product_image(
|
|
103
|
+
# self,
|
|
104
|
+
# product_id: int, # Path参数
|
|
105
|
+
# file_path: str, # 本地文件路径(会被转为文件上传)
|
|
106
|
+
# image_type: str, # 表单字段(图片类型)
|
|
107
|
+
# is_primary: bool = False, # 表单字段(是否主图)
|
|
108
|
+
# remark: Optional[str] = None # 可选表单字段
|
|
109
|
+
# ) -> Dict[str, Any]:
|
|
110
|
+
# """上传商品图片(文件+表单字段)"""
|
|
111
|
+
# pass
|
|
112
|
+
#
|
|
113
|
+
# # ------------------------------
|
|
114
|
+
# # 场景7: 多Path参数 + DELETE请求
|
|
115
|
+
# # 请求示例: DELETE /products/{product_id}/skus/{sku_id}
|
|
116
|
+
# # ------------------------------
|
|
117
|
+
# @feign_request("DELETE", "/products/{product_id}/skus/{sku_id}")
|
|
118
|
+
# async def delete_product_sku(
|
|
119
|
+
# self,
|
|
120
|
+
# product_id: int, # Path参数1
|
|
121
|
+
# sku_id: int # Path参数2
|
|
122
|
+
# ) -> Dict[str, Any]:
|
|
123
|
+
# """删除商品SKU(多路径参数)"""
|
|
124
|
+
# pass
|
|
125
|
+
#
|
|
126
|
+
# # ------------------------------
|
|
127
|
+
# # 场景8: 复杂JSON Body + Query参数
|
|
128
|
+
# # 请求示例: POST /products/filter?include_out_of_stock=false
|
|
129
|
+
# # ------------------------------
|
|
130
|
+
# @feign_request("POST", "/products/filter")
|
|
131
|
+
# async def advanced_filter(
|
|
132
|
+
# self,
|
|
133
|
+
# filter_condition: Dict[str, Any], # JSON请求体(复杂筛选条件)
|
|
134
|
+
# include_out_of_stock: bool = False, # Query参数
|
|
135
|
+
# page: int = 1, # Query参数
|
|
136
|
+
# size: int = 20 # Query参数
|
|
137
|
+
# ) -> Dict[str, Any]:
|
|
138
|
+
# """高级筛选商品(JSON体+查询参数)"""
|
|
139
|
+
# pass
|
|
140
|
+
#
|
|
141
|
+
#
|
|
142
|
+
# # 2. 完整调用示例
|
|
143
|
+
# async def feign_complete_demo():
|
|
144
|
+
# # ------------------------------
|
|
145
|
+
# # 调用场景1: Path参数 + Query参数
|
|
146
|
+
# # ------------------------------
|
|
147
|
+
# reviews = await ProductServiceClient().get_product_reviews(
|
|
148
|
+
# product_id=10086, # Path参数
|
|
149
|
+
# status="APPROVED", # Query参数
|
|
150
|
+
# page=1,
|
|
151
|
+
# size=20
|
|
152
|
+
# )
|
|
153
|
+
# print(f"场景1 - 商品评价: {reviews.get('total', 0)}条评价")
|
|
154
|
+
#
|
|
155
|
+
# # ------------------------------
|
|
156
|
+
# # 调用场景2: 仅Query参数
|
|
157
|
+
# # ------------------------------
|
|
158
|
+
# electronics = await ProductServiceClient().search_products(
|
|
159
|
+
# category="electronics", # 必选Query
|
|
160
|
+
# min_price=1000,
|
|
161
|
+
# max_price=5000,
|
|
162
|
+
# sort="price_asc"
|
|
163
|
+
# )
|
|
164
|
+
# print(f"场景2 - 搜索结果: {len(electronics.get('items', []))}个商品")
|
|
165
|
+
#
|
|
166
|
+
# # ------------------------------
|
|
167
|
+
# # 调用场景3: JSON Body参数 (POST)
|
|
168
|
+
# # ------------------------------
|
|
169
|
+
# new_product = await ProductServiceClient().create_product({
|
|
170
|
+
# "name": "无线蓝牙耳机",
|
|
171
|
+
# "category": "electronics",
|
|
172
|
+
# "price": 299.99,
|
|
173
|
+
# "stock": 500,
|
|
174
|
+
# "attributes": {
|
|
175
|
+
# "brand": "Feign",
|
|
176
|
+
# "battery_life": "24h"
|
|
177
|
+
# }
|
|
178
|
+
# })
|
|
179
|
+
# print(f"场景3 - 新建商品: ID={new_product.get('id')}")
|
|
180
|
+
# product_id = new_product.get('id') # 用于后续示例
|
|
181
|
+
#
|
|
182
|
+
# # ------------------------------
|
|
183
|
+
# # 调用场景4: Path参数 + JSON Body (PUT)
|
|
184
|
+
# # ------------------------------
|
|
185
|
+
# if product_id:
|
|
186
|
+
# updated = await ProductServiceClient().update_product(
|
|
187
|
+
# product_id=product_id,
|
|
188
|
+
# update_data={
|
|
189
|
+
# "price": 279.99, # 降价
|
|
190
|
+
# "stock": 600
|
|
191
|
+
# }
|
|
192
|
+
# )
|
|
193
|
+
# print(f"场景4 - 商品更新: 状态={updated.get('success')}")
|
|
194
|
+
#
|
|
195
|
+
# # ------------------------------
|
|
196
|
+
# # 调用场景5: FormData表单提交
|
|
197
|
+
# # ------------------------------
|
|
198
|
+
# batch_result = await ProductServiceClient().batch_update_status(
|
|
199
|
+
# product_ids="1001,1002,1003", # 多个ID用逗号分隔
|
|
200
|
+
# status="ON_SALE",
|
|
201
|
+
# operator="system"
|
|
202
|
+
# )
|
|
203
|
+
# print(f"场景5 - 批量更新: 成功{batch_result.get('success_count')}个")
|
|
204
|
+
#
|
|
205
|
+
# # ------------------------------
|
|
206
|
+
# # 调用场景6: 文件上传 + 表单字段
|
|
207
|
+
# # ------------------------------
|
|
208
|
+
# if product_id:
|
|
209
|
+
# upload_result = await ProductServiceClient().upload_product_image(
|
|
210
|
+
# product_id=product_id,
|
|
211
|
+
# file_path="/tmp/product_main.jpg", # 本地图片路径
|
|
212
|
+
# image_type="main",
|
|
213
|
+
# is_primary=True,
|
|
214
|
+
# remark="商品主图"
|
|
215
|
+
# )
|
|
216
|
+
# print(f"场景6 - 图片上传: URL={upload_result.get('image_url')}")
|
|
217
|
+
#
|
|
218
|
+
# # ------------------------------
|
|
219
|
+
# # 调用场景7: 多Path参数 + DELETE
|
|
220
|
+
# # ------------------------------
|
|
221
|
+
# delete_result = await ProductServiceClient().delete_product_sku(
|
|
222
|
+
# product_id=10086,
|
|
223
|
+
# sku_id=5001
|
|
224
|
+
# )
|
|
225
|
+
# print(f"场景7 - 删除SKU: {delete_result.get('message')}")
|
|
226
|
+
#
|
|
227
|
+
# # ------------------------------
|
|
228
|
+
# # 调用场景8: 复杂JSON Body + Query参数
|
|
229
|
+
# # ------------------------------
|
|
230
|
+
# filtered = await ProductServiceClient().advanced_filter(
|
|
231
|
+
# filter_condition={ # 复杂JSON条件
|
|
232
|
+
# "categories": ["electronics", "home"],
|
|
233
|
+
# "price_range": {"min": 500, "max": 3000},
|
|
234
|
+
# "tags": ["new", "promotion"]
|
|
235
|
+
# },
|
|
236
|
+
# include_out_of_stock=False, # Query参数
|
|
237
|
+
# page=1,
|
|
238
|
+
# size=10
|
|
239
|
+
# )
|
|
240
|
+
# print(f"场景8 - 高级筛选: {filtered.get('total')}个匹配商品")
|
|
47
241
|
|
|
48
242
|
|
|
49
243
|
def feign_client(service_name: str, path_prefix: str = "", default_timeout: float | None = None):
|
|
244
|
+
"""Feign客户端装饰器,每次请求后自动关闭会话"""
|
|
50
245
|
def decorator(cls):
|
|
51
246
|
class FeignWrapper:
|
|
52
247
|
def __init__(self):
|
|
53
248
|
self.service_name = service_name
|
|
54
|
-
self.nacos_manager = NacosService(None)
|
|
55
249
|
self.path_prefix = path_prefix
|
|
56
|
-
self.session = aiohttp.ClientSession()
|
|
57
250
|
self.default_timeout = default_timeout
|
|
251
|
+
self.nacos_manager = None # 延迟初始化Nacos
|
|
252
|
+
self.session = None # 延迟初始化aiohttp会话
|
|
58
253
|
|
|
59
254
|
def __getattr__(self, name):
|
|
255
|
+
"""动态获取方法并包装为Feign调用,请求后自动关闭会话"""
|
|
60
256
|
func = getattr(cls, name)
|
|
61
257
|
|
|
62
258
|
async def wrapper(*args, **kwargs):
|
|
63
|
-
#
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
timeout = kwargs.pop('timeout', self.default_timeout)
|
|
71
|
-
|
|
72
|
-
# 处理JSON请求的Content-Type
|
|
73
|
-
is_json_request = method.upper() in [
|
|
74
|
-
"POST", "PUT", "PATCH"] and not request_meta.get("files")
|
|
75
|
-
if is_json_request and "Content-Type" not in headers:
|
|
76
|
-
headers["Content-Type"] = "application/json"
|
|
77
|
-
|
|
78
|
-
# 获取版本信息
|
|
79
|
-
version = headers.get('s-y-version')
|
|
80
|
-
|
|
81
|
-
# 构建完整URL
|
|
82
|
-
full_path = f"{self.path_prefix}{path}"
|
|
83
|
-
for k, v in kwargs.items():
|
|
84
|
-
full_path = full_path.replace(f"{{{k}}}", str(v))
|
|
85
|
-
|
|
86
|
-
# 服务发现与负载均衡
|
|
87
|
-
instances = self.nacos_manager.get_service_instances(
|
|
88
|
-
self.service_name, version=version)
|
|
89
|
-
if not instances:
|
|
90
|
-
SYLogger.error(
|
|
91
|
-
f"nacos:未找到 {self.service_name} 的健康实例")
|
|
92
|
-
raise RuntimeError(
|
|
93
|
-
f"No instances available for {self.service_name}")
|
|
94
|
-
|
|
95
|
-
# 简单轮询负载均衡
|
|
96
|
-
instance = instances[int(time.time()) % len(instances)]
|
|
97
|
-
base_url = f"http://{instance['ip']}:{instance['port']}"
|
|
98
|
-
url = urljoin(base_url, full_path)
|
|
99
|
-
|
|
100
|
-
SYLogger.info(
|
|
101
|
-
f"nacos:调用服务: {self.service_name} -> {url}")
|
|
102
|
-
SYLogger.info(f"nacos:请求头: {headers}")
|
|
103
|
-
|
|
104
|
-
# 构建请求
|
|
105
|
-
params = request_meta.get("params", {})
|
|
106
|
-
body = request_meta.get("body", {})
|
|
107
|
-
files = request_meta.get("files", None)
|
|
108
|
-
form_data = request_meta.get("form_data", None)
|
|
109
|
-
|
|
110
|
-
# 发送请求
|
|
259
|
+
# 确保会话初始化
|
|
260
|
+
if self.session is None:
|
|
261
|
+
self.session = aiohttp.ClientSession()
|
|
262
|
+
if self.nacos_manager is None:
|
|
263
|
+
self.nacos_manager = NacosService(None)
|
|
264
|
+
|
|
111
265
|
try:
|
|
112
|
-
#
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
266
|
+
# 1. 解析参数
|
|
267
|
+
sig = inspect.signature(func)
|
|
268
|
+
param_names = list(sig.parameters.keys())
|
|
269
|
+
try:
|
|
270
|
+
if param_names and param_names[0] == 'self':
|
|
271
|
+
bound_args = sig.bind(self, *args, **kwargs)
|
|
272
|
+
else:
|
|
273
|
+
bound_args = sig.bind(*args, **kwargs)
|
|
274
|
+
bound_args.apply_defaults()
|
|
275
|
+
params = dict(bound_args.arguments)
|
|
276
|
+
params.pop('self', None)
|
|
277
|
+
SYLogger.debug(f"解析参数: {params}")
|
|
278
|
+
except TypeError as e:
|
|
279
|
+
SYLogger.error(f"参数绑定失败: {str(e)}")
|
|
280
|
+
raise
|
|
281
|
+
|
|
282
|
+
# 2. 构建请求
|
|
283
|
+
request_meta = getattr(func, "_feign_meta", {})
|
|
284
|
+
method = request_meta.get("method", "GET")
|
|
285
|
+
path = request_meta.get("path", "")
|
|
286
|
+
headers = request_meta.get("headers", {}).copy()
|
|
287
|
+
timeout = kwargs.pop('timeout', self.default_timeout)
|
|
288
|
+
|
|
289
|
+
full_path = f"{self.path_prefix}{path}"
|
|
290
|
+
for param_name, param_value in params.items():
|
|
291
|
+
if param_value is not None:
|
|
292
|
+
full_path = full_path.replace(
|
|
293
|
+
f"{{{param_name}}}", str(param_value))
|
|
294
|
+
|
|
295
|
+
is_json_request = method.upper() in ["POST", "PUT", "PATCH"] and \
|
|
296
|
+
not request_meta.get("is_upload", False)
|
|
297
|
+
if is_json_request and "Content-Type" not in headers:
|
|
298
|
+
headers["Content-Type"] = "application/json"
|
|
299
|
+
|
|
300
|
+
# 3. 服务发现
|
|
301
|
+
version = headers.get('s-y-version')
|
|
302
|
+
instances = self.nacos_manager.get_service_instances(
|
|
303
|
+
self.service_name, version=version)
|
|
304
|
+
if not instances:
|
|
305
|
+
raise RuntimeError(
|
|
306
|
+
f"未找到服务 {self.service_name} 的健康实例")
|
|
307
|
+
|
|
308
|
+
instance = instances[int(time.time()) % len(instances)]
|
|
309
|
+
base_url = f"http://{instance['ip']}:{instance['port']}"
|
|
310
|
+
url = urljoin(base_url, full_path)
|
|
311
|
+
SYLogger.info(f"请求: {method} {url}")
|
|
312
|
+
|
|
313
|
+
# 4. 准备请求参数
|
|
314
|
+
query_params = {k: v for k, v in params.items()
|
|
315
|
+
if f"{{{k}}}" not in path and v is not None}
|
|
316
|
+
request_data = None
|
|
317
|
+
files = None
|
|
318
|
+
|
|
319
|
+
if request_meta.get("is_upload", False):
|
|
320
|
+
files = aiohttp.FormData()
|
|
321
|
+
file_path = params.get('file_path')
|
|
322
|
+
if file_path and os.path.exists(file_path):
|
|
323
|
+
file_field = request_meta.get(
|
|
324
|
+
"upload_field", "file")
|
|
325
|
+
with open(file_path, 'rb') as f:
|
|
326
|
+
files.add_field(
|
|
327
|
+
file_field,
|
|
328
|
+
f.read(),
|
|
329
|
+
filename=os.path.basename(file_path)
|
|
330
|
+
)
|
|
331
|
+
for key, value in params.items():
|
|
332
|
+
if key != 'file_path' and value is not None:
|
|
333
|
+
files.add_field(key, str(value))
|
|
124
334
|
headers.pop('Content-Type', None)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
335
|
+
elif is_json_request:
|
|
336
|
+
body_params = [k for k in params if k not in query_params
|
|
337
|
+
and f"{{{k}}}" not in path]
|
|
338
|
+
if body_params:
|
|
339
|
+
request_data = params[body_params[0]] if len(body_params) == 1 else \
|
|
340
|
+
{k: params[k] for k in body_params}
|
|
341
|
+
|
|
342
|
+
# 5. 发送请求并获取响应
|
|
343
|
+
async with self.session.request(
|
|
344
|
+
method=method,
|
|
345
|
+
url=url,
|
|
346
|
+
headers=headers,
|
|
347
|
+
params=query_params,
|
|
348
|
+
json=request_data if not files else None,
|
|
349
|
+
data=files,
|
|
350
|
+
timeout=timeout
|
|
351
|
+
) as response:
|
|
352
|
+
return await self._handle_response(response)
|
|
353
|
+
|
|
354
|
+
finally:
|
|
355
|
+
# 请求完成后自动关闭会话
|
|
356
|
+
if self.session:
|
|
357
|
+
await self.session.close()
|
|
358
|
+
self.session = None # 重置会话,下次调用重新创建
|
|
359
|
+
SYLogger.info(
|
|
360
|
+
f"自动关闭aiohttp会话: {self.service_name}")
|
|
149
361
|
|
|
150
362
|
return wrapper
|
|
151
363
|
|
|
152
364
|
async def _handle_response(self, response):
|
|
153
|
-
|
|
154
|
-
|
|
365
|
+
"""处理响应结果(保持不变)"""
|
|
366
|
+
status = response.status
|
|
367
|
+
if 200 <= status < 300:
|
|
155
368
|
content_type = response.headers.get('Content-Type', '')
|
|
156
369
|
if 'application/json' in content_type:
|
|
157
370
|
return await response.json()
|
|
158
371
|
else:
|
|
159
372
|
return await response.read()
|
|
160
|
-
|
|
161
|
-
|
|
373
|
+
else:
|
|
374
|
+
error_msg = await response.text()
|
|
375
|
+
SYLogger.error(f"响应错误: {status} - {error_msg}")
|
|
376
|
+
raise RuntimeError(f"HTTP {status}: {error_msg}")
|
|
162
377
|
|
|
163
|
-
|
|
164
|
-
"""关闭 aiohttp 会话"""
|
|
165
|
-
await self.session.close()
|
|
166
|
-
|
|
167
|
-
return FeignWrapper()
|
|
378
|
+
return FeignWrapper
|
|
168
379
|
return decorator
|
|
169
380
|
|
|
170
381
|
|
|
171
382
|
def feign_request(method: str, path: str, headers: dict = None):
|
|
383
|
+
"""定义请求元数据的装饰器"""
|
|
172
384
|
def decorator(func):
|
|
173
|
-
# 初始化请求元数据,确保headers是可修改的字典
|
|
174
385
|
func._feign_meta = {
|
|
175
386
|
"method": method.upper(),
|
|
176
387
|
"path": path,
|
|
@@ -181,13 +392,12 @@ def feign_request(method: str, path: str, headers: dict = None):
|
|
|
181
392
|
|
|
182
393
|
|
|
183
394
|
def feign_upload(field_name: str = "file"):
|
|
184
|
-
|
|
395
|
+
"""处理文件上传的装饰器"""
|
|
185
396
|
def decorator(func):
|
|
186
397
|
async def wrapper(*args, **kwargs):
|
|
187
398
|
file_path = kwargs.get('file_path')
|
|
188
|
-
if not file_path:
|
|
189
|
-
raise ValueError("file_path
|
|
190
|
-
|
|
399
|
+
if not file_path or not os.path.exists(file_path):
|
|
400
|
+
raise ValueError(f"文件路径不存在: {file_path}")
|
|
191
401
|
with open(file_path, 'rb') as f:
|
|
192
402
|
files = {field_name: (os.path.basename(file_path), f.read())}
|
|
193
403
|
kwargs['files'] = files
|
|
@@ -276,11 +486,13 @@ async def feign(service_name, api_path, method='GET', params=None, headers=None,
|
|
|
276
486
|
) as response:
|
|
277
487
|
return await _handle_feign_response(response)
|
|
278
488
|
except aiohttp.ClientError as e:
|
|
279
|
-
SYLogger.error(
|
|
489
|
+
SYLogger.error(
|
|
490
|
+
f"nacos:请求服务接口时出错ClientError path: {api_path} error:{e}")
|
|
280
491
|
return None
|
|
281
492
|
except Exception as e:
|
|
282
493
|
import traceback
|
|
283
|
-
SYLogger.error(
|
|
494
|
+
SYLogger.error(
|
|
495
|
+
f"nacos:请求服务接口时出错 path: {api_path} error:{traceback.format_exc()}")
|
|
284
496
|
return None
|
|
285
497
|
finally:
|
|
286
498
|
await session.close()
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
command/cli.py,sha256=bP2LCLkRvfETIwWkVD70q5xFxMI4D3BpH09Ws1f-ENc,5849
|
|
1
2
|
sycommon/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
sycommon/services.py,sha256=
|
|
3
|
+
sycommon/services.py,sha256=4oE0KXNJxM284nSMTO3PcbJFnMQpmXN6kUmeyIGLWfI,8272
|
|
3
4
|
sycommon/config/Config.py,sha256=9yO5b8WfvEDvkyrGrlwrLFasgh_-MjcEvGF20Gz5Xo4,3041
|
|
4
5
|
sycommon/config/DatabaseConfig.py,sha256=ILiUuYT9_xJZE2W-RYuC3JCt_YLKc1sbH13-MHIOPhg,804
|
|
5
6
|
sycommon/config/EmbeddingConfig.py,sha256=gPKwiDYbeu1GpdIZXMmgqM7JqBIzCXi0yYuGRLZooMI,362
|
|
@@ -26,22 +27,23 @@ sycommon/middleware/mq.py,sha256=4wBqiT5wJGcrfjk2GSr0_U3TStBxoNpHTzcRxVlMEHE,183
|
|
|
26
27
|
sycommon/middleware/timeout.py,sha256=fImlAPLm4Oa8N9goXtT_0os1GZPCi9F92OgXU81DgDU,656
|
|
27
28
|
sycommon/middleware/traceid.py,sha256=oGTJ2jtdea_3NgaAwXLpUug5dGUYRQeM4r1n2icuvC8,6839
|
|
28
29
|
sycommon/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
|
-
sycommon/models/base_http.py,sha256=
|
|
30
|
+
sycommon/models/base_http.py,sha256=EICAAibx3xhjBsLqm35Mi3DCqxp0FME4rD_3iQVjT_E,3051
|
|
30
31
|
sycommon/models/log.py,sha256=rZpj6VkDRxK3B6H7XSeWdYZshU8F0Sks8bq1p6pPlDw,500
|
|
31
32
|
sycommon/models/mqlistener_config.py,sha256=PPwhAVJ2AWvVAvNox_1t0fuBKTyRH3Ui9cuuU-q7Byo,1590
|
|
32
33
|
sycommon/models/mqmsg_model.py,sha256=cxn0M5b0utQK6crMYmL-1waeGYHvK3AlGaRy23clqTE,277
|
|
33
34
|
sycommon/models/mqsend_config.py,sha256=NQX9dc8PpuquMG36GCVhJe8omAW1KVXXqr6lSRU6D7I,268
|
|
34
35
|
sycommon/models/sso_user.py,sha256=i1WAN6k5sPcPApQEdtjpWDy7VrzWLpOrOQewGLGoGIw,2702
|
|
35
|
-
sycommon/rabbitmq/rabbitmq_client.py,sha256=
|
|
36
|
-
sycommon/rabbitmq/rabbitmq_service.py,sha256=
|
|
36
|
+
sycommon/rabbitmq/rabbitmq_client.py,sha256=ki9BgfHxyCLbf0IUuIt8RiTV0ZvWlusE3vHqRbAbl_c,37533
|
|
37
|
+
sycommon/rabbitmq/rabbitmq_service.py,sha256=bkClaE-mqcM1X_z1xvTSOHplB7jzI-buHPn9EV7v5hk,27908
|
|
37
38
|
sycommon/synacos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
|
-
sycommon/synacos/feign.py,sha256=
|
|
39
|
+
sycommon/synacos/feign.py,sha256=PX7jqexICGm-a9oKULPFUTcNQVeXbXwRo12XXy5oHnM,21268
|
|
39
40
|
sycommon/synacos/nacos_service.py,sha256=8l2qaVg5GQSTybGrcdmg0B5jRNAzbOPM3mjQ6T04A2I,30816
|
|
40
41
|
sycommon/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
42
|
sycommon/tools/docs.py,sha256=OPj2ETheuWjXLyaXtaZPbwmJKfJaYXV5s4XMVAUNrms,1607
|
|
42
43
|
sycommon/tools/snowflake.py,sha256=rc-VUjBMMpdAvbnHroVwfVt1xzApJbTCthUy9mglAuw,237
|
|
43
44
|
sycommon/tools/timing.py,sha256=OiiE7P07lRoMzX9kzb8sZU9cDb0zNnqIlY5pWqHcnkY,2064
|
|
44
|
-
sycommon_python_lib-0.1.
|
|
45
|
-
sycommon_python_lib-0.1.
|
|
46
|
-
sycommon_python_lib-0.1.
|
|
47
|
-
sycommon_python_lib-0.1.
|
|
45
|
+
sycommon_python_lib-0.1.11.dist-info/METADATA,sha256=N3Oh7lcmc1oXQqPsfemPT8QGmQi9oJSzjVbo6nFeHl4,7005
|
|
46
|
+
sycommon_python_lib-0.1.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
47
|
+
sycommon_python_lib-0.1.11.dist-info/entry_points.txt,sha256=q_h2nbvhhmdnsOUZEIwpuoDjaNfBF9XqppDEmQn9d_A,46
|
|
48
|
+
sycommon_python_lib-0.1.11.dist-info/top_level.txt,sha256=98CJ-cyM2WIKxLz-Pf0AitWLhJyrfXvyY8slwjTXNuc,17
|
|
49
|
+
sycommon_python_lib-0.1.11.dist-info/RECORD,,
|
|
File without changes
|