169 lines
6.0 KiB
Python
169 lines
6.0 KiB
Python
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:])
|