sdk/dingding-sdk/dingding_attendance.py

169 lines
6.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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