diff --git a/src/app/api/admin/data_migration/export/route.ts b/src/app/api/admin/data_migration/export/route.ts index 4319c24..ace7451 100644 --- a/src/app/api/admin/data_migration/export/route.ts +++ b/src/app/api/admin/data_migration/export/route.ts @@ -8,7 +8,7 @@ import { getAuthInfoFromCookie } from '@/lib/auth'; import { SimpleCrypto } from '@/lib/crypto'; import { db } from '@/lib/db'; import { CURRENT_VERSION } from '@/lib/version'; -import { updateProgress, clearProgress } from '../progress/route'; +import { updateProgress, clearProgress } from '@/lib/data-migration-progress'; export const runtime = 'nodejs'; @@ -36,6 +36,8 @@ export async function POST(req: NextRequest) { return NextResponse.json({ error: '权限不足,只有站长可以导出数据' }, { status: 401 }); } + const username = authInfo.username; // 存储到局部变量以便 TypeScript 类型推断 + const config = await db.getAdminConfig(); if (!config) { return NextResponse.json({ error: '无法获取配置' }, { status: 500 }); @@ -80,7 +82,7 @@ export async function POST(req: NextRequest) { // 为每个用户收集数据(只导出V2用户)- 使用并行处理 console.log(`开始并行导出 ${allUsers.length} 个用户的数据...`); - updateProgress(authInfo.username, 'export', 'collecting', 0, allUsers.length, '开始收集用户数据...'); + updateProgress(username, 'export', 'collecting', 0, allUsers.length, '开始收集用户数据...'); // 分块处理用户,每批处理数量可通过环境变量配置 const CHUNK_SIZE = parseInt(process.env.DATA_MIGRATION_CHUNK_SIZE || '10', 10); @@ -160,7 +162,7 @@ export async function POST(req: NextRequest) { exportedCount++; // 每处理完一个用户就更新进度 updateProgress( - authInfo.username, + username, 'export', 'collecting', exportedCount, @@ -176,15 +178,15 @@ export async function POST(req: NextRequest) { console.log(`成功导出 ${exportedCount} 个用户的数据`); // 将数据转换为JSON字符串 - updateProgress(authInfo.username, 'export', 'serializing', exportedCount, exportedCount, '正在序列化数据...'); + updateProgress(username, 'export', 'serializing', exportedCount, exportedCount, '正在序列化数据...'); const jsonData = JSON.stringify(exportData); // 先压缩数据 - updateProgress(authInfo.username, 'export', 'compressing', exportedCount, exportedCount, '正在压缩数据...'); + updateProgress(username, 'export', 'compressing', exportedCount, exportedCount, '正在压缩数据...'); const compressedData = await gzipAsync(jsonData); // 使用提供的密码加密压缩后的数据 - updateProgress(authInfo.username, 'export', 'encrypting', exportedCount, exportedCount, '正在加密数据...'); + updateProgress(username, 'export', 'encrypting', exportedCount, exportedCount, '正在加密数据...'); const encryptedData = SimpleCrypto.encrypt(compressedData.toString('base64'), password); // 生成文件名 @@ -193,8 +195,8 @@ export async function POST(req: NextRequest) { const filename = `moontv-backup-${timestamp}.dat`; // 清除进度信息 - updateProgress(authInfo.username, 'export', 'completed', exportedCount, exportedCount, '导出完成!'); - setTimeout(() => clearProgress(authInfo.username, 'export'), 3000); + updateProgress(username, 'export', 'completed', exportedCount, exportedCount, '导出完成!'); + setTimeout(() => clearProgress(username, 'export'), 3000); // 返回加密的数据作为文件下载 return new NextResponse(encryptedData, { diff --git a/src/app/api/admin/data_migration/import/route.ts b/src/app/api/admin/data_migration/import/route.ts index f06fa5e..203cc73 100644 --- a/src/app/api/admin/data_migration/import/route.ts +++ b/src/app/api/admin/data_migration/import/route.ts @@ -8,7 +8,7 @@ import { getAuthInfoFromCookie } from '@/lib/auth'; import { configSelfCheck, setCachedConfig } from '@/lib/config'; import { SimpleCrypto } from '@/lib/crypto'; import { db } from '@/lib/db'; -import { updateProgress, clearProgress } from '../progress/route'; +import { updateProgress, clearProgress } from '@/lib/data-migration-progress'; export const runtime = 'nodejs'; @@ -36,6 +36,8 @@ export async function POST(req: NextRequest) { return NextResponse.json({ error: '权限不足,只有站长可以导入数据' }, { status: 401 }); } + const username = authInfo.username; // 存储到局部变量以便 TypeScript 类型推断 + // 解析表单数据 const formData = await req.formData(); const file = formData.get('file') as File; @@ -79,7 +81,7 @@ export async function POST(req: NextRequest) { } // 开始导入数据 - 先清空现有数据 - updateProgress(authInfo.username, 'import', 'clearing', 0, 1, '正在清空现有数据...'); + updateProgress(username, 'import', 'clearing', 0, 1, '正在清空现有数据...'); await db.clearAllData(); // 额外清除所有V2用户(clearAllData可能只清除旧版用户) @@ -111,7 +113,7 @@ export async function POST(req: NextRequest) { const userCount = Object.keys(userData).length; console.log(`准备导入 ${userCount} 个用户的数据`); - updateProgress(authInfo.username, 'import', 'importing', 0, userCount, '开始导入用户数据...'); + updateProgress(username, 'import', 'importing', 0, userCount, '开始导入用户数据...'); // 分块处理用户,每批处理数量可通过环境变量配置 const CHUNK_SIZE = parseInt(process.env.DATA_MIGRATION_CHUNK_SIZE || '10', 10); @@ -122,7 +124,7 @@ export async function POST(req: NextRequest) { const chunk = usernames.slice(i, i + CHUNK_SIZE); console.log(`处理第 ${Math.floor(i / CHUNK_SIZE) + 1} 批用户 (${chunk.length} 个)`); updateProgress( - authInfo.username, + username, 'import', 'importing', importedCount, @@ -268,7 +270,7 @@ export async function POST(req: NextRequest) { for (let j = 0; j < reversed.length; j += DATA_BATCH_SIZE) { const batch = reversed.slice(j, j + DATA_BATCH_SIZE); await Promise.all( - batch.map(keyword => db.addSearchHistory(username, keyword)) + batch.map((keyword: string) => db.addSearchHistory(username, keyword)) ); } } @@ -328,7 +330,7 @@ export async function POST(req: NextRequest) { for (let j = 0; j < playlist.songs.length; j += DATA_BATCH_SIZE) { const batch = playlist.songs.slice(j, j + DATA_BATCH_SIZE); await Promise.all( - batch.map(song => + batch.map((song: any) => db.addSongToPlaylist(playlist.id, { platform: song.platform, id: song.id, @@ -360,7 +362,7 @@ export async function POST(req: NextRequest) { console.log(`已完成 ${importedCount}/${userCount} 个用户`); updateProgress( - authInfo.username, + username, 'import', 'importing', importedCount, @@ -370,8 +372,8 @@ export async function POST(req: NextRequest) { } console.log(`成功导入 ${importedCount} 个用户的user:info`); - updateProgress(authInfo.username, 'import', 'completed', importedCount, userCount, '导入完成!'); - setTimeout(() => clearProgress(authInfo.username, 'import'), 3000); + updateProgress(username, 'import', 'completed', importedCount, userCount, '导入完成!'); + setTimeout(() => clearProgress(username, 'import'), 3000); return NextResponse.json({ message: '数据导入成功', diff --git a/src/app/api/admin/data_migration/progress/route.ts b/src/app/api/admin/data_migration/progress/route.ts index 27cea81..2b7dfed 100644 --- a/src/app/api/admin/data_migration/progress/route.ts +++ b/src/app/api/admin/data_migration/progress/route.ts @@ -3,28 +3,10 @@ import { NextRequest } from 'next/server'; import { getAuthInfoFromCookie } from '@/lib/auth'; +import { getProgress } from '@/lib/data-migration-progress'; export const runtime = 'nodejs'; -// 存储进度信息的 Map -const progressStore = new Map(); - -// 清理过期的进度信息(超过5分钟) -setInterval(() => { - const now = Date.now(); - for (const [key, value] of progressStore.entries()) { - if (now - value.timestamp > 5 * 60 * 1000) { - progressStore.delete(key); - } - } -}, 60 * 1000); - export async function GET(req: NextRequest) { // 验证身份和权限 const authInfo = getAuthInfoFromCookie(req); @@ -36,6 +18,8 @@ export async function GET(req: NextRequest) { return new Response('Forbidden', { status: 403 }); } + const username = authInfo.username; // 存储到局部变量以便 TypeScript 类型推断 + const { searchParams } = new URL(req.url); const operation = searchParams.get('operation'); // 'export' or 'import' @@ -43,8 +27,6 @@ export async function GET(req: NextRequest) { return new Response('Missing operation parameter', { status: 400 }); } - const progressKey = `${authInfo.username}:${operation}`; - // 创建 SSE 响应 const encoder = new TextEncoder(); let interval: NodeJS.Timeout | null = null; @@ -54,7 +36,7 @@ export async function GET(req: NextRequest) { start(controller) { const sendProgress = () => { try { - const progress = progressStore.get(progressKey); + const progress = getProgress(username, operation as 'export' | 'import'); if (progress) { const data = JSON.stringify(progress); controller.enqueue(encoder.encode(`data: ${data}\n\n`)); @@ -97,28 +79,3 @@ export async function GET(req: NextRequest) { }, }); } - -// 辅助函数:更新进度 -export function updateProgress( - username: string, - operation: 'export' | 'import', - phase: string, - current: number, - total: number, - message: string -) { - const progressKey = `${username}:${operation}`; - progressStore.set(progressKey, { - phase, - current, - total, - message, - timestamp: Date.now(), - }); -} - -// 辅助函数:清除进度 -export function clearProgress(username: string, operation: 'export' | 'import') { - const progressKey = `${username}:${operation}`; - progressStore.delete(progressKey); -} diff --git a/src/lib/data-migration-progress.ts b/src/lib/data-migration-progress.ts new file mode 100644 index 0000000..be16959 --- /dev/null +++ b/src/lib/data-migration-progress.ts @@ -0,0 +1,50 @@ +// 存储进度信息的 Map +const progressStore = new Map(); + +// 清理过期的进度信息(超过5分钟) +setInterval(() => { + const now = Date.now(); + const entries = Array.from(progressStore.entries()); + for (const [key, value] of entries) { + if (now - value.timestamp > 5 * 60 * 1000) { + progressStore.delete(key); + } + } +}, 60 * 1000); + +// 辅助函数:更新进度 +export function updateProgress( + username: string, + operation: 'export' | 'import', + phase: string, + current: number, + total: number, + message: string +) { + const progressKey = `${username}:${operation}`; + progressStore.set(progressKey, { + phase, + current, + total, + message, + timestamp: Date.now(), + }); +} + +// 辅助函数:清除进度 +export function clearProgress(username: string, operation: 'export' | 'import') { + const progressKey = `${username}:${operation}`; + progressStore.delete(progressKey); +} + +// 辅助函数:获取进度 +export function getProgress(username: string, operation: 'export' | 'import') { + const progressKey = `${username}:${operation}`; + return progressStore.get(progressKey); +}