增强钉钉用户详情字段解析

This commit is contained in:
kx_guo 2026-09-13 16:47:18 +08:00
parent ca4c0e2d58
commit 0eecb9dc3c
2 changed files with 27 additions and 7 deletions

View File

@ -1,6 +1,7 @@
"""Server-side DingTalk auth-code exchange and local account upsert.""" """Server-side DingTalk auth-code exchange and local account upsert."""
import json import json
import logging
import time import time
from datetime import UTC, datetime from datetime import UTC, datetime
@ -18,6 +19,7 @@ TOKEN_URL = "https://oapi.dingtalk.com/gettoken"
USERINFO_URL = "https://oapi.dingtalk.com/topapi/v2/user/getuserinfo" USERINFO_URL = "https://oapi.dingtalk.com/topapi/v2/user/getuserinfo"
USERDETAIL_URL = "https://oapi.dingtalk.com/topapi/v2/user/get" USERDETAIL_URL = "https://oapi.dingtalk.com/topapi/v2/user/get"
DEPARTMENT_DETAIL_URL = "https://oapi.dingtalk.com/topapi/v2/department/get" DEPARTMENT_DETAIL_URL = "https://oapi.dingtalk.com/topapi/v2/department/get"
logger = logging.getLogger("dieya_risk_prediction.dingtalk")
class DingTalkServiceError(AppException): class DingTalkServiceError(AppException):
@ -122,12 +124,24 @@ class DingTalkAuthService:
) )
response.raise_for_status() response.raise_for_status()
data = response.json() data = response.json()
except httpx.HTTPError: except httpx.HTTPError as exc:
logger.warning("钉钉用户详情请求失败 user_id=%s error=%s", user_id, exc.__class__.__name__)
return {"userid": user_id} return {"userid": user_id}
if data.get("errcode") not in (None, 0, "0"):
logger.warning(
"钉钉用户详情接口返回错误 user_id=%s errcode=%s errmsg=%s",
user_id,
data.get("errcode"),
data.get("errmsg") or data.get("message"),
)
detail = data.get("result") or data.get("user") or {"userid": user_id} detail = data.get("result") or data.get("user") or {"userid": user_id}
if not isinstance(detail, dict): if not isinstance(detail, dict):
detail = {"userid": user_id} detail = {"userid": user_id}
detail.setdefault("userid", user_id) detail.setdefault("userid", user_id)
# Some DingTalk responses wrap the actual user object one level deeper.
nested = detail.get("user") or detail.get("data")
if isinstance(nested, dict):
detail = {**detail, **nested}
dept_ids = self._department_ids(detail) dept_ids = self._department_ids(detail)
if dept_ids and not self._department_name(detail): if dept_ids and not self._department_name(detail):
dept_name = await self._department_name_from_id(access_token, dept_ids[0]) dept_name = await self._department_name_from_id(access_token, dept_ids[0])
@ -155,12 +169,12 @@ class DingTalkAuthService:
@staticmethod @staticmethod
def _department_name(detail: dict) -> str | None: def _department_name(detail: dict) -> str | None:
value = detail.get("dept_name") or detail.get("deptName") or detail.get("department_name") value = detail.get("dept_name") or detail.get("deptName") or detail.get("department_name") or detail.get("departmentName")
return str(value).strip() if value else None return str(value).strip() if value else None
@staticmethod @staticmethod
def _department_ids(detail: dict) -> list[int]: def _department_ids(detail: dict) -> list[int]:
raw = (detail.get("dept_id_list") or detail.get("deptIdList") or detail.get("department_ids") or detail.get("dept_id") or detail.get("main_dept_id") or detail.get("mainDeptId") or []) raw = (detail.get("dept_id_list") or detail.get("deptIdList") or detail.get("dept_id_list_str") or detail.get("department_ids") or detail.get("dept_id") or detail.get("main_dept_id") or detail.get("mainDeptId") or [])
if not isinstance(raw, list): if not isinstance(raw, list):
raw = [raw] raw = [raw]
result: list[int] = [] result: list[int] = []
@ -173,7 +187,7 @@ class DingTalkAuthService:
@staticmethod @staticmethod
def _display_name(detail: dict, fallback: str) -> str: def _display_name(detail: dict, fallback: str) -> str:
value = detail.get("name") or detail.get("nick") or detail.get("nickname") or detail.get("real_name") or detail.get("display_name") or fallback value = detail.get("name") or detail.get("nick") or detail.get("nickname") or detail.get("real_name") or detail.get("display_name") or detail.get("user_name") or detail.get("userName") or fallback
return str(value).strip()[:50] or fallback return str(value).strip()[:50] or fallback
async def _upsert_user(self, user_id: str, detail: dict) -> UserAccount: async def _upsert_user(self, user_id: str, detail: dict) -> UserAccount:
@ -200,9 +214,8 @@ class DingTalkAuthService:
user.dingtalk_avatar = str(detail.get("avatar") or user.dingtalk_avatar or "")[:512] or None user.dingtalk_avatar = str(detail.get("avatar") or user.dingtalk_avatar or "")[:512] or None
user.dingtalk_job_number = str(detail.get("job_number") or user.dingtalk_job_number or "")[:64] or None user.dingtalk_job_number = str(detail.get("job_number") or user.dingtalk_job_number or "")[:64] or None
user.dingtalk_title = str(detail.get("title") or user.dingtalk_title or "")[:128] or None user.dingtalk_title = str(detail.get("title") or user.dingtalk_title or "")[:128] or None
dept_id = detail.get("main_dept_id") or detail.get("dept_id") or user.dingtalk_dept_id dept_ids = self._department_ids(detail)
if isinstance(dept_id, list): dept_id = dept_ids[0] if dept_ids else user.dingtalk_dept_id
dept_id = dept_id[0] if dept_id else None
try: try:
user.dingtalk_dept_id = int(dept_id) if dept_id is not None else None user.dingtalk_dept_id = int(dept_id) if dept_id is not None else None
except (TypeError, ValueError): except (TypeError, ValueError):

View File

@ -21,3 +21,10 @@ def test_dingtalk_detail_fields_are_normalized() -> None:
assert DingTalkAuthService._display_name(detail, "fallback") == "张三" assert DingTalkAuthService._display_name(detail, "fallback") == "张三"
assert DingTalkAuthService._department_ids(detail) == [123, 456] assert DingTalkAuthService._department_ids(detail) == [123, 456]
assert DingTalkAuthService._is_dingtalk_manager(detail) assert DingTalkAuthService._is_dingtalk_manager(detail)
def test_dingtalk_nested_detail_fields_are_supported() -> None:
detail = {"user": {"userName": "李四", "deptIdList": [789]}}
nested = detail["user"]
assert DingTalkAuthService._display_name(nested, "fallback") == "李四"
assert DingTalkAuthService._department_ids(nested) == [789]