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,
});
|