dev #1

Merged
kx_guo merged 24 commits from dev into main 2026-09-21 10:51:18 +08:00
2 changed files with 27 additions and 7 deletions
Showing only changes of commit 0eecb9dc3c - Show all commits

View File

@ -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):

View File

@ -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]