diff --git a/app/services/dingtalk_auth.py b/app/services/dingtalk_auth.py index 2258d89..5681d23 100644 --- a/app/services/dingtalk_auth.py +++ b/app/services/dingtalk_auth.py @@ -1,6 +1,7 @@ """Server-side DingTalk auth-code exchange and local account upsert.""" import json +import logging import time 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" USERDETAIL_URL = "https://oapi.dingtalk.com/topapi/v2/user/get" DEPARTMENT_DETAIL_URL = "https://oapi.dingtalk.com/topapi/v2/department/get" +logger = logging.getLogger("dieya_risk_prediction.dingtalk") class DingTalkServiceError(AppException): @@ -122,12 +124,24 @@ class DingTalkAuthService: ) response.raise_for_status() 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} + 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} if not isinstance(detail, dict): detail = {"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) if dept_ids and not self._department_name(detail): dept_name = await self._department_name_from_id(access_token, dept_ids[0]) @@ -155,12 +169,12 @@ class DingTalkAuthService: @staticmethod 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 @staticmethod 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): raw = [raw] result: list[int] = [] @@ -173,7 +187,7 @@ class DingTalkAuthService: @staticmethod 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 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_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 - dept_id = detail.get("main_dept_id") or detail.get("dept_id") or user.dingtalk_dept_id - if isinstance(dept_id, list): - dept_id = dept_id[0] if dept_id else None + dept_ids = self._department_ids(detail) + dept_id = dept_ids[0] if dept_ids else user.dingtalk_dept_id try: user.dingtalk_dept_id = int(dept_id) if dept_id is not None else None except (TypeError, ValueError): diff --git a/tests/test_auth.py b/tests/test_auth.py index 04c1bd6..bdcfe63 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -21,3 +21,10 @@ def test_dingtalk_detail_fields_are_normalized() -> None: assert DingTalkAuthService._display_name(detail, "fallback") == "张三" assert DingTalkAuthService._department_ids(detail) == [123, 456] 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]