huace-aigc-auth-client 1.1.0__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.
- huace_aigc_auth_client/__init__.py +93 -0
- huace_aigc_auth_client/legacy_adapter.py +483 -0
- huace_aigc_auth_client/sdk.py +704 -0
- huace_aigc_auth_client-1.1.0.dist-info/METADATA +616 -0
- huace_aigc_auth_client-1.1.0.dist-info/RECORD +8 -0
- huace_aigc_auth_client-1.1.0.dist-info/WHEEL +5 -0
- huace_aigc_auth_client-1.1.0.dist-info/licenses/LICENSE +22 -0
- huace_aigc_auth_client-1.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIGC Auth Python SDK
|
|
3
|
+
|
|
4
|
+
使用方法:
|
|
5
|
+
from sdk import AigcAuthClient, require_auth
|
|
6
|
+
|
|
7
|
+
# 创建客户端
|
|
8
|
+
client = AigcAuthClient(app_id="your_app_id", app_secret="your_app_secret")
|
|
9
|
+
|
|
10
|
+
# 验证 token
|
|
11
|
+
result = client.verify_token(token)
|
|
12
|
+
|
|
13
|
+
# 获取用户信息
|
|
14
|
+
user_info = client.get_user_info(token)
|
|
15
|
+
|
|
16
|
+
# FastAPI 中间件使用
|
|
17
|
+
@app.get("/protected")
|
|
18
|
+
@require_auth(client)
|
|
19
|
+
def protected_route(user_info: dict):
|
|
20
|
+
return {"user": user_info}
|
|
21
|
+
|
|
22
|
+
旧系统接入:
|
|
23
|
+
from sdk import AigcAuthClient
|
|
24
|
+
from sdk.legacy_adapter import (
|
|
25
|
+
LegacySystemAdapter,
|
|
26
|
+
SyncConfig,
|
|
27
|
+
UserSyncService,
|
|
28
|
+
FieldMapping,
|
|
29
|
+
create_sync_config,
|
|
30
|
+
create_default_field_mappings
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# 创建自定义字段映射(根据接入系统的用户表定制)
|
|
34
|
+
field_mappings = create_default_field_mappings()
|
|
35
|
+
# 或自定义: field_mappings = [FieldMapping(...), ...]
|
|
36
|
+
|
|
37
|
+
sync_config = create_sync_config(
|
|
38
|
+
field_mappings=field_mappings,
|
|
39
|
+
webhook_url="https://your-domain.com/webhook"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# 实现适配器并创建同步服务
|
|
43
|
+
adapter = YourLegacyAdapter(sync_config)
|
|
44
|
+
sync_service = UserSyncService(client, adapter)
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
from .sdk import (
|
|
48
|
+
AigcAuthClient,
|
|
49
|
+
require_auth,
|
|
50
|
+
AuthMiddleware,
|
|
51
|
+
UserInfo,
|
|
52
|
+
TokenVerifyResult,
|
|
53
|
+
AigcAuthError,
|
|
54
|
+
create_fastapi_auth_dependency
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
from .legacy_adapter import (
|
|
58
|
+
LegacySystemAdapter,
|
|
59
|
+
LegacyUserData,
|
|
60
|
+
SyncConfig,
|
|
61
|
+
SyncDirection,
|
|
62
|
+
PasswordMode,
|
|
63
|
+
FieldMapping,
|
|
64
|
+
UserSyncService,
|
|
65
|
+
WebhookSender,
|
|
66
|
+
SyncResult,
|
|
67
|
+
create_sync_config,
|
|
68
|
+
create_default_field_mappings,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
__all__ = [
|
|
72
|
+
# 核心类
|
|
73
|
+
"AigcAuthClient",
|
|
74
|
+
"require_auth",
|
|
75
|
+
"AuthMiddleware",
|
|
76
|
+
"UserInfo",
|
|
77
|
+
"TokenVerifyResult",
|
|
78
|
+
"AigcAuthError",
|
|
79
|
+
"create_fastapi_auth_dependency",
|
|
80
|
+
# 旧系统接入
|
|
81
|
+
"LegacySystemAdapter",
|
|
82
|
+
"LegacyUserData",
|
|
83
|
+
"SyncConfig",
|
|
84
|
+
"SyncDirection",
|
|
85
|
+
"PasswordMode",
|
|
86
|
+
"FieldMapping",
|
|
87
|
+
"UserSyncService",
|
|
88
|
+
"WebhookSender",
|
|
89
|
+
"SyncResult",
|
|
90
|
+
"create_sync_config",
|
|
91
|
+
"create_default_field_mappings",
|
|
92
|
+
]
|
|
93
|
+
__version__ = "1.1.0"
|
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIGC Auth Legacy System Adapter
|
|
3
|
+
|
|
4
|
+
提供旧系统接入支持,包括:
|
|
5
|
+
1. 字段映射配置
|
|
6
|
+
2. 用户数据同步
|
|
7
|
+
3. 密码处理策略
|
|
8
|
+
4. Webhook 推送支持
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import hmac
|
|
13
|
+
import hashlib
|
|
14
|
+
import json
|
|
15
|
+
import logging
|
|
16
|
+
import requests
|
|
17
|
+
from typing import Optional, List, Dict, Any, Callable, TypeVar, Generic
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from enum import Enum
|
|
20
|
+
from abc import ABC, abstractmethod
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class PasswordMode(Enum):
|
|
26
|
+
"""密码处理模式"""
|
|
27
|
+
UNIFIED = "unified" # 统一初始密码
|
|
28
|
+
CUSTOM_MAPPING = "custom_mapping" # 自定义映射函数
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class SyncDirection(Enum):
|
|
32
|
+
"""同步方向"""
|
|
33
|
+
AUTH_TO_LEGACY = "auth_to_legacy" # aigc-auth → 旧系统
|
|
34
|
+
LEGACY_TO_AUTH = "legacy_to_auth" # 旧系统 → aigc-auth
|
|
35
|
+
BIDIRECTIONAL = "bidirectional" # 双向同步
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class FieldMapping:
|
|
40
|
+
"""字段映射配置"""
|
|
41
|
+
auth_field: str # aigc-auth 字段名
|
|
42
|
+
legacy_field: str # 旧系统字段名
|
|
43
|
+
transform_to_legacy: Optional[Callable[[Any], Any]] = None # auth → legacy 转换函数
|
|
44
|
+
transform_to_auth: Optional[Callable[[Any], Any]] = None # legacy → auth 转换函数
|
|
45
|
+
required: bool = False # 是否必填
|
|
46
|
+
default_value: Any = None # 默认值
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class SyncConfig:
|
|
51
|
+
"""同步配置"""
|
|
52
|
+
# 基本配置
|
|
53
|
+
enabled: bool = True
|
|
54
|
+
direction: SyncDirection = SyncDirection.AUTH_TO_LEGACY
|
|
55
|
+
|
|
56
|
+
# 字段映射
|
|
57
|
+
field_mappings: List[FieldMapping] = field(default_factory=list)
|
|
58
|
+
|
|
59
|
+
# 唯一标识字段(用于匹配用户)
|
|
60
|
+
unique_field: str = "username"
|
|
61
|
+
|
|
62
|
+
# 密码处理
|
|
63
|
+
password_mode: PasswordMode = PasswordMode.UNIFIED
|
|
64
|
+
unified_password: str = "Abc@123456" # 统一初始密码
|
|
65
|
+
password_mapper: Optional[Callable[[str], str]] = None # 自定义密码映射函数
|
|
66
|
+
|
|
67
|
+
# Webhook 配置
|
|
68
|
+
webhook_enabled: bool = False
|
|
69
|
+
webhook_url: Optional[str] = None
|
|
70
|
+
webhook_secret: Optional[str] = None
|
|
71
|
+
webhook_retry_count: int = 3
|
|
72
|
+
webhook_timeout: int = 10
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass
|
|
76
|
+
class LegacyUserData:
|
|
77
|
+
"""旧系统用户数据"""
|
|
78
|
+
data: Dict[str, Any]
|
|
79
|
+
|
|
80
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
81
|
+
return self.data.get(key, default)
|
|
82
|
+
|
|
83
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
84
|
+
return self.data.copy()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass
|
|
88
|
+
class SyncResult:
|
|
89
|
+
"""同步结果"""
|
|
90
|
+
success: bool
|
|
91
|
+
user_id: Optional[int] = None
|
|
92
|
+
auth_user_id: Optional[int] = None
|
|
93
|
+
legacy_user_id: Optional[Any] = None
|
|
94
|
+
message: str = ""
|
|
95
|
+
errors: List[str] = field(default_factory=list)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class LegacySystemAdapter(ABC):
|
|
99
|
+
"""
|
|
100
|
+
旧系统适配器抽象基类
|
|
101
|
+
|
|
102
|
+
接入系统需要继承此类并实现以下方法:
|
|
103
|
+
- get_user_by_unique_field: 通过唯一字段获取用户
|
|
104
|
+
- create_user: 创建用户
|
|
105
|
+
- update_user: 更新用户(可选)
|
|
106
|
+
- get_all_users: 获取所有用户(用于初始化同步)
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
def __init__(self, sync_config: SyncConfig):
|
|
110
|
+
self.config = sync_config
|
|
111
|
+
|
|
112
|
+
@abstractmethod
|
|
113
|
+
def get_user_by_unique_field(self, value: Any) -> Optional[LegacyUserData]:
|
|
114
|
+
"""通过唯一字段获取旧系统用户"""
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
@abstractmethod
|
|
118
|
+
def create_user(self, user_data: Dict[str, Any]) -> Optional[Any]:
|
|
119
|
+
"""在旧系统创建用户,返回用户ID"""
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
def update_user(self, unique_value: Any, user_data: Dict[str, Any]) -> bool:
|
|
123
|
+
"""更新旧系统用户(可选实现)"""
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
def get_all_users(self) -> List[LegacyUserData]:
|
|
127
|
+
"""获取所有旧系统用户(用于初始化同步)"""
|
|
128
|
+
return []
|
|
129
|
+
|
|
130
|
+
def transform_auth_to_legacy(self, auth_user: Dict[str, Any]) -> Dict[str, Any]:
|
|
131
|
+
"""将 aigc-auth 用户数据转换为旧系统格式"""
|
|
132
|
+
result = {}
|
|
133
|
+
|
|
134
|
+
for mapping in self.config.field_mappings:
|
|
135
|
+
auth_value = auth_user.get(mapping.auth_field)
|
|
136
|
+
|
|
137
|
+
if auth_value is None:
|
|
138
|
+
if mapping.required and mapping.default_value is None:
|
|
139
|
+
raise ValueError(f"Required field '{mapping.auth_field}' is missing")
|
|
140
|
+
auth_value = mapping.default_value
|
|
141
|
+
|
|
142
|
+
if mapping.transform_to_legacy:
|
|
143
|
+
auth_value = mapping.transform_to_legacy(auth_value)
|
|
144
|
+
|
|
145
|
+
if auth_value is not None:
|
|
146
|
+
result[mapping.legacy_field] = auth_value
|
|
147
|
+
|
|
148
|
+
return result
|
|
149
|
+
|
|
150
|
+
def transform_legacy_to_auth(self, legacy_user: LegacyUserData) -> Dict[str, Any]:
|
|
151
|
+
"""将旧系统用户数据转换为 aigc-auth 格式"""
|
|
152
|
+
result = {}
|
|
153
|
+
|
|
154
|
+
for mapping in self.config.field_mappings:
|
|
155
|
+
legacy_value = legacy_user.get(mapping.legacy_field)
|
|
156
|
+
|
|
157
|
+
if legacy_value is None:
|
|
158
|
+
if mapping.required and mapping.default_value is None:
|
|
159
|
+
raise ValueError(f"Required field '{mapping.legacy_field}' is missing")
|
|
160
|
+
legacy_value = mapping.default_value
|
|
161
|
+
|
|
162
|
+
if mapping.transform_to_auth:
|
|
163
|
+
legacy_value = mapping.transform_to_auth(legacy_value)
|
|
164
|
+
|
|
165
|
+
if legacy_value is not None:
|
|
166
|
+
result[mapping.auth_field] = legacy_value
|
|
167
|
+
|
|
168
|
+
return result
|
|
169
|
+
|
|
170
|
+
def get_password_for_sync(self, legacy_user: Optional[LegacyUserData] = None) -> str:
|
|
171
|
+
"""获取同步时使用的密码"""
|
|
172
|
+
if self.config.password_mode == PasswordMode.UNIFIED:
|
|
173
|
+
return self.config.unified_password
|
|
174
|
+
elif self.config.password_mode == PasswordMode.CUSTOM_MAPPING:
|
|
175
|
+
if self.config.password_mapper and legacy_user:
|
|
176
|
+
return self.config.password_mapper(legacy_user.data)
|
|
177
|
+
return self.config.unified_password
|
|
178
|
+
return self.config.unified_password
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class WebhookSender:
|
|
182
|
+
"""Webhook 发送器"""
|
|
183
|
+
|
|
184
|
+
def __init__(self, config: SyncConfig):
|
|
185
|
+
self.config = config
|
|
186
|
+
|
|
187
|
+
def generate_signature(self, payload: str) -> str:
|
|
188
|
+
"""生成 HMAC-SHA256 签名"""
|
|
189
|
+
if not self.config.webhook_secret:
|
|
190
|
+
return ""
|
|
191
|
+
|
|
192
|
+
return hmac.new(
|
|
193
|
+
self.config.webhook_secret.encode('utf-8'),
|
|
194
|
+
payload.encode('utf-8'),
|
|
195
|
+
hashlib.sha256
|
|
196
|
+
).hexdigest()
|
|
197
|
+
|
|
198
|
+
def send(self, event_type: str, data: Dict[str, Any]) -> bool:
|
|
199
|
+
"""发送 webhook 通知"""
|
|
200
|
+
if not self.config.webhook_enabled or not self.config.webhook_url:
|
|
201
|
+
return False
|
|
202
|
+
|
|
203
|
+
payload = json.dumps({
|
|
204
|
+
"event": event_type,
|
|
205
|
+
"data": data
|
|
206
|
+
}, ensure_ascii=False)
|
|
207
|
+
|
|
208
|
+
signature = self.generate_signature(payload)
|
|
209
|
+
|
|
210
|
+
headers = {
|
|
211
|
+
"Content-Type": "application/json",
|
|
212
|
+
"X-Webhook-Signature": signature,
|
|
213
|
+
"X-Webhook-Event": event_type
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
for attempt in range(self.config.webhook_retry_count):
|
|
217
|
+
try:
|
|
218
|
+
response = requests.post(
|
|
219
|
+
self.config.webhook_url,
|
|
220
|
+
data=payload,
|
|
221
|
+
headers=headers,
|
|
222
|
+
timeout=self.config.webhook_timeout
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
if response.status_code == 200:
|
|
226
|
+
logger.info(f"Webhook sent successfully: {event_type}")
|
|
227
|
+
return True
|
|
228
|
+
else:
|
|
229
|
+
logger.warning(f"Webhook failed with status {response.status_code}: {response.text}")
|
|
230
|
+
|
|
231
|
+
except requests.RequestException as e:
|
|
232
|
+
logger.error(f"Webhook request failed (attempt {attempt + 1}): {e}")
|
|
233
|
+
|
|
234
|
+
return False
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class UserSyncService:
|
|
238
|
+
"""
|
|
239
|
+
用户同步服务
|
|
240
|
+
|
|
241
|
+
提供以下功能:
|
|
242
|
+
1. 登录时自动同步(auth → legacy)
|
|
243
|
+
2. 初始化批量同步(legacy → auth)
|
|
244
|
+
3. Webhook 增量推送
|
|
245
|
+
"""
|
|
246
|
+
|
|
247
|
+
def __init__(
|
|
248
|
+
self,
|
|
249
|
+
auth_client, # AigcAuthClient
|
|
250
|
+
legacy_adapter: LegacySystemAdapter
|
|
251
|
+
):
|
|
252
|
+
self.auth_client = auth_client
|
|
253
|
+
self.adapter = legacy_adapter
|
|
254
|
+
self.config = legacy_adapter.config
|
|
255
|
+
self.webhook_sender = WebhookSender(self.config)
|
|
256
|
+
|
|
257
|
+
def sync_on_login(self, auth_user_info) -> SyncResult:
|
|
258
|
+
"""
|
|
259
|
+
登录时同步用户到旧系统
|
|
260
|
+
|
|
261
|
+
当用户通过 aigc-auth 登录成功后调用,
|
|
262
|
+
如果旧系统没有该用户则自动创建。
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
auth_user_info: aigc-auth 返回的 UserInfo 对象
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
SyncResult: 同步结果
|
|
269
|
+
"""
|
|
270
|
+
if not self.config.enabled:
|
|
271
|
+
return SyncResult(success=True, message="Sync disabled")
|
|
272
|
+
|
|
273
|
+
if self.config.direction == SyncDirection.LEGACY_TO_AUTH:
|
|
274
|
+
return SyncResult(success=True, message="Direction is legacy_to_auth, skip")
|
|
275
|
+
|
|
276
|
+
try:
|
|
277
|
+
# 获取唯一标识值
|
|
278
|
+
unique_value = getattr(auth_user_info, self.config.unique_field, None)
|
|
279
|
+
if not unique_value:
|
|
280
|
+
return SyncResult(
|
|
281
|
+
success=False,
|
|
282
|
+
message=f"Unique field '{self.config.unique_field}' not found in auth user"
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
# 检查旧系统是否已有该用户
|
|
286
|
+
legacy_user = self.adapter.get_user_by_unique_field(unique_value)
|
|
287
|
+
|
|
288
|
+
if legacy_user:
|
|
289
|
+
# 用户已存在,可选更新
|
|
290
|
+
if self.config.direction == SyncDirection.BIDIRECTIONAL:
|
|
291
|
+
auth_data = self._user_info_to_dict(auth_user_info)
|
|
292
|
+
legacy_data = self.adapter.transform_auth_to_legacy(auth_data)
|
|
293
|
+
self.adapter.update_user(unique_value, legacy_data)
|
|
294
|
+
|
|
295
|
+
return SyncResult(
|
|
296
|
+
success=True,
|
|
297
|
+
auth_user_id=auth_user_info.id,
|
|
298
|
+
legacy_user_id=legacy_user.get("id"),
|
|
299
|
+
message="User already exists in legacy system"
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
# 创建新用户
|
|
303
|
+
auth_data = self._user_info_to_dict(auth_user_info)
|
|
304
|
+
legacy_data = self.adapter.transform_auth_to_legacy(auth_data)
|
|
305
|
+
|
|
306
|
+
# 添加密码
|
|
307
|
+
legacy_data["password"] = self.adapter.get_password_for_sync()
|
|
308
|
+
|
|
309
|
+
legacy_user_id = self.adapter.create_user(legacy_data)
|
|
310
|
+
|
|
311
|
+
if legacy_user_id:
|
|
312
|
+
return SyncResult(
|
|
313
|
+
success=True,
|
|
314
|
+
auth_user_id=auth_user_info.id,
|
|
315
|
+
legacy_user_id=legacy_user_id,
|
|
316
|
+
message="User synced to legacy system"
|
|
317
|
+
)
|
|
318
|
+
else:
|
|
319
|
+
return SyncResult(
|
|
320
|
+
success=False,
|
|
321
|
+
auth_user_id=auth_user_info.id,
|
|
322
|
+
message="Failed to create user in legacy system"
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
except Exception as e:
|
|
326
|
+
logger.exception("Error syncing user on login")
|
|
327
|
+
return SyncResult(success=False, message=str(e), errors=[str(e)])
|
|
328
|
+
|
|
329
|
+
def _user_info_to_dict(self, user_info) -> Dict[str, Any]:
|
|
330
|
+
"""将 UserInfo 对象转换为字典"""
|
|
331
|
+
return {
|
|
332
|
+
"id": user_info.id,
|
|
333
|
+
"username": user_info.username,
|
|
334
|
+
"nickname": user_info.nickname,
|
|
335
|
+
"email": user_info.email,
|
|
336
|
+
"phone": user_info.phone,
|
|
337
|
+
"avatar": user_info.avatar,
|
|
338
|
+
"roles": user_info.roles,
|
|
339
|
+
"permissions": user_info.permissions,
|
|
340
|
+
"department": user_info.department,
|
|
341
|
+
"company": user_info.company,
|
|
342
|
+
"is_admin": user_info.is_admin
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
def batch_sync_to_auth(
|
|
346
|
+
self,
|
|
347
|
+
users: List[LegacyUserData],
|
|
348
|
+
on_progress: Optional[Callable[[int, int], None]] = None
|
|
349
|
+
) -> Dict[str, Any]:
|
|
350
|
+
"""
|
|
351
|
+
批量同步旧系统用户到 aigc-auth
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
users: 旧系统用户列表
|
|
355
|
+
on_progress: 进度回调函数 (current, total)
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
Dict: 同步结果统计
|
|
359
|
+
"""
|
|
360
|
+
results = {
|
|
361
|
+
"total": len(users),
|
|
362
|
+
"success": 0,
|
|
363
|
+
"failed": 0,
|
|
364
|
+
"skipped": 0,
|
|
365
|
+
"errors": []
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
for i, user in enumerate(users):
|
|
369
|
+
try:
|
|
370
|
+
auth_data = self.adapter.transform_legacy_to_auth(user)
|
|
371
|
+
|
|
372
|
+
# 添加密码
|
|
373
|
+
auth_data["password"] = self.adapter.get_password_for_sync(user)
|
|
374
|
+
|
|
375
|
+
# 调用 SDK 同步接口
|
|
376
|
+
result = self.auth_client.sync_user_to_auth(auth_data)
|
|
377
|
+
|
|
378
|
+
if result.get("success"):
|
|
379
|
+
if result.get("created"):
|
|
380
|
+
results["success"] += 1
|
|
381
|
+
else:
|
|
382
|
+
results["skipped"] += 1
|
|
383
|
+
else:
|
|
384
|
+
results["failed"] += 1
|
|
385
|
+
results["errors"].append({
|
|
386
|
+
"user": user.get(self.config.unique_field),
|
|
387
|
+
"error": result.get("message")
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
except Exception as e:
|
|
391
|
+
results["failed"] += 1
|
|
392
|
+
results["errors"].append({
|
|
393
|
+
"user": user.get(self.config.unique_field),
|
|
394
|
+
"error": str(e)
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
if on_progress:
|
|
398
|
+
on_progress(i + 1, len(users))
|
|
399
|
+
|
|
400
|
+
return results
|
|
401
|
+
|
|
402
|
+
def send_user_created_webhook(self, auth_user_data: Dict[str, Any]) -> bool:
|
|
403
|
+
"""发送用户创建 webhook"""
|
|
404
|
+
return self.webhook_sender.send("user.created", auth_user_data)
|
|
405
|
+
|
|
406
|
+
def send_user_updated_webhook(self, auth_user_data: Dict[str, Any]) -> bool:
|
|
407
|
+
"""发送用户更新 webhook"""
|
|
408
|
+
return self.webhook_sender.send("user.updated", auth_user_data)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
# ============ 预设字段映射 ============
|
|
412
|
+
|
|
413
|
+
def create_default_field_mappings() -> List[FieldMapping]:
|
|
414
|
+
"""创建默认字段映射配置(通用基础映射)"""
|
|
415
|
+
return [
|
|
416
|
+
FieldMapping(
|
|
417
|
+
auth_field="username",
|
|
418
|
+
legacy_field="username",
|
|
419
|
+
required=True
|
|
420
|
+
),
|
|
421
|
+
FieldMapping(
|
|
422
|
+
auth_field="email",
|
|
423
|
+
legacy_field="email"
|
|
424
|
+
),
|
|
425
|
+
FieldMapping(
|
|
426
|
+
auth_field="nickname",
|
|
427
|
+
legacy_field="nickname"
|
|
428
|
+
),
|
|
429
|
+
FieldMapping(
|
|
430
|
+
auth_field="phone",
|
|
431
|
+
legacy_field="phone"
|
|
432
|
+
),
|
|
433
|
+
FieldMapping(
|
|
434
|
+
auth_field="avatar",
|
|
435
|
+
legacy_field="avatar"
|
|
436
|
+
),
|
|
437
|
+
FieldMapping(
|
|
438
|
+
auth_field="company",
|
|
439
|
+
legacy_field="company"
|
|
440
|
+
),
|
|
441
|
+
FieldMapping(
|
|
442
|
+
auth_field="department",
|
|
443
|
+
legacy_field="department"
|
|
444
|
+
),
|
|
445
|
+
]
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
# ============ 便捷函数 ============
|
|
449
|
+
|
|
450
|
+
def create_sync_config(
|
|
451
|
+
field_mappings: List[FieldMapping] = None,
|
|
452
|
+
password_mode: PasswordMode = PasswordMode.UNIFIED,
|
|
453
|
+
unified_password: str = "Abc@123456",
|
|
454
|
+
webhook_url: Optional[str] = None,
|
|
455
|
+
webhook_secret: Optional[str] = None,
|
|
456
|
+
direction: SyncDirection = SyncDirection.AUTH_TO_LEGACY,
|
|
457
|
+
**kwargs
|
|
458
|
+
) -> SyncConfig:
|
|
459
|
+
"""
|
|
460
|
+
创建同步配置的便捷函数
|
|
461
|
+
|
|
462
|
+
Args:
|
|
463
|
+
field_mappings: 字段映射列表,默认使用通用映射
|
|
464
|
+
password_mode: 密码处理模式
|
|
465
|
+
unified_password: 统一初始密码
|
|
466
|
+
webhook_url: Webhook 接收地址
|
|
467
|
+
webhook_secret: Webhook 签名密钥
|
|
468
|
+
direction: 同步方向
|
|
469
|
+
|
|
470
|
+
Returns:
|
|
471
|
+
SyncConfig: 同步配置对象
|
|
472
|
+
"""
|
|
473
|
+
return SyncConfig(
|
|
474
|
+
enabled=True,
|
|
475
|
+
direction=direction,
|
|
476
|
+
field_mappings=field_mappings or create_default_field_mappings(),
|
|
477
|
+
password_mode=password_mode,
|
|
478
|
+
unified_password=unified_password,
|
|
479
|
+
webhook_enabled=bool(webhook_url),
|
|
480
|
+
webhook_url=webhook_url,
|
|
481
|
+
webhook_secret=webhook_secret,
|
|
482
|
+
**kwargs
|
|
483
|
+
)
|