From bc8b9515a897130791a0da3c88744addd651e1d9 Mon Sep 17 00:00:00 2001 From: mtvpls Date: Wed, 24 Dec 2025 00:42:28 +0800 Subject: [PATCH] =?UTF-8?q?=E6=95=B0=E6=8D=AE=E8=BF=81=E7=A7=BB=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=96=B0=E7=94=A8=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/admin/page.tsx | 3 +- .../api/admin/data_migration/export/route.ts | 36 +++++++++- .../api/admin/data_migration/import/route.ts | 65 ++++++++++++++++++- src/lib/config.ts | 25 +++++++ src/lib/db.ts | 1 + src/lib/upstash.db.ts | 3 + 6 files changed, 127 insertions(+), 6 deletions(-) diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 5cdfb98..92943c3 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -427,6 +427,7 @@ interface UserConfigProps { role: 'owner' | 'admin' | 'user'; banned: boolean; tags?: string[]; + oidcSub?: string; enabledApis?: string[]; created_at: number; }> | null; @@ -1453,7 +1454,7 @@ const UserConfig = ({ config, role, refreshConfig, usersV2, userPage, userTotalP
{user.username} - {(user as any).oidcSub && ( + {user.oidcSub && ( OIDC diff --git a/src/app/api/admin/data_migration/export/route.ts b/src/app/api/admin/data_migration/export/route.ts index 1ccb281..77ea112 100644 --- a/src/app/api/admin/data_migration/export/route.ts +++ b/src/app/api/admin/data_migration/export/route.ts @@ -54,14 +54,26 @@ export async function POST(req: NextRequest) { // 管理员配置 adminConfig: config, // 所有用户数据 - userData: {} as { [username: string]: any } + userData: {} as { [username: string]: any }, + // V2用户信息 + usersV2: [] as any[] } }; - // 获取所有用户 + // 获取所有V2用户 + const usersV2Result = await db.getUserListV2(0, 10000, process.env.USERNAME); + exportData.data.usersV2 = usersV2Result.users; + + // 获取所有用户(包括旧版用户) let allUsers = await db.getAllUsers(); // 添加站长用户 allUsers.push(process.env.USERNAME); + // 添加V2用户 + usersV2Result.users.forEach(user => { + if (!allUsers.includes(user.username)) { + allUsers.push(user.username); + } + }); allUsers = Array.from(new Set(allUsers)); // 为每个用户收集数据 @@ -76,7 +88,9 @@ export async function POST(req: NextRequest) { // 跳过片头片尾配置 skipConfigs: await db.getAllSkipConfigs(username), // 用户密码(通过验证空密码来检查用户是否存在,然后获取密码) - password: await getUserPassword(username) + password: await getUserPassword(username), + // V2用户的加密密码 + passwordV2: await getUserPasswordV2(username) }; exportData.data.userData[username] = userData; @@ -134,3 +148,19 @@ async function getUserPassword(username: string): Promise { return null; } } + +// 辅助函数:获取V2用户的加密密码 +async function getUserPasswordV2(username: string): Promise { + try { + const storage = (db as any).storage; + if (storage && typeof storage.client?.hget === 'function') { + const userInfoKey = `user:${username}:info`; + const password = await storage.client.hget(userInfoKey, 'password'); + return password; + } + return null; + } catch (error) { + console.error(`获取用户 ${username} V2密码失败:`, error); + return null; + } +} diff --git a/src/app/api/admin/data_migration/import/route.ts b/src/app/api/admin/data_migration/import/route.ts index 73aafcb..7b246ac 100644 --- a/src/app/api/admin/data_migration/import/route.ts +++ b/src/app/api/admin/data_migration/import/route.ts @@ -94,13 +94,73 @@ export async function POST(req: NextRequest) { // 不影响主流程,继续执行 } + // 导入V2用户信息 + if (importData.data.usersV2 && Array.isArray(importData.data.usersV2)) { + for (const userV2 of importData.data.usersV2) { + try { + // 跳过站长(站长使用环境变量认证) + if (userV2.role === 'owner') { + continue; + } + + // 获取用户的加密密码 + const userData = importData.data.userData[userV2.username]; + const passwordV2 = userData?.passwordV2; + + if (passwordV2) { + // 直接使用加密后的密码创建用户 + const storage = (db as any).storage; + if (storage && typeof storage.client?.hset === 'function') { + const userInfoKey = `user:${userV2.username}:info`; + const createdAt = userV2.created_at || Date.now(); + + const userInfo: any = { + role: userV2.role, + banned: userV2.banned, + password: passwordV2, + created_at: createdAt.toString(), + }; + + if (userV2.tags && userV2.tags.length > 0) { + userInfo.tags = JSON.stringify(userV2.tags); + } + + if (userV2.oidcSub) { + userInfo.oidcSub = userV2.oidcSub; + // 创建OIDC映射 + const oidcSubKey = `oidc:sub:${userV2.oidcSub}`; + await storage.client.set(oidcSubKey, userV2.username); + } + + if (userV2.enabledApis && userV2.enabledApis.length > 0) { + userInfo.enabledApis = JSON.stringify(userV2.enabledApis); + } + + await storage.client.hset(userInfoKey, userInfo); + + // 添加到用户列表(Sorted Set) + const userListKey = 'user:list'; + await storage.client.zadd(userListKey, { + score: createdAt, + member: userV2.username, + }); + + console.log(`V2用户 ${userV2.username} 导入成功`); + } + } + } catch (error) { + console.error(`导入V2用户 ${userV2.username} 失败:`, error); + } + } + } + // 导入用户数据 const userData = importData.data.userData; for (const username in userData) { const user = userData[username]; - // 重新注册用户(包含密码) - if (user.password) { + // 重新注册用户(包含密码)- 仅用于旧版用户 + if (user.password && !importData.data.usersV2?.find((u: any) => u.username === username)) { await db.registerUser(username, user.password); } @@ -139,6 +199,7 @@ export async function POST(req: NextRequest) { return NextResponse.json({ message: '数据导入成功', importedUsers: Object.keys(userData).length, + importedUsersV2: importData.data.usersV2?.length || 0, timestamp: importData.timestamp, serverVersion: typeof importData.serverVersion === 'string' ? importData.serverVersion : '未知版本' }); diff --git a/src/lib/config.ts b/src/lib/config.ts index 67e5432..bfbebbf 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -328,6 +328,31 @@ export async function getConfig(): Promise { } adminConfig = configSelfCheck(adminConfig); cachedConfig = adminConfig; + + // 自动迁移用户(如果配置中有用户且V2存储支持) + // 过滤掉站长后检查是否有需要迁移的用户 + const nonOwnerUsers = adminConfig.UserConfig.Users.filter( + (u) => u.username !== process.env.USERNAME + ); + if (!dbReadFailed && nonOwnerUsers.length > 0) { + try { + // 检查是否支持V2存储 + const storage = (db as any).storage; + if (storage && typeof storage.createUserV2 === 'function') { + console.log('检测到配置中有用户,开始自动迁移...'); + await db.migrateUsersFromConfig(adminConfig); + // 迁移完成后,清空配置中的用户列表并保存 + adminConfig.UserConfig.Users = []; + await db.saveAdminConfig(adminConfig); + cachedConfig = adminConfig; + console.log('用户自动迁移完成'); + } + } catch (error) { + console.error('自动迁移用户失败:', error); + // 不影响主流程,继续执行 + } + } + return cachedConfig; } diff --git a/src/lib/db.ts b/src/lib/db.ts index 7291d6a..06c12ce 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -234,6 +234,7 @@ export class DbManager { role: 'owner' | 'admin' | 'user'; banned: boolean; tags?: string[]; + oidcSub?: string; enabledApis?: string[]; created_at: number; }>; diff --git a/src/lib/upstash.db.ts b/src/lib/upstash.db.ts index 4044f72..6580d9c 100644 --- a/src/lib/upstash.db.ts +++ b/src/lib/upstash.db.ts @@ -504,6 +504,7 @@ export class UpstashRedisStorage implements IStorage { role: 'owner' | 'admin' | 'user'; banned: boolean; tags?: string[]; + oidcSub?: string; enabledApis?: string[]; created_at: number; }>; @@ -528,6 +529,7 @@ export class UpstashRedisStorage implements IStorage { role: 'owner' as const, banned: ownerInfo.banned, tags: ownerInfo.tags, + oidcSub: ownerInfo.oidcSub, enabledApis: ownerInfo.enabledApis, created_at: ownerInfo.created_at, }); @@ -549,6 +551,7 @@ export class UpstashRedisStorage implements IStorage { role: userInfo.role, banned: userInfo.banned, tags: userInfo.tags, + oidcSub: userInfo.oidcSub, enabledApis: userInfo.enabledApis, created_at: userInfo.created_at, });