pixelarraythirdparty 1.3.7__tar.gz → 1.3.9__tar.gz
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-1.3.7 → pixelarraythirdparty-1.3.9}/PKG-INFO +1 -1
- {pixelarraythirdparty-1.3.7 → pixelarraythirdparty-1.3.9}/pixelarraythirdparty/__init__.py +1 -7
- {pixelarraythirdparty-1.3.7 → pixelarraythirdparty-1.3.9}/pixelarraythirdparty/client.py +1 -1
- {pixelarraythirdparty-1.3.7 → pixelarraythirdparty-1.3.9}/pixelarraythirdparty/order/order.py +44 -1
- pixelarraythirdparty-1.3.9/pixelarraythirdparty/unified_login/__init__.py +11 -0
- pixelarraythirdparty-1.3.9/pixelarraythirdparty/unified_login/unified_login.py +377 -0
- {pixelarraythirdparty-1.3.7 → pixelarraythirdparty-1.3.9}/pixelarraythirdparty.egg-info/PKG-INFO +1 -1
- {pixelarraythirdparty-1.3.7 → pixelarraythirdparty-1.3.9}/pixelarraythirdparty.egg-info/SOURCES.txt +0 -4
- {pixelarraythirdparty-1.3.7 → pixelarraythirdparty-1.3.9}/pyproject.toml +1 -1
- pixelarraythirdparty-1.3.7/pixelarraythirdparty/cron/cron.py +0 -80
- pixelarraythirdparty-1.3.7/pixelarraythirdparty/custom_events/__init__.py +0 -3
- pixelarraythirdparty-1.3.7/pixelarraythirdparty/custom_events/custom_events.py +0 -220
- pixelarraythirdparty-1.3.7/pixelarraythirdparty/unified_login/__init__.py +0 -26
- pixelarraythirdparty-1.3.7/pixelarraythirdparty/unified_login/unified_login.py +0 -1150
- pixelarraythirdparty-1.3.7/pixelarraythirdparty/user/__init__.py +0 -0
- {pixelarraythirdparty-1.3.7 → pixelarraythirdparty-1.3.9}/LICENSE +0 -0
- {pixelarraythirdparty-1.3.7 → pixelarraythirdparty-1.3.9}/MANIFEST.in +0 -0
- {pixelarraythirdparty-1.3.7/pixelarraythirdparty/cron → pixelarraythirdparty-1.3.9/pixelarraythirdparty/feedback}/__init__.py +0 -0
- {pixelarraythirdparty-1.3.7 → pixelarraythirdparty-1.3.9}/pixelarraythirdparty/feedback/feedback.py +0 -0
- {pixelarraythirdparty-1.3.7/pixelarraythirdparty/feedback → pixelarraythirdparty-1.3.9/pixelarraythirdparty/order}/__init__.py +0 -0
- {pixelarraythirdparty-1.3.7/pixelarraythirdparty/order → pixelarraythirdparty-1.3.9/pixelarraythirdparty/product}/__init__.py +0 -0
- {pixelarraythirdparty-1.3.7 → pixelarraythirdparty-1.3.9}/pixelarraythirdparty/product/product.py +0 -0
- {pixelarraythirdparty-1.3.7/pixelarraythirdparty/product → pixelarraythirdparty-1.3.9/pixelarraythirdparty/project}/__init__.py +0 -0
- {pixelarraythirdparty-1.3.7 → pixelarraythirdparty-1.3.9}/pixelarraythirdparty/project/project.py +0 -0
- {pixelarraythirdparty-1.3.7 → pixelarraythirdparty-1.3.9}/pixelarraythirdparty/project_dashboard/__init__.py +0 -0
- {pixelarraythirdparty-1.3.7 → pixelarraythirdparty-1.3.9}/pixelarraythirdparty/project_dashboard/project_dashboard.py +0 -0
- {pixelarraythirdparty-1.3.7/pixelarraythirdparty/project → pixelarraythirdparty-1.3.9/pixelarraythirdparty/support_chat}/__init__.py +0 -0
- {pixelarraythirdparty-1.3.7 → pixelarraythirdparty-1.3.9}/pixelarraythirdparty/support_chat/support_chat.py +0 -0
- {pixelarraythirdparty-1.3.7/pixelarraythirdparty/support_chat → pixelarraythirdparty-1.3.9/pixelarraythirdparty/user}/__init__.py +0 -0
- {pixelarraythirdparty-1.3.7 → pixelarraythirdparty-1.3.9}/pixelarraythirdparty/user/user.py +0 -0
- {pixelarraythirdparty-1.3.7 → pixelarraythirdparty-1.3.9}/pixelarraythirdparty.egg-info/dependency_links.txt +0 -0
- {pixelarraythirdparty-1.3.7 → pixelarraythirdparty-1.3.9}/pixelarraythirdparty.egg-info/requires.txt +0 -0
- {pixelarraythirdparty-1.3.7 → pixelarraythirdparty-1.3.9}/pixelarraythirdparty.egg-info/top_level.txt +0 -0
- {pixelarraythirdparty-1.3.7 → pixelarraythirdparty-1.3.9}/setup.cfg +0 -0
|
@@ -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
|
]
|
|
@@ -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",
|
{pixelarraythirdparty-1.3.7 → pixelarraythirdparty-1.3.9}/pixelarraythirdparty/order/order.py
RENAMED
|
@@ -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
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import urllib.parse
|
|
3
|
+
import webbrowser
|
|
4
|
+
from typing import Any, Dict, Optional, Tuple
|
|
5
|
+
|
|
6
|
+
from pixelarraythirdparty.client import AsyncClient
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _normalized_project_name(project_name: Optional[str]) -> Optional[str]:
|
|
10
|
+
if project_name is None:
|
|
11
|
+
return None
|
|
12
|
+
s = str(project_name).strip()
|
|
13
|
+
return s or None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _auth_url_json(project_name: Optional[str]) -> Optional[Dict[str, Any]]:
|
|
17
|
+
s = _normalized_project_name(project_name)
|
|
18
|
+
if s is None:
|
|
19
|
+
return None
|
|
20
|
+
return {"project_name": s}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class OAuth2Login(AsyncClient):
|
|
24
|
+
"""
|
|
25
|
+
统一的 OAuth2 登录客户端基类
|
|
26
|
+
|
|
27
|
+
支持所有基于 OAuth2 的第三方登录(Google、微信、GitHub、GitLab、抖音、TikTok等)
|
|
28
|
+
通过配置不同的端点来支持不同的提供商
|
|
29
|
+
|
|
30
|
+
使用示例:
|
|
31
|
+
```
|
|
32
|
+
# 服务端使用场景(推荐)
|
|
33
|
+
oauth2_login = OAuth2Login(
|
|
34
|
+
api_key="your_api_key",
|
|
35
|
+
provider="google" # 支持的提供商:google, wechat, github, gitlab, douyin
|
|
36
|
+
)
|
|
37
|
+
# 1. 获取授权URL
|
|
38
|
+
auth_data, success = await oauth2_login.get_auth_url()
|
|
39
|
+
if success:
|
|
40
|
+
auth_url = auth_data.get("auth_url")
|
|
41
|
+
state = auth_data.get("state")
|
|
42
|
+
# 将auth_url返回给前端,让用户点击授权
|
|
43
|
+
# 2. 等待登录结果(在服务端轮询)
|
|
44
|
+
user_info, success = await oauth2_login.wait_for_login(state, timeout=180)
|
|
45
|
+
print(user_info, success)
|
|
46
|
+
```
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
# 提供商端点映射
|
|
50
|
+
PROVIDER_ENDPOINTS = {
|
|
51
|
+
"wechat": {
|
|
52
|
+
"auth_url": "/api/unified-login/wechat/auth-url",
|
|
53
|
+
"wait_login": "/api/unified-login/wechat/wait-login",
|
|
54
|
+
},
|
|
55
|
+
"wechat-official": {
|
|
56
|
+
"auth_url": "/api/unified-login/wechat-official/auth-url",
|
|
57
|
+
"wait_login": "/api/unified-login/wechat-official/wait-login",
|
|
58
|
+
},
|
|
59
|
+
"douyin": {
|
|
60
|
+
"auth_url": "/api/unified-login/douyin/auth-url",
|
|
61
|
+
"wait_login": "/api/unified-login/douyin/wait-login",
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
def __init__(
|
|
66
|
+
self,
|
|
67
|
+
api_key: str,
|
|
68
|
+
provider: str,
|
|
69
|
+
login_type: Optional[str] = None,
|
|
70
|
+
project_name: Optional[str] = None,
|
|
71
|
+
):
|
|
72
|
+
"""
|
|
73
|
+
description:
|
|
74
|
+
初始化OAuth2登录客户端
|
|
75
|
+
parameters:
|
|
76
|
+
api_key(str): API密钥
|
|
77
|
+
provider(str): 提供商名称,可选值:wechat, douyin
|
|
78
|
+
login_type(str, optional): 登录类型,仅对微信有效,可选值:desktop(PC端扫码), mobile(手机端)
|
|
79
|
+
project_name(str, optional): 绑定项目名;不传或空字符串则匿名登录
|
|
80
|
+
"""
|
|
81
|
+
super().__init__(api_key)
|
|
82
|
+
self.provider = provider.lower()
|
|
83
|
+
self.login_type = login_type
|
|
84
|
+
self._project_name = project_name
|
|
85
|
+
|
|
86
|
+
# 验证提供商是否支持
|
|
87
|
+
if self.provider not in self.PROVIDER_ENDPOINTS:
|
|
88
|
+
raise ValueError(
|
|
89
|
+
f"不支持的提供商: {provider}。"
|
|
90
|
+
f"支持的提供商: {', '.join(self.PROVIDER_ENDPOINTS.keys())}"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# 微信特殊处理:根据login_type选择不同的端点
|
|
94
|
+
if self.provider == "wechat":
|
|
95
|
+
if login_type == "mobile":
|
|
96
|
+
self.provider = "wechat-official"
|
|
97
|
+
|
|
98
|
+
def _get_endpoint(self, endpoint_type: str) -> str:
|
|
99
|
+
"""
|
|
100
|
+
description:
|
|
101
|
+
获取指定类型的端点URL
|
|
102
|
+
parameters:
|
|
103
|
+
endpoint_type(str): 端点类型,可选值:auth_url, wait_login, refresh_token
|
|
104
|
+
return:
|
|
105
|
+
endpoint(str): 端点URL
|
|
106
|
+
"""
|
|
107
|
+
endpoints = self.PROVIDER_ENDPOINTS.get(self.provider, {})
|
|
108
|
+
endpoint = endpoints.get(endpoint_type)
|
|
109
|
+
if not endpoint:
|
|
110
|
+
raise ValueError(f"提供商 {self.provider} 不支持 {endpoint_type} 端点")
|
|
111
|
+
return endpoint
|
|
112
|
+
|
|
113
|
+
async def get_auth_url(self) -> Tuple[Optional[Dict[str, str]], bool]:
|
|
114
|
+
"""
|
|
115
|
+
description:
|
|
116
|
+
获取OAuth授权URL(公共方法,供服务端调用)
|
|
117
|
+
|
|
118
|
+
服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
|
|
119
|
+
获取到state后,使用wait_for_login方法等待登录结果。
|
|
120
|
+
return:
|
|
121
|
+
auth_data(dict, optional): 授权数据字典,包含auth_url和state
|
|
122
|
+
success(bool): 是否成功
|
|
123
|
+
"""
|
|
124
|
+
endpoint = self._get_endpoint("auth_url")
|
|
125
|
+
payload = _auth_url_json(self._project_name)
|
|
126
|
+
if payload:
|
|
127
|
+
data, success = await self._request("POST", endpoint, json=payload)
|
|
128
|
+
else:
|
|
129
|
+
data, success = await self._request("POST", endpoint)
|
|
130
|
+
if not success:
|
|
131
|
+
return data, False
|
|
132
|
+
auth_url = data.get("auth_url")
|
|
133
|
+
if not auth_url:
|
|
134
|
+
return None, False
|
|
135
|
+
return data, True
|
|
136
|
+
|
|
137
|
+
async def wait_for_login(self, state: str, timeout: int = 180) -> Tuple[Dict, bool]:
|
|
138
|
+
"""
|
|
139
|
+
description:
|
|
140
|
+
等待登录结果,轮询检查登录状态(公共方法,供服务端调用)
|
|
141
|
+
|
|
142
|
+
服务端在用户点击授权后,调用此方法轮询等待登录结果。
|
|
143
|
+
此方法会持续轮询直到登录成功或超时。
|
|
144
|
+
parameters:
|
|
145
|
+
state(str): 登录状态标识,从get_auth_url返回的auth_data中获取
|
|
146
|
+
timeout(int, optional): 超时时间(秒),默认为180
|
|
147
|
+
return:
|
|
148
|
+
user_info(dict): 用户信息字典
|
|
149
|
+
success(bool): 是否成功
|
|
150
|
+
"""
|
|
151
|
+
interval = 2
|
|
152
|
+
total_checks = max(1, timeout // interval) if timeout > 0 else 1
|
|
153
|
+
endpoint = self._get_endpoint("wait_login")
|
|
154
|
+
|
|
155
|
+
for _ in range(total_checks):
|
|
156
|
+
status, response = await self._request_raw(
|
|
157
|
+
"POST",
|
|
158
|
+
endpoint,
|
|
159
|
+
json={"state": state},
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
if status == 200 and response.get("success") is True:
|
|
163
|
+
return response.get("data", {}), True
|
|
164
|
+
|
|
165
|
+
if status in (400, 408):
|
|
166
|
+
break
|
|
167
|
+
|
|
168
|
+
await asyncio.sleep(interval)
|
|
169
|
+
|
|
170
|
+
return {"message": "登录超时或未完成授权"}, False
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class WechatLogin(AsyncClient):
|
|
174
|
+
"""
|
|
175
|
+
微信 OAuth2 登录客户端
|
|
176
|
+
|
|
177
|
+
支持两种登录方式:
|
|
178
|
+
- desktop: PC端扫码登录(使用微信开放平台)
|
|
179
|
+
- mobile: 微信公众号OAuth登录(手机端微信内打开)
|
|
180
|
+
|
|
181
|
+
使用示例:
|
|
182
|
+
```
|
|
183
|
+
# 服务端使用场景(推荐)
|
|
184
|
+
wechat = WechatLogin(api_key="your_api_key")
|
|
185
|
+
# PC端扫码登录
|
|
186
|
+
auth_data, success = await wechat.get_auth_url(login_type="desktop")
|
|
187
|
+
if success:
|
|
188
|
+
auth_url = auth_data.get("auth_url")
|
|
189
|
+
state = auth_data.get("state")
|
|
190
|
+
# 将auth_url返回给前端,让用户扫码授权
|
|
191
|
+
# 等待登录结果
|
|
192
|
+
user_info, success = await wechat.wait_for_login(state, timeout=180, login_type="desktop")
|
|
193
|
+
print(user_info, success)
|
|
194
|
+
|
|
195
|
+
# 微信公众号登录(手机端)
|
|
196
|
+
auth_data, success = await wechat.get_auth_url(login_type="mobile")
|
|
197
|
+
if success:
|
|
198
|
+
auth_url = auth_data.get("auth_url")
|
|
199
|
+
state = auth_data.get("state")
|
|
200
|
+
# 将auth_url返回给前端,让用户在微信内打开授权
|
|
201
|
+
# 等待登录结果
|
|
202
|
+
user_info, success = await wechat.wait_for_login(state, timeout=180, login_type="mobile")
|
|
203
|
+
print(user_info, success)
|
|
204
|
+
```
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
def __init__(self, api_key: str):
|
|
208
|
+
super().__init__(api_key)
|
|
209
|
+
|
|
210
|
+
async def get_auth_url(
|
|
211
|
+
self, login_type: str = "desktop", project_name: Optional[str] = None
|
|
212
|
+
) -> Tuple[Optional[Dict[str, str]], bool]:
|
|
213
|
+
"""
|
|
214
|
+
description:
|
|
215
|
+
获取微信授权URL(公共方法,供服务端调用)
|
|
216
|
+
|
|
217
|
+
服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
|
|
218
|
+
获取到state后,使用wait_for_login方法等待登录结果。
|
|
219
|
+
parameters:
|
|
220
|
+
login_type(str, optional): 登录类型,desktop表示PC端扫码登录,mobile表示微信公众号登录,默认为desktop
|
|
221
|
+
project_name(str, optional): 绑定项目名;不传或空字符串则匿名登录
|
|
222
|
+
return:
|
|
223
|
+
auth_data(dict, optional): 授权数据字典,包含auth_url和state
|
|
224
|
+
success(bool): 是否成功
|
|
225
|
+
"""
|
|
226
|
+
if login_type == "mobile":
|
|
227
|
+
# 微信公众号OAuth登录(手机端)
|
|
228
|
+
endpoint = "/api/unified-login/wechat-official/auth-url"
|
|
229
|
+
else:
|
|
230
|
+
# PC端扫码登录
|
|
231
|
+
endpoint = "/api/unified-login/wechat/auth-url"
|
|
232
|
+
|
|
233
|
+
payload = _auth_url_json(project_name)
|
|
234
|
+
if payload:
|
|
235
|
+
data, success = await self._request("POST", endpoint, json=payload)
|
|
236
|
+
else:
|
|
237
|
+
data, success = await self._request("POST", endpoint)
|
|
238
|
+
if not success:
|
|
239
|
+
return data, False
|
|
240
|
+
auth_url = data.get("auth_url")
|
|
241
|
+
if not auth_url:
|
|
242
|
+
return None, False
|
|
243
|
+
return data, True
|
|
244
|
+
|
|
245
|
+
async def wait_for_login(
|
|
246
|
+
self, state: str, timeout: int = 180, login_type: str = "desktop"
|
|
247
|
+
) -> Tuple[Dict, bool]:
|
|
248
|
+
"""
|
|
249
|
+
description:
|
|
250
|
+
等待微信登录结果,轮询检查登录状态(公共方法,供服务端调用)
|
|
251
|
+
|
|
252
|
+
服务端在用户点击授权后,调用此方法轮询等待登录结果。
|
|
253
|
+
此方法会持续轮询直到登录成功或超时。
|
|
254
|
+
parameters:
|
|
255
|
+
state(str): 登录状态标识,从get_auth_url返回的auth_data中获取
|
|
256
|
+
timeout(int, optional): 超时时间(秒),默认为180
|
|
257
|
+
login_type(str, optional): 登录类型,desktop表示PC端扫码登录,mobile表示微信公众号登录,默认为desktop
|
|
258
|
+
return:
|
|
259
|
+
user_info(dict): 用户信息字典
|
|
260
|
+
success(bool): 是否成功
|
|
261
|
+
"""
|
|
262
|
+
interval = 2
|
|
263
|
+
total_checks = max(1, timeout // interval) if timeout > 0 else 1
|
|
264
|
+
|
|
265
|
+
# 根据登录类型选择不同的等待接口
|
|
266
|
+
if login_type == "mobile":
|
|
267
|
+
endpoint = "/api/unified-login/wechat-official/wait-login"
|
|
268
|
+
else:
|
|
269
|
+
endpoint = "/api/unified-login/wechat/wait-login"
|
|
270
|
+
|
|
271
|
+
for _ in range(total_checks):
|
|
272
|
+
status, response = await self._request_raw(
|
|
273
|
+
"POST",
|
|
274
|
+
endpoint,
|
|
275
|
+
json={"state": state},
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
if status == 200 and response.get("success") is True:
|
|
279
|
+
return response.get("data", {}), True
|
|
280
|
+
|
|
281
|
+
if status in (400, 408):
|
|
282
|
+
break
|
|
283
|
+
|
|
284
|
+
await asyncio.sleep(interval)
|
|
285
|
+
|
|
286
|
+
return {"message": "登录超时或未完成授权"}, False
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class DouyinLogin(AsyncClient):
|
|
290
|
+
"""
|
|
291
|
+
抖音 OAuth2 登录客户端
|
|
292
|
+
|
|
293
|
+
使用示例:
|
|
294
|
+
```
|
|
295
|
+
# 服务端使用场景(推荐)
|
|
296
|
+
douyin = DouyinLogin(api_key="your_api_key")
|
|
297
|
+
# 1. 获取授权URL
|
|
298
|
+
auth_data, success = await douyin.get_auth_url()
|
|
299
|
+
if success:
|
|
300
|
+
auth_url = auth_data.get("auth_url")
|
|
301
|
+
state = auth_data.get("state")
|
|
302
|
+
# 将auth_url返回给前端,让用户点击授权
|
|
303
|
+
# 2. 等待登录结果(在服务端轮询)
|
|
304
|
+
user_info, success = await douyin.wait_for_login(state, timeout=180)
|
|
305
|
+
print(user_info, success)
|
|
306
|
+
```
|
|
307
|
+
"""
|
|
308
|
+
|
|
309
|
+
def __init__(self, api_key: str):
|
|
310
|
+
super().__init__(api_key)
|
|
311
|
+
|
|
312
|
+
async def get_auth_url(
|
|
313
|
+
self, project_name: Optional[str] = None
|
|
314
|
+
) -> Tuple[Optional[Dict[str, str]], bool]:
|
|
315
|
+
"""
|
|
316
|
+
description:
|
|
317
|
+
获取抖音OAuth授权URL(公共方法,供服务端调用)
|
|
318
|
+
|
|
319
|
+
服务端应该调用此方法获取授权URL,然后将URL返回给前端让用户点击授权。
|
|
320
|
+
获取到state后,使用wait_for_login方法等待登录结果。
|
|
321
|
+
parameters:
|
|
322
|
+
project_name(str, optional): 绑定项目名;不传或空字符串则匿名登录
|
|
323
|
+
return:
|
|
324
|
+
auth_data(dict, optional): 授权数据字典,包含auth_url和state
|
|
325
|
+
success(bool): 是否成功
|
|
326
|
+
"""
|
|
327
|
+
payload = _auth_url_json(project_name)
|
|
328
|
+
if payload:
|
|
329
|
+
data, success = await self._request(
|
|
330
|
+
"POST", "/api/unified-login/douyin/auth-url", json=payload
|
|
331
|
+
)
|
|
332
|
+
else:
|
|
333
|
+
data, success = await self._request(
|
|
334
|
+
"POST", "/api/unified-login/douyin/auth-url"
|
|
335
|
+
)
|
|
336
|
+
if not success:
|
|
337
|
+
return data, False
|
|
338
|
+
auth_url = data.get("auth_url")
|
|
339
|
+
if not auth_url:
|
|
340
|
+
return None, False
|
|
341
|
+
return data, True
|
|
342
|
+
|
|
343
|
+
async def wait_for_login(self, state: str, timeout: int = 180) -> Tuple[Dict, bool]:
|
|
344
|
+
"""
|
|
345
|
+
description:
|
|
346
|
+
等待抖音登录结果,轮询检查登录状态(公共方法,供服务端调用)
|
|
347
|
+
|
|
348
|
+
服务端在用户点击授权后,调用此方法轮询等待登录结果。
|
|
349
|
+
此方法会持续轮询直到登录成功或超时。
|
|
350
|
+
parameters:
|
|
351
|
+
state(str): 登录状态标识,从get_auth_url返回的auth_data中获取
|
|
352
|
+
timeout(int, optional): 超时时间(秒),默认为180
|
|
353
|
+
return:
|
|
354
|
+
user_info(dict): 用户信息字典
|
|
355
|
+
success(bool): 是否成功
|
|
356
|
+
"""
|
|
357
|
+
interval = 2
|
|
358
|
+
total_checks = max(1, timeout // interval) if timeout > 0 else 1
|
|
359
|
+
|
|
360
|
+
for _ in range(total_checks):
|
|
361
|
+
status, response = await self._request_raw(
|
|
362
|
+
"POST",
|
|
363
|
+
"/api/unified-login/douyin/wait-login",
|
|
364
|
+
json={"state": state},
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
if status == 200 and response.get("success") is True:
|
|
368
|
+
return response.get("data", {}), True
|
|
369
|
+
|
|
370
|
+
if status in (400, 408):
|
|
371
|
+
break
|
|
372
|
+
|
|
373
|
+
await asyncio.sleep(interval)
|
|
374
|
+
|
|
375
|
+
return {"message": "登录超时或未完成授权"}, False
|
|
376
|
+
|
|
377
|
+
|
{pixelarraythirdparty-1.3.7 → pixelarraythirdparty-1.3.9}/pixelarraythirdparty.egg-info/SOURCES.txt
RENAMED
|
@@ -8,10 +8,6 @@ pixelarraythirdparty.egg-info/SOURCES.txt
|
|
|
8
8
|
pixelarraythirdparty.egg-info/dependency_links.txt
|
|
9
9
|
pixelarraythirdparty.egg-info/requires.txt
|
|
10
10
|
pixelarraythirdparty.egg-info/top_level.txt
|
|
11
|
-
pixelarraythirdparty/cron/__init__.py
|
|
12
|
-
pixelarraythirdparty/cron/cron.py
|
|
13
|
-
pixelarraythirdparty/custom_events/__init__.py
|
|
14
|
-
pixelarraythirdparty/custom_events/custom_events.py
|
|
15
11
|
pixelarraythirdparty/feedback/__init__.py
|
|
16
12
|
pixelarraythirdparty/feedback/feedback.py
|
|
17
13
|
pixelarraythirdparty/order/__init__.py
|
|
@@ -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
|