sycommon-python-lib 0.1.0__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.
- sycommon/__init__.py +0 -0
- sycommon/config/Config.py +73 -0
- sycommon/config/DatabaseConfig.py +34 -0
- sycommon/config/EmbeddingConfig.py +16 -0
- sycommon/config/LLMConfig.py +16 -0
- sycommon/config/RerankerConfig.py +13 -0
- sycommon/config/__init__.py +0 -0
- sycommon/database/database_service.py +79 -0
- sycommon/health/__init__.py +0 -0
- sycommon/health/health_check.py +17 -0
- sycommon/health/ping.py +13 -0
- sycommon/logging/__init__.py +0 -0
- sycommon/logging/kafka_log.py +551 -0
- sycommon/logging/logger_wrapper.py +19 -0
- sycommon/middleware/__init__.py +0 -0
- sycommon/middleware/context.py +3 -0
- sycommon/middleware/cors.py +14 -0
- sycommon/middleware/exception.py +85 -0
- sycommon/middleware/middleware.py +32 -0
- sycommon/middleware/monitor_memory.py +22 -0
- sycommon/middleware/timeout.py +19 -0
- sycommon/middleware/traceid.py +138 -0
- sycommon/models/__init__.py +0 -0
- sycommon/models/log.py +30 -0
- sycommon/services.py +29 -0
- sycommon/synacos/__init__.py +0 -0
- sycommon/synacos/feign.py +307 -0
- sycommon/synacos/nacos_service.py +689 -0
- sycommon/tools/__init__.py +0 -0
- sycommon/tools/snowflake.py +11 -0
- sycommon/tools/timing.py +73 -0
- sycommon_python_lib-0.1.0.dist-info/METADATA +128 -0
- sycommon_python_lib-0.1.0.dist-info/RECORD +35 -0
- sycommon_python_lib-0.1.0.dist-info/WHEEL +5 -0
- sycommon_python_lib-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from sycommon.health.ping import setup_ping_handler
|
|
2
|
+
from sycommon.middleware.cors import setup_cors_handler
|
|
3
|
+
from sycommon.middleware.exception import setup_exception_handler
|
|
4
|
+
from sycommon.middleware.monitor_memory import setup_monitor_memory_middleware
|
|
5
|
+
from sycommon.middleware.timeout import setup_request_timeout_middleware
|
|
6
|
+
from sycommon.middleware.traceid import setup_trace_id_handler
|
|
7
|
+
from sycommon.health.health_check import setup_health_handler
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def setup_middleware(app, config: dict):
|
|
11
|
+
# 设置请求超时中间件
|
|
12
|
+
app = setup_request_timeout_middleware(app, config)
|
|
13
|
+
|
|
14
|
+
# 设置异常处理
|
|
15
|
+
app = setup_exception_handler(app, config)
|
|
16
|
+
|
|
17
|
+
# 设置 trace_id 处理中间件
|
|
18
|
+
app = setup_trace_id_handler(app)
|
|
19
|
+
|
|
20
|
+
# 设置内存监控中间件
|
|
21
|
+
# app = setup_monitor_memory_middleware(app)
|
|
22
|
+
|
|
23
|
+
# 设置cors
|
|
24
|
+
# app = setup_cors_handler(app)
|
|
25
|
+
|
|
26
|
+
# 健康检查
|
|
27
|
+
app = setup_health_handler(app)
|
|
28
|
+
|
|
29
|
+
# ping
|
|
30
|
+
app = setup_ping_handler(app)
|
|
31
|
+
|
|
32
|
+
return app
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
import tracemalloc
|
|
3
|
+
from fastapi import Request
|
|
4
|
+
|
|
5
|
+
from sycommon.logging.kafka_log import SYLogger
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def setup_monitor_memory_middleware(app):
|
|
9
|
+
@app.middleware("http")
|
|
10
|
+
async def before_request(request: Request, call_next):
|
|
11
|
+
# if not tracemalloc.is_tracing():
|
|
12
|
+
# tracemalloc.start()
|
|
13
|
+
|
|
14
|
+
# snapshot1 = tracemalloc.take_snapshot()
|
|
15
|
+
# await call_next(request)
|
|
16
|
+
# snapshot2 = tracemalloc.take_snapshot()
|
|
17
|
+
|
|
18
|
+
# top_stats = snapshot2.compare_to(snapshot1, 'lineno')
|
|
19
|
+
# if top_stats:
|
|
20
|
+
# SYLogger.info(f"内存增长最大项: {top_stats[0]}")
|
|
21
|
+
pass
|
|
22
|
+
return app
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
import time
|
|
3
|
+
from fastapi import Request
|
|
4
|
+
from fastapi.responses import JSONResponse
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def setup_request_timeout_middleware(app, config: dict):
|
|
8
|
+
# 设置全局请求超时时间
|
|
9
|
+
REQUEST_TIMEOUT = int(config.get('Timeout', 30000))/1000
|
|
10
|
+
|
|
11
|
+
@app.middleware("http")
|
|
12
|
+
async def before_request(request: Request, call_next):
|
|
13
|
+
request.state.start_time = time.time()
|
|
14
|
+
response = await call_next(request)
|
|
15
|
+
duration = time.time() - request.state.start_time
|
|
16
|
+
if duration > REQUEST_TIMEOUT:
|
|
17
|
+
return JSONResponse(content={'code': 1, 'error': 'Request timed out'}, status_code=504)
|
|
18
|
+
return response
|
|
19
|
+
return app
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import re
|
|
3
|
+
from typing import Dict, Any
|
|
4
|
+
from fastapi import Request, Response
|
|
5
|
+
from sycommon.logging.kafka_log import SYLogger
|
|
6
|
+
from sycommon.tools.snowflake import Snowflake
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def setup_trace_id_handler(app):
|
|
10
|
+
@app.middleware("http")
|
|
11
|
+
async def trace_id_and_log_middleware(request: Request, call_next):
|
|
12
|
+
# 生成或获取 traceId
|
|
13
|
+
trace_id = request.headers.get("x-traceId-header")
|
|
14
|
+
if not trace_id:
|
|
15
|
+
trace_id = Snowflake.next_id()
|
|
16
|
+
|
|
17
|
+
# 设置 trace_id 上下文
|
|
18
|
+
token = SYLogger.set_trace_id(trace_id)
|
|
19
|
+
|
|
20
|
+
# 获取请求参数
|
|
21
|
+
query_params = dict(request.query_params)
|
|
22
|
+
request_body: Dict[str, Any] = {}
|
|
23
|
+
files_info: Dict[str, str] = {}
|
|
24
|
+
|
|
25
|
+
# 检测请求内容类型
|
|
26
|
+
content_type = request.headers.get("content-type", "").lower()
|
|
27
|
+
|
|
28
|
+
if "application/json" in content_type and request.method in ["POST", "PUT", "PATCH"]:
|
|
29
|
+
try:
|
|
30
|
+
request_body = await request.json()
|
|
31
|
+
except Exception as e:
|
|
32
|
+
request_body = {"error": f"Failed to parse JSON: {str(e)}"}
|
|
33
|
+
|
|
34
|
+
elif "multipart/form-data" in content_type and request.method in ["POST", "PUT"]:
|
|
35
|
+
try:
|
|
36
|
+
# 从请求头中提取boundary
|
|
37
|
+
boundary = None
|
|
38
|
+
if "boundary=" in content_type:
|
|
39
|
+
boundary = content_type.split("boundary=")[1].strip()
|
|
40
|
+
boundary = boundary.encode('ascii')
|
|
41
|
+
|
|
42
|
+
if boundary:
|
|
43
|
+
# 读取原始请求体
|
|
44
|
+
body = await request.body()
|
|
45
|
+
|
|
46
|
+
# 尝试从原始请求体中提取文件名
|
|
47
|
+
parts = body.split(boundary)
|
|
48
|
+
for part in parts:
|
|
49
|
+
part_str = part.decode('utf-8', errors='ignore')
|
|
50
|
+
|
|
51
|
+
# 使用正则表达式查找文件名
|
|
52
|
+
filename_match = re.search(
|
|
53
|
+
r'filename="([^"]+)"', part_str)
|
|
54
|
+
if filename_match:
|
|
55
|
+
field_name_match = re.search(
|
|
56
|
+
r'name="([^"]+)"', part_str)
|
|
57
|
+
field_name = field_name_match.group(
|
|
58
|
+
1) if field_name_match else "unknown"
|
|
59
|
+
filename = filename_match.group(1)
|
|
60
|
+
files_info[field_name] = filename
|
|
61
|
+
except Exception as e:
|
|
62
|
+
request_body = {
|
|
63
|
+
"error": f"Failed to process form data: {str(e)}"}
|
|
64
|
+
|
|
65
|
+
# 构建请求日志信息
|
|
66
|
+
request_message = {
|
|
67
|
+
"method": request.method,
|
|
68
|
+
"url": str(request.url),
|
|
69
|
+
"query_params": query_params,
|
|
70
|
+
"request_body": request_body,
|
|
71
|
+
"uploaded_files": files_info if files_info else None
|
|
72
|
+
}
|
|
73
|
+
request_message_str = json.dumps(request_message, ensure_ascii=False)
|
|
74
|
+
SYLogger.info(request_message_str)
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
# 处理请求
|
|
78
|
+
response = await call_next(request)
|
|
79
|
+
|
|
80
|
+
content_type = response.headers.get("Content-Type", "")
|
|
81
|
+
if "text/event-stream" in content_type:
|
|
82
|
+
# 处理 SSE 响应
|
|
83
|
+
response.headers["x-traceId-header"] = trace_id
|
|
84
|
+
return response
|
|
85
|
+
|
|
86
|
+
response_body = b""
|
|
87
|
+
try:
|
|
88
|
+
async for chunk in response.body_iterator:
|
|
89
|
+
response_body += chunk
|
|
90
|
+
|
|
91
|
+
content_disposition = response.headers.get(
|
|
92
|
+
"Content-Disposition", "")
|
|
93
|
+
|
|
94
|
+
# 判断是否能添加 trace_id
|
|
95
|
+
if "application/json" in content_type and not content_disposition.startswith("attachment"):
|
|
96
|
+
try:
|
|
97
|
+
data = json.loads(response_body)
|
|
98
|
+
data["trace_id"] = trace_id
|
|
99
|
+
new_body = json.dumps(
|
|
100
|
+
data, ensure_ascii=False).encode()
|
|
101
|
+
response = Response(
|
|
102
|
+
content=new_body,
|
|
103
|
+
status_code=response.status_code,
|
|
104
|
+
headers=dict(response.headers)
|
|
105
|
+
)
|
|
106
|
+
response.headers["Content-Length"] = str(len(new_body))
|
|
107
|
+
except json.JSONDecodeError:
|
|
108
|
+
pass
|
|
109
|
+
except StopAsyncIteration:
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
# 构建响应日志信息
|
|
113
|
+
response_message = {
|
|
114
|
+
"status_code": response.status_code,
|
|
115
|
+
"response_body": response_body.decode('utf-8', errors='ignore'),
|
|
116
|
+
}
|
|
117
|
+
response_message_str = json.dumps(
|
|
118
|
+
response_message, ensure_ascii=False)
|
|
119
|
+
SYLogger.info(response_message_str)
|
|
120
|
+
|
|
121
|
+
response.headers["x-traceId-header"] = trace_id
|
|
122
|
+
|
|
123
|
+
return response
|
|
124
|
+
except Exception as e:
|
|
125
|
+
error_message = {
|
|
126
|
+
"error": str(e),
|
|
127
|
+
"query_params": query_params,
|
|
128
|
+
"request_body": request_body,
|
|
129
|
+
"uploaded_files": files_info if files_info else None
|
|
130
|
+
}
|
|
131
|
+
error_message_str = json.dumps(error_message, ensure_ascii=False)
|
|
132
|
+
SYLogger.error(error_message_str) # 无需显式传递 trace_id
|
|
133
|
+
raise
|
|
134
|
+
finally:
|
|
135
|
+
# 清理上下文变量,防止泄漏
|
|
136
|
+
SYLogger.reset_trace_id(token)
|
|
137
|
+
|
|
138
|
+
return app
|
|
File without changes
|
sycommon/models/log.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class LogModel(BaseModel):
|
|
5
|
+
traceId: str
|
|
6
|
+
sySpanId: str
|
|
7
|
+
syBizId: str
|
|
8
|
+
ptxId: str
|
|
9
|
+
time: str
|
|
10
|
+
day: str
|
|
11
|
+
msg: str
|
|
12
|
+
detail: str
|
|
13
|
+
IP: str
|
|
14
|
+
hostName: str
|
|
15
|
+
tenantId: str
|
|
16
|
+
userId: str
|
|
17
|
+
customerId: str
|
|
18
|
+
env: str
|
|
19
|
+
priReqSource: str
|
|
20
|
+
reqSource: str
|
|
21
|
+
serviceId: str
|
|
22
|
+
logLevel: str
|
|
23
|
+
classShortName: str
|
|
24
|
+
method: str
|
|
25
|
+
line: str
|
|
26
|
+
theadName: str
|
|
27
|
+
className: str
|
|
28
|
+
sqlCost: float
|
|
29
|
+
size: float
|
|
30
|
+
uid: float
|
sycommon/services.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from typing import Callable, List, Tuple
|
|
2
|
+
|
|
3
|
+
from sycommon.config.Config import SingletonMeta
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Services(metaclass=SingletonMeta):
|
|
7
|
+
def __init__(self, config):
|
|
8
|
+
self.config = config
|
|
9
|
+
|
|
10
|
+
def plugins(self, nacos_service=None, logging_service=None, database_service: Tuple[Callable, str] | List[Tuple[Callable, str]] = None):
|
|
11
|
+
# 注册nacos服务
|
|
12
|
+
if nacos_service:
|
|
13
|
+
nacos_service(self.config)
|
|
14
|
+
# 注册日志服务
|
|
15
|
+
if logging_service:
|
|
16
|
+
logging_service(self.config)
|
|
17
|
+
# 注册数据库服务
|
|
18
|
+
if database_service:
|
|
19
|
+
if isinstance(database_service, tuple):
|
|
20
|
+
# 单个数据库服务
|
|
21
|
+
db_setup, db_name = database_service
|
|
22
|
+
db_setup(self.config, db_name)
|
|
23
|
+
elif isinstance(database_service, list):
|
|
24
|
+
# 多个数据库服务
|
|
25
|
+
for db_setup, db_name in database_service:
|
|
26
|
+
db_setup(self.config, db_name)
|
|
27
|
+
else:
|
|
28
|
+
raise TypeError(
|
|
29
|
+
"database_service must be a tuple or a list of tuples")
|
|
File without changes
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import os
|
|
3
|
+
import time
|
|
4
|
+
from urllib.parse import urljoin
|
|
5
|
+
|
|
6
|
+
import aiohttp
|
|
7
|
+
from sycommon.logging.kafka_log import SYLogger
|
|
8
|
+
from sycommon.synacos.nacos_service import NacosService
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
支持异步Feign客户端
|
|
12
|
+
方式一: 使用 @feign_client 和 @feign_request 装饰器
|
|
13
|
+
方式二: 使用 feign 函数
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
# 示例Feign客户端接口
|
|
17
|
+
# @feign_client(service_name="user-service", path_prefix="/api/v1")
|
|
18
|
+
# class UserServiceClient:
|
|
19
|
+
#
|
|
20
|
+
# @feign_request("GET", "/users/{user_id}")
|
|
21
|
+
# async def get_user(self, user_id):
|
|
22
|
+
# """获取用户信息"""
|
|
23
|
+
# pass
|
|
24
|
+
#
|
|
25
|
+
# @feign_request("POST", "/users", headers={"Content-Type": "application/json"})
|
|
26
|
+
# async def create_user(self, user_data):
|
|
27
|
+
# """创建用户"""
|
|
28
|
+
# pass
|
|
29
|
+
#
|
|
30
|
+
# @feign_upload("avatar")
|
|
31
|
+
# @feign_request("POST", "/users/{user_id}/avatar")
|
|
32
|
+
# async def upload_avatar(self, user_id, file_path):
|
|
33
|
+
# """上传用户头像"""
|
|
34
|
+
# pass
|
|
35
|
+
|
|
36
|
+
# # 使用示例
|
|
37
|
+
# async def get_user_info(user_id: int, request=None):
|
|
38
|
+
# """获取用户信息"""
|
|
39
|
+
# try:
|
|
40
|
+
# user_service = UserServiceClient()
|
|
41
|
+
# # 设置请求头中的版本信息
|
|
42
|
+
# user_service.get_user._feign_meta['headers']['s-y-version'] = "1.0.0"
|
|
43
|
+
# return await user_service.get_user(user_id=user_id, request=request)
|
|
44
|
+
# except Exception as e:
|
|
45
|
+
# SYLogger.error(f"获取用户信息失败: {str(e)}", TraceId(request))
|
|
46
|
+
# return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def feign_client(service_name: str, path_prefix: str = ""):
|
|
50
|
+
# Feign风格客户端装饰器
|
|
51
|
+
"""声明式HTTP客户端装饰器"""
|
|
52
|
+
def decorator(cls):
|
|
53
|
+
class FeignWrapper:
|
|
54
|
+
def __init__(self):
|
|
55
|
+
self.service_name = service_name
|
|
56
|
+
self.nacos_manager = NacosService(None)
|
|
57
|
+
self.path_prefix = path_prefix
|
|
58
|
+
self.session = aiohttp.ClientSession()
|
|
59
|
+
|
|
60
|
+
def __getattr__(self, name):
|
|
61
|
+
func = getattr(cls, name)
|
|
62
|
+
|
|
63
|
+
async def wrapper(*args, **kwargs):
|
|
64
|
+
# 获取请求元数据
|
|
65
|
+
request_meta = getattr(func, "_feign_meta", {})
|
|
66
|
+
method = request_meta.get("method", "GET")
|
|
67
|
+
path = request_meta.get("path", "")
|
|
68
|
+
headers = request_meta.get("headers", {})
|
|
69
|
+
|
|
70
|
+
# 获取版本信息
|
|
71
|
+
version = headers.get('s-y-version')
|
|
72
|
+
|
|
73
|
+
# 构建完整URL
|
|
74
|
+
full_path = f"{self.path_prefix}{path}"
|
|
75
|
+
for k, v in kwargs.items():
|
|
76
|
+
full_path = full_path.replace(f"{{{k}}}", str(v))
|
|
77
|
+
|
|
78
|
+
# 服务发现与负载均衡
|
|
79
|
+
instances = self.nacos_manager.get_service_instances(
|
|
80
|
+
self.service_name, version=version)
|
|
81
|
+
if not instances:
|
|
82
|
+
SYLogger.error(
|
|
83
|
+
f"nacos:未找到 {self.service_name} 的健康实例")
|
|
84
|
+
raise RuntimeError(
|
|
85
|
+
f"No instances available for {self.service_name}")
|
|
86
|
+
|
|
87
|
+
# 简单轮询负载均衡
|
|
88
|
+
instance = instances[int(time.time()) % len(instances)]
|
|
89
|
+
base_url = f"http://{instance['ip']}:{instance['port']}"
|
|
90
|
+
url = urljoin(base_url, full_path)
|
|
91
|
+
|
|
92
|
+
SYLogger.info(
|
|
93
|
+
f"nacos:调用服务: {self.service_name} -> {url}")
|
|
94
|
+
|
|
95
|
+
# 构建请求
|
|
96
|
+
params = request_meta.get("params", {})
|
|
97
|
+
body = request_meta.get("body", {})
|
|
98
|
+
files = request_meta.get("files", None)
|
|
99
|
+
form_data = request_meta.get("form_data", None)
|
|
100
|
+
|
|
101
|
+
# 发送请求
|
|
102
|
+
try:
|
|
103
|
+
# 处理文件上传
|
|
104
|
+
if files or form_data:
|
|
105
|
+
# 创建表单数据
|
|
106
|
+
data = aiohttp.FormData()
|
|
107
|
+
if form_data:
|
|
108
|
+
for key, value in form_data.items():
|
|
109
|
+
data.add_field(key, value)
|
|
110
|
+
if files:
|
|
111
|
+
for field_name, (filename, content) in files.items():
|
|
112
|
+
data.add_field(
|
|
113
|
+
field_name, content, filename=filename)
|
|
114
|
+
# 移除 Content-Type 头,让 aiohttp 自动设置 boundary
|
|
115
|
+
headers.pop('Content-Type', None)
|
|
116
|
+
# 发送表单数据
|
|
117
|
+
async with self.session.request(
|
|
118
|
+
method=method,
|
|
119
|
+
url=url,
|
|
120
|
+
headers=headers,
|
|
121
|
+
params=params,
|
|
122
|
+
data=data,
|
|
123
|
+
timeout=10
|
|
124
|
+
) as response:
|
|
125
|
+
return await self._handle_response(response)
|
|
126
|
+
else:
|
|
127
|
+
# 普通请求
|
|
128
|
+
async with self.session.request(
|
|
129
|
+
method=method,
|
|
130
|
+
url=url,
|
|
131
|
+
headers=headers,
|
|
132
|
+
params=params,
|
|
133
|
+
json=body,
|
|
134
|
+
timeout=10
|
|
135
|
+
) as response:
|
|
136
|
+
return await self._handle_response(response)
|
|
137
|
+
except Exception as e:
|
|
138
|
+
SYLogger.error(f"nacos:服务调用失败: {str(e)}")
|
|
139
|
+
raise RuntimeError(f"Feign call failed: {str(e)}")
|
|
140
|
+
|
|
141
|
+
return wrapper
|
|
142
|
+
|
|
143
|
+
async def _handle_response(self, response):
|
|
144
|
+
# 处理响应
|
|
145
|
+
if 200 <= response.status < 300:
|
|
146
|
+
content_type = response.headers.get('Content-Type', '')
|
|
147
|
+
if 'application/json' in content_type:
|
|
148
|
+
return await response.json()
|
|
149
|
+
else:
|
|
150
|
+
return await response.read()
|
|
151
|
+
raise RuntimeError(
|
|
152
|
+
f"请求失败: {response.status} - {await response.text()}")
|
|
153
|
+
|
|
154
|
+
async def close(self):
|
|
155
|
+
"""关闭 aiohttp 会话"""
|
|
156
|
+
await self.session.close()
|
|
157
|
+
|
|
158
|
+
return FeignWrapper()
|
|
159
|
+
return decorator
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def feign_request(method: str, path: str, headers: dict = None):
|
|
163
|
+
# Feign请求方法装饰器
|
|
164
|
+
def decorator(func):
|
|
165
|
+
func._feign_meta = {
|
|
166
|
+
"method": method.upper(),
|
|
167
|
+
"path": path,
|
|
168
|
+
"headers": headers or {}
|
|
169
|
+
}
|
|
170
|
+
return func
|
|
171
|
+
return decorator
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def feign_upload(field_name: str = "file"):
|
|
175
|
+
# 文件上传装饰器
|
|
176
|
+
def decorator(func):
|
|
177
|
+
async def wrapper(*args, **kwargs):
|
|
178
|
+
file_path = kwargs.get('file_path')
|
|
179
|
+
if not file_path:
|
|
180
|
+
raise ValueError("file_path is required for upload")
|
|
181
|
+
|
|
182
|
+
with open(file_path, 'rb') as f:
|
|
183
|
+
files = {field_name: (os.path.basename(file_path), f.read())}
|
|
184
|
+
kwargs['files'] = files
|
|
185
|
+
return await func(*args, **kwargs)
|
|
186
|
+
return wrapper
|
|
187
|
+
return decorator
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
async def feign(service_name, api_path, method='GET', params=None, headers=None, file_path=None,
|
|
191
|
+
path_params=None, request=None, body=None, files=None, form_data=None):
|
|
192
|
+
"""
|
|
193
|
+
feign 函数,支持 form-data 表单上传文件和其他字段
|
|
194
|
+
|
|
195
|
+
参数:
|
|
196
|
+
files: 字典,用于上传文件,格式: {'field_name': (filename, file_content)}
|
|
197
|
+
form_data: 字典,用于上传表单字段
|
|
198
|
+
"""
|
|
199
|
+
file_stream = None
|
|
200
|
+
session = aiohttp.ClientSession()
|
|
201
|
+
try:
|
|
202
|
+
# 获取健康的服务实例
|
|
203
|
+
if not headers:
|
|
204
|
+
headers = {}
|
|
205
|
+
|
|
206
|
+
nacos_service = NacosService(None)
|
|
207
|
+
version = headers.get('s-y-version')
|
|
208
|
+
|
|
209
|
+
# 使用 discover_services 方法获取服务实例列表
|
|
210
|
+
instances = nacos_service.get_service_instances(
|
|
211
|
+
service_name, version=version)
|
|
212
|
+
if not instances:
|
|
213
|
+
SYLogger.error(f"nacos:未找到 {service_name} 的健康实例")
|
|
214
|
+
return None
|
|
215
|
+
|
|
216
|
+
# 简单轮询负载均衡
|
|
217
|
+
instance = instances[int(time.time()) % len(instances)]
|
|
218
|
+
|
|
219
|
+
SYLogger.info(f"nacos:开始调用服务: {service_name}")
|
|
220
|
+
SYLogger.info(
|
|
221
|
+
f"nacos:服务实例信息: IP={instance['ip']}, Port={instance['port']}")
|
|
222
|
+
|
|
223
|
+
if instance:
|
|
224
|
+
ip = instance.get('ip')
|
|
225
|
+
port = instance.get('port')
|
|
226
|
+
|
|
227
|
+
SYLogger.info(f"nacos:服务实例信息: IP={ip}, Port={port}")
|
|
228
|
+
|
|
229
|
+
# 处理 path 参数
|
|
230
|
+
if path_params:
|
|
231
|
+
for key, value in path_params.items():
|
|
232
|
+
api_path = api_path.replace(f"{{{key}}}", str(value))
|
|
233
|
+
|
|
234
|
+
url = f"http://{ip}:{port}{api_path}"
|
|
235
|
+
SYLogger.info(f"nacos:请求地址: {url}")
|
|
236
|
+
|
|
237
|
+
try:
|
|
238
|
+
# 处理文件上传
|
|
239
|
+
if files or form_data or file_path:
|
|
240
|
+
# 创建表单数据
|
|
241
|
+
data = aiohttp.FormData()
|
|
242
|
+
if form_data:
|
|
243
|
+
for key, value in form_data.items():
|
|
244
|
+
data.add_field(key, value)
|
|
245
|
+
if files:
|
|
246
|
+
for field_name, (filename, content) in files.items():
|
|
247
|
+
data.add_field(field_name, content,
|
|
248
|
+
filename=filename)
|
|
249
|
+
if file_path:
|
|
250
|
+
filename = os.path.basename(file_path)
|
|
251
|
+
with open(file_path, 'rb') as f:
|
|
252
|
+
data.add_field('file', f, filename=filename)
|
|
253
|
+
# 移除 Content-Type 头,让 aiohttp 自动设置 boundary
|
|
254
|
+
headers.pop('Content-Type', None)
|
|
255
|
+
# 发送表单数据
|
|
256
|
+
async with session.request(
|
|
257
|
+
method=method.upper(),
|
|
258
|
+
url=url,
|
|
259
|
+
headers=headers,
|
|
260
|
+
params=params,
|
|
261
|
+
data=data,
|
|
262
|
+
timeout=10
|
|
263
|
+
) as response:
|
|
264
|
+
return await _handle_feign_response(response)
|
|
265
|
+
else:
|
|
266
|
+
# 普通请求
|
|
267
|
+
if body and 'Content-Type' not in headers:
|
|
268
|
+
headers['Content-Type'] = 'application/json'
|
|
269
|
+
async with session.request(
|
|
270
|
+
method=method.upper(),
|
|
271
|
+
url=url,
|
|
272
|
+
headers=headers,
|
|
273
|
+
params=params,
|
|
274
|
+
json=body,
|
|
275
|
+
timeout=10
|
|
276
|
+
) as response:
|
|
277
|
+
return await _handle_feign_response(response)
|
|
278
|
+
except aiohttp.ClientError as e:
|
|
279
|
+
SYLogger.error(
|
|
280
|
+
f"nacos:请求服务接口时出错ClientError: {e}")
|
|
281
|
+
print(f"请求服务接口时出错: {e}")
|
|
282
|
+
return None
|
|
283
|
+
except Exception as e:
|
|
284
|
+
SYLogger.error(f"nacos:请求服务接口时出错: {e}")
|
|
285
|
+
print(f"nacos:请求服务接口时出错: {e}")
|
|
286
|
+
return None
|
|
287
|
+
finally:
|
|
288
|
+
SYLogger.info(f"nacos:结束调用服务: {service_name}, {api_path}")
|
|
289
|
+
await session.close()
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
async def _handle_feign_response(response):
|
|
293
|
+
"""处理Feign请求的响应"""
|
|
294
|
+
if response.status == 200:
|
|
295
|
+
content_type = response.headers.get('Content-Type')
|
|
296
|
+
if 'application/json' in content_type:
|
|
297
|
+
return await response.json()
|
|
298
|
+
else:
|
|
299
|
+
# 如果是文件流,将其封装成 io.BytesIO 对象返回
|
|
300
|
+
content = await response.read()
|
|
301
|
+
return io.BytesIO(content)
|
|
302
|
+
else:
|
|
303
|
+
print(
|
|
304
|
+
f"nacos:请求失败,状态码: {response.status},响应内容: {await response.text()}")
|
|
305
|
+
SYLogger.error(
|
|
306
|
+
f"nacos:请求服务接口时出错: {response.status}, 响应内容: {await response.text()}")
|
|
307
|
+
return None
|