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.
- pixelarraythirdparty/__init__.py +1 -7
- pixelarraythirdparty/client.py +1 -1
- pixelarraythirdparty/order/order.py +44 -1
- pixelarraythirdparty/unified_login/__init__.py +0 -15
- pixelarraythirdparty/unified_login/unified_login.py +1 -774
- {pixelarraythirdparty-1.3.7.dist-info → pixelarraythirdparty-1.3.9.dist-info}/METADATA +1 -1
- {pixelarraythirdparty-1.3.7.dist-info → pixelarraythirdparty-1.3.9.dist-info}/RECORD +10 -14
- {pixelarraythirdparty-1.3.7.dist-info → pixelarraythirdparty-1.3.9.dist-info}/WHEEL +1 -1
- pixelarraythirdparty/cron/__init__.py +0 -0
- pixelarraythirdparty/cron/cron.py +0 -80
- pixelarraythirdparty/custom_events/__init__.py +0 -3
- pixelarraythirdparty/custom_events/custom_events.py +0 -220
- {pixelarraythirdparty-1.3.7.dist-info → pixelarraythirdparty-1.3.9.dist-info}/licenses/LICENSE +0 -0
- {pixelarraythirdparty-1.3.7.dist-info → pixelarraythirdparty-1.3.9.dist-info}/top_level.txt +0 -0
pixelarraythirdparty/__init__.py
CHANGED
|
@@ -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.
|
|
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
|
]
|
pixelarraythirdparty/client.py
CHANGED
|
@@ -10,7 +10,7 @@ class AsyncClient:
|
|
|
10
10
|
parameters:
|
|
11
11
|
api_key(str): API密钥,用于身份验证
|
|
12
12
|
"""
|
|
13
|
-
self.base_url = "https://thirdparty.
|
|
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"
|
|
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): 提供商名称,可选值:
|
|
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,13 +1,9 @@
|
|
|
1
|
-
pixelarraythirdparty/__init__.py,sha256=
|
|
2
|
-
pixelarraythirdparty/client.py,sha256=
|
|
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=
|
|
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=
|
|
20
|
-
pixelarraythirdparty/unified_login/unified_login.py,sha256=
|
|
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.
|
|
24
|
-
pixelarraythirdparty-1.3.
|
|
25
|
-
pixelarraythirdparty-1.3.
|
|
26
|
-
pixelarraythirdparty-1.3.
|
|
27
|
-
pixelarraythirdparty-1.3.
|
|
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,,
|
|
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,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
|
-
)
|
{pixelarraythirdparty-1.3.7.dist-info → pixelarraythirdparty-1.3.9.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|