import { NextResponse } from "next/server"; import { db } from "@/lib/db"; import { user } from "@/lib/db/schema"; import { eq, isNotNull, isNull, or } from "drizzle-orm"; import { auth } from "@/app/(auth)/auth"; // 获取钉钉配置 async function getDingtalkConfig() { const settings = await db.query.authSettings.findFirst(); if (!settings?.isDingTalkEnabled || !settings.dingTalkAppKey || !settings.dingTalkAppSecret) { throw new Error("钉钉配置未启用或配置不完整"); } return { appKey: settings.dingTalkAppKey, appSecret: settings.dingTalkAppSecret, }; } // 获取钉钉 access_token async function getDingtalkAccessToken(appKey: string, appSecret: string) { const tokenResponse = await fetch( `https://oapi.dingtalk.com/gettoken?appkey=${appKey}&appsecret=${appSecret}` ); const tokenData = await tokenResponse.json(); if (tokenData.errcode !== 0) { throw new Error(`获取钉钉token失败: ${tokenData.errmsg}`); } return tokenData.access_token; } // 获取用户详细信息(包括部门) async function getUserDetailFromDingtalk(accessToken: string, userId: string) { const response = await fetch( `https://oapi.dingtalk.com/topapi/v2/user/get?access_token=${accessToken}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userid: userId }), } ); const data = await response.json(); if (data.errcode !== 0) { console.error(`获取用户 ${userId} 信息失败:`, data.errmsg); return null; } return data.result; } // 获取部门名称 async function getDepartmentName(accessToken: string, deptId: number) { const response = await fetch( `https://oapi.dingtalk.com/topapi/v2/department/get?access_token=${accessToken}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ dept_id: deptId }), } ); const data = await response.json(); if (data.errcode !== 0) { console.error(`获取部门 ${deptId} 信息失败:`, data.errmsg); return null; } return data.result?.name; } // POST: 批量同步钉钉用户信息 export async function POST() { try { const session = await auth(); if (!session?.user?.id) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } // 检查是否为管理员 const currentUser = await db.query.user.findFirst({ where: eq(user.id, session.user.id), }); if (currentUser?.role !== "admin") { return NextResponse.json({ error: "Forbidden" }, { status: 403 }); } // 获取钉钉配置 const config = await getDingtalkConfig(); const accessToken = await getDingtalkAccessToken(config.appKey, config.appSecret); // 获取需要同步的用户(有钉钉userId但没有部门信息的用户) const usersToSync = await db .select({ id: user.id, email: user.email, dingtalkUserId: user.dingtalkUserId, department: user.department, }) .from(user) .where( // 有钉钉userId但没有部门信息 isNotNull(user.dingtalkUserId) ); console.log(`[Sync DingTalk] Found ${usersToSync.length} users to check`); let syncedCount = 0; let skippedCount = 0; let errorCount = 0; const results: Array<{ email: string; status: string; department?: string }> = []; for (const u of usersToSync) { if (!u.dingtalkUserId) { skippedCount++; continue; } try { // 获取用户详细信息 const userDetail = await getUserDetailFromDingtalk(accessToken, u.dingtalkUserId); if (!userDetail) { errorCount++; results.push({ email: u.email, status: "failed", department: undefined }); continue; } // 获取部门名称列表 const deptIdList = userDetail.dept_id_list || []; const departmentNames: string[] = []; for (const deptId of deptIdList) { const deptName = await getDepartmentName(accessToken, deptId); if (deptName) { departmentNames.push(deptName); } } const departmentStr = departmentNames.length > 0 ? departmentNames.join(" / ") : null; // 只有部门信息变化时才更新 if (departmentStr && departmentStr !== u.department) { await db .update(user) .set({ department: departmentStr, name: userDetail.name || undefined, // 同时更新姓名 }) .where(eq(user.id, u.id)); syncedCount++; results.push({ email: u.email, status: "synced", department: departmentStr }); console.log(`[Sync DingTalk] Updated user ${u.email}: ${departmentStr}`); } else { skippedCount++; results.push({ email: u.email, status: "skipped", department: u.department || undefined }); } // 添加延迟避免触发钉钉API限流 await new Promise(resolve => setTimeout(resolve, 100)); } catch (error) { console.error(`[Sync DingTalk] Error syncing user ${u.email}:`, error); errorCount++; results.push({ email: u.email, status: "error" }); } } return NextResponse.json({ success: true, message: `同步完成`, stats: { total: usersToSync.length, synced: syncedCount, skipped: skippedCount, errors: errorCount, }, results, }); } catch (error) { console.error("[Sync DingTalk API] Error:", error); return NextResponse.json( { error: (error as Error).message || "同步失败" }, { status: 500 } ); } } // GET: 获取同步状态统计 export async function GET() { try { const session = await auth(); if (!session?.user?.id) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } // 检查是否为管理员 const currentUser = await db.query.user.findFirst({ where: eq(user.id, session.user.id), }); if (currentUser?.role !== "admin") { return NextResponse.json({ error: "Forbidden" }, { status: 403 }); } // 统计用户数据 const allUsers = await db .select({ id: user.id, dingtalkUserId: user.dingtalkUserId, department: user.department, }) .from(user); const stats = { total: allUsers.length, withDingtalkId: allUsers.filter(u => u.dingtalkUserId).length, withDepartment: allUsers.filter(u => u.department).length, needSync: allUsers.filter(u => u.dingtalkUserId && !u.department).length, }; return NextResponse.json({ stats }); } catch (error) { console.error("[Sync DingTalk API] Error:", error); return NextResponse.json({ error: "获取统计失败" }, { status: 500 }); } }