huace-aigc-auth-client 1.1.22__py3-none-any.whl → 1.1.24__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.
@@ -172,4 +172,4 @@ __all__ = [
172
172
  # 用户上下文
173
173
  "get_current_user",
174
174
  ]
175
- __version__ = "1.1.22"
175
+ __version__ = "1.1.24"
@@ -18,8 +18,7 @@ class ApiStatsCollector:
18
18
  api_url: str,
19
19
  app_id: str,
20
20
  app_secret: str,
21
- token: str,
22
- batch_size: int = 10,
21
+ batch_size: int = 50,
23
22
  flush_interval: float = 5.0,
24
23
  enabled: bool = True
25
24
  ):
@@ -30,7 +29,6 @@ class ApiStatsCollector:
30
29
  api_url: 统计接口 URL(如:http://auth.example.com/api/sdk/stats/report/batch)
31
30
  app_id: 应用 ID
32
31
  app_secret: 应用密钥
33
- token: 用户访问令牌
34
32
  batch_size: 批量提交大小
35
33
  flush_interval: 刷新间隔(秒)
36
34
  enabled: 是否启用
@@ -38,7 +36,6 @@ class ApiStatsCollector:
38
36
  self.api_url = api_url.rstrip('/')
39
37
  self.app_id = app_id
40
38
  self.app_secret = app_secret
41
- self.token = token
42
39
  self.batch_size = batch_size
43
40
  self.flush_interval = flush_interval
44
41
  self.enabled = enabled
@@ -69,6 +66,7 @@ class ApiStatsCollector:
69
66
  api_method: str,
70
67
  status_code: int,
71
68
  response_time: float,
69
+ token: str,
72
70
  error_message: Optional[str] = None,
73
71
  request_params: Optional[Dict[str, Any]] = None
74
72
  ):
@@ -80,6 +78,7 @@ class ApiStatsCollector:
80
78
  api_method: 请求方法
81
79
  status_code: 状态码
82
80
  response_time: 响应时间(秒)
81
+ token: 用户访问令牌
83
82
  error_message: 错误信息
84
83
  request_params: 请求参数(包含 headers, query_params, view_params, request_body, form_params)
85
84
  """
@@ -96,6 +95,7 @@ class ApiStatsCollector:
96
95
  'api_method': api_method,
97
96
  'status_code': status_code,
98
97
  'response_time': response_time,
98
+ 'token': token,
99
99
  'error_message': error_message,
100
100
  'request_params': request_params
101
101
  }
@@ -138,19 +138,50 @@ class ApiStatsCollector:
138
138
  self._flush_buffer(buffer)
139
139
 
140
140
  def _flush_buffer(self, buffer: List[Dict[str, Any]]):
141
- """刷新缓冲区:批量提交统计数据"""
141
+ """刷新缓冲区:按token分组批量提交统计数据"""
142
142
  if not buffer:
143
143
  return
144
144
 
145
+ try:
146
+ # 按token分组
147
+ token_groups: Dict[str, List[Dict[str, Any]]] = {}
148
+ for stat in buffer:
149
+ token = stat.get('token')
150
+ if not token:
151
+ continue
152
+
153
+ if token not in token_groups:
154
+ token_groups[token] = []
155
+
156
+ # 移除token字段后添加到分组
157
+ stat_copy = stat.copy()
158
+ stat_copy.pop('token', None)
159
+ token_groups[token].append(stat_copy)
160
+
161
+ # 对每个token分组分别提交
162
+ for token, stats in token_groups.items():
163
+ self._submit_stats(token, stats)
164
+
165
+ except Exception:
166
+ pass # 静默失败,不影响主流程
167
+
168
+ def _submit_stats(self, token: str, stats: List[Dict[str, Any]]):
169
+ """
170
+ 提交统计数据到服务端
171
+
172
+ Args:
173
+ token: 用户访问令牌
174
+ stats: 统计数据列表(已移除token字段)
175
+ """
145
176
  try:
146
177
  headers = {
147
178
  'X-App-Id': self.app_id,
148
179
  'X-App-Secret': self.app_secret,
149
- 'Authorization': f'Bearer {self.token}',
180
+ 'Authorization': f'Bearer {token}',
150
181
  'Content-Type': 'application/json'
151
182
  }
152
183
 
153
- payload = {'stats': buffer}
184
+ payload = {'stats': stats}
154
185
 
155
186
  response = requests.post(
156
187
  f'{self.api_url}/stats/report/batch',
@@ -176,7 +207,6 @@ def init_api_stats_collector(
176
207
  api_url: str,
177
208
  app_id: str,
178
209
  app_secret: str,
179
- token: str,
180
210
  batch_size: int = 10,
181
211
  flush_interval: float = 5.0,
182
212
  enabled: bool = True
@@ -188,7 +218,6 @@ def init_api_stats_collector(
188
218
  api_url: 统计接口 URL
189
219
  app_id: 应用 ID
190
220
  app_secret: 应用密钥
191
- token: 用户访问令牌
192
221
  batch_size: 批量提交大小
193
222
  flush_interval: 刷新间隔(秒)
194
223
  enabled: 是否启用
@@ -201,7 +230,6 @@ def init_api_stats_collector(
201
230
  api_url=api_url,
202
231
  app_id=app_id,
203
232
  app_secret=app_secret,
204
- token=token,
205
233
  batch_size=batch_size,
206
234
  flush_interval=flush_interval,
207
235
  enabled=enabled
@@ -227,6 +255,7 @@ def collect_api_stat(
227
255
  api_method: str,
228
256
  status_code: int,
229
257
  response_time: float,
258
+ token: str,
230
259
  error_message: Optional[str] = None,
231
260
  request_params: Optional[Dict[str, Any]] = None
232
261
  ):
@@ -235,6 +264,15 @@ def collect_api_stat(
235
264
 
236
265
  使用全局收集器实例
237
266
  注意:会自动过滤 3xx 重定向状态码的请求
267
+
268
+ Args:
269
+ api_path: 接口路径
270
+ api_method: 请求方法
271
+ status_code: 状态码
272
+ response_time: 响应时间(秒)
273
+ token: 用户访问令牌
274
+ error_message: 错误信息
275
+ request_params: 请求参数
238
276
  """
239
277
  # 过滤重定向请求(3xx 状态码),这些通常是框架自动处理的,不应统计
240
278
  if 300 <= status_code < 400:
@@ -247,6 +285,7 @@ def collect_api_stat(
247
285
  api_method=api_method,
248
286
  status_code=status_code,
249
287
  response_time=response_time,
288
+ token=token,
250
289
  error_message=error_message,
251
290
  request_params=request_params
252
291
  )
@@ -570,24 +570,28 @@ class AuthMiddleware:
570
570
  self.exclude_paths = exclude_paths or []
571
571
  self.exclude_prefixes = exclude_prefixes or []
572
572
  self.enable_stats = enable_stats
573
- self.stats_collector = None
574
573
 
575
- # 如果启用统计,设置统计接口 URL(默认使用 client 的 base_url)
574
+ # 如果启用统计,初始化全局统计收集器
576
575
  if self.enable_stats:
577
576
  self.stats_api_url = stats_api_url or f"{self.client.base_url}/sdk"
577
+ self._init_global_stats_collector()
578
578
 
579
- def _init_stats_collector(self, token: str):
580
- """初始化统计收集器(延迟初始化)"""
581
- if not self.enable_stats or self.stats_collector is not None:
579
+ def _init_global_stats_collector(self):
580
+ """初始化全局统计收集器(只初始化一次)"""
581
+ if not self.enable_stats:
582
582
  return
583
583
 
584
584
  try:
585
- from .api_stats_collector import init_api_stats_collector
586
- self.stats_collector = init_api_stats_collector(
585
+ from .api_stats_collector import init_api_stats_collector, get_api_stats_collector
586
+
587
+ # 检查是否已经初始化
588
+ if get_api_stats_collector() is not None:
589
+ return
590
+
591
+ init_api_stats_collector(
587
592
  api_url=self.stats_api_url,
588
593
  app_id=self.client.app_id,
589
594
  app_secret=self.client.app_secret,
590
- token=token,
591
595
  batch_size=10,
592
596
  flush_interval=5.0,
593
597
  enabled=True
@@ -615,21 +619,31 @@ class AuthMiddleware:
615
619
  "form_params": None
616
620
  }
617
621
 
618
- # 获取请求体(JSON 或文本)
622
+ # 获取 content-type
623
+ content_type = request.headers.get('Content-Type', '').lower()
624
+
625
+ # 获取请求体(使用白名单方式,只处理已知安全的内容类型)
619
626
  if request.is_json:
620
627
  try:
621
628
  params["request_body"] = request.get_json(silent=True)
622
629
  except Exception:
623
630
  pass
624
- elif request.data:
631
+ elif 'application/x-www-form-urlencoded' in content_type:
632
+ # 表单数据
633
+ if request.form:
634
+ params["form_params"] = request.form.to_dict(flat=False)
635
+ elif 'multipart/form-data' in content_type:
636
+ # 文件上传请求,不读取 request.data,避免损坏文件流
637
+ # 只收集表单字段(非文件字段)
638
+ if request.form:
639
+ params["form_params"] = request.form.to_dict(flat=False)
640
+ elif content_type.startswith('text/') and request.data and len(request.data) < 10240:
641
+ # 只读取文本类型的请求体,且限制大小避免内存问题
625
642
  try:
626
643
  params["request_body"] = request.data.decode('utf-8')
627
644
  except Exception:
628
- params["request_body"] = str(request.data)
629
-
630
- # 获取表单数据
631
- if request.form:
632
- params["form_params"] = request.form.to_dict(flat=False)
645
+ pass
646
+ # 其他类型(如 application/octet-stream 等二进制流)直接跳过,不读取
633
647
 
634
648
  return params
635
649
  except Exception as e:
@@ -656,28 +670,33 @@ class AuthMiddleware:
656
670
  "form_params": None
657
671
  }
658
672
 
659
- # 获取请求体
660
- content_type = request.headers.get("content-type", "")
673
+ # 获取请求体(使用白名单方式,只处理已知安全的内容类型)
674
+ content_type = request.headers.get("content-type", "").lower()
661
675
 
662
676
  if "application/json" in content_type:
663
677
  try:
664
678
  params["request_body"] = await request.json()
665
679
  except Exception:
666
680
  pass
667
- elif "application/x-www-form-urlencoded" in content_type or "multipart/form-data" in content_type:
681
+ elif "application/x-www-form-urlencoded" in content_type:
668
682
  try:
669
683
  form = await request.form()
670
684
  params["form_params"] = {k: v for k, v in form.items()}
671
685
  except Exception:
672
686
  pass
673
- else:
674
- # 尝试读取原始body
687
+ elif "multipart/form-data" in content_type:
688
+ # 文件上传请求,不读取 body,避免损坏文件流
689
+ # 只收集表单字段(非文件字段)
690
+ pass
691
+ elif content_type.startswith('text/'):
692
+ # 只读取文本类型的请求体,且限制大小避免内存问题
675
693
  try:
676
694
  body = await request.body()
677
- if body:
695
+ if body and len(body) < 10240: # 限制10KB避免内存问题
678
696
  params["request_body"] = body.decode('utf-8')
679
697
  except Exception:
680
698
  pass
699
+ # 其他类型(如 application/octet-stream 等二进制流)直接跳过,不读取
681
700
 
682
701
  return params
683
702
  except Exception as e:
@@ -690,22 +709,27 @@ class AuthMiddleware:
690
709
  api_method: str,
691
710
  status_code: int,
692
711
  response_time: float,
712
+ token: str,
693
713
  error_message: Optional[str] = None,
694
714
  request_params: Optional[Dict[str, Any]] = None
695
715
  ):
696
716
  """收集接口统计"""
697
- if not self.enable_stats or not self.stats_collector:
717
+ if not self.enable_stats:
698
718
  return
699
719
 
700
720
  try:
701
- self.stats_collector.collect(
702
- api_path=api_path,
703
- api_method=api_method,
704
- status_code=status_code,
705
- response_time=response_time,
706
- error_message=error_message,
707
- request_params=request_params
708
- )
721
+ from .api_stats_collector import get_api_stats_collector
722
+ collector = get_api_stats_collector()
723
+ if collector:
724
+ collector.collect(
725
+ api_path=api_path,
726
+ api_method=api_method,
727
+ status_code=status_code,
728
+ response_time=response_time,
729
+ token=token,
730
+ error_message=error_message,
731
+ request_params=request_params
732
+ )
709
733
  except Exception:
710
734
  pass # 静默失败
711
735
 
@@ -736,6 +760,11 @@ class AuthMiddleware:
736
760
  return await auth_middleware.fastapi_middleware(request, call_next)
737
761
  """
738
762
  from fastapi.responses import JSONResponse
763
+
764
+ # 处理代理头部,确保重定向(如果有)使用正确的协议
765
+ forwarded_proto = request.headers.get("x-forwarded-proto")
766
+ if forwarded_proto:
767
+ request.scope["scheme"] = forwarded_proto
739
768
 
740
769
  path = request.url.path
741
770
  start_time = time.time()
@@ -754,7 +783,7 @@ class AuthMiddleware:
754
783
  if not token:
755
784
  logger.warning("AuthMiddleware未提供认证信息")
756
785
  response_time = time.time() - start_time
757
- self._collect_stats(path, request.method, 401, response_time, "未提供认证信息", request_params)
786
+ self._collect_stats(path, request.method, 401, response_time, "", "未提供认证信息", request_params)
758
787
  return JSONResponse(
759
788
  status_code=401,
760
789
  content={"code": 401, "message": "未提供认证信息", "data": None}
@@ -763,24 +792,16 @@ class AuthMiddleware:
763
792
  # 验证 token
764
793
  try:
765
794
  user_info = self.client.get_user_info(token)
766
- # 初始化统计收集器(第一次有token时)
767
- if self.enable_stats and self.stats_collector is None:
768
- self._init_stats_collector(token)
769
795
  # 将用户信息存储到 request.state
770
796
  request.state.user_info = user_info
771
797
  # 设置上下文
772
798
  set_current_user(dataclasses.asdict(user_info))
773
-
774
- # 处理代理头部,确保重定向(如果有)使用正确的协议
775
- forwarded_proto = request.headers.get("x-forwarded-proto")
776
- if forwarded_proto:
777
- request.scope["scheme"] = forwarded_proto
778
799
  if user_info_callback:
779
800
  await user_info_callback(request, user_info)
780
801
  except AigcAuthError as e:
781
802
  logger.error(f"AuthMiddleware认证失败: {e.message}")
782
803
  response_time = time.time() - start_time
783
- self._collect_stats(path, request.method, 401, response_time, e.message, request_params)
804
+ self._collect_stats(path, request.method, 401, response_time, token, e.message, request_params)
784
805
  return JSONResponse(
785
806
  status_code=401,
786
807
  content={"code": e.code, "message": e.message, "data": None}
@@ -790,11 +811,11 @@ class AuthMiddleware:
790
811
  try:
791
812
  response = await call_next(request)
792
813
  response_time = time.time() - start_time
793
- self._collect_stats(path, request.method, response.status_code, response_time, None, request_params)
814
+ self._collect_stats(path, request.method, response.status_code, response_time, token, None, request_params)
794
815
  return response
795
816
  except Exception as e:
796
817
  response_time = time.time() - start_time
797
- self._collect_stats(path, request.method, 500, response_time, str(e), request_params)
818
+ self._collect_stats(path, request.method, 500, response_time, token, str(e), request_params)
798
819
  raise
799
820
  finally:
800
821
  clear_current_user()
@@ -828,7 +849,7 @@ class AuthMiddleware:
828
849
  if not token:
829
850
  logger.warning("AuthMiddleware未提供认证信息")
830
851
  response_time = time.time() - g.start_time
831
- self._collect_stats(path, request.method, 401, response_time, "未提供认证信息", g.request_params)
852
+ self._collect_stats(path, request.method, 401, response_time, "", "未提供认证信息", g.request_params)
832
853
  return jsonify({
833
854
  "code": 401,
834
855
  "message": "未提供认证信息",
@@ -838,9 +859,6 @@ class AuthMiddleware:
838
859
  # 验证 token
839
860
  try:
840
861
  user_info = self.client.get_user_info(token)
841
- # 初始化统计收集器(第一次有token时)
842
- if self.enable_stats and self.stats_collector is None:
843
- self._init_stats_collector(token)
844
862
  # 将用户信息存储到 flask.g
845
863
  g.user_info = user_info
846
864
  # 设置上下文
@@ -850,7 +868,7 @@ class AuthMiddleware:
850
868
  except AigcAuthError as e:
851
869
  logger.error(f"AuthMiddleware认证失败: {e.message}")
852
870
  response_time = time.time() - g.start_time
853
- self._collect_stats(path, request.method, 401, response_time, e.message, g.request_params)
871
+ self._collect_stats(path, request.method, 401, response_time, token, e.message, g.request_params)
854
872
  return jsonify({
855
873
  "code": e.code,
856
874
  "message": e.message,
@@ -876,11 +894,15 @@ class AuthMiddleware:
876
894
  if hasattr(g, 'start_time'):
877
895
  response_time = time.time() - g.start_time
878
896
  request_params = getattr(g, 'request_params', None)
897
+ # 从 request 的 Authorization header 获取 token
898
+ authorization = request.headers.get("Authorization")
899
+ token = self._extract_token(authorization) or ""
879
900
  self._collect_stats(
880
901
  request.path,
881
902
  request.method,
882
903
  response.status_code,
883
904
  response_time,
905
+ token,
884
906
  None,
885
907
  request_params
886
908
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: huace-aigc-auth-client
3
- Version: 1.1.22
3
+ Version: 1.1.24
4
4
  Summary: 华策AIGC Auth Client - 提供 Token 验证、用户信息获取、权限检查、旧系统接入等功能
5
5
  Author-email: Huace <support@huace.com>
6
6
  License: MIT
@@ -0,0 +1,12 @@
1
+ huace_aigc_auth_client/__init__.py,sha256=ZmJouQkB-fPBeBjouEH3dUf4zZv1DSPX7P9XiUo6gZw,4630
2
+ huace_aigc_auth_client/api_stats_collector.py,sha256=UCUu2CHWsJyiAM58-XLQtfxWK0fjZ8TTdw90gv_Gx04,10347
3
+ huace_aigc_auth_client/legacy_adapter.py,sha256=TVCBAKejE2z2HQFsEwDW8LMiaIkXNfz3Mxv6_E-UJFY,24102
4
+ huace_aigc_auth_client/sdk.py,sha256=BZ8jDDQBwSopK6uvSI3JknB1mpofoDFj0u0r1xK1Q_g,32728
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.24.dist-info/licenses/LICENSE,sha256=z7dgC7KljhBLNvKjN15391nMj3aLt0gbud8-Yf1F8EQ,1063
9
+ huace_aigc_auth_client-1.1.24.dist-info/METADATA,sha256=Egs2H3j2JDqCCjjmvHfo-neg4Nj1r_f8atrYt8GXUl4,23629
10
+ huace_aigc_auth_client-1.1.24.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
11
+ huace_aigc_auth_client-1.1.24.dist-info/top_level.txt,sha256=kbv0nQ6PQ0JVneWPH7O2AbtlJnP7AjvFJ6JjM6ZEBxo,23
12
+ huace_aigc_auth_client-1.1.24.dist-info/RECORD,,
@@ -1,12 +0,0 @@
1
- huace_aigc_auth_client/__init__.py,sha256=ci3wft3OSN-HynwYWyHtgyECiMx9c7TGtkzmALxf6jQ,4630
2
- huace_aigc_auth_client/api_stats_collector.py,sha256=gVtbcmLv83KdE4jwXTqPaeuKZMz8ET7QdlbSqdQ_KNY,9023
3
- huace_aigc_auth_client/legacy_adapter.py,sha256=TVCBAKejE2z2HQFsEwDW8LMiaIkXNfz3Mxv6_E-UJFY,24102
4
- huace_aigc_auth_client/sdk.py,sha256=03OzVIhawoBXWNfq8AUfdUEPLlQMrBhfsNSYANOrnwg,31346
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.22.dist-info/licenses/LICENSE,sha256=z7dgC7KljhBLNvKjN15391nMj3aLt0gbud8-Yf1F8EQ,1063
9
- huace_aigc_auth_client-1.1.22.dist-info/METADATA,sha256=o2KeTOZF1PSqR4_SgX_iD_wD3dR2DLJARnwl0Vqk5j8,23629
10
- huace_aigc_auth_client-1.1.22.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
11
- huace_aigc_auth_client-1.1.22.dist-info/top_level.txt,sha256=kbv0nQ6PQ0JVneWPH7O2AbtlJnP7AjvFJ6JjM6ZEBxo,23
12
- huace_aigc_auth_client-1.1.22.dist-info/RECORD,,