完善钉钉同步功能和更新文档
This commit is contained in:
parent
9285fc1822
commit
6646a7a099
@ -30,10 +30,10 @@ async function getDingtalkAccessToken(appKey: string, appSecret: string) {
|
||||
return tokenData.access_token;
|
||||
}
|
||||
|
||||
// 获取部门用户列表(分页)
|
||||
async function getDeptUsers(accessToken: string, deptId: number, cursor: number = 0): Promise<any[]> {
|
||||
// 获取部门用户列表(使用新版 v2 API,带限流重试)
|
||||
async function getDeptUsers(accessToken: string, deptId: number, cursor: number = 0, retryCount: number = 0): Promise<any[]> {
|
||||
const response = await fetch(
|
||||
`https://oapi.dingtalk.com/topapi/user/listbypage?access_token=${accessToken}`,
|
||||
`https://oapi.dingtalk.com/topapi/v2/user/list?access_token=${accessToken}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
@ -41,12 +41,18 @@ async function getDeptUsers(accessToken: string, deptId: number, cursor: number
|
||||
dept_id: deptId,
|
||||
cursor: cursor,
|
||||
size: 100,
|
||||
order_field: "modify_desc"
|
||||
}),
|
||||
}
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
// 如果是限流错误,等待后重试(最多重试3次)
|
||||
if (data.errcode === 90002 && retryCount < 3) {
|
||||
console.log(`[Sync DingTalk] 部门 ${deptId} API限流,等待2秒后重试 (${retryCount + 1}/3)...`);
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
return getDeptUsers(accessToken, deptId, cursor, retryCount + 1);
|
||||
}
|
||||
|
||||
if (data.errcode !== 0) {
|
||||
console.error(`获取部门 ${deptId} 用户列表失败:`, data.errmsg);
|
||||
return [];
|
||||
@ -56,6 +62,7 @@ async function getDeptUsers(accessToken: string, deptId: number, cursor: number
|
||||
|
||||
// 如果还有更多数据,递归获取
|
||||
if (data.result?.has_more && data.result?.next_cursor) {
|
||||
await new Promise(resolve => setTimeout(resolve, 300)); // 获取下一页前延迟
|
||||
const moreUsers = await getDeptUsers(accessToken, deptId, data.result.next_cursor);
|
||||
return [...users, ...moreUsers];
|
||||
}
|
||||
@ -63,29 +70,51 @@ async function getDeptUsers(accessToken: string, deptId: number, cursor: number
|
||||
return users;
|
||||
}
|
||||
|
||||
// 获取所有子部门ID
|
||||
async function getAllDeptIds(accessToken: string, parentId: number = 1): Promise<number[]> {
|
||||
const response = await fetch(
|
||||
`https://oapi.dingtalk.com/topapi/v2/department/listsub?access_token=${accessToken}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ dept_id: parentId }),
|
||||
// 获取所有子部门ID(使用队列方式避免递归导致的大量并发请求)
|
||||
async function getAllDeptIds(accessToken: string, rootId: number = 1): Promise<number[]> {
|
||||
const allDeptIds: number[] = [rootId];
|
||||
const queue: number[] = [rootId];
|
||||
|
||||
while (queue.length > 0) {
|
||||
const parentId = queue.shift()!;
|
||||
|
||||
// 添加延迟避免限流(增加到150ms)
|
||||
await new Promise(resolve => setTimeout(resolve, 150));
|
||||
|
||||
let retryCount = 0;
|
||||
let data: any = null;
|
||||
|
||||
while (retryCount < 3) {
|
||||
const response = await fetch(
|
||||
`https://oapi.dingtalk.com/topapi/v2/department/listsub?access_token=${accessToken}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ dept_id: parentId }),
|
||||
}
|
||||
);
|
||||
data = await response.json();
|
||||
|
||||
// 如果是限流错误,等待后重试
|
||||
if (data.errcode === 90002) {
|
||||
console.log(`[Sync DingTalk] 获取子部门API限流,等待2秒后重试...`);
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
retryCount++;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!data || data.errcode !== 0) {
|
||||
console.error(`获取子部门列表失败:`, data?.errmsg);
|
||||
continue;
|
||||
}
|
||||
|
||||
const subDepts = data.result || [];
|
||||
for (const dept of subDepts) {
|
||||
allDeptIds.push(dept.dept_id);
|
||||
queue.push(dept.dept_id);
|
||||
}
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.errcode !== 0) {
|
||||
console.error(`获取子部门列表失败:`, data.errmsg);
|
||||
return [parentId];
|
||||
}
|
||||
|
||||
const subDepts = data.result || [];
|
||||
let allDeptIds = [parentId];
|
||||
|
||||
for (const dept of subDepts) {
|
||||
const childDeptIds = await getAllDeptIds(accessToken, dept.dept_id);
|
||||
allDeptIds = [...allDeptIds, ...childDeptIds];
|
||||
}
|
||||
|
||||
return allDeptIds;
|
||||
@ -161,25 +190,43 @@ export async function POST() {
|
||||
console.log(`[Sync DingTalk] Found ${allDeptIds.length} departments`);
|
||||
|
||||
// 获取所有钉钉用户(按部门获取)
|
||||
const dingtalkUsersMap = new Map<string, any>(); // email -> user info
|
||||
const dingtalkUsersByEmail = new Map<string, any>(); // email -> user info
|
||||
const dingtalkUsersByName = new Map<string, any>(); // name -> user info
|
||||
let totalDtUsers = 0;
|
||||
let usersWithEmail = 0;
|
||||
|
||||
for (const deptId of allDeptIds) {
|
||||
const deptUsers = await getDeptUsers(accessToken, deptId);
|
||||
|
||||
// 调试:打印第一个部门的第一个用户结构
|
||||
if (totalDtUsers === 0 && deptUsers.length > 0) {
|
||||
console.log(`[Sync DingTalk] Sample user data structure:`, JSON.stringify(deptUsers[0], null, 2));
|
||||
}
|
||||
|
||||
for (const dtUser of deptUsers) {
|
||||
totalDtUsers++;
|
||||
|
||||
// 使用邮箱作为匹配键
|
||||
if (dtUser.email) {
|
||||
dingtalkUsersMap.set(dtUser.email.toLowerCase(), dtUser);
|
||||
usersWithEmail++;
|
||||
dingtalkUsersByEmail.set(dtUser.email.toLowerCase(), dtUser);
|
||||
}
|
||||
// 也尝试使用 org_email
|
||||
if (dtUser.org_email) {
|
||||
dingtalkUsersMap.set(dtUser.org_email.toLowerCase(), dtUser);
|
||||
dingtalkUsersByEmail.set(dtUser.org_email.toLowerCase(), dtUser);
|
||||
}
|
||||
// 使用姓名作为备选匹配键
|
||||
if (dtUser.name) {
|
||||
dingtalkUsersByName.set(dtUser.name, dtUser);
|
||||
}
|
||||
}
|
||||
// 添加延迟避免触发钉钉API限流
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
// 添加延迟避免触发钉钉API限流(增加到300ms)
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
}
|
||||
|
||||
console.log(`[Sync DingTalk] Found ${dingtalkUsersMap.size} DingTalk users with email`);
|
||||
console.log(`[Sync DingTalk] Found ${totalDtUsers} total DingTalk users`);
|
||||
console.log(`[Sync DingTalk] Found ${usersWithEmail} users with email`);
|
||||
console.log(`[Sync DingTalk] Found ${dingtalkUsersByName.size} unique names`);
|
||||
|
||||
// 获取我们数据库中的所有用户
|
||||
const dbUsers = await db
|
||||
@ -203,7 +250,14 @@ export async function POST() {
|
||||
// 遍历数据库用户,匹配钉钉用户
|
||||
for (const dbUser of dbUsers) {
|
||||
const emailLower = dbUser.email.toLowerCase();
|
||||
const dtUser = dingtalkUsersMap.get(emailLower);
|
||||
|
||||
// 优先通过邮箱匹配
|
||||
let dtUser = dingtalkUsersByEmail.get(emailLower);
|
||||
|
||||
// 如果邮箱匹配不到,尝试通过姓名匹配
|
||||
if (!dtUser && dbUser.name) {
|
||||
dtUser = dingtalkUsersByName.get(dbUser.name);
|
||||
}
|
||||
|
||||
if (!dtUser) {
|
||||
// 没有找到匹配的钉钉用户
|
||||
@ -287,7 +341,8 @@ export async function POST() {
|
||||
message: `同步完成`,
|
||||
stats: {
|
||||
total: dbUsers.length,
|
||||
dingtalkUsers: dingtalkUsersMap.size,
|
||||
dingtalkUsers: totalDtUsers,
|
||||
dingtalkUsersWithEmail: usersWithEmail,
|
||||
matched: matchedCount,
|
||||
synced: syncedCount,
|
||||
skipped: skippedCount,
|
||||
|
||||
@ -522,3 +522,5 @@ curl http://localhost:8002/health
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user