import concurrent.futures import sys import requests from typing import List, Set, Dict, Tuple class DingTalkDocsFetcher: @staticmethod def get_access_token(app_key: str, app_secret: str) -> str: url = "https://oapi.dingtalk.com/gettoken" params = { "appkey": app_key, "appsecret": app_secret } resp = requests.get(url, params=params) data = resp.json() if data.get("errcode") == 0: print("✅ AccessToken 获取成功:", data.get("access_token")) return data.get("access_token") print("❌ 获取 AccessToken 失败:", data) return "" @staticmethod def get_sub_departments(token: str, dept_id: int = None) -> List[Dict]: url = "https://oapi.dingtalk.com/topapi/v2/department/listsub" params = {"access_token": token} payload = {} if dept_id is not None: payload["dept_id"] = dept_id resp = requests.post(url, params=params, json=payload) data = resp.json() if data.get("errcode") == 0: return data.get("result", []) else: print(f"❌ 获取部门列表失败 dept_id={dept_id}: {data}") return [] @staticmethod def get_users_by_dept(token: str, dept_id: int) -> List[str]: url = "https://oapi.dingtalk.com/topapi/user/listid" params = {"access_token": token} payload = {"dept_id": dept_id} resp = requests.post(url, params=params, json=payload) data = resp.json() if data.get("errcode") == 0: return data.get("result", {}).get("userid_list", []) else: print(f"❌ 获取部门用户失败 dept_id={dept_id}: {data}") return [] @staticmethod def get_all_departments(token: str) -> List[Dict]: all_depts = [] def recurse(dept_id: int = None, parent_id: int = 0): depts = DingTalkDocsFetcher.get_sub_departments(token, dept_id) for d in depts: dept_info = { "dept_id": d["dept_id"], "name": d["name"], "parent_id": parent_id # 这里用调用时的 parent_id } all_depts.append(dept_info) recurse(d["dept_id"], parent_id=d["dept_id"]) # 递归时传当前dept_id作为父部门ID recurse() return all_depts @staticmethod def get_user_detail(token: str, userid: str) -> dict: url = "https://oapi.dingtalk.com/topapi/v2/user/get" params = {"access_token": token} payload = {"userid": userid} resp = requests.post(url, params=params, json=payload) data = resp.json() if data.get("errcode") == 0: return data.get("result", {}) else: print(f"获取用户详情失败: {data}") return None @staticmethod def get_all_user_ids(token: str, departments: List[Dict]) -> Set[str]: """ 根据部门列表,获取所有部门用户ID集合(去重) """ user_ids = set() for dept in departments: dept_id = dept["dept_id"] ids = DingTalkDocsFetcher.get_users_by_dept(token, dept_id) user_ids.update(ids) return user_ids @staticmethod def get_attendance_records(token: str, user_ids: List[str], date_from: str, date_to: str): url = f"https://oapi.dingtalk.com/attendance/listRecord?access_token={token}" payload = { "checkDateFrom": date_from, "checkDateTo": date_to, "userIds": user_ids, "isI18n": "false" } resp = requests.post(url, json=payload) r = resp.json() if r.get("errcode") == 0: return r.get("recordresult", []) else: print(f"❌ 获取打卡记录失败: {r}") return [] @staticmethod def main(args: List[str]) -> None: app_key = "dinguetojbaxvvhzpk3d" app_secret = "lMFqns_ceLIcXvfLL8GKfa3ZiPKHcaZq0VbGtJXJlDuK8AEJ2WV3-PN8zv61ajm3" token = DingTalkDocsFetcher.get_access_token(app_key, app_secret) if not token: return print("开始获取所有部门...") departments = DingTalkDocsFetcher.get_all_departments(token) print(f"✅ 获取部门总数: {len(departments)}") print("开始获取所有部门用户ID...") user_ids_set = DingTalkDocsFetcher.get_all_user_ids(token, departments) user_ids = list(user_ids_set) print(f"✅ 获取用户总数: {len(user_ids)}") date_from = "2025-08-10 12:00:00" date_to = "2025-08-12 12:00:00" print("开始获取打卡记录...") records = DingTalkDocsFetcher.get_attendance_records(token, user_ids, date_from, date_to) print(f"✅ 打卡记录数: {len(records)}") user_cache = {} def fetch_user_detail(userid): if userid in user_cache: return userid, user_cache[userid] detail = DingTalkDocsFetcher.get_user_detail(token, userid) user_cache[userid] = detail return userid, detail user_ids_in_records = list({r.get("userId") for r in records if r.get("userId")}) with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor: futures = [executor.submit(fetch_user_detail, uid) for uid in user_ids_in_records] for future in concurrent.futures.as_completed(futures): uid, detail = future.result() user_cache[uid] = detail enhanced_records = [] for r in records: userid = r.get("userId") user_detail = user_cache.get(userid) if user_detail: r["user_name"] = user_detail.get("name", "") else: r["user_name"] = "" enhanced_records.append(r) for r in enhanced_records: print(r) if __name__ == '__main__': DingTalkDocsFetcher.main(sys.argv[1:])