234 lines
6.8 KiB
TypeScript
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 { 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 });
}
}