huace-aigc-auth-client 1.1.13__py3-none-any.whl → 1.1.20__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.
- huace_aigc_auth_client/__init__.py +5 -1
- huace_aigc_auth_client/api_stats_collector.py +300 -0
- huace_aigc_auth_client/legacy_adapter.py +12 -5
- huace_aigc_auth_client/sdk.py +216 -5
- huace_aigc_auth_client/user_context.py +189 -0
- {huace_aigc_auth_client-1.1.13.dist-info → huace_aigc_auth_client-1.1.20.dist-info}/METADATA +30 -5
- huace_aigc_auth_client-1.1.20.dist-info/RECORD +12 -0
- {huace_aigc_auth_client-1.1.13.dist-info → huace_aigc_auth_client-1.1.20.dist-info}/WHEEL +1 -1
- huace_aigc_auth_client-1.1.13.dist-info/RECORD +0 -10
- {huace_aigc_auth_client-1.1.13.dist-info → huace_aigc_auth_client-1.1.20.dist-info}/licenses/LICENSE +0 -0
- {huace_aigc_auth_client-1.1.13.dist-info → huace_aigc_auth_client-1.1.20.dist-info}/top_level.txt +0 -0
|
@@ -54,6 +54,8 @@ from .sdk import (
|
|
|
54
54
|
create_fastapi_auth_dependency
|
|
55
55
|
)
|
|
56
56
|
|
|
57
|
+
from .user_context import get_current_user
|
|
58
|
+
|
|
57
59
|
from .legacy_adapter import (
|
|
58
60
|
LegacySystemAdapter,
|
|
59
61
|
LegacyUserData,
|
|
@@ -167,5 +169,7 @@ __all__ = [
|
|
|
167
169
|
"register_flask_webhook_routes",
|
|
168
170
|
# Logger 设置
|
|
169
171
|
"setLogger",
|
|
172
|
+
# 用户上下文
|
|
173
|
+
"get_current_user",
|
|
170
174
|
]
|
|
171
|
-
__version__ = "1.1.
|
|
175
|
+
__version__ = "1.1.20"
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SDK 客户端接口监控模块
|
|
3
|
+
提供异步队列提交接口统计数据到服务端
|
|
4
|
+
"""
|
|
5
|
+
import time
|
|
6
|
+
import queue
|
|
7
|
+
import threading
|
|
8
|
+
import requests
|
|
9
|
+
from typing import Optional, Dict, Any, List
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ApiStatsCollector:
|
|
14
|
+
"""接口统计收集器(异步队列方式)"""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
api_url: str,
|
|
19
|
+
app_secret: str,
|
|
20
|
+
token: str,
|
|
21
|
+
batch_size: int = 10,
|
|
22
|
+
flush_interval: float = 5.0,
|
|
23
|
+
enabled: bool = True
|
|
24
|
+
):
|
|
25
|
+
"""
|
|
26
|
+
初始化统计收集器
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
api_url: 统计接口 URL(如:http://auth.example.com/api/sdk/stats/report/batch)
|
|
30
|
+
app_secret: 应用密钥
|
|
31
|
+
token: 用户访问令牌
|
|
32
|
+
batch_size: 批量提交大小
|
|
33
|
+
flush_interval: 刷新间隔(秒)
|
|
34
|
+
enabled: 是否启用
|
|
35
|
+
"""
|
|
36
|
+
self.api_url = api_url.rstrip('/')
|
|
37
|
+
self.app_secret = app_secret
|
|
38
|
+
self.token = token
|
|
39
|
+
self.batch_size = batch_size
|
|
40
|
+
self.flush_interval = flush_interval
|
|
41
|
+
self.enabled = enabled
|
|
42
|
+
|
|
43
|
+
self.queue = queue.Queue()
|
|
44
|
+
self.running = True
|
|
45
|
+
self.worker_thread = None
|
|
46
|
+
|
|
47
|
+
if self.enabled:
|
|
48
|
+
self.start()
|
|
49
|
+
|
|
50
|
+
def start(self):
|
|
51
|
+
"""启动工作线程"""
|
|
52
|
+
if self.worker_thread is None or not self.worker_thread.is_alive():
|
|
53
|
+
self.running = True
|
|
54
|
+
self.worker_thread = threading.Thread(target=self._worker, daemon=True)
|
|
55
|
+
self.worker_thread.start()
|
|
56
|
+
|
|
57
|
+
def stop(self):
|
|
58
|
+
"""停止工作线程"""
|
|
59
|
+
self.running = False
|
|
60
|
+
if self.worker_thread:
|
|
61
|
+
self.worker_thread.join(timeout=10)
|
|
62
|
+
|
|
63
|
+
def collect(
|
|
64
|
+
self,
|
|
65
|
+
api_path: str,
|
|
66
|
+
api_method: str,
|
|
67
|
+
status_code: int,
|
|
68
|
+
response_time: float,
|
|
69
|
+
error_message: Optional[str] = None,
|
|
70
|
+
request_params: Optional[Dict[str, Any]] = None
|
|
71
|
+
):
|
|
72
|
+
"""
|
|
73
|
+
收集接口统计数据
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
api_path: 接口路径
|
|
77
|
+
api_method: 请求方法
|
|
78
|
+
status_code: 状态码
|
|
79
|
+
response_time: 响应时间(秒)
|
|
80
|
+
error_message: 错误信息
|
|
81
|
+
request_params: 请求参数(包含 headers, query_params, view_params, request_body, form_params)
|
|
82
|
+
"""
|
|
83
|
+
if not self.enabled:
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
stat_data = {
|
|
88
|
+
'api_path': api_path,
|
|
89
|
+
'api_method': api_method,
|
|
90
|
+
'status_code': status_code,
|
|
91
|
+
'response_time': response_time,
|
|
92
|
+
'error_message': error_message,
|
|
93
|
+
'request_params': request_params,
|
|
94
|
+
'timestamp': datetime.utcnow().isoformat()
|
|
95
|
+
}
|
|
96
|
+
self.queue.put_nowait(stat_data)
|
|
97
|
+
except queue.Full:
|
|
98
|
+
pass # 队列满了,丢弃数据
|
|
99
|
+
except Exception:
|
|
100
|
+
pass # 静默失败,不影响主流程
|
|
101
|
+
|
|
102
|
+
def _worker(self):
|
|
103
|
+
"""后台工作线程:批量提交统计数据"""
|
|
104
|
+
buffer = []
|
|
105
|
+
last_flush_time = time.time()
|
|
106
|
+
|
|
107
|
+
while self.running:
|
|
108
|
+
try:
|
|
109
|
+
# 尝试从队列获取数据
|
|
110
|
+
try:
|
|
111
|
+
stat_data = self.queue.get(timeout=1.0)
|
|
112
|
+
buffer.append(stat_data)
|
|
113
|
+
except queue.Empty:
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
current_time = time.time()
|
|
117
|
+
should_flush = (
|
|
118
|
+
len(buffer) >= self.batch_size or
|
|
119
|
+
(buffer and (current_time - last_flush_time) >= self.flush_interval)
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if should_flush:
|
|
123
|
+
self._flush_buffer(buffer)
|
|
124
|
+
buffer = []
|
|
125
|
+
last_flush_time = current_time
|
|
126
|
+
|
|
127
|
+
except Exception:
|
|
128
|
+
pass # 静默失败
|
|
129
|
+
|
|
130
|
+
# 停止前刷新剩余数据
|
|
131
|
+
if buffer:
|
|
132
|
+
self._flush_buffer(buffer)
|
|
133
|
+
|
|
134
|
+
def _flush_buffer(self, buffer: List[Dict[str, Any]]):
|
|
135
|
+
"""刷新缓冲区:批量提交统计数据"""
|
|
136
|
+
if not buffer:
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
headers = {
|
|
141
|
+
'X-App-Secret': self.app_secret,
|
|
142
|
+
'Authorization': f'Bearer {self.token}',
|
|
143
|
+
'Content-Type': 'application/json'
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
payload = {'stats': buffer}
|
|
147
|
+
|
|
148
|
+
response = requests.post(
|
|
149
|
+
f'{self.api_url}/stats/report/batch',
|
|
150
|
+
json=payload,
|
|
151
|
+
headers=headers,
|
|
152
|
+
timeout=5
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# 静默失败,不抛出异常
|
|
156
|
+
if response.status_code != 200:
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
except Exception:
|
|
160
|
+
pass # 静默失败,不影响主流程
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# ============ 全局实例 ============
|
|
164
|
+
|
|
165
|
+
_global_collector: Optional[ApiStatsCollector] = None
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def init_api_stats_collector(
|
|
169
|
+
api_url: str,
|
|
170
|
+
app_secret: str,
|
|
171
|
+
token: str,
|
|
172
|
+
batch_size: int = 10,
|
|
173
|
+
flush_interval: float = 5.0,
|
|
174
|
+
enabled: bool = True
|
|
175
|
+
) -> ApiStatsCollector:
|
|
176
|
+
"""
|
|
177
|
+
初始化全局统计收集器
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
api_url: 统计接口 URL
|
|
181
|
+
app_secret: 应用密钥
|
|
182
|
+
token: 用户访问令牌
|
|
183
|
+
batch_size: 批量提交大小
|
|
184
|
+
flush_interval: 刷新间隔(秒)
|
|
185
|
+
enabled: 是否启用
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
统计收集器实例
|
|
189
|
+
"""
|
|
190
|
+
global _global_collector
|
|
191
|
+
_global_collector = ApiStatsCollector(
|
|
192
|
+
api_url=api_url,
|
|
193
|
+
app_secret=app_secret,
|
|
194
|
+
token=token,
|
|
195
|
+
batch_size=batch_size,
|
|
196
|
+
flush_interval=flush_interval,
|
|
197
|
+
enabled=enabled
|
|
198
|
+
)
|
|
199
|
+
return _global_collector
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def get_api_stats_collector() -> Optional[ApiStatsCollector]:
|
|
203
|
+
"""获取全局统计收集器实例"""
|
|
204
|
+
return _global_collector
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def stop_api_stats_collector():
|
|
208
|
+
"""停止全局统计收集器"""
|
|
209
|
+
global _global_collector
|
|
210
|
+
if _global_collector:
|
|
211
|
+
_global_collector.stop()
|
|
212
|
+
_global_collector = None
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def collect_api_stat(
|
|
216
|
+
api_path: str,
|
|
217
|
+
api_method: str,
|
|
218
|
+
status_code: int,
|
|
219
|
+
response_time: float,
|
|
220
|
+
error_message: Optional[str] = None,
|
|
221
|
+
request_params: Optional[Dict[str, Any]] = None
|
|
222
|
+
):
|
|
223
|
+
"""
|
|
224
|
+
快捷方法:收集接口统计数据
|
|
225
|
+
|
|
226
|
+
使用全局收集器实例
|
|
227
|
+
"""
|
|
228
|
+
collector = get_api_stats_collector()
|
|
229
|
+
if collector:
|
|
230
|
+
collector.collect(
|
|
231
|
+
api_path=api_path,
|
|
232
|
+
api_method=api_method,
|
|
233
|
+
status_code=status_code,
|
|
234
|
+
response_time=response_time,
|
|
235
|
+
error_message=error_message,
|
|
236
|
+
request_params=request_params
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
# ============ 使用示例 ============
|
|
241
|
+
"""
|
|
242
|
+
使用示例:
|
|
243
|
+
|
|
244
|
+
1. 应用启动时初始化:
|
|
245
|
+
|
|
246
|
+
from huace_aigc_auth_client.api_stats_collector import init_api_stats_collector
|
|
247
|
+
|
|
248
|
+
# 在应用启动时初始化
|
|
249
|
+
init_api_stats_collector(
|
|
250
|
+
api_url='http://auth.example.com/api/sdk',
|
|
251
|
+
app_secret='your-app-secret',
|
|
252
|
+
token='user-access-token',
|
|
253
|
+
batch_size=10,
|
|
254
|
+
flush_interval=5.0,
|
|
255
|
+
enabled=True
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
2. 在拦截器中使用:
|
|
260
|
+
|
|
261
|
+
from huace_aigc_auth_client.api_stats_collector import collect_api_stat
|
|
262
|
+
import time
|
|
263
|
+
|
|
264
|
+
@app.middleware("http")
|
|
265
|
+
async def monitor_middleware(request: Request, call_next):
|
|
266
|
+
start_time = time.time()
|
|
267
|
+
|
|
268
|
+
try:
|
|
269
|
+
response = await call_next(request)
|
|
270
|
+
response_time = time.time() - start_time
|
|
271
|
+
|
|
272
|
+
# 收集统计
|
|
273
|
+
collect_api_stat(
|
|
274
|
+
api_path=request.url.path,
|
|
275
|
+
api_method=request.method,
|
|
276
|
+
status_code=response.status_code,
|
|
277
|
+
response_time=response_time
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
return response
|
|
281
|
+
except Exception as e:
|
|
282
|
+
response_time = time.time() - start_time
|
|
283
|
+
collect_api_stat(
|
|
284
|
+
api_path=request.url.path,
|
|
285
|
+
api_method=request.method,
|
|
286
|
+
status_code=500,
|
|
287
|
+
response_time=response_time,
|
|
288
|
+
error_message=str(e)
|
|
289
|
+
)
|
|
290
|
+
raise
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
3. 应用关闭时停止:
|
|
294
|
+
|
|
295
|
+
from huace_aigc_auth_client.api_stats_collector import stop_api_stats_collector
|
|
296
|
+
|
|
297
|
+
@app.on_event("shutdown")
|
|
298
|
+
async def shutdown_event():
|
|
299
|
+
stop_api_stats_collector()
|
|
300
|
+
"""
|
|
@@ -246,7 +246,7 @@ class LegacySystemAdapter(ABC):
|
|
|
246
246
|
else:
|
|
247
247
|
auth_data["password"] = password
|
|
248
248
|
|
|
249
|
-
result =
|
|
249
|
+
result = self.auth_client.sync_user_to_auth(auth_data)
|
|
250
250
|
logger.info(f"Sync result for user {legacy_user.get('username')}: {result}")
|
|
251
251
|
|
|
252
252
|
return result
|
|
@@ -276,7 +276,13 @@ class LegacySystemAdapter(ABC):
|
|
|
276
276
|
|
|
277
277
|
for user in users:
|
|
278
278
|
try:
|
|
279
|
-
|
|
279
|
+
# 将 dict 转换为 LegacyUserData 对象
|
|
280
|
+
if isinstance(user, dict):
|
|
281
|
+
legacy_user = LegacyUserData(data=user)
|
|
282
|
+
else:
|
|
283
|
+
legacy_user = user
|
|
284
|
+
|
|
285
|
+
result = await self.sync_user_to_auth(legacy_user)
|
|
280
286
|
logger.info(f"Sync result for user {user.get('username')}: {result}")
|
|
281
287
|
|
|
282
288
|
if result.get("success"):
|
|
@@ -291,7 +297,8 @@ class LegacySystemAdapter(ABC):
|
|
|
291
297
|
"error": result.get("message")
|
|
292
298
|
})
|
|
293
299
|
except Exception as e:
|
|
294
|
-
|
|
300
|
+
import traceback
|
|
301
|
+
logger.error(f"Error syncing user {user.get('username')}: {e}\n{traceback.format_exc()}")
|
|
295
302
|
results["failed"] += 1
|
|
296
303
|
results["errors"].append({
|
|
297
304
|
"user": user.get("username"),
|
|
@@ -331,7 +338,7 @@ class LegacySystemAdapter(ABC):
|
|
|
331
338
|
|
|
332
339
|
# 创建或更新用户
|
|
333
340
|
logger.info(f"Handling {event} event for user: {legacy_data}")
|
|
334
|
-
result = await self.upsert_user_async(legacy_data)
|
|
341
|
+
result = await self.upsert_user_async(legacy_data, data)
|
|
335
342
|
|
|
336
343
|
return {
|
|
337
344
|
"success": True,
|
|
@@ -561,7 +568,7 @@ class UserSyncService:
|
|
|
561
568
|
legacy_data["password"] = password
|
|
562
569
|
|
|
563
570
|
# 使用 upsert 方法(存在则更新,不存在则创建)
|
|
564
|
-
result = await self.adapter.upsert_user_async(legacy_data)
|
|
571
|
+
result = await self.adapter.upsert_user_async(legacy_data, auth_data)
|
|
565
572
|
|
|
566
573
|
return {
|
|
567
574
|
"success": True,
|
huace_aigc_auth_client/sdk.py
CHANGED
|
@@ -14,9 +14,11 @@ import time
|
|
|
14
14
|
import hashlib
|
|
15
15
|
import requests
|
|
16
16
|
import logging
|
|
17
|
+
import dataclasses
|
|
17
18
|
from functools import wraps
|
|
18
19
|
from typing import Optional, List, Dict, Any, Callable, Tuple
|
|
19
20
|
from dataclasses import dataclass
|
|
21
|
+
from .user_context import set_current_user, clear_current_user
|
|
20
22
|
|
|
21
23
|
logger = logging.getLogger(__name__)
|
|
22
24
|
def setLogger(log):
|
|
@@ -95,7 +97,7 @@ class AigcAuthClient:
|
|
|
95
97
|
client = AigcAuthClient(
|
|
96
98
|
app_id="your_app_id",
|
|
97
99
|
app_secret="your_app_secret",
|
|
98
|
-
base_url="
|
|
100
|
+
base_url="后端 API 鉴权地址"
|
|
99
101
|
)
|
|
100
102
|
|
|
101
103
|
# 验证 token
|
|
@@ -130,8 +132,7 @@ class AigcAuthClient:
|
|
|
130
132
|
self.app_secret = app_secret or os.getenv("AIGC_AUTH_APP_SECRET")
|
|
131
133
|
self.base_url = (
|
|
132
134
|
base_url or
|
|
133
|
-
os.getenv("AIGC_AUTH_BASE_URL")
|
|
134
|
-
"https://aigc-auth.huacemedia.com/aigc-auth/api/v1"
|
|
135
|
+
os.getenv("AIGC_AUTH_BASE_URL")
|
|
135
136
|
)
|
|
136
137
|
self.timeout = timeout
|
|
137
138
|
self.cache_ttl = cache_ttl
|
|
@@ -551,7 +552,9 @@ class AuthMiddleware:
|
|
|
551
552
|
self,
|
|
552
553
|
client: AigcAuthClient,
|
|
553
554
|
exclude_paths: List[str] = None,
|
|
554
|
-
exclude_prefixes: List[str] = None
|
|
555
|
+
exclude_prefixes: List[str] = None,
|
|
556
|
+
enable_stats: bool = True,
|
|
557
|
+
stats_api_url: Optional[str] = None
|
|
555
558
|
):
|
|
556
559
|
"""
|
|
557
560
|
初始化中间件
|
|
@@ -560,10 +563,150 @@ class AuthMiddleware:
|
|
|
560
563
|
client: AigcAuthClient 实例
|
|
561
564
|
exclude_paths: 排除的路径列表(精确匹配)
|
|
562
565
|
exclude_prefixes: 排除的路径前缀列表
|
|
566
|
+
enable_stats: 是否启用接口统计(默认启用)
|
|
567
|
+
stats_api_url: 统计接口 URL(可选,默认使用 client.base_url/sdk)
|
|
563
568
|
"""
|
|
564
569
|
self.client = client
|
|
565
570
|
self.exclude_paths = exclude_paths or []
|
|
566
571
|
self.exclude_prefixes = exclude_prefixes or []
|
|
572
|
+
self.enable_stats = enable_stats
|
|
573
|
+
self.stats_collector = None
|
|
574
|
+
|
|
575
|
+
# 如果启用统计,设置统计接口 URL(默认使用 client 的 base_url)
|
|
576
|
+
if self.enable_stats:
|
|
577
|
+
self.stats_api_url = stats_api_url or f"{self.client.base_url}/sdk"
|
|
578
|
+
|
|
579
|
+
def _init_stats_collector(self, token: str):
|
|
580
|
+
"""初始化统计收集器(延迟初始化)"""
|
|
581
|
+
if not self.enable_stats or self.stats_collector is not None:
|
|
582
|
+
return
|
|
583
|
+
|
|
584
|
+
try:
|
|
585
|
+
from .api_stats_collector import init_api_stats_collector
|
|
586
|
+
self.stats_collector = init_api_stats_collector(
|
|
587
|
+
api_url=self.stats_api_url,
|
|
588
|
+
app_secret=self.client.app_secret,
|
|
589
|
+
token=token,
|
|
590
|
+
batch_size=10,
|
|
591
|
+
flush_interval=5.0,
|
|
592
|
+
enabled=True
|
|
593
|
+
)
|
|
594
|
+
except Exception as e:
|
|
595
|
+
logger.warning(f"初始化统计收集器失败: {e}")
|
|
596
|
+
|
|
597
|
+
@staticmethod
|
|
598
|
+
def _collect_flask_request_params(request) -> Dict[str, Any]:
|
|
599
|
+
"""
|
|
600
|
+
收集 Flask 请求的所有参数
|
|
601
|
+
|
|
602
|
+
Args:
|
|
603
|
+
request: Flask request 对象
|
|
604
|
+
|
|
605
|
+
Returns:
|
|
606
|
+
包含 headers, query_params, view_params, request_body, form_params 的字典
|
|
607
|
+
"""
|
|
608
|
+
try:
|
|
609
|
+
params = {
|
|
610
|
+
"headers": dict(request.headers),
|
|
611
|
+
"query_params": request.args.to_dict(flat=False),
|
|
612
|
+
"view_params": request.view_args or {},
|
|
613
|
+
"request_body": None,
|
|
614
|
+
"form_params": None
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
# 获取请求体(JSON 或文本)
|
|
618
|
+
if request.is_json:
|
|
619
|
+
try:
|
|
620
|
+
params["request_body"] = request.get_json(silent=True)
|
|
621
|
+
except Exception:
|
|
622
|
+
pass
|
|
623
|
+
elif request.data:
|
|
624
|
+
try:
|
|
625
|
+
params["request_body"] = request.data.decode('utf-8')
|
|
626
|
+
except Exception:
|
|
627
|
+
params["request_body"] = str(request.data)
|
|
628
|
+
|
|
629
|
+
# 获取表单数据
|
|
630
|
+
if request.form:
|
|
631
|
+
params["form_params"] = request.form.to_dict(flat=False)
|
|
632
|
+
|
|
633
|
+
return params
|
|
634
|
+
except Exception as e:
|
|
635
|
+
logger.warning(f"收集Flask请求参数失败: {e}")
|
|
636
|
+
return {}
|
|
637
|
+
|
|
638
|
+
@staticmethod
|
|
639
|
+
async def _collect_fastapi_request_params(request) -> Dict[str, Any]:
|
|
640
|
+
"""
|
|
641
|
+
收集 FastAPI 请求的所有参数
|
|
642
|
+
|
|
643
|
+
Args:
|
|
644
|
+
request: FastAPI Request 对象
|
|
645
|
+
|
|
646
|
+
Returns:
|
|
647
|
+
包含 headers, query_params, view_params, request_body, form_params 的字典
|
|
648
|
+
"""
|
|
649
|
+
try:
|
|
650
|
+
params = {
|
|
651
|
+
"headers": dict(request.headers),
|
|
652
|
+
"query_params": dict(request.query_params),
|
|
653
|
+
"view_params": dict(request.path_params),
|
|
654
|
+
"request_body": None,
|
|
655
|
+
"form_params": None
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
# 获取请求体
|
|
659
|
+
content_type = request.headers.get("content-type", "")
|
|
660
|
+
|
|
661
|
+
if "application/json" in content_type:
|
|
662
|
+
try:
|
|
663
|
+
params["request_body"] = await request.json()
|
|
664
|
+
except Exception:
|
|
665
|
+
pass
|
|
666
|
+
elif "application/x-www-form-urlencoded" in content_type or "multipart/form-data" in content_type:
|
|
667
|
+
try:
|
|
668
|
+
form = await request.form()
|
|
669
|
+
params["form_params"] = {k: v for k, v in form.items()}
|
|
670
|
+
except Exception:
|
|
671
|
+
pass
|
|
672
|
+
else:
|
|
673
|
+
# 尝试读取原始body
|
|
674
|
+
try:
|
|
675
|
+
body = await request.body()
|
|
676
|
+
if body:
|
|
677
|
+
params["request_body"] = body.decode('utf-8')
|
|
678
|
+
except Exception:
|
|
679
|
+
pass
|
|
680
|
+
|
|
681
|
+
return params
|
|
682
|
+
except Exception as e:
|
|
683
|
+
logger.warning(f"收集FastAPI请求参数失败: {e}")
|
|
684
|
+
return {}
|
|
685
|
+
|
|
686
|
+
def _collect_stats(
|
|
687
|
+
self,
|
|
688
|
+
api_path: str,
|
|
689
|
+
api_method: str,
|
|
690
|
+
status_code: int,
|
|
691
|
+
response_time: float,
|
|
692
|
+
error_message: Optional[str] = None,
|
|
693
|
+
request_params: Optional[Dict[str, Any]] = None
|
|
694
|
+
):
|
|
695
|
+
"""收集接口统计"""
|
|
696
|
+
if not self.enable_stats or not self.stats_collector:
|
|
697
|
+
return
|
|
698
|
+
|
|
699
|
+
try:
|
|
700
|
+
self.stats_collector.collect(
|
|
701
|
+
api_path=api_path,
|
|
702
|
+
api_method=api_method,
|
|
703
|
+
status_code=status_code,
|
|
704
|
+
response_time=response_time,
|
|
705
|
+
error_message=error_message,
|
|
706
|
+
request_params=request_params
|
|
707
|
+
)
|
|
708
|
+
except Exception:
|
|
709
|
+
pass # 静默失败
|
|
567
710
|
|
|
568
711
|
def _should_skip(self, path: str) -> bool:
|
|
569
712
|
"""检查是否应该跳过验证"""
|
|
@@ -594,6 +737,10 @@ class AuthMiddleware:
|
|
|
594
737
|
from fastapi.responses import JSONResponse
|
|
595
738
|
|
|
596
739
|
path = request.url.path
|
|
740
|
+
start_time = time.time()
|
|
741
|
+
|
|
742
|
+
# 收集请求参数
|
|
743
|
+
request_params = await self._collect_fastapi_request_params(request) if self.enable_stats else None
|
|
597
744
|
|
|
598
745
|
# 检查是否跳过
|
|
599
746
|
if self._should_skip(path):
|
|
@@ -605,6 +752,8 @@ class AuthMiddleware:
|
|
|
605
752
|
|
|
606
753
|
if not token:
|
|
607
754
|
logger.warning("AuthMiddleware未提供认证信息")
|
|
755
|
+
response_time = time.time() - start_time
|
|
756
|
+
self._collect_stats(path, request.method, 401, response_time, "未提供认证信息", request_params)
|
|
608
757
|
return JSONResponse(
|
|
609
758
|
status_code=401,
|
|
610
759
|
content={"code": 401, "message": "未提供认证信息", "data": None}
|
|
@@ -613,8 +762,14 @@ class AuthMiddleware:
|
|
|
613
762
|
# 验证 token
|
|
614
763
|
try:
|
|
615
764
|
user_info = self.client.get_user_info(token)
|
|
765
|
+
# 初始化统计收集器(第一次有token时)
|
|
766
|
+
if self.enable_stats and self.stats_collector is None:
|
|
767
|
+
self._init_stats_collector(token)
|
|
616
768
|
# 将用户信息存储到 request.state
|
|
617
769
|
request.state.user_info = user_info
|
|
770
|
+
# 设置上下文
|
|
771
|
+
set_current_user(dataclasses.asdict(user_info))
|
|
772
|
+
|
|
618
773
|
# 处理代理头部,确保重定向(如果有)使用正确的协议
|
|
619
774
|
forwarded_proto = request.headers.get("x-forwarded-proto")
|
|
620
775
|
if forwarded_proto:
|
|
@@ -623,11 +778,25 @@ class AuthMiddleware:
|
|
|
623
778
|
await user_info_callback(request, user_info)
|
|
624
779
|
except AigcAuthError as e:
|
|
625
780
|
logger.error(f"AuthMiddleware认证失败: {e.message}")
|
|
781
|
+
response_time = time.time() - start_time
|
|
782
|
+
self._collect_stats(path, request.method, 401, response_time, e.message, request_params)
|
|
626
783
|
return JSONResponse(
|
|
627
784
|
status_code=401,
|
|
628
785
|
content={"code": e.code, "message": e.message, "data": None}
|
|
629
786
|
)
|
|
630
|
-
|
|
787
|
+
|
|
788
|
+
# 处理请求
|
|
789
|
+
try:
|
|
790
|
+
response = await call_next(request)
|
|
791
|
+
response_time = time.time() - start_time
|
|
792
|
+
self._collect_stats(path, request.method, response.status_code, response_time, None, request_params)
|
|
793
|
+
return response
|
|
794
|
+
except Exception as e:
|
|
795
|
+
response_time = time.time() - start_time
|
|
796
|
+
self._collect_stats(path, request.method, 500, response_time, str(e), request_params)
|
|
797
|
+
raise
|
|
798
|
+
finally:
|
|
799
|
+
clear_current_user()
|
|
631
800
|
|
|
632
801
|
def flask_before_request(self, user_info_callback: Callable = None):
|
|
633
802
|
"""
|
|
@@ -641,6 +810,11 @@ class AuthMiddleware:
|
|
|
641
810
|
from flask import request, jsonify, g
|
|
642
811
|
|
|
643
812
|
path = request.path
|
|
813
|
+
# 记录开始时间到 g 对象
|
|
814
|
+
g.start_time = time.time()
|
|
815
|
+
|
|
816
|
+
# 收集请求参数
|
|
817
|
+
g.request_params = self._collect_flask_request_params(request) if self.enable_stats else None
|
|
644
818
|
|
|
645
819
|
# 检查是否跳过
|
|
646
820
|
if self._should_skip(path):
|
|
@@ -652,6 +826,8 @@ class AuthMiddleware:
|
|
|
652
826
|
|
|
653
827
|
if not token:
|
|
654
828
|
logger.warning("AuthMiddleware未提供认证信息")
|
|
829
|
+
response_time = time.time() - g.start_time
|
|
830
|
+
self._collect_stats(path, request.method, 401, response_time, "未提供认证信息", g.request_params)
|
|
655
831
|
return jsonify({
|
|
656
832
|
"code": 401,
|
|
657
833
|
"message": "未提供认证信息",
|
|
@@ -661,12 +837,19 @@ class AuthMiddleware:
|
|
|
661
837
|
# 验证 token
|
|
662
838
|
try:
|
|
663
839
|
user_info = self.client.get_user_info(token)
|
|
840
|
+
# 初始化统计收集器(第一次有token时)
|
|
841
|
+
if self.enable_stats and self.stats_collector is None:
|
|
842
|
+
self._init_stats_collector(token)
|
|
664
843
|
# 将用户信息存储到 flask.g
|
|
665
844
|
g.user_info = user_info
|
|
845
|
+
# 设置上下文
|
|
846
|
+
set_current_user(dataclasses.asdict(user_info))
|
|
666
847
|
if user_info_callback:
|
|
667
848
|
user_info_callback(request, user_info)
|
|
668
849
|
except AigcAuthError as e:
|
|
669
850
|
logger.error(f"AuthMiddleware认证失败: {e.message}")
|
|
851
|
+
response_time = time.time() - g.start_time
|
|
852
|
+
self._collect_stats(path, request.method, 401, response_time, e.message, g.request_params)
|
|
670
853
|
return jsonify({
|
|
671
854
|
"code": e.code,
|
|
672
855
|
"message": e.message,
|
|
@@ -674,6 +857,34 @@ class AuthMiddleware:
|
|
|
674
857
|
}), 401
|
|
675
858
|
|
|
676
859
|
return None
|
|
860
|
+
|
|
861
|
+
def flask_after_request(self, response):
|
|
862
|
+
"""
|
|
863
|
+
Flask after_request 处理器(用于收集响应统计)
|
|
864
|
+
|
|
865
|
+
使用方法:
|
|
866
|
+
@app.after_request
|
|
867
|
+
def after_request(response):
|
|
868
|
+
return auth_middleware.flask_after_request(response)
|
|
869
|
+
"""
|
|
870
|
+
from flask import request, g
|
|
871
|
+
|
|
872
|
+
# 清除上下文
|
|
873
|
+
clear_current_user()
|
|
874
|
+
|
|
875
|
+
if hasattr(g, 'start_time'):
|
|
876
|
+
response_time = time.time() - g.start_time
|
|
877
|
+
request_params = getattr(g, 'request_params', None)
|
|
878
|
+
self._collect_stats(
|
|
879
|
+
request.path,
|
|
880
|
+
request.method,
|
|
881
|
+
response.status_code,
|
|
882
|
+
response_time,
|
|
883
|
+
None,
|
|
884
|
+
request_params
|
|
885
|
+
)
|
|
886
|
+
|
|
887
|
+
return response
|
|
677
888
|
|
|
678
889
|
def get_current_user_fastapi(self, request) -> Optional[UserInfo]:
|
|
679
890
|
"""
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""
|
|
2
|
+
用户上下文管理模块
|
|
3
|
+
使用 threading.local() 和 contextvars.ContextVar 统一管理用户信息
|
|
4
|
+
兼容同步和异步场景
|
|
5
|
+
"""
|
|
6
|
+
import threading
|
|
7
|
+
from contextvars import ContextVar
|
|
8
|
+
from typing import Optional, Dict, Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# 使用 threading.local() 存储同步上下文的用户信息(兼容多线程)
|
|
12
|
+
_local = threading.local()
|
|
13
|
+
|
|
14
|
+
# 使用 contextvars.ContextVar 存储异步上下文的用户信息(兼容异步任务)
|
|
15
|
+
_async_user_info: ContextVar[Optional[Dict[str, Any]]] = ContextVar('user_info', default=None)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def set_current_user(user_info: Dict[str, Any]):
|
|
19
|
+
"""
|
|
20
|
+
设置当前上下文的用户信息(同时支持同步和异步)
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
user_info: 用户信息字典,包含以下字段:
|
|
24
|
+
- user_id: 用户ID
|
|
25
|
+
- username: 用户名
|
|
26
|
+
- app_id: 应用ID
|
|
27
|
+
- app_code: 应用代码
|
|
28
|
+
- token: 访问令牌(可选)
|
|
29
|
+
- roles: 角色列表(可选)
|
|
30
|
+
- permissions: 权限列表(可选)
|
|
31
|
+
- is_admin: 是否管理员(可选)
|
|
32
|
+
"""
|
|
33
|
+
# 同时设置两个上下文,确保兼容性
|
|
34
|
+
_local.user_info = user_info
|
|
35
|
+
_async_user_info.set(user_info)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_current_user() -> Optional[Dict[str, Any]]:
|
|
39
|
+
"""
|
|
40
|
+
获取当前上下文的用户信息(同时支持同步和异步)
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
用户信息字典,如果未设置则返回 None
|
|
44
|
+
"""
|
|
45
|
+
# 优先从 async context 获取(异步场景)
|
|
46
|
+
async_info = _async_user_info.get(None)
|
|
47
|
+
if async_info is not None:
|
|
48
|
+
return async_info
|
|
49
|
+
|
|
50
|
+
# 从 threading.local 获取(同步场景)
|
|
51
|
+
return getattr(_local, 'user_info', None)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_current_user_id() -> Optional[int]:
|
|
55
|
+
"""获取当前用户ID"""
|
|
56
|
+
user_info = get_current_user()
|
|
57
|
+
return user_info.get('user_id') if user_info else None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_current_username() -> Optional[str]:
|
|
61
|
+
"""获取当前用户名"""
|
|
62
|
+
user_info = get_current_user()
|
|
63
|
+
return user_info.get('username') if user_info else None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def get_current_app_id() -> Optional[int]:
|
|
67
|
+
"""获取当前应用ID"""
|
|
68
|
+
user_info = get_current_user()
|
|
69
|
+
return user_info.get('app_id') if user_info else None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def get_current_app_code() -> Optional[str]:
|
|
73
|
+
"""获取当前应用代码"""
|
|
74
|
+
user_info = get_current_user()
|
|
75
|
+
return user_info.get('app_code') if user_info else None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def is_current_user_admin() -> bool:
|
|
79
|
+
"""判断当前用户是否为管理员"""
|
|
80
|
+
user_info = get_current_user()
|
|
81
|
+
return user_info.get('is_admin', False) if user_info else False
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def clear_current_user():
|
|
85
|
+
"""清理当前上下文的用户信息"""
|
|
86
|
+
if hasattr(_local, 'user_info'):
|
|
87
|
+
delattr(_local, 'user_info')
|
|
88
|
+
# ContextVar 不需要手动清理,会自动管理
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# ============ 请求上下文管理(可选的额外信息) ============
|
|
92
|
+
|
|
93
|
+
_async_request_context: ContextVar[Optional[Dict[str, Any]]] = ContextVar('request_context', default=None)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def set_request_context(**kwargs):
|
|
97
|
+
"""
|
|
98
|
+
设置请求上下文信息
|
|
99
|
+
|
|
100
|
+
可以存储以下信息:
|
|
101
|
+
- ip_address: 客户端IP
|
|
102
|
+
- user_agent: User Agent
|
|
103
|
+
- request_id: 请求ID
|
|
104
|
+
- trace_id: 追踪ID
|
|
105
|
+
"""
|
|
106
|
+
# 同时设置两个上下文
|
|
107
|
+
_local.request_context = kwargs
|
|
108
|
+
_async_request_context.set(kwargs)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def get_request_context() -> Optional[Dict[str, Any]]:
|
|
112
|
+
"""获取请求上下文信息"""
|
|
113
|
+
# 优先从 async context 获取
|
|
114
|
+
async_ctx = _async_request_context.get(None)
|
|
115
|
+
if async_ctx is not None:
|
|
116
|
+
return async_ctx
|
|
117
|
+
|
|
118
|
+
return getattr(_local, 'request_context', None)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def get_client_ip() -> Optional[str]:
|
|
122
|
+
"""获取客户端IP"""
|
|
123
|
+
ctx = get_request_context()
|
|
124
|
+
return ctx.get('ip_address') if ctx else None
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def clear_request_context():
|
|
128
|
+
"""清理请求上下文"""
|
|
129
|
+
if hasattr(_local, 'request_context'):
|
|
130
|
+
delattr(_local, 'request_context')
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# ============ 使用示例 ============
|
|
134
|
+
"""
|
|
135
|
+
使用示例:
|
|
136
|
+
|
|
137
|
+
1. 在 FastAPI 中间件中设置用户信息:
|
|
138
|
+
|
|
139
|
+
from app.utils.user_context import set_current_user, clear_current_user
|
|
140
|
+
|
|
141
|
+
@app.middleware("http")
|
|
142
|
+
async def auth_middleware(request: Request, call_next):
|
|
143
|
+
# 验证 token 并获取用户信息
|
|
144
|
+
user_info = await verify_token_and_get_user(request)
|
|
145
|
+
if user_info:
|
|
146
|
+
set_current_user({
|
|
147
|
+
'user_id': user_info.id,
|
|
148
|
+
'username': user_info.username,
|
|
149
|
+
'app_id': user_info.app_id,
|
|
150
|
+
'app_code': user_info.app.code,
|
|
151
|
+
'is_admin': user_info.is_admin
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
response = await call_next(request)
|
|
156
|
+
return response
|
|
157
|
+
finally:
|
|
158
|
+
clear_current_user()
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
2. 在业务代码中使用:
|
|
162
|
+
|
|
163
|
+
from app.utils.user_context import get_current_user_id, get_current_username
|
|
164
|
+
|
|
165
|
+
async def some_business_logic():
|
|
166
|
+
user_id = get_current_user_id()
|
|
167
|
+
username = get_current_username()
|
|
168
|
+
|
|
169
|
+
if user_id:
|
|
170
|
+
logger.info(f"用户 {username}({user_id}) 执行了操作")
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
3. 在装饰器中使用:
|
|
174
|
+
|
|
175
|
+
from functools import wraps
|
|
176
|
+
from app.utils.user_context import is_current_user_admin
|
|
177
|
+
|
|
178
|
+
def require_admin(func):
|
|
179
|
+
@wraps(func)
|
|
180
|
+
async def wrapper(*args, **kwargs):
|
|
181
|
+
if not is_current_user_admin():
|
|
182
|
+
raise HTTPException(status_code=403, detail="需要管理员权限")
|
|
183
|
+
return await func(*args, **kwargs)
|
|
184
|
+
return wrapper
|
|
185
|
+
|
|
186
|
+
@require_admin
|
|
187
|
+
async def admin_only_endpoint():
|
|
188
|
+
pass
|
|
189
|
+
"""
|
{huace_aigc_auth_client-1.1.13.dist-info → huace_aigc_auth_client-1.1.20.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: huace-aigc-auth-client
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.20
|
|
4
4
|
Summary: 华策AIGC Auth Client - 提供 Token 验证、用户信息获取、权限检查、旧系统接入等功能
|
|
5
5
|
Author-email: Huace <support@huace.com>
|
|
6
6
|
License: MIT
|
|
@@ -44,8 +44,8 @@ pip install huace-aigc-auth-client
|
|
|
44
44
|
# 必填:应用 ID 和密钥(在鉴权中心创建应用后获取)
|
|
45
45
|
AIGC_AUTH_APP_ID=your_app_id
|
|
46
46
|
AIGC_AUTH_APP_SECRET=your_app_secret
|
|
47
|
-
#
|
|
48
|
-
AIGC_AUTH_BASE_URL=
|
|
47
|
+
# 鉴权服务地址(默认为生产环境)
|
|
48
|
+
AIGC_AUTH_BASE_URL=your-auth-api-url-prefix
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
如需通过 Nginx 代理:
|
|
@@ -74,7 +74,7 @@ client = AigcAuthClient()
|
|
|
74
74
|
client = AigcAuthClient(
|
|
75
75
|
app_id="your_app_id",
|
|
76
76
|
app_secret="your_app_secret",
|
|
77
|
-
base_url="
|
|
77
|
+
base_url="your-auth-api-url-prefix"
|
|
78
78
|
)
|
|
79
79
|
```
|
|
80
80
|
|
|
@@ -272,6 +272,31 @@ def admin_only():
|
|
|
272
272
|
return jsonify({"message": "欢迎管理员"})
|
|
273
273
|
```
|
|
274
274
|
|
|
275
|
+
## 上下文管理
|
|
276
|
+
|
|
277
|
+
SDK 提供了上下文管理功能,可以在任意位置(Service、Utility 等)获取当前请求的用户信息,无需层层传递参数。
|
|
278
|
+
支持 Flask 和 FastAPI,兼容同步和异步环境。
|
|
279
|
+
|
|
280
|
+
注意:使用此功能前必须先注册 `AuthMiddleware`。
|
|
281
|
+
|
|
282
|
+
```python
|
|
283
|
+
from huace_aigc_auth_client import get_current_user
|
|
284
|
+
|
|
285
|
+
def do_something_logic():
|
|
286
|
+
# 获取当前用户信息(返回字典,非 UserInfo 对象)
|
|
287
|
+
user = get_current_user()
|
|
288
|
+
|
|
289
|
+
if user:
|
|
290
|
+
print(f"当前操作用户ID: {user['id']}")
|
|
291
|
+
print(f"当前操作用户名: {user['username']}")
|
|
292
|
+
|
|
293
|
+
# 也可以获取 app_id 等信息(如果未过滤)
|
|
294
|
+
if 'app_id' in user:
|
|
295
|
+
print(f"应用ID: {user['app_id']}")
|
|
296
|
+
else:
|
|
297
|
+
print("无用户上下文")
|
|
298
|
+
```
|
|
299
|
+
|
|
275
300
|
## API 参考
|
|
276
301
|
|
|
277
302
|
### UserInfo 对象
|
|
@@ -352,7 +377,7 @@ except AigcAuthError as e:
|
|
|
352
377
|
# .env 文件
|
|
353
378
|
AIGC_AUTH_APP_ID=your_app_id
|
|
354
379
|
AIGC_AUTH_APP_SECRET=your_app_secret
|
|
355
|
-
AIGC_AUTH_BASE_URL=
|
|
380
|
+
AIGC_AUTH_BASE_URL=your-auth-api-url-prefix
|
|
356
381
|
|
|
357
382
|
# 同步配置
|
|
358
383
|
AIGC_AUTH_SYNC_ENABLED=true
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
huace_aigc_auth_client/__init__.py,sha256=e-Y0M75DKW4t-hhipxgH_UDhoVSdH1uKFmJKyacyRJo,4630
|
|
2
|
+
huace_aigc_auth_client/api_stats_collector.py,sha256=VL_z-3WUWhEkAKDhGnKX0H-nNUn3x2IO_1FASsFA-Ss,8511
|
|
3
|
+
huace_aigc_auth_client/legacy_adapter.py,sha256=TVCBAKejE2z2HQFsEwDW8LMiaIkXNfz3Mxv6_E-UJFY,24102
|
|
4
|
+
huace_aigc_auth_client/sdk.py,sha256=0E0EmfLVxpOI5rOiVdAGcFzajdNZnDgjwrmOX7aYzGc,31303
|
|
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.20.dist-info/licenses/LICENSE,sha256=z7dgC7KljhBLNvKjN15391nMj3aLt0gbud8-Yf1F8EQ,1063
|
|
9
|
+
huace_aigc_auth_client-1.1.20.dist-info/METADATA,sha256=NkVUBXJLpANzcTh_VpBm2Lm1UquzxgjCK_2l-Xj6Kfo,23629
|
|
10
|
+
huace_aigc_auth_client-1.1.20.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
11
|
+
huace_aigc_auth_client-1.1.20.dist-info/top_level.txt,sha256=kbv0nQ6PQ0JVneWPH7O2AbtlJnP7AjvFJ6JjM6ZEBxo,23
|
|
12
|
+
huace_aigc_auth_client-1.1.20.dist-info/RECORD,,
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
huace_aigc_auth_client/__init__.py,sha256=Pjau66hZz0MeL7WWf-G4nGWkjw-4UazjSy2f8n5jdGM,4540
|
|
2
|
-
huace_aigc_auth_client/legacy_adapter.py,sha256=t-1QekRecn0B_gQ5r-ksX0hhuqUTBB2lvIUiNT4SowI,23781
|
|
3
|
-
huace_aigc_auth_client/sdk.py,sha256=ypClZfQm4Ux4db8XDP51I5Cuk1Uc9F2VPgECpXXphkQ,23272
|
|
4
|
-
huace_aigc_auth_client/webhook.py,sha256=XQZYEbMoqIdqZWCGSTcedeDKJpDbUVSq5g08g-6Qucg,4124
|
|
5
|
-
huace_aigc_auth_client/webhook_flask.py,sha256=Iosu4dBtRhQZM_ytn-bn82MpVsyOiV28FBnt7Tfh31U,7225
|
|
6
|
-
huace_aigc_auth_client-1.1.13.dist-info/licenses/LICENSE,sha256=z7dgC7KljhBLNvKjN15391nMj3aLt0gbud8-Yf1F8EQ,1063
|
|
7
|
-
huace_aigc_auth_client-1.1.13.dist-info/METADATA,sha256=XspgJA5ZklRRqN7zRy37eXffkcWINrLMT31mmVtEzmo,22971
|
|
8
|
-
huace_aigc_auth_client-1.1.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
-
huace_aigc_auth_client-1.1.13.dist-info/top_level.txt,sha256=kbv0nQ6PQ0JVneWPH7O2AbtlJnP7AjvFJ6JjM6ZEBxo,23
|
|
10
|
-
huace_aigc_auth_client-1.1.13.dist-info/RECORD,,
|
{huace_aigc_auth_client-1.1.13.dist-info → huace_aigc_auth_client-1.1.20.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{huace_aigc_auth_client-1.1.13.dist-info → huace_aigc_auth_client-1.1.20.dist-info}/top_level.txt
RENAMED
|
File without changes
|