pixelarraythirdparty 1.3.7__py3-none-any.whl → 1.3.9__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.
@@ -6,13 +6,11 @@ PixelArray 第三方微服务客户端
6
6
  这个库包含了常用的开发工具和服务集成:
7
7
  - order: 订单管理模块和支付模块
8
8
  - product: 产品管理模块
9
- - cron: 定时任务管理模块
10
9
  - user: 用户管理模块
11
10
  - unified_login: 统一登录模块
12
11
  - feedback: 客户反馈模块
13
12
  - project: 项目管理模块
14
13
  - support_chat: 在线客服(临时会话链接)
15
- - custom_events: 自定义事件(新建定义、上报与报表查询)
16
14
  - project_dashboard: 项目看板(按 scope 查询 DNU/DAU/留存)
17
15
  """
18
16
 
@@ -21,27 +19,23 @@ from .support_chat.support_chat import (
21
19
  SupportChatManagerAsync,
22
20
  build_staff_portal_session_url,
23
21
  )
24
- from .custom_events.custom_events import CustomEventsManagerAsync
25
22
  from .project_dashboard.project_dashboard import ProjectDashboardManagerAsync
26
23
 
27
- __version__ = "1.3.7"
24
+ __version__ = "1.3.9"
28
25
  __author__ = "Lu qi"
29
26
  __email__ = "qi.lu@pixelarrayai.com"
30
27
 
31
28
  # 导出主要模块
32
29
  __all__ = [
33
30
  "product",
34
- "cron",
35
31
  "user",
36
32
  "order",
37
33
  "feedback",
38
34
  "project",
39
35
  "support_chat",
40
- "custom_events",
41
36
  "project_dashboard",
42
37
  "FeedbackManagerAsync",
43
38
  "SupportChatManagerAsync",
44
39
  "build_staff_portal_session_url",
45
- "CustomEventsManagerAsync",
46
40
  "ProjectDashboardManagerAsync",
47
41
  ]
@@ -10,7 +10,7 @@ class AsyncClient:
10
10
  parameters:
11
11
  api_key(str): API密钥,用于身份验证
12
12
  """
13
- self.base_url = "https://thirdparty.pixelarrayai.com"
13
+ self.base_url = "https://thirdparty.arraypixelai.com"
14
14
  self.api_key = api_key
15
15
  self.headers = {
16
16
  "Content-Type": "application/json",
@@ -1,3 +1,34 @@
1
+ """
2
+ 订单与支付客户端。
3
+
4
+ 商户业务回调(callback_url)
5
+ --------------------------------
6
+ 创建订单时传入 ``callback_url``(http/https)后,支付成功或退款成功时,
7
+ 本服务会向该地址 **POST** ``Content-Type: application/json``,Body 结构如下。
8
+
9
+ 公共字段(两种事件均包含)::
10
+
11
+ event (str): "order.paid" 或 "order.refunded"
12
+ out_trade_no (str): 商户订单号
13
+ payment_status (str): "PAID" 或 "REFUNDED"
14
+ payment_channel (str): "WECHAT" | "ALIPAY"
15
+ product_id (str|None): 产品 ID
16
+ amount (float|None): 订单金额(元)
17
+ total_fee (int|None): 订单金额(分)
18
+ transaction_id (str|None): 渠道交易号
19
+ timestamp (str): 回调发送时刻,ISO 8601 UTC
20
+
21
+ 支付成功(event="order.paid")额外字段::
22
+
23
+ paid_at (str|None): 支付时间
24
+
25
+ 退款成功(event="order.refunded")额外字段::
26
+
27
+ refunded_at (str|None): 退款时间
28
+
29
+ 幂等:同一状态重复通知不会重复回调。失败时服务端最多重试 2 次(间隔 1 秒)。
30
+ 业务方接口应返回 HTTP 2xx 表示接收成功。
31
+ """
1
32
  from pixelarraythirdparty.client import AsyncClient
2
33
 
3
34
 
@@ -9,6 +40,7 @@ class OrderManagerAsync(AsyncClient):
9
40
  remark: str = None,
10
41
  payment_channel: str = "WECHAT",
11
42
  amount_yuan: float = None,
43
+ callback_url: str = None,
12
44
  ):
13
45
  """
14
46
  description:
@@ -19,6 +51,9 @@ class OrderManagerAsync(AsyncClient):
19
51
  remark(str): 订单备注
20
52
  payment_channel(str): 支付渠道,可选值:"WECHAT"(微信支付)、"ALIPAY"(支付宝支付)
21
53
  amount_yuan(float, optional): 订单金额(元),可变金额产品必填
54
+ callback_url(str, optional): 支付/退款结果业务回调地址(http/https)。
55
+ 支付/退款成功后服务端 POST JSON 至该地址,Body 字段见本模块顶部
56
+ 「商户业务回调(callback_url)」说明。
22
57
  return:
23
58
  data(dict): 订单信息
24
59
  - id(int): 订单ID
@@ -30,6 +65,7 @@ class OrderManagerAsync(AsyncClient):
30
65
  - total_fee(int): 订单金额(分),用于支付接口
31
66
  - body(str): 商品描述
32
67
  - remark(str): 订单备注
68
+ - callback_url(str, optional): 商户业务回调地址
33
69
  - created_at(str): 订单创建时间
34
70
  - updated_at(str): 订单更新时间
35
71
  success(bool): 操作是否成功
@@ -42,6 +78,8 @@ class OrderManagerAsync(AsyncClient):
42
78
  }
43
79
  if amount_yuan is not None:
44
80
  data["amount_yuan"] = amount_yuan
81
+ if callback_url is not None:
82
+ data["callback_url"] = callback_url
45
83
  data, success = await self._request("POST", "/api/orders/create", json=data)
46
84
  if not success:
47
85
  return data, False
@@ -54,6 +92,7 @@ class OrderManagerAsync(AsyncClient):
54
92
  remark: str = None,
55
93
  payment_channel: str = "WECHAT",
56
94
  amount_yuan: float = None,
95
+ callback_url: str = None,
57
96
  ):
58
97
  """
59
98
  description:
@@ -63,8 +102,10 @@ class OrderManagerAsync(AsyncClient):
63
102
  product_id(str): 产品ID
64
103
  body(str): 商品描述
65
104
  remark(str): 订单备注
66
- payment_channel(str): 支付渠道,可选值:"WECHAT"、"ALIPAY"、"PAYPAL"
105
+ payment_channel(str): 支付渠道,可选值:"WECHAT"、"ALIPAY"
67
106
  amount_yuan(float, optional): 订单金额(元),可变金额产品必填
107
+ callback_url(str, optional): 支付/退款结果业务回调地址(http/https)。
108
+ 回调 POST Body 结构见本模块顶部「商户业务回调(callback_url)」说明。
68
109
  return:
69
110
  data(dict): 返回数据
70
111
  - order(dict): 订单信息(字段同 create_order 返回)
@@ -79,6 +120,8 @@ class OrderManagerAsync(AsyncClient):
79
120
  }
80
121
  if amount_yuan is not None:
81
122
  data["amount_yuan"] = amount_yuan
123
+ if callback_url is not None:
124
+ data["callback_url"] = callback_url
82
125
  data, success = await self._request("POST", "/api/orders/create_v2", json=data)
83
126
  if not success:
84
127
  return data, False
@@ -1,26 +1,11 @@
1
1
  from .unified_login import (
2
2
  OAuth2Login,
3
- GoogleLogin,
4
3
  WechatLogin,
5
- GitHubLogin,
6
4
  DouyinLogin,
7
- TiktokLogin,
8
- GitLabLogin,
9
- SMSLogin,
10
- EmailLogin,
11
- PasswordLogin,
12
5
  )
13
6
 
14
7
  __all__ = [
15
8
  "OAuth2Login",
16
- "GoogleLogin",
17
9
  "WechatLogin",
18
- "GitHubLogin",
19
10
  "DouyinLogin",
20
- "TiktokLogin",
21
- "GitLabLogin",
22
- "SMSLogin",
23
- "EmailLogin",
24
- "PasswordLogin",
25
11
  ]
26
-
@@ -48,11 +48,6 @@ class OAuth2Login(AsyncClient):
48
48
 
49
49
  # 提供商端点映射
50
50
  PROVIDER_ENDPOINTS = {
51
- "google": {
52
- "auth_url": "/api/unified-login/google/auth-url",
53
- "wait_login": "/api/unified-login/google/wait-login",
54
- "refresh_token": "/api/unified-login/google/refresh-token",
55
- },
56
51
  "wechat": {
57
52
  "auth_url": "/api/unified-login/wechat/auth-url",
58
53
  "wait_login": "/api/unified-login/wechat/wait-login",
@@ -61,23 +56,10 @@ class OAuth2Login(AsyncClient):
61
56
  "auth_url": "/api/unified-login/wechat-official/auth-url",
62
57
  "wait_login": "/api/unified-login/wechat-official/wait-login",
63
58
  },
64
- "github": {
65
- "auth_url": "/api/unified-login/github/auth-url",
66
- "wait_login": "/api/unified-login/github/wait-login",
67
- },
68
- "gitlab": {
69
- "auth_url": "/api/unified-login/gitlab/auth-url",
70
- "wait_login": "/api/unified-login/gitlab/wait-login",
71
- "refresh_token": "/api/unified-login/gitlab/refresh-token",
72
- },
73
59
  "douyin": {
74
60
  "auth_url": "/api/unified-login/douyin/auth-url",
75
61
  "wait_login": "/api/unified-login/douyin/wait-login",
76
62
  },
77
- "tiktok": {
78
- "auth_url": "/api/unified-login/tiktok/auth-url",
79
- "wait_login": "/api/unified-login/tiktok/wait-login",
80
- },
81
63
  }
82
64
 
83
65
  def __init__(
@@ -92,7 +74,7 @@ class OAuth2Login(AsyncClient):
92
74
  初始化OAuth2登录客户端
93
75
  parameters:
94
76
  api_key(str): API密钥
95
- provider(str): 提供商名称,可选值:google, wechat, github, gitlab, douyin, tiktok
77
+ provider(str): 提供商名称,可选值:wechat, douyin
96
78
  login_type(str, optional): 登录类型,仅对微信有效,可选值:desktop(PC端扫码), mobile(手机端)
97
79
  project_name(str, optional): 绑定项目名;不传或空字符串则匿名登录
98
80
  """
@@ -187,144 +169,6 @@ class OAuth2Login(AsyncClient):
187
169
 
188
170
  return {"message": "登录超时或未完成授权"}, False
189
171
 
190
- async def refresh_access_token(self, refresh_token: str) -> Tuple[Dict, bool]:
191
- """
192
- description:
193
- 使用refresh_token刷新access_token(仅支持Google和GitLab)
194
-
195
- 注意:GitLab 采用 token rotation(令牌轮换)机制,每次刷新时会返回新的 refresh_token,
196
- 旧的 refresh_token 会立即失效。必须保存新的 refresh_token 并替换旧的。
197
- Google 也可能在某些情况下返回新的 refresh_token。
198
- parameters:
199
- refresh_token(str): OAuth refresh_token
200
- return:
201
- token_data(dict): 包含新的access_token和可能的refresh_token的字典
202
- success(bool): 是否成功
203
- """
204
- try:
205
- endpoint = self._get_endpoint("refresh_token")
206
- except ValueError:
207
- return {"message": "该登录方式不支持 refresh_token"}, False
208
-
209
- data, success = await self._request(
210
- "POST",
211
- endpoint,
212
- json={"refresh_token": refresh_token},
213
- )
214
- if not success:
215
- return data, False
216
- return data, True
217
-
218
-
219
- class GoogleLogin(AsyncClient):
220
- """
221
- Google OAuth2 登录客户端
222
-
223
- 使用示例:
224
- ```
225
- # 服务端使用场景(推荐)
226
- google = GoogleLogin(api_key="your_api_key")
227
- # 1. 获取授权URL
228
- auth_data, success = await google.get_auth_url()
229
- if success:
230
- auth_url = auth_data.get("auth_url")
231
- state = auth_data.get("state")
232
- # 将auth_url返回给前端,让用户点击授权
233
- # 2. 等待登录结果(在服务端轮询)
234
- user_info, success = await google.wait_for_login(state, timeout=180)
235
- if success:
236
- access_token = user_info.get("access_token")
237
- refresh_token = user_info.get("refresh_token")
238
- ```
239
- """
240
-
241
- def __init__(self, api_key: str):
242
- super().__init__(api_key)
243
-
244
- async def get_auth_url(
245
- self, project_name: Optional[str] = None
246
- ) -> Tuple[Optional[Dict[str, str]], bool]:
247
- """
248
- description:
249
- 获取Google OAuth授权URL(公共方法,供服务端调用)
250
-
251
- 服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
252
- 获取到state后,使用wait_for_login方法等待登录结果。
253
- parameters:
254
- project_name(str, optional): 绑定项目名;不传或空字符串则匿名登录
255
- return:
256
- auth_data(dict, optional): 授权数据字典,包含auth_url和state
257
- success(bool): 是否成功
258
- """
259
- payload = _auth_url_json(project_name)
260
- if payload:
261
- data, success = await self._request(
262
- "POST", "/api/unified-login/google/auth-url", json=payload
263
- )
264
- else:
265
- data, success = await self._request(
266
- "POST", "/api/unified-login/google/auth-url"
267
- )
268
- if not success:
269
- return data, False
270
- auth_url = data.get("auth_url")
271
- if not auth_url:
272
- return None, False
273
- return data, True
274
-
275
- async def wait_for_login(self, state: str, timeout: int = 180) -> Tuple[Dict, bool]:
276
- """
277
- description:
278
- 等待Google登录结果,轮询检查登录状态(公共方法,供服务端调用)
279
-
280
- 服务端在用户点击授权后,调用此方法轮询等待登录结果。
281
- 此方法会持续轮询直到登录成功或超时。
282
- parameters:
283
- state(str): 登录状态标识,从get_auth_url返回的auth_data中获取
284
- timeout(int, optional): 超时时间(秒),默认为180
285
- return:
286
- user_info(dict): 用户信息字典
287
- success(bool): 是否成功
288
- """
289
- interval = 2
290
- total_checks = max(1, timeout // interval) if timeout > 0 else 1
291
-
292
- for _ in range(total_checks):
293
- status, response = await self._request_raw(
294
- "POST",
295
- "/api/unified-login/google/wait-login",
296
- json={"state": state},
297
- )
298
-
299
- if status == 200 and response.get("success") is True:
300
- return response.get("data", {}), True
301
-
302
- if status in (400, 408):
303
- break
304
-
305
- await asyncio.sleep(interval)
306
-
307
- return {"message": "登录超时或未完成授权"}, False
308
-
309
- async def refresh_access_token(self, refresh_token: str) -> Tuple[Dict, bool]:
310
- """
311
- description:
312
- 使用refresh_token刷新access_token
313
- parameters:
314
- refresh_token(str): Google OAuth refresh_token
315
- return:
316
- token_data(dict): 包含新的access_token和可能的refresh_token的字典
317
- success(bool): 是否成功
318
- """
319
- data, success = await self._request(
320
- "POST",
321
- "/api/unified-login/google/refresh-token",
322
- json={"refresh_token": refresh_token},
323
- )
324
- if not success:
325
- return data, False
326
- return data, True
327
-
328
172
 
329
173
  class WechatLogin(AsyncClient):
330
174
  """
@@ -442,95 +286,6 @@ class WechatLogin(AsyncClient):
442
286
  return {"message": "登录超时或未完成授权"}, False
443
287
 
444
288
 
445
- class GitHubLogin(AsyncClient):
446
- """
447
- GitHub OAuth2 登录客户端
448
-
449
- 使用示例:
450
- ```
451
- # 服务端使用场景(推荐)
452
- github = GitHubLogin(api_key="your_api_key")
453
- # 1. 获取授权URL
454
- auth_data, success = await github.get_auth_url()
455
- if success:
456
- auth_url = auth_data.get("auth_url")
457
- state = auth_data.get("state")
458
- # 将auth_url返回给前端,让用户点击授权
459
- # 2. 等待登录结果(在服务端轮询)
460
- user_info, success = await github.wait_for_login(state, timeout=180)
461
- print(user_info, success)
462
- ```
463
- """
464
-
465
- def __init__(self, api_key: str):
466
- super().__init__(api_key)
467
-
468
- async def get_auth_url(
469
- self, project_name: Optional[str] = None
470
- ) -> Tuple[Optional[Dict[str, str]], bool]:
471
- """
472
- description:
473
- 获取GitHub OAuth授权URL(公共方法,供服务端调用)
474
-
475
- 服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
476
- 获取到state后,使用wait_for_login方法等待登录结果。
477
- parameters:
478
- project_name(str, optional): 绑定项目名;不传或空字符串则匿名登录
479
- return:
480
- auth_data(dict, optional): 授权数据字典,包含auth_url和state
481
- success(bool): 是否成功
482
- """
483
- payload = _auth_url_json(project_name)
484
- if payload:
485
- data, success = await self._request(
486
- "POST", "/api/unified-login/github/auth-url", json=payload
487
- )
488
- else:
489
- data, success = await self._request(
490
- "POST", "/api/unified-login/github/auth-url"
491
- )
492
- if not success:
493
- return data, False
494
- auth_url = data.get("auth_url")
495
- if not auth_url:
496
- return None, False
497
- return data, True
498
-
499
- async def wait_for_login(self, state: str, timeout: int = 180) -> Tuple[Dict, bool]:
500
- """
501
- description:
502
- 等待GitHub登录结果,轮询检查登录状态(公共方法,供服务端调用)
503
-
504
- 服务端在用户点击授权后,调用此方法轮询等待登录结果。
505
- 此方法会持续轮询直到登录成功或超时。
506
- parameters:
507
- state(str): 登录状态标识,从get_auth_url返回的auth_data中获取
508
- timeout(int, optional): 超时时间(秒),默认为180
509
- return:
510
- user_info(dict): 用户信息字典
511
- success(bool): 是否成功
512
- """
513
- interval = 2
514
- total_checks = max(1, timeout // interval) if timeout > 0 else 1
515
-
516
- for _ in range(total_checks):
517
- status, response = await self._request_raw(
518
- "POST",
519
- "/api/unified-login/github/wait-login",
520
- json={"state": state},
521
- )
522
-
523
- if status == 200 and response.get("success") is True:
524
- return response.get("data", {}), True
525
-
526
- if status in (400, 408):
527
- break
528
-
529
- await asyncio.sleep(interval)
530
-
531
- return {"message": "登录超时或未完成授权"}, False
532
-
533
-
534
289
  class DouyinLogin(AsyncClient):
535
290
  """
536
291
  抖音 OAuth2 登录客户端
@@ -620,531 +375,3 @@ class DouyinLogin(AsyncClient):
620
375
  return {"message": "登录超时或未完成授权"}, False
621
376
 
622
377
 
623
- class TiktokLogin(AsyncClient):
624
- """
625
- TikTok OAuth2 登录客户端
626
-
627
- 使用示例:
628
- ```
629
- # 服务端使用场景(推荐)
630
- tiktok = TiktokLogin(api_key="your_api_key")
631
- # 1. 获取授权URL
632
- auth_data, success = await tiktok.get_auth_url()
633
- if success:
634
- auth_url = auth_data.get("auth_url")
635
- state = auth_data.get("state")
636
- # 将auth_url返回给前端,让用户点击授权
637
- # 2. 等待登录结果(在服务端轮询)
638
- user_info, success = await tiktok.wait_for_login(state, timeout=180)
639
- print(user_info, success)
640
- ```
641
- """
642
-
643
- def __init__(self, api_key: str):
644
- super().__init__(api_key)
645
-
646
- async def get_auth_url(
647
- self, project_name: Optional[str] = None
648
- ) -> Tuple[Optional[Dict[str, str]], bool]:
649
- """
650
- description:
651
- 获取TikTok OAuth授权URL(公共方法,供服务端调用)
652
-
653
- 服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
654
- 获取到state后,使用wait_for_login方法等待登录结果。
655
- parameters:
656
- project_name(str, optional): 绑定项目名;不传或空字符串则匿名登录
657
- return:
658
- auth_data(dict, optional): 授权数据字典,包含auth_url和state
659
- success(bool): 是否成功
660
- """
661
- payload = _auth_url_json(project_name)
662
- if payload:
663
- data, success = await self._request(
664
- "POST", "/api/unified-login/tiktok/auth-url", json=payload
665
- )
666
- else:
667
- data, success = await self._request(
668
- "POST", "/api/unified-login/tiktok/auth-url"
669
- )
670
- if not success:
671
- return data, False
672
- auth_url = data.get("auth_url")
673
- if not auth_url:
674
- return None, False
675
- return data, True
676
-
677
- async def wait_for_login(self, state: str, timeout: int = 180) -> Tuple[Dict, bool]:
678
- """
679
- description:
680
- 等待TikTok登录结果,轮询检查登录状态(公共方法,供服务端调用)
681
-
682
- 服务端在用户点击授权后,调用此方法轮询等待登录结果。
683
- 此方法会持续轮询直到登录成功或超时。
684
- parameters:
685
- state(str): 登录状态标识,从get_auth_url返回的auth_data中获取
686
- timeout(int, optional): 超时时间(秒),默认为180
687
- return:
688
- user_info(dict): 用户信息字典
689
- success(bool): 是否成功
690
- """
691
- interval = 2
692
- total_checks = max(1, timeout // interval) if timeout > 0 else 1
693
-
694
- for _ in range(total_checks):
695
- status, response = await self._request_raw(
696
- "POST",
697
- "/api/unified-login/tiktok/wait-login",
698
- json={"state": state},
699
- )
700
-
701
- if status == 200 and response.get("success") is True:
702
- return response.get("data", {}), True
703
-
704
- if status in (400, 408):
705
- break
706
-
707
- await asyncio.sleep(interval)
708
-
709
- return {"message": "登录超时或未完成授权"}, False
710
-
711
-
712
- class GitLabLogin(AsyncClient):
713
- """
714
- GitLab OAuth2 登录客户端
715
-
716
- 使用示例:
717
- ```
718
- # 服务端使用场景(推荐)
719
- gitlab = GitLabLogin(api_key="your_api_key")
720
- # 1. 获取授权URL
721
- auth_data, success = await gitlab.get_auth_url()
722
- if success:
723
- auth_url = auth_data.get("auth_url")
724
- state = auth_data.get("state")
725
- # 将auth_url返回给前端,让用户点击授权
726
- # 2. 等待登录结果(在服务端轮询)
727
- user_info, success = await gitlab.wait_for_login(state, timeout=180)
728
- print(user_info, success)
729
- ```
730
- """
731
-
732
- def __init__(self, api_key: str):
733
- super().__init__(api_key)
734
-
735
- async def get_auth_url(
736
- self, project_name: Optional[str] = None
737
- ) -> Tuple[Optional[Dict[str, str]], bool]:
738
- """
739
- description:
740
- 获取GitLab OAuth授权URL(公共方法,供服务端调用)
741
-
742
- 服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
743
- 获取到state后,使用wait_for_login方法等待登录结果。
744
- parameters:
745
- project_name(str, optional): 绑定项目名;不传或空字符串则匿名登录
746
- return:
747
- auth_data(dict, optional): 授权数据字典,包含auth_url和state
748
- success(bool): 是否成功
749
- """
750
- payload = _auth_url_json(project_name)
751
- if payload:
752
- data, success = await self._request(
753
- "POST", "/api/unified-login/gitlab/auth-url", json=payload
754
- )
755
- else:
756
- data, success = await self._request(
757
- "POST", "/api/unified-login/gitlab/auth-url"
758
- )
759
- if not success:
760
- return data, False
761
- auth_url = data.get("auth_url")
762
- if not auth_url:
763
- return None, False
764
- return data, True
765
-
766
- async def wait_for_login(self, state: str, timeout: int = 180) -> Tuple[Dict, bool]:
767
- """
768
- description:
769
- 等待GitLab登录结果,轮询检查登录状态(公共方法,供服务端调用)
770
-
771
- 服务端在用户点击授权后,调用此方法轮询等待登录结果。
772
- 此方法会持续轮询直到登录成功或超时。
773
- parameters:
774
- state(str): 登录状态标识,从get_auth_url返回的auth_data中获取
775
- timeout(int, optional): 超时时间(秒),默认为180
776
- return:
777
- user_info(dict): 用户信息字典
778
- success(bool): 是否成功
779
- """
780
- interval = 2
781
- total_checks = max(1, timeout // interval) if timeout > 0 else 1
782
-
783
- for _ in range(total_checks):
784
- status, response = await self._request_raw(
785
- "POST",
786
- "/api/unified-login/gitlab/wait-login",
787
- json={"state": state},
788
- )
789
-
790
- if status == 200 and response.get("success") is True:
791
- return response.get("data", {}), True
792
-
793
- if status in (400, 408):
794
- break
795
-
796
- await asyncio.sleep(interval)
797
-
798
- return {"message": "登录超时或未完成授权"}, False
799
-
800
- async def refresh_access_token(self, refresh_token: str) -> Tuple[Dict, bool]:
801
- """
802
- description:
803
- 使用refresh_token刷新access_token
804
-
805
- 注意:GitLab 采用 token rotation(令牌轮换)机制,每次刷新时会返回新的 refresh_token,
806
- 旧的 refresh_token 会立即失效。必须保存新的 refresh_token 并替换旧的。
807
- parameters:
808
- refresh_token(str): GitLab OAuth refresh_token
809
- return:
810
- token_data(dict): 包含新的access_token和可能的refresh_token的字典
811
- success(bool): 是否成功
812
- """
813
- data, success = await self._request(
814
- "POST",
815
- "/api/unified-login/gitlab/refresh-token",
816
- json={"refresh_token": refresh_token},
817
- )
818
- if not success:
819
- return data, False
820
- return data, True
821
-
822
-
823
- class SMSLogin(AsyncClient):
824
- """
825
- 短信验证码登录客户端
826
-
827
- 使用示例:
828
- ```
829
- sms = SMSLogin(api_key="your_api_key")
830
- # 发送验证码
831
- success = await sms.send_code(phone="13800138000")
832
- if success:
833
- # 验证验证码并登录
834
- user_info, success = await sms.login(phone="13800138000", code="123456")
835
- ```
836
- """
837
-
838
- def __init__(self, api_key: str):
839
- super().__init__(api_key)
840
-
841
- async def send_code(self, phone: str) -> bool:
842
- """
843
- 发送短信验证码
844
-
845
- :param phone: 手机号码
846
- :return: success 是否成功
847
- """
848
- data, success = await self._request(
849
- "POST", "/api/unified-login/sms/send-code", json={"phone": phone}
850
- )
851
- return bool(success)
852
-
853
- async def verify_code(
854
- self, phone: str, code: str, project_name: Optional[str] = None
855
- ) -> bool:
856
- """
857
- 验证短信验证码
858
-
859
- :param phone: 手机号码
860
- :param code: 验证码
861
- :param project_name: 绑定项目名;不传或空字符串则匿名登录
862
- :return: success 是否成功
863
- """
864
- payload: Dict[str, Any] = {"phone": phone, "code": code}
865
- pn = _normalized_project_name(project_name)
866
- if pn is not None:
867
- payload["project_name"] = pn
868
- _, success = await self._request(
869
- "POST",
870
- "/api/unified-login/sms/verify-code",
871
- json=payload,
872
- )
873
- return bool(success)
874
-
875
- async def _wait_for_login(self, phone: str, timeout: int) -> Tuple[Dict, bool]:
876
- """
877
- 等待短信登录结果
878
-
879
- :param phone: 手机号码
880
- :param timeout: 超时时间(秒)
881
- """
882
- interval = 2
883
- total_checks = max(1, timeout // interval) if timeout > 0 else 1
884
-
885
- for _ in range(total_checks):
886
- status, response = await self._request_raw(
887
- "POST",
888
- "/api/unified-login/sms/wait-sms-login",
889
- json={"phone": phone},
890
- )
891
-
892
- if status == 200 and response.get("success") is True:
893
- return response.get("data", {}), True
894
-
895
- if status in (400, 408):
896
- break
897
-
898
- await asyncio.sleep(interval)
899
-
900
- return {"message": "登录超时或未完成授权"}, False
901
-
902
- async def login(
903
- self,
904
- phone: str,
905
- code: str,
906
- timeout: int = 180,
907
- project_name: Optional[str] = None,
908
- ) -> Tuple[Dict, bool]:
909
- """
910
- 验证验证码并等待登录结果
911
-
912
- :param phone: 手机号码
913
- :param code: 验证码
914
- :param timeout: 等待登录结果的超时时间(秒)
915
- :param project_name: 绑定项目名;不传或空字符串则匿名登录
916
- :return: (用户信息, 是否成功)
917
- """
918
- success = await self.verify_code(phone, code, project_name=project_name)
919
- if not success:
920
- return {"message": "验证码校验失败"}, False
921
-
922
- return await self._wait_for_login(phone, timeout)
923
-
924
-
925
- class EmailLogin(AsyncClient):
926
- """
927
- 邮箱验证码登录客户端
928
-
929
- 使用示例:
930
- ```
931
- email = EmailLogin(api_key="your_api_key")
932
- # 发送验证码
933
- success = await email.send_code(email="user@example.com")
934
- if success:
935
- # 验证验证码并登录
936
- user_info, success = await email.login(email="user@example.com", code="123456")
937
- ```
938
- """
939
-
940
- def __init__(self, api_key: str):
941
- super().__init__(api_key)
942
-
943
- async def send_code(self, email: str) -> bool:
944
- """
945
- 发送邮箱验证码
946
-
947
- :param email: 邮箱地址
948
- :return: success 是否成功
949
- """
950
- _, success = await self._request(
951
- "POST", "/api/unified-login/email/send-code", json={"email": email}
952
- )
953
- return bool(success)
954
-
955
- async def verify_code(
956
- self, email: str, code: str, project_name: Optional[str] = None
957
- ) -> bool:
958
- """
959
- 验证邮箱验证码
960
-
961
- :param email: 邮箱地址
962
- :param code: 验证码
963
- :param project_name: 绑定项目名;不传或空字符串则匿名登录
964
- :return: success 是否成功
965
- """
966
- payload: Dict[str, Any] = {"email": email, "code": code}
967
- pn = _normalized_project_name(project_name)
968
- if pn is not None:
969
- payload["project_name"] = pn
970
- _, success = await self._request(
971
- "POST",
972
- "/api/unified-login/email/verify-code",
973
- json=payload,
974
- )
975
- return bool(success)
976
-
977
- async def _wait_for_login(self, email: str, timeout: int) -> Tuple[Dict, bool]:
978
- """
979
- 等待邮箱登录结果
980
-
981
- :param email: 邮箱地址
982
- :param timeout: 超时时间(秒)
983
- """
984
- interval = 2
985
- total_checks = max(1, timeout // interval) if timeout > 0 else 1
986
-
987
- for _ in range(total_checks):
988
- status, response = await self._request_raw(
989
- "POST",
990
- "/api/unified-login/email/wait-email-login",
991
- json={"email": email},
992
- )
993
-
994
- if status == 200 and response.get("success") is True:
995
- return response.get("data", {}), True
996
-
997
- if status in (400, 408):
998
- break
999
-
1000
- await asyncio.sleep(interval)
1001
-
1002
- return {"message": "登录超时或未完成授权"}, False
1003
-
1004
- async def login(
1005
- self,
1006
- email: str,
1007
- code: str,
1008
- timeout: int = 180,
1009
- project_name: Optional[str] = None,
1010
- ) -> Tuple[Dict, bool]:
1011
- """
1012
- 验证验证码并等待登录结果
1013
-
1014
- :param email: 邮箱地址
1015
- :param code: 验证码
1016
- :param timeout: 等待登录结果的超时时间(秒)
1017
- :param project_name: 绑定项目名;不传或空字符串则匿名登录
1018
- :return: (用户信息, 是否成功)
1019
- """
1020
- success = await self.verify_code(email, code, project_name=project_name)
1021
- if not success:
1022
- return {"message": "验证码校验失败"}, False
1023
-
1024
- return await self._wait_for_login(email, timeout)
1025
-
1026
-
1027
- class PasswordLogin(AsyncClient):
1028
- """
1029
- 密码登录客户端(邮箱 + 密码,统一登录外部用户表)
1030
-
1031
- 使用示例:
1032
- ```
1033
- password_client = PasswordLogin(api_key="your_api_key")
1034
- # 注册/设置密码
1035
- success = await password_client.register(
1036
- user_identifier="user@example.com",
1037
- password="your_password",
1038
- display_name="可选展示名",
1039
- )
1040
- if success:
1041
- # 登录
1042
- user_info, success = await password_client.login(
1043
- user_identifier="user@example.com",
1044
- password="your_password",
1045
- )
1046
- # 修改密码
1047
- success = await password_client.change_password(
1048
- user_identifier="user@example.com",
1049
- old_password="old_password",
1050
- new_password="new_password",
1051
- )
1052
- ```
1053
- """
1054
-
1055
- def __init__(self, api_key: str):
1056
- super().__init__(api_key)
1057
-
1058
- async def register(
1059
- self,
1060
- user_identifier: str,
1061
- password: str,
1062
- display_name: Optional[str] = None,
1063
- project_name: Optional[str] = None,
1064
- ) -> bool:
1065
- """
1066
- 注册/设置密码:将密码哈希后写入外部用户表(login_method=password)。
1067
- 若该邮箱已存在则更新密码与展示名称。
1068
-
1069
- :param user_identifier: 用户标识(邮箱)
1070
- :param password: 密码(6~100 位)
1071
- :param display_name: 展示名称,可选
1072
- :param project_name: 绑定项目名;不传或空字符串则匿名登录
1073
- :return: 是否成功
1074
- """
1075
- payload: Dict[str, Any] = {
1076
- "user_identifier": user_identifier,
1077
- "password": password,
1078
- }
1079
- if display_name is not None:
1080
- payload["display_name"] = display_name
1081
- pn = _normalized_project_name(project_name)
1082
- if pn is not None:
1083
- payload["project_name"] = pn
1084
- _, success = await self._request(
1085
- "POST",
1086
- "/api/unified-login/password/register",
1087
- json=payload,
1088
- )
1089
- return bool(success)
1090
-
1091
- async def login(
1092
- self,
1093
- user_identifier: str,
1094
- password: str,
1095
- project_name: Optional[str] = None,
1096
- ) -> Tuple[Dict, bool]:
1097
- """
1098
- 密码登录:校验邮箱与密码,成功则返回用户信息(与其它统一登录方式格式一致)。
1099
-
1100
- :param user_identifier: 用户标识(邮箱)
1101
- :param password: 密码
1102
- :param project_name: 绑定项目名;不传或空字符串则匿名登录
1103
- :return: (用户信息字典, 是否成功)
1104
- """
1105
- body: Dict[str, Any] = {
1106
- "user_identifier": user_identifier,
1107
- "password": password,
1108
- }
1109
- pn = _normalized_project_name(project_name)
1110
- if pn is not None:
1111
- body["project_name"] = pn
1112
- data, success = await self._request(
1113
- "POST",
1114
- "/api/unified-login/password/login",
1115
- json=body,
1116
- )
1117
- if not success:
1118
- return data if isinstance(data, dict) else {"message": data}, False
1119
- return data, True
1120
-
1121
- async def change_password(
1122
- self,
1123
- user_identifier: str,
1124
- old_password: str,
1125
- new_password: str,
1126
- project_name: Optional[str] = None,
1127
- ) -> bool:
1128
- """
1129
- 修改密码:校验原密码后更新为新密码。
1130
-
1131
- :param user_identifier: 用户标识(邮箱)
1132
- :param old_password: 当前密码
1133
- :param new_password: 新密码(6~100 位)
1134
- :param project_name: 绑定项目名;不传或空字符串则匿名登录
1135
- :return: 是否成功
1136
- """
1137
- body: Dict[str, Any] = {
1138
- "user_identifier": user_identifier,
1139
- "old_password": old_password,
1140
- "new_password": new_password,
1141
- }
1142
- pn = _normalized_project_name(project_name)
1143
- if pn is not None:
1144
- body["project_name"] = pn
1145
- _, success = await self._request(
1146
- "POST",
1147
- "/api/unified-login/password/change",
1148
- json=body,
1149
- )
1150
- return bool(success)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pixelarraythirdparty
3
- Version: 1.3.7
3
+ Version: 1.3.9
4
4
  Summary: PixelArray 第三方微服务客户端
5
5
  Author-email: Lu qi <qi.lu@pixelarrayai.com>
6
6
  License-Expression: MIT
@@ -1,13 +1,9 @@
1
- pixelarraythirdparty/__init__.py,sha256=sqCqzAKk3COl1injSXS2ZAENGLFhlWkOW5SEx5KDsS8,1317
2
- pixelarraythirdparty/client.py,sha256=hICdd1MSBrxtoo4BgXSdmKchLehbpJcQ486SO9_fmuI,4459
3
- pixelarraythirdparty/cron/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- pixelarraythirdparty/cron/cron.py,sha256=Cm5J1XppXh1raqtlWwpkdcsztWpQZIGY7FOt1VH9Pz0,3152
5
- pixelarraythirdparty/custom_events/__init__.py,sha256=dwB0mRElWp--5KmDVT7ZhGZ5xZdrO_ZKybngfAHm0zw,92
6
- pixelarraythirdparty/custom_events/custom_events.py,sha256=_5TvbzW0istvX9HGsCWVCSoCy5eHlMHWlab_01i9XzE,8468
1
+ pixelarraythirdparty/__init__.py,sha256=BWY-vq7lCNKdEbkiAoeCCqTsBYw9_cAF5yFlczz5jfc,1078
2
+ pixelarraythirdparty/client.py,sha256=tzlH-aJ0ZeeoHStvF2omDRFv0Kl-why1FLr3fb_J0pM,4459
7
3
  pixelarraythirdparty/feedback/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
4
  pixelarraythirdparty/feedback/feedback.py,sha256=bwR2qeors_G2zFoKkefsJrPrLzCxndoXGru-aVsiRSI,11596
9
5
  pixelarraythirdparty/order/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- pixelarraythirdparty/order/order.py,sha256=ghpnzEbrQLcyDhRBFeBKodZeqhxQhIBQsuAzFM_XCdg,15952
6
+ pixelarraythirdparty/order/order.py,sha256=5bAHV5bvTCnurHgj7SnusRK7rBgjO9DCgBPrWo8MtdE,17841
11
7
  pixelarraythirdparty/product/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
8
  pixelarraythirdparty/product/product.py,sha256=fELowDk073SAmNJ2iW14UjyePH5g1X5YDJMTkVysA3E,7509
13
9
  pixelarraythirdparty/project/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -16,12 +12,12 @@ pixelarraythirdparty/project_dashboard/__init__.py,sha256=yeVhw4smnQ8-xN-BG3aZ7s
16
12
  pixelarraythirdparty/project_dashboard/project_dashboard.py,sha256=vOL6mTU69c8rrotaodrf0pU8GGYDLBk23USyQw_TC50,2747
17
13
  pixelarraythirdparty/support_chat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
14
  pixelarraythirdparty/support_chat/support_chat.py,sha256=AF82p_vaN5Oefywf0H7wqnOAPPDKw2X2W2St2gKdZ7I,5594
19
- pixelarraythirdparty/unified_login/__init__.py,sha256=bWG_I7GMHilWZvnxjVJFmHgxioRdFFvpi95XJunl_fY,403
20
- pixelarraythirdparty/unified_login/unified_login.py,sha256=15fNyhm6HKgjPMO1RCqpxzlAzBAJuGZbRMr4gABh4MA,40575
15
+ pixelarraythirdparty/unified_login/__init__.py,sha256=OUJkqR1uH7pR_733s-tVwUhvpQKnbPvSqHSLg4xD6xM,154
16
+ pixelarraythirdparty/unified_login/unified_login.py,sha256=46ZJruQSIxO6KVKyF4aW1i4trBtE5mEXWOhHGxjMmQ8,13865
21
17
  pixelarraythirdparty/user/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
18
  pixelarraythirdparty/user/user.py,sha256=G-dldmbuUQN1JPicuC2G4qvIY4Mq7pZIPh3lOyWpSqo,5773
23
- pixelarraythirdparty-1.3.7.dist-info/licenses/LICENSE,sha256=O-g1dUr0U50rSIvmWE9toiVkSgFpVt72_MHITbWvAqA,1067
24
- pixelarraythirdparty-1.3.7.dist-info/METADATA,sha256=0vWr-FQSTMLHpopS0gKif1v54y8sqpEZtnCAeejQ6_M,1044
25
- pixelarraythirdparty-1.3.7.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
26
- pixelarraythirdparty-1.3.7.dist-info/top_level.txt,sha256=dzG2Ut8j7noUqj_0ZQjcIDAeHYCh_9WtlxjAxtoyufo,21
27
- pixelarraythirdparty-1.3.7.dist-info/RECORD,,
19
+ pixelarraythirdparty-1.3.9.dist-info/licenses/LICENSE,sha256=O-g1dUr0U50rSIvmWE9toiVkSgFpVt72_MHITbWvAqA,1067
20
+ pixelarraythirdparty-1.3.9.dist-info/METADATA,sha256=sstKgVVJ9xHxQRrcH8N4PjhrRMlHvYejJn8HXxgXCGE,1044
21
+ pixelarraythirdparty-1.3.9.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
22
+ pixelarraythirdparty-1.3.9.dist-info/top_level.txt,sha256=dzG2Ut8j7noUqj_0ZQjcIDAeHYCh_9WtlxjAxtoyufo,21
23
+ pixelarraythirdparty-1.3.9.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (82.0.1)
2
+ Generator: setuptools (83.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
File without changes
@@ -1,80 +0,0 @@
1
- from pixelarraythirdparty.client import AsyncClient
2
-
3
-
4
- class CronManagerAsync(AsyncClient):
5
- async def list_cron_task(self):
6
- """
7
- description:
8
- 获取所有配置的定时任务列表,包括任务详情、执行时间、状态等。
9
- return:
10
- data(dict): 定时任务列表信息
11
- - tasks(list): 定时任务列表
12
- - id(str): 任务ID
13
- - name(str): 任务名称
14
- - description(str): 任务描述
15
- - schedule(str): 执行时间
16
- - enabled(bool): 是否启用
17
- - task_name(str): 任务函数名
18
- - module_name(str): 模块名
19
- - function_name(str): 函数名
20
- - file_path(str): 文件路径
21
- - parameters(list): 参数列表
22
- - task_config(dict): 任务配置
23
- - registration_info(dict): 注册信息
24
- - count(int): 任务数量
25
- - timestamp(str): 获取时间
26
- success(bool): 操作是否成功
27
- """
28
- data, success = await self._request("GET", "/api/cron/tasks/scheduled")
29
- if not success:
30
- return data, False
31
- return data, True
32
-
33
- async def get_cron_tasks_detail(self, task_name: str):
34
- """
35
- description:
36
- 根据任务名称获取指定任务的详细信息。
37
- parameters:
38
- task_name(str): 任务名称
39
- return:
40
- data(dict): 任务详细信息
41
- - task_name(str): 任务名称
42
- - module_name(str): 模块名
43
- - function_name(str): 函数名
44
- - file_path(str): 文件路径
45
- - description(str): 任务描述
46
- - parameters(list): 参数列表
47
- - task_config(dict): 任务配置
48
- - registration_info(dict): 注册信息
49
- - timestamp(str): 获取时间
50
- success(bool): 操作是否成功
51
- """
52
- data, success = await self._request("GET", f"/api/cron/tasks/{task_name}")
53
- if not success:
54
- return data, False
55
- return data, True
56
-
57
- async def trigger_cron_task(self, task_name: str, args: list, kwargs: dict):
58
- """
59
- description:
60
- 手动触发指定任务的执行,支持传递参数。
61
- parameters:
62
- task_name(str): 任务名称
63
- args(list): 任务参数列表
64
- kwargs(dict): 任务关键字参数
65
- return:
66
- data(dict): 任务触发信息
67
- - task_id(str): 任务ID
68
- - task_name(str): 任务名称
69
- - status(str): 任务状态,初始为"PENDING"
70
- - message(str): 触发消息
71
- success(bool): 操作是否成功
72
- """
73
- data, success = await self._request(
74
- "POST",
75
- f"/api/cron/tasks/{task_name}/trigger",
76
- json={"args": args, "kwargs": kwargs},
77
- )
78
- if not success:
79
- return data, False
80
- return data, True
@@ -1,3 +0,0 @@
1
- from .custom_events import CustomEventsManagerAsync
2
-
3
- __all__ = ["CustomEventsManagerAsync"]
@@ -1,220 +0,0 @@
1
- from pixelarraythirdparty.client import AsyncClient
2
- from typing import Any, Dict, List, Optional, Tuple
3
-
4
-
5
- class CustomEventsManagerAsync(AsyncClient):
6
- """自定义事件:新建定义与上报(上报前须存在对应事件定义与字段 schema)。"""
7
-
8
- def _dashboard_params(
9
- self,
10
- project_name: str,
11
- *,
12
- start_date: Optional[str] = None,
13
- end_date: Optional[str] = None,
14
- top_events_limit: Optional[int] = None,
15
- ) -> Dict[str, Any]:
16
- p: Dict[str, Any] = {"project_name": project_name}
17
- if start_date is not None:
18
- p["start_date"] = start_date
19
- if end_date is not None:
20
- p["end_date"] = end_date
21
- if top_events_limit is not None:
22
- p["top_events_limit"] = top_events_limit
23
- return p
24
-
25
- async def create_custom_event_definition(
26
- self,
27
- project_name: str,
28
- event_key: str,
29
- display_name: str,
30
- *,
31
- description: Optional[str] = None,
32
- category: str = "custom",
33
- field_schema: Optional[List[Dict[str, Any]]] = None,
34
- is_active: bool = True,
35
- ) -> Tuple[Dict[str, Any], bool]:
36
- """
37
- description:
38
- 新建自定义事件定义(需 API Key;与 Portal「新建定义」一致,对应 POST /api/custom-events/definitions/create)
39
- parameters:
40
- project_name(str): 项目名称(与 projects.name 一致)
41
- event_key(str): 事件键(1~64 字符)
42
- display_name(str): 展示名
43
- description(str, optional): 说明
44
- category(str): 分类白名单,默认 custom
45
- field_schema(list, optional): 字段定义列表,每项含 name、type(string|number|boolean)、required、description
46
- is_active(bool): 是否启用,默认 True
47
- return:
48
- data(dict): 成功时为定义记录;失败时含 message 等
49
- success(bool): 是否成功
50
- """
51
- body: Dict[str, Any] = {
52
- "project_name": project_name,
53
- "event_key": event_key,
54
- "display_name": display_name,
55
- "category": category,
56
- "field_schema": field_schema or [],
57
- "is_active": is_active,
58
- }
59
- if description is not None:
60
- body["description"] = description
61
- return await self._request(
62
- "POST", "/api/custom-events/definitions/create", json=body
63
- )
64
-
65
- async def get_custom_event_common_field_schema(
66
- self, project_name: str
67
- ) -> Tuple[Dict[str, Any], bool]:
68
- """
69
- description:
70
- 查询项目自定义事件公共维度(GET /api/custom-events/common-field-schema)
71
- """
72
- return await self._request(
73
- "GET",
74
- "/api/custom-events/common-field-schema",
75
- params={"project_name": project_name},
76
- )
77
-
78
- async def update_custom_event_common_field_schema(
79
- self,
80
- project_name: str,
81
- field_schema: List[Dict[str, Any]],
82
- ) -> Tuple[Dict[str, Any], bool]:
83
- """
84
- description:
85
- 更新项目公共维度;传空列表表示恢复服务端内置默认(POST /api/custom-events/common-field-schema/update)
86
- parameters:
87
- field_schema: 每项含 name、type、required、可选 nullable、description
88
- """
89
- body: Dict[str, Any] = {
90
- "project_name": project_name,
91
- "field_schema": field_schema or [],
92
- }
93
- return await self._request(
94
- "POST", "/api/custom-events/common-field-schema/update", json=body
95
- )
96
-
97
- async def report_custom_event(
98
- self,
99
- project_name: str,
100
- event_key: str,
101
- payload: Dict[str, Any],
102
- client_occurred_at: Optional[str] = None,
103
- ) -> Tuple[Dict[str, Any], bool]:
104
- """
105
- description:
106
- 上报单条自定义事件日志(有效载荷 = 项目公共维度 ∪ 该事件 field_schema;未传的公共键由服务端补 null)
107
- parameters:
108
- project_name(str): 项目名称(与 projects.name 一致)
109
- event_key(str): 事件键
110
- payload(dict): JSON 对象,键与类型须与定义一致
111
- client_occurred_at(str, optional): 客户端事件发生时间,ISO8601 字符串
112
- return:
113
- data(dict): 成功时为日志记录;失败时含 message 等
114
- success(bool): 是否成功
115
- """
116
- data: Dict[str, Any] = {
117
- "project_name": project_name,
118
- "event_key": event_key,
119
- "payload": payload,
120
- }
121
- if client_occurred_at is not None:
122
- data["client_occurred_at"] = client_occurred_at
123
- return await self._request("POST", "/api/custom-events/report", json=data)
124
-
125
- async def report_custom_events_batch(
126
- self,
127
- project_name: str,
128
- items: List[Dict[str, Any]],
129
- ) -> Tuple[Dict[str, Any], bool]:
130
- """
131
- description:
132
- 批量上报自定义事件(同一 project_name;任一条校验失败则整批失败)
133
- parameters:
134
- project_name(str): 项目名称(与 projects.name 一致)
135
- items(list): 每项为 dict,须含 event_key、payload;可选 client_occurred_at(字符串)
136
- return:
137
- data(dict): 成功时为日志列表;失败时含 message
138
- success(bool): 是否成功
139
- """
140
- body = {"project_name": project_name, "items": items}
141
- return await self._request("POST", "/api/custom-events/report/batch", json=body)
142
-
143
- async def get_custom_events_dashboard_summary(
144
- self,
145
- project_name: str,
146
- *,
147
- start_date: Optional[str] = None,
148
- end_date: Optional[str] = None,
149
- ) -> Tuple[Dict[str, Any], bool]:
150
- """GET /api/custom-events/dashboard/summary:KPI 摘要(需 API Key 或 JWT)。"""
151
- params = self._dashboard_params(
152
- project_name, start_date=start_date, end_date=end_date
153
- )
154
- return await self._request(
155
- "GET", "/api/custom-events/dashboard/summary", params=params
156
- )
157
-
158
- async def get_custom_events_dashboard_daily(
159
- self,
160
- project_name: str,
161
- *,
162
- start_date: Optional[str] = None,
163
- end_date: Optional[str] = None,
164
- ) -> Tuple[Dict[str, Any], bool]:
165
- """GET /api/custom-events/dashboard/daily:按日事件量(UTC)。"""
166
- params = self._dashboard_params(
167
- project_name, start_date=start_date, end_date=end_date
168
- )
169
- return await self._request(
170
- "GET", "/api/custom-events/dashboard/daily", params=params
171
- )
172
-
173
- async def get_custom_events_dashboard_top_events(
174
- self,
175
- project_name: str,
176
- *,
177
- start_date: Optional[str] = None,
178
- end_date: Optional[str] = None,
179
- top_events_limit: Optional[int] = None,
180
- ) -> Tuple[Dict[str, Any], bool]:
181
- """GET /api/custom-events/dashboard/top-events:事件量 TOP N。"""
182
- params = self._dashboard_params(
183
- project_name,
184
- start_date=start_date,
185
- end_date=end_date,
186
- top_events_limit=top_events_limit,
187
- )
188
- return await self._request(
189
- "GET", "/api/custom-events/dashboard/top-events", params=params
190
- )
191
-
192
- async def get_custom_events_dashboard_by_category(
193
- self,
194
- project_name: str,
195
- *,
196
- start_date: Optional[str] = None,
197
- end_date: Optional[str] = None,
198
- ) -> Tuple[Dict[str, Any], bool]:
199
- """GET /api/custom-events/dashboard/by-category:按分类分布。"""
200
- params = self._dashboard_params(
201
- project_name, start_date=start_date, end_date=end_date
202
- )
203
- return await self._request(
204
- "GET", "/api/custom-events/dashboard/by-category", params=params
205
- )
206
-
207
- async def get_custom_events_dashboard_by_hour(
208
- self,
209
- project_name: str,
210
- *,
211
- start_date: Optional[str] = None,
212
- end_date: Optional[str] = None,
213
- ) -> Tuple[Dict[str, Any], bool]:
214
- """GET /api/custom-events/dashboard/by-hour:按 UTC 小时分布。"""
215
- params = self._dashboard_params(
216
- project_name, start_date=start_date, end_date=end_date
217
- )
218
- return await self._request(
219
- "GET", "/api/custom-events/dashboard/by-hour", params=params
220
- )