huace-aigc-auth-client 1.1.29__py3-none-any.whl → 1.1.30__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.
@@ -178,4 +178,4 @@ __all__ = [
178
178
  # 用户上下文
179
179
  "get_current_user",
180
180
  ]
181
- __version__ = "1.1.29"
181
+ __version__ = "1.1.30"
@@ -92,6 +92,7 @@ class ApiStatsCollector:
92
92
 
93
93
  # 过滤重定向请求(3xx 状态码),这些通常是框架自动处理的,不应统计
94
94
  if 300 <= status_code < 400:
95
+ logger.debug(f"跳过重定向请求统计: {api_method} {api_path} 状态码: {status_code}")
95
96
  return
96
97
 
97
98
  try:
@@ -105,9 +106,11 @@ class ApiStatsCollector:
105
106
  'request_params': request_params
106
107
  }
107
108
  self.queue.put_nowait(stat_data)
108
- except queue.Full:
109
+ except queue.Full as e:
110
+ logger.debug(f"统计数据队列已满,丢弃当前数据: {e}")
109
111
  pass # 队列满了,丢弃数据
110
- except Exception:
112
+ except Exception as e:
113
+ logger.debug(f"收集接口统计数据失败: {e}")
111
114
  pass # 静默失败,不影响主流程
112
115
 
113
116
  def _worker(self):
@@ -135,7 +138,8 @@ class ApiStatsCollector:
135
138
  buffer = []
136
139
  last_flush_time = current_time
137
140
 
138
- except Exception:
141
+ except Exception as e:
142
+ logger.debug(f"统计数据提交失败: {e}")
139
143
  pass # 静默失败
140
144
 
141
145
  # 停止前刷新剩余数据
@@ -168,7 +172,8 @@ class ApiStatsCollector:
168
172
  for token, stats in token_groups.items():
169
173
  self._submit_stats(token, stats)
170
174
 
171
- except Exception:
175
+ except Exception as e:
176
+ logger.debug(f"统计数据提交失败: {e}")
172
177
  pass # 静默失败,不影响主流程
173
178
 
174
179
  def _submit_stats(self, token: str, stats: List[Dict[str, Any]]):
@@ -200,7 +205,8 @@ class ApiStatsCollector:
200
205
  if response.status_code != 200:
201
206
  pass
202
207
 
203
- except Exception:
208
+ except Exception as e:
209
+ logger.debug(f"统计数据提交失败: {e}")
204
210
  pass # 静默失败,不影响主流程
205
211
 
206
212
 
@@ -0,0 +1,316 @@
1
+ """
2
+ 认证请求封装模块
3
+ 提供对 requests.request 的封装,自动添加认证信息和统计上报
4
+ """
5
+ import time
6
+ import logging
7
+ from typing import Optional, Dict, Any, Union
8
+ import requests
9
+ from requests import Response
10
+
11
+ from .user_context import get_request_context
12
+ from .api_stats_collector import get_api_stats_collector
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ def auth_request(
18
+ method: str,
19
+ url: str,
20
+ params: Optional[Dict[str, Any]] = None,
21
+ data: Optional[Union[Dict[str, Any], str, bytes]] = None,
22
+ json: Optional[Dict[str, Any]] = None,
23
+ headers: Optional[Dict[str, str]] = None,
24
+ cookies: Optional[Dict[str, str]] = None,
25
+ files: Optional[Dict[str, Any]] = None,
26
+ auth: Optional[tuple] = None,
27
+ timeout: Optional[Union[float, tuple]] = None,
28
+ allow_redirects: bool = True,
29
+ proxies: Optional[Dict[str, str]] = None,
30
+ verify: Optional[Union[bool, str]] = None,
31
+ stream: bool = False,
32
+ cert: Optional[Union[str, tuple]] = None,
33
+ **kwargs
34
+ ) -> Response:
35
+ """
36
+ 认证请求封装函数
37
+
38
+ 基本参数与 requests.request 一致,会自动从 request_context 中获取认证信息并添加到请求头
39
+
40
+ Args:
41
+ method: HTTP 方法 (GET, POST, PUT, DELETE 等)
42
+ url: 请求 URL
43
+ params: URL 查询参数
44
+ data: 请求体数据 (form-data 或 raw)
45
+ json: JSON 请求体数据
46
+ headers: 请求头(会自动添加认证相关的头)
47
+ cookies: Cookies
48
+ files: 文件上传
49
+ auth: HTTP 认证元组
50
+ timeout: 超时时间(秒)
51
+ allow_redirects: 是否允许重定向
52
+ proxies: 代理配置
53
+ verify: SSL 证书验证
54
+ stream: 是否流式响应
55
+ cert: 客户端证书
56
+ **kwargs: 其他 requests.request 支持的参数
57
+
58
+ Returns:
59
+ Response: 响应对象
60
+
61
+ 自动添加的请求头(如果在 request_context 中存在):
62
+ - X-App-ID: 应用ID
63
+ - X-App-Secret: 应用密钥
64
+ - Authorization: Bearer token
65
+ - x-real-ip: 客户端真实IP
66
+ - user-agent: User Agent
67
+ - X-Trace-ID: 追踪ID
68
+ """
69
+ # 初始化 headers
70
+ if headers is None:
71
+ headers = {}
72
+ else:
73
+ headers = headers.copy() # 避免修改原始 headers
74
+
75
+ # 从 request_context 获取认证信息
76
+ request_context = get_request_context()
77
+
78
+ if request_context:
79
+ # 添加应用ID
80
+ app_id = request_context.get('app_id')
81
+ if app_id is not None:
82
+ headers['X-App-ID'] = str(app_id)
83
+
84
+ # 添加应用密钥
85
+ app_secret = request_context.get('app_secret')
86
+ if app_secret:
87
+ headers['X-App-Secret'] = app_secret
88
+
89
+ # 添加认证令牌
90
+ token = request_context.get('token')
91
+ if token:
92
+ headers['Authorization'] = f'Bearer {token}'
93
+
94
+ # 添加客户端IP
95
+ ip_address = request_context.get('ip_address')
96
+ if ip_address:
97
+ headers['x-real-ip'] = ip_address
98
+
99
+ # 添加 User Agent
100
+ user_agent = request_context.get('user_agent')
101
+ if user_agent:
102
+ headers['user-agent'] = user_agent
103
+
104
+ # 添加追踪ID
105
+ trace_id = request_context.get('trace_id')
106
+ if trace_id:
107
+ headers['X-Trace-ID'] = trace_id
108
+
109
+ # 记录开始时间
110
+ start_time = time.time()
111
+ response = None
112
+ error_message = None
113
+
114
+ try:
115
+ # 执行请求
116
+ logger.info(f"发起认证请求: {method} {url} with headers={headers} params={params} json={json} data={data}")
117
+ response = requests.request(
118
+ method=method,
119
+ url=url,
120
+ params=params,
121
+ data=data,
122
+ json=json,
123
+ headers=headers,
124
+ cookies=cookies,
125
+ files=files,
126
+ auth=auth,
127
+ timeout=timeout,
128
+ allow_redirects=allow_redirects,
129
+ proxies=proxies,
130
+ verify=verify,
131
+ stream=stream,
132
+ cert=cert,
133
+ **kwargs
134
+ )
135
+ logger.info(f"认证请求响应: {method} {url} 状态码: {response.status_code}")
136
+ return response
137
+
138
+ except Exception as e:
139
+ error_message = str(e)
140
+ logger.error(f"认证请求失败: {method} {url} 错误: {error_message}")
141
+ raise
142
+
143
+ finally:
144
+ # 计算响应时间
145
+ response_time = time.time() - start_time
146
+
147
+ # 上报统计信息
148
+ _report_stats(
149
+ url=url,
150
+ method=method,
151
+ response=response,
152
+ response_time=response_time,
153
+ error_message=error_message,
154
+ request_context=request_context,
155
+ headers=headers,
156
+ params=params,
157
+ json_data=json,
158
+ form_data=data
159
+ )
160
+
161
+
162
+ def _report_stats(
163
+ url: str,
164
+ method: str,
165
+ response: Optional[Response],
166
+ response_time: float,
167
+ error_message: Optional[str],
168
+ request_context: Optional[Dict[str, Any]],
169
+ headers: Optional[Dict[str, str]],
170
+ params: Optional[Dict[str, Any]],
171
+ json_data: Optional[Dict[str, Any]],
172
+ form_data: Optional[Union[Dict[str, Any], str, bytes]]
173
+ ):
174
+ """
175
+ 上报统计信息到远程服务
176
+
177
+ Args:
178
+ url: 请求 URL
179
+ method: HTTP 方法
180
+ response: 响应对象
181
+ response_time: 响应时间
182
+ error_message: 错误信息
183
+ request_context: 请求上下文
184
+ params: 查询参数
185
+ json_data: JSON 数据
186
+ form_data: 表单数据
187
+ """
188
+ try:
189
+ # 获取统计收集器
190
+ collector = get_api_stats_collector()
191
+ if not collector:
192
+ return
193
+
194
+ # 获取 token
195
+ token = request_context.get('token') if request_context else None
196
+ if not token:
197
+ return
198
+
199
+ # 解析 URL 获取路径
200
+ from urllib.parse import urlparse
201
+ parsed_url = urlparse(url)
202
+ api_path = parsed_url.path or '/'
203
+
204
+ # 获取状态码
205
+ status_code = response.status_code if response else 500
206
+
207
+ # 构建请求参数(与 api_stats_collector 格式一致)
208
+ request_params = {
209
+ 'query_params': params or {},
210
+ 'headers': headers
211
+ }
212
+
213
+ # 添加请求体数据
214
+ if json_data:
215
+ request_params['request_body'] = json_data
216
+ elif isinstance(form_data, dict):
217
+ request_params['form_params'] = form_data
218
+
219
+ # 收集统计
220
+ collector.collect(
221
+ api_path=api_path,
222
+ api_method=method.upper(),
223
+ status_code=status_code,
224
+ response_time=response_time,
225
+ token=token,
226
+ error_message=error_message,
227
+ request_params=request_params
228
+ )
229
+
230
+ except Exception as e:
231
+ # 静默失败,不影响主流程
232
+ logger.debug(f"统计上报失败: {e}")
233
+
234
+
235
+ # ============ 使用示例 ============
236
+ """
237
+ 使用示例:
238
+
239
+ 1. 基本使用(自动从 request_context 获取认证信息):
240
+
241
+ from huace_aigc_auth_client.auth_request import auth_request
242
+ from huace_aigc_auth_client.user_context import set_request_context
243
+
244
+ # 设置请求上下文
245
+ set_request_context(
246
+ app_id='your-app-id',
247
+ app_secret='your-app-secret',
248
+ token='user-access-token',
249
+ ip_address='192.168.1.1',
250
+ user_agent='Mozilla/5.0...',
251
+ trace_id='trace-123'
252
+ )
253
+
254
+ # 发起请求(会自动添加认证信息)
255
+ response = auth_request('GET', 'https://api.example.com/data')
256
+ print(response.json())
257
+
258
+
259
+ 2. 带参数的请求:
260
+
261
+ # GET 请求带查询参数
262
+ response = auth_request(
263
+ 'GET',
264
+ 'https://api.example.com/users',
265
+ params={'page': 1, 'size': 10}
266
+ )
267
+
268
+ # POST 请求带 JSON 数据
269
+ response = auth_request(
270
+ 'POST',
271
+ 'https://api.example.com/users',
272
+ json={'name': 'John', 'email': 'john@example.com'}
273
+ )
274
+
275
+ # POST 请求带表单数据
276
+ response = auth_request(
277
+ 'POST',
278
+ 'https://api.example.com/upload',
279
+ data={'field1': 'value1', 'field2': 'value2'}
280
+ )
281
+
282
+
283
+ 3. 覆盖默认 headers:
284
+
285
+ # 可以手动指定 headers,会与自动添加的 headers 合并
286
+ response = auth_request(
287
+ 'GET',
288
+ 'https://api.example.com/data',
289
+ headers={'Custom-Header': 'custom-value'}
290
+ )
291
+
292
+
293
+ 4. 与 API 统计收集器配合使用:
294
+
295
+ from huace_aigc_auth_client.api_stats_collector import init_api_stats_collector
296
+ from huace_aigc_auth_client.auth_request import auth_request
297
+ from huace_aigc_auth_client.user_context import set_request_context
298
+
299
+ # 初始化统计收集器
300
+ init_api_stats_collector(
301
+ api_url='http://auth.example.com/api/sdk',
302
+ app_id='your-app-id',
303
+ app_secret='your-app-secret',
304
+ enabled=True
305
+ )
306
+
307
+ # 设置请求上下文
308
+ set_request_context(
309
+ app_id='your-app-id',
310
+ app_secret='your-app-secret',
311
+ token='user-access-token'
312
+ )
313
+
314
+ # 发起请求(会自动上报统计)
315
+ response = auth_request('GET', 'https://api.example.com/data')
316
+ """
@@ -18,7 +18,7 @@ import dataclasses
18
18
  from functools import wraps
19
19
  from typing import Optional, List, Dict, Any, Callable, Tuple
20
20
  from dataclasses import dataclass
21
- from .user_context import set_current_user, clear_current_user
21
+ from .user_context import set_current_user, clear_current_user, add_to_request_context
22
22
 
23
23
  logger = logging.getLogger(__name__)
24
24
  def setLogger(log):
@@ -697,6 +697,21 @@ class AuthMiddleware:
697
697
  params["form_params"] = {"skipped_multipart": True}
698
698
  # 其他类型(如 application/octet-stream 等二进制流)直接跳过,不读取
699
699
 
700
+ # 添加 ip_address、user_agent 等常用字段到 request_context
701
+ # 提取 IP 地址(优先使用 x-real-ip)
702
+ headers = params['headers']
703
+ ip_address = headers.get('x-real-ip') or headers.get('X-Real-IP') or headers.get('x-forwarded-for') or headers.get('X-Forwarded-For', '')
704
+ if ip_address:
705
+ # x-forwarded-for 可能包含多个 IP,取第一个
706
+ ip_address = ip_address.split(',')[0].strip()
707
+ # 提取 User-Agent
708
+ user_agent = headers.get('user-agent') or headers.get('User-Agent')
709
+
710
+ add_to_request_context(
711
+ ip_address=ip_address,
712
+ user_agent=user_agent
713
+ )
714
+
700
715
  return params
701
716
  except Exception as e:
702
717
  logger.warning(f"收集FastAPI请求参数失败: {e}")
@@ -743,11 +758,16 @@ class AuthMiddleware:
743
758
 
744
759
  def _extract_token(self, authorization: str) -> Optional[str]:
745
760
  """从 Authorization header 提取 token"""
761
+ token = None
746
762
  if not authorization:
747
- return None
748
- if not authorization.startswith("Bearer "):
749
- return None
750
- return authorization[7:]
763
+ token = None
764
+ elif not authorization.startswith("Bearer "):
765
+ token = None
766
+ else:
767
+ token = authorization[7:]
768
+ # 添加鉴权应用相关的信息
769
+ add_to_request_context(token=token, app_id=self.client.app_id, app_secret=self.client.app_secret)
770
+ return token
751
771
 
752
772
  def get_fastapi_middleware_class(self, user_info_callback: Callable = None):
753
773
  """
@@ -841,15 +861,15 @@ class AuthMiddleware:
841
861
 
842
862
  path = request.url.path
843
863
  start_time = time.time()
864
+
865
+ # 获取 Authorization header
866
+ authorization = request.headers.get("Authorization")
867
+ token = self._extract_token(authorization)
844
868
 
845
869
  # 检查是否跳过
846
870
  if self._should_skip(path):
847
871
  return await call_next(request)
848
872
 
849
- # 获取 Authorization header
850
- authorization = request.headers.get("Authorization")
851
- token = self._extract_token(authorization)
852
-
853
873
  if not token:
854
874
  logger.warning("AuthMiddleware未提供认证信息")
855
875
  response_time = time.time() - start_time
@@ -915,15 +935,15 @@ class AuthMiddleware:
915
935
 
916
936
  # 收集请求参数
917
937
  g.request_params = self._collect_flask_request_params(request) if self.enable_stats else None
938
+
939
+ # 获取 Authorization header
940
+ authorization = request.headers.get("Authorization")
941
+ token = self._extract_token(authorization)
918
942
 
919
943
  # 检查是否跳过
920
944
  if self._should_skip(path):
921
945
  return None
922
946
 
923
- # 获取 Authorization header
924
- authorization = request.headers.get("Authorization")
925
- token = self._extract_token(authorization)
926
-
927
947
  if not token:
928
948
  logger.warning("AuthMiddleware未提供认证信息")
929
949
  response_time = time.time() - g.start_time
@@ -102,11 +102,20 @@ def set_request_context(**kwargs):
102
102
  - user_agent: User Agent
103
103
  - request_id: 请求ID
104
104
  - trace_id: 追踪ID
105
+ - token: 访问令牌
106
+ - app_id: 应用ID
107
+ - app_secret: 应用密钥
108
+ - 其他自定义字段
105
109
  """
106
110
  # 同时设置两个上下文
107
111
  _local.request_context = kwargs
108
112
  _async_request_context.set(kwargs)
109
-
113
+
114
+ def add_to_request_context(**kwargs):
115
+ """向请求上下文添加信息"""
116
+ ctx = get_request_context() or {}
117
+ ctx.update(kwargs)
118
+ set_request_context(**ctx)
110
119
 
111
120
  def get_request_context() -> Optional[Dict[str, Any]]:
112
121
  """获取请求上下文信息"""
@@ -117,19 +126,46 @@ def get_request_context() -> Optional[Dict[str, Any]]:
117
126
 
118
127
  return getattr(_local, 'request_context', None)
119
128
 
120
-
121
129
  def get_client_ip() -> Optional[str]:
122
130
  """获取客户端IP"""
123
131
  ctx = get_request_context()
124
132
  return ctx.get('ip_address') if ctx else None
125
133
 
134
+ def get_user_agent() -> Optional[str]:
135
+ """获取 User Agent"""
136
+ ctx = get_request_context()
137
+ return ctx.get('user_agent') if ctx else None
138
+
139
+ def get_request_id() -> Optional[str]:
140
+ """获取请求ID"""
141
+ ctx = get_request_context()
142
+ return ctx.get('request_id') if ctx else None
143
+
144
+ def get_trace_id() -> Optional[str]:
145
+ """获取追踪ID"""
146
+ ctx = get_request_context()
147
+ return ctx.get('trace_id') if ctx else None
148
+
149
+ def get_request_token() -> Optional[str]:
150
+ """获取请求令牌"""
151
+ ctx = get_request_context()
152
+ return ctx.get('token') if ctx else None
153
+
154
+ def get_request_app_id() -> Optional[int]:
155
+ """获取请求应用ID"""
156
+ ctx = get_request_context()
157
+ return ctx.get('app_id') if ctx else None
158
+
159
+ def get_request_app_secret() -> Optional[str]:
160
+ """获取请求应用密钥"""
161
+ ctx = get_request_context()
162
+ return ctx.get('app_secret') if ctx else None
126
163
 
127
164
  def clear_request_context():
128
165
  """清理请求上下文"""
129
166
  if hasattr(_local, 'request_context'):
130
167
  delattr(_local, 'request_context')
131
168
 
132
-
133
169
  # ============ 使用示例 ============
134
170
  """
135
171
  使用示例:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: huace-aigc-auth-client
3
- Version: 1.1.29
3
+ Version: 1.1.30
4
4
  Summary: 华策AIGC Auth Client - 提供 Token 验证、用户信息获取、权限检查、旧系统接入等功能
5
5
  Author-email: Huace <support@huace.com>
6
6
  License: MIT
@@ -0,0 +1,13 @@
1
+ huace_aigc_auth_client/__init__.py,sha256=9wZM_9CM2SukN3pw2JpiXe-oVqXh0FD3h0y4S8ueckQ,4857
2
+ huace_aigc_auth_client/api_stats_collector.py,sha256=ADpjpHXMqn80YI4UltWHbzAO_szykU9ZCvwXgBRWFIM,11046
3
+ huace_aigc_auth_client/auth_request.py,sha256=f7KgmyamoEk1HWP-47nCxUBnOgRbQjYnnNZ7yRzb4j0,9285
4
+ huace_aigc_auth_client/legacy_adapter.py,sha256=TVCBAKejE2z2HQFsEwDW8LMiaIkXNfz3Mxv6_E-UJFY,24102
5
+ huace_aigc_auth_client/sdk.py,sha256=rproo913OAi37wz_rMYgxzP3F1YyY3nc5e35JS5WvoY,37751
6
+ huace_aigc_auth_client/user_context.py,sha256=IqdX6Xd2jJwvij6Hc2qWAFWj5pn3wHqk0RBsaXKLP8g,6795
7
+ huace_aigc_auth_client/webhook.py,sha256=XQZYEbMoqIdqZWCGSTcedeDKJpDbUVSq5g08g-6Qucg,4124
8
+ huace_aigc_auth_client/webhook_flask.py,sha256=Iosu4dBtRhQZM_ytn-bn82MpVsyOiV28FBnt7Tfh31U,7225
9
+ huace_aigc_auth_client-1.1.30.dist-info/licenses/LICENSE,sha256=z7dgC7KljhBLNvKjN15391nMj3aLt0gbud8-Yf1F8EQ,1063
10
+ huace_aigc_auth_client-1.1.30.dist-info/METADATA,sha256=3rtqM-PxSFyXEc0l_UW2gy6R5rN-J81VpKfuMgVNfYI,23629
11
+ huace_aigc_auth_client-1.1.30.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
12
+ huace_aigc_auth_client-1.1.30.dist-info/top_level.txt,sha256=kbv0nQ6PQ0JVneWPH7O2AbtlJnP7AjvFJ6JjM6ZEBxo,23
13
+ huace_aigc_auth_client-1.1.30.dist-info/RECORD,,
@@ -1,12 +0,0 @@
1
- huace_aigc_auth_client/__init__.py,sha256=P062wMHHDRDU9vEvsYxGoWTq8BWX5tmoAriZ8L1a6TA,4857
2
- huace_aigc_auth_client/api_stats_collector.py,sha256=AGbqRJyHvYwtchGq6BYe4Q9xwVU-xUNOMffnmLnleh8,10588
3
- huace_aigc_auth_client/legacy_adapter.py,sha256=TVCBAKejE2z2HQFsEwDW8LMiaIkXNfz3Mxv6_E-UJFY,24102
4
- huace_aigc_auth_client/sdk.py,sha256=49UrQnS96a_m-FM9KVSqmE8x4J-R4qpYsE4KgzasvFE,36742
5
- huace_aigc_auth_client/user_context.py,sha256=KzevYLsLv1hv8rlvRw83FT-HugeoBJSJ1Pi56iLWyTE,5592
6
- huace_aigc_auth_client/webhook.py,sha256=XQZYEbMoqIdqZWCGSTcedeDKJpDbUVSq5g08g-6Qucg,4124
7
- huace_aigc_auth_client/webhook_flask.py,sha256=Iosu4dBtRhQZM_ytn-bn82MpVsyOiV28FBnt7Tfh31U,7225
8
- huace_aigc_auth_client-1.1.29.dist-info/licenses/LICENSE,sha256=z7dgC7KljhBLNvKjN15391nMj3aLt0gbud8-Yf1F8EQ,1063
9
- huace_aigc_auth_client-1.1.29.dist-info/METADATA,sha256=vA68_R9XfMBn3LTPABOpiIeYXXMHe0ArtmzLsupXm90,23629
10
- huace_aigc_auth_client-1.1.29.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
11
- huace_aigc_auth_client-1.1.29.dist-info/top_level.txt,sha256=kbv0nQ6PQ0JVneWPH7O2AbtlJnP7AjvFJ6JjM6ZEBxo,23
12
- huace_aigc_auth_client-1.1.29.dist-info/RECORD,,