huace-aigc-auth-client 1.1.18__py3-none-any.whl → 1.1.19__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.
@@ -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.18"
175
+ __version__ = "1.1.19"
@@ -0,0 +1,301 @@
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
+ query_string: Optional[str] = None,
70
+ error_message: Optional[str] = None
71
+ ):
72
+ """
73
+ 收集接口统计数据
74
+
75
+ Args:
76
+ api_path: 接口路径
77
+ api_method: 请求方法
78
+ status_code: 状态码
79
+ response_time: 响应时间(秒)
80
+ query_string: 查询字符串
81
+ error_message: 错误信息
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
+ 'query_string': query_string,
93
+ 'error_message': error_message,
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
+ query_string: Optional[str] = None,
221
+ error_message: Optional[str] = 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
+ query_string=query_string,
236
+ error_message=error_message
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
+ query_string=str(request.url.query)
279
+ )
280
+
281
+ return response
282
+ except Exception as e:
283
+ response_time = time.time() - start_time
284
+ collect_api_stat(
285
+ api_path=request.url.path,
286
+ api_method=request.method,
287
+ status_code=500,
288
+ response_time=response_time,
289
+ error_message=str(e)
290
+ )
291
+ raise
292
+
293
+
294
+ 3. 应用关闭时停止:
295
+
296
+ from huace_aigc_auth_client.api_stats_collector import stop_api_stats_collector
297
+
298
+ @app.on_event("shutdown")
299
+ async def shutdown_event():
300
+ stop_api_stats_collector()
301
+ """
@@ -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):
@@ -550,7 +552,9 @@ class AuthMiddleware:
550
552
  self,
551
553
  client: AigcAuthClient,
552
554
  exclude_paths: List[str] = None,
553
- exclude_prefixes: List[str] = None
555
+ exclude_prefixes: List[str] = None,
556
+ enable_stats: bool = True,
557
+ stats_api_url: Optional[str] = None
554
558
  ):
555
559
  """
556
560
  初始化中间件
@@ -559,10 +563,61 @@ class AuthMiddleware:
559
563
  client: AigcAuthClient 实例
560
564
  exclude_paths: 排除的路径列表(精确匹配)
561
565
  exclude_prefixes: 排除的路径前缀列表
566
+ enable_stats: 是否启用接口统计(默认启用)
567
+ stats_api_url: 统计接口 URL(可选,默认使用 client.base_url/sdk)
562
568
  """
563
569
  self.client = client
564
570
  self.exclude_paths = exclude_paths or []
565
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
+ def _collect_stats(
598
+ self,
599
+ api_path: str,
600
+ api_method: str,
601
+ status_code: int,
602
+ response_time: float,
603
+ query_string: Optional[str] = None,
604
+ error_message: Optional[str] = None
605
+ ):
606
+ """收集接口统计"""
607
+ if not self.enable_stats or not self.stats_collector:
608
+ return
609
+
610
+ try:
611
+ self.stats_collector.collect(
612
+ api_path=api_path,
613
+ api_method=api_method,
614
+ status_code=status_code,
615
+ response_time=response_time,
616
+ query_string=query_string,
617
+ error_message=error_message
618
+ )
619
+ except Exception:
620
+ pass # 静默失败
566
621
 
567
622
  def _should_skip(self, path: str) -> bool:
568
623
  """检查是否应该跳过验证"""
@@ -593,6 +648,7 @@ class AuthMiddleware:
593
648
  from fastapi.responses import JSONResponse
594
649
 
595
650
  path = request.url.path
651
+ start_time = time.time()
596
652
 
597
653
  # 检查是否跳过
598
654
  if self._should_skip(path):
@@ -604,6 +660,8 @@ class AuthMiddleware:
604
660
 
605
661
  if not token:
606
662
  logger.warning("AuthMiddleware未提供认证信息")
663
+ response_time = time.time() - start_time
664
+ self._collect_stats(path, request.method, 401, response_time, str(request.url.query), "未提供认证信息")
607
665
  return JSONResponse(
608
666
  status_code=401,
609
667
  content={"code": 401, "message": "未提供认证信息", "data": None}
@@ -612,8 +670,14 @@ class AuthMiddleware:
612
670
  # 验证 token
613
671
  try:
614
672
  user_info = self.client.get_user_info(token)
673
+ # 初始化统计收集器(第一次有token时)
674
+ if self.enable_stats and self.stats_collector is None:
675
+ self._init_stats_collector(token)
615
676
  # 将用户信息存储到 request.state
616
677
  request.state.user_info = user_info
678
+ # 设置上下文
679
+ set_current_user(dataclasses.asdict(user_info))
680
+
617
681
  # 处理代理头部,确保重定向(如果有)使用正确的协议
618
682
  forwarded_proto = request.headers.get("x-forwarded-proto")
619
683
  if forwarded_proto:
@@ -622,11 +686,25 @@ class AuthMiddleware:
622
686
  await user_info_callback(request, user_info)
623
687
  except AigcAuthError as e:
624
688
  logger.error(f"AuthMiddleware认证失败: {e.message}")
689
+ response_time = time.time() - start_time
690
+ self._collect_stats(path, request.method, 401, response_time, str(request.url.query), e.message)
625
691
  return JSONResponse(
626
692
  status_code=401,
627
693
  content={"code": e.code, "message": e.message, "data": None}
628
694
  )
629
- return await call_next(request)
695
+
696
+ # 处理请求
697
+ try:
698
+ response = await call_next(request)
699
+ response_time = time.time() - start_time
700
+ self._collect_stats(path, request.method, response.status_code, response_time, str(request.url.query))
701
+ return response
702
+ except Exception as e:
703
+ response_time = time.time() - start_time
704
+ self._collect_stats(path, request.method, 500, response_time, str(request.url.query), str(e))
705
+ raise
706
+ finally:
707
+ clear_current_user()
630
708
 
631
709
  def flask_before_request(self, user_info_callback: Callable = None):
632
710
  """
@@ -640,6 +718,8 @@ class AuthMiddleware:
640
718
  from flask import request, jsonify, g
641
719
 
642
720
  path = request.path
721
+ # 记录开始时间到 g 对象
722
+ g.start_time = time.time()
643
723
 
644
724
  # 检查是否跳过
645
725
  if self._should_skip(path):
@@ -651,6 +731,8 @@ class AuthMiddleware:
651
731
 
652
732
  if not token:
653
733
  logger.warning("AuthMiddleware未提供认证信息")
734
+ response_time = time.time() - g.start_time
735
+ self._collect_stats(path, request.method, 401, response_time, request.query_string.decode(), "未提供认证信息")
654
736
  return jsonify({
655
737
  "code": 401,
656
738
  "message": "未提供认证信息",
@@ -660,12 +742,19 @@ class AuthMiddleware:
660
742
  # 验证 token
661
743
  try:
662
744
  user_info = self.client.get_user_info(token)
745
+ # 初始化统计收集器(第一次有token时)
746
+ if self.enable_stats and self.stats_collector is None:
747
+ self._init_stats_collector(token)
663
748
  # 将用户信息存储到 flask.g
664
749
  g.user_info = user_info
750
+ # 设置上下文
751
+ set_current_user(dataclasses.asdict(user_info))
665
752
  if user_info_callback:
666
753
  user_info_callback(request, user_info)
667
754
  except AigcAuthError as e:
668
755
  logger.error(f"AuthMiddleware认证失败: {e.message}")
756
+ response_time = time.time() - g.start_time
757
+ self._collect_stats(path, request.method, 401, response_time, request.query_string.decode(), e.message)
669
758
  return jsonify({
670
759
  "code": e.code,
671
760
  "message": e.message,
@@ -673,6 +762,32 @@ class AuthMiddleware:
673
762
  }), 401
674
763
 
675
764
  return None
765
+
766
+ def flask_after_request(self, response):
767
+ """
768
+ Flask after_request 处理器(用于收集响应统计)
769
+
770
+ 使用方法:
771
+ @app.after_request
772
+ def after_request(response):
773
+ return auth_middleware.flask_after_request(response)
774
+ """
775
+ from flask import request, g
776
+
777
+ # 清除上下文
778
+ clear_current_user()
779
+
780
+ if hasattr(g, 'start_time'):
781
+ response_time = time.time() - g.start_time
782
+ self._collect_stats(
783
+ request.path,
784
+ request.method,
785
+ response.status_code,
786
+ response_time,
787
+ request.query_string.decode()
788
+ )
789
+
790
+ return response
676
791
 
677
792
  def get_current_user_fastapi(self, request) -> Optional[UserInfo]:
678
793
  """
@@ -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
+ """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: huace-aigc-auth-client
3
- Version: 1.1.18
3
+ Version: 1.1.19
4
4
  Summary: 华策AIGC Auth Client - 提供 Token 验证、用户信息获取、权限检查、旧系统接入等功能
5
5
  Author-email: Huace <support@huace.com>
6
6
  License: MIT
@@ -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 对象
@@ -0,0 +1,12 @@
1
+ huace_aigc_auth_client/__init__.py,sha256=5N25gQAbj3A9m9RnYPsEK2AkXCEv_QnbYMVULhss2VQ,4630
2
+ huace_aigc_auth_client/api_stats_collector.py,sha256=7V-mhA2G-QUPKOK005cJw1MglecWK58tfjKot2GfEAU,8457
3
+ huace_aigc_auth_client/legacy_adapter.py,sha256=TVCBAKejE2z2HQFsEwDW8LMiaIkXNfz3Mxv6_E-UJFY,24102
4
+ huace_aigc_auth_client/sdk.py,sha256=Zfyd7uqXwqv4XsOoc_bJZzP3PPeompcCyoyM05aH-fM,27837
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.19.dist-info/licenses/LICENSE,sha256=z7dgC7KljhBLNvKjN15391nMj3aLt0gbud8-Yf1F8EQ,1063
9
+ huace_aigc_auth_client-1.1.19.dist-info/METADATA,sha256=oqciLoSNGTc1T2RKnHFQpeNBUgn1ZIvT0SqOL8Z4ZK8,23629
10
+ huace_aigc_auth_client-1.1.19.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
11
+ huace_aigc_auth_client-1.1.19.dist-info/top_level.txt,sha256=kbv0nQ6PQ0JVneWPH7O2AbtlJnP7AjvFJ6JjM6ZEBxo,23
12
+ huace_aigc_auth_client-1.1.19.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.10.1)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,10 +0,0 @@
1
- huace_aigc_auth_client/__init__.py,sha256=HBQTjrm7GHuwNFZkY6rlTlKNu07pqlK6rv7y2piR45E,4540
2
- huace_aigc_auth_client/legacy_adapter.py,sha256=TVCBAKejE2z2HQFsEwDW8LMiaIkXNfz3Mxv6_E-UJFY,24102
3
- huace_aigc_auth_client/sdk.py,sha256=OCZkA8IG81VJsYqgvirZqcDltJqdivahbDzpbcPul8E,23179
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.18.dist-info/licenses/LICENSE,sha256=z7dgC7KljhBLNvKjN15391nMj3aLt0gbud8-Yf1F8EQ,1063
7
- huace_aigc_auth_client-1.1.18.dist-info/METADATA,sha256=skjOnRd2CQolRj42uuvfMM3QmR27PqdETnZd_WDoIIQ,22814
8
- huace_aigc_auth_client-1.1.18.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
9
- huace_aigc_auth_client-1.1.18.dist-info/top_level.txt,sha256=kbv0nQ6PQ0JVneWPH7O2AbtlJnP7AjvFJ6JjM6ZEBxo,23
10
- huace_aigc_auth_client-1.1.18.dist-info/RECORD,,