156 lines
4.8 KiB
TypeScript
156 lines
4.8 KiB
TypeScript
import { createError, defineEventHandler } from 'h3'
|
|
import { promises as fs } from 'fs'
|
|
import path from 'path'
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
try {
|
|
// 验证管理员权限
|
|
const user = event.context.user
|
|
if (!user || !['ADMIN', 'SUPER_ADMIN'].includes(user.role)) {
|
|
throw createError({
|
|
statusCode: 403,
|
|
statusMessage: '权限不足'
|
|
})
|
|
}
|
|
|
|
// 检测运行环境
|
|
const isVercel = process.env.VERCEL || process.env.VERCEL_ENV
|
|
const isNetlify = process.env.NETLIFY
|
|
const isServerless = isVercel || isNetlify
|
|
|
|
if (isServerless) {
|
|
// 在无服务器环境中,无法持久化存储文件,返回空列表
|
|
console.log('🌐 检测到无服务器环境,备份文件无法持久化存储')
|
|
return {
|
|
success: true,
|
|
backups: [],
|
|
total: 0,
|
|
message: '无服务器环境中备份文件无法持久化存储,请使用直接下载功能'
|
|
}
|
|
}
|
|
|
|
const backupDir = path.join(process.cwd(), 'backups')
|
|
|
|
// 检查备份目录是否存在
|
|
try {
|
|
await fs.access(backupDir)
|
|
} catch {
|
|
// 如果目录不存在,尝试创建它
|
|
try {
|
|
await fs.mkdir(backupDir, { recursive: true })
|
|
} catch (mkdirError) {
|
|
console.warn('无法创建备份目录,可能在只读文件系统中:', mkdirError.message)
|
|
return {
|
|
success: true,
|
|
backups: [],
|
|
total: 0,
|
|
message: '文件系统只读,备份文件无法持久化存储'
|
|
}
|
|
}
|
|
return {
|
|
success: true,
|
|
backups: [],
|
|
total: 0
|
|
}
|
|
}
|
|
|
|
// 读取备份目录中的文件
|
|
const files = await fs.readdir(backupDir)
|
|
const backupFiles = files.filter(
|
|
(file) =>
|
|
file.endsWith('.json') &&
|
|
(file.startsWith('database-backup-') ||
|
|
file.startsWith('users-backup-') ||
|
|
file.startsWith('users-system-backup-') ||
|
|
file.startsWith('songs-backup-') ||
|
|
file.startsWith('songs-system-backup-') ||
|
|
file.startsWith('system-settings-backup-') ||
|
|
file.startsWith('uploaded-'))
|
|
)
|
|
|
|
const backups = []
|
|
|
|
for (const filename of backupFiles) {
|
|
try {
|
|
const filepath = path.join(backupDir, filename)
|
|
const stats = await fs.stat(filepath)
|
|
|
|
// 尝试读取备份文件的元数据
|
|
let metadata = null
|
|
try {
|
|
const fileContent = await fs.readFile(filepath, 'utf8')
|
|
const backupData = JSON.parse(fileContent)
|
|
|
|
if (backupData.metadata) {
|
|
metadata = backupData.metadata
|
|
} else if (backupData.users) {
|
|
// 兼容旧格式的用户备份文件
|
|
metadata = {
|
|
version: '0.1',
|
|
timestamp: backupData.timestamp || stats.mtime.toISOString(),
|
|
creator: 'system',
|
|
description: '用户数据备份(旧格式)',
|
|
tables: [
|
|
{ name: 'users', recordCount: backupData.totalUsers || backupData.users.length }
|
|
],
|
|
totalRecords: backupData.totalUsers || backupData.users.length
|
|
}
|
|
}
|
|
} catch (parseError) {
|
|
console.warn(`无法解析备份文件 ${filename}:`, parseError.message)
|
|
}
|
|
|
|
// 判断备份类型
|
|
let type = 'users' // 默认为用户备份
|
|
if (filename.startsWith('database-backup-')) {
|
|
type = 'full'
|
|
} else if (
|
|
filename.startsWith('users-backup-') ||
|
|
filename.startsWith('users-system-backup-')
|
|
) {
|
|
type = 'users'
|
|
} else if (filename.startsWith('songs-backup-') || filename.startsWith('songs-system-backup-')) {
|
|
type = 'songs'
|
|
} else if (filename.startsWith('system-settings-backup-')) {
|
|
type = 'system'
|
|
} else if (filename.startsWith('uploaded-')) {
|
|
// 对于上传的文件,根据元数据判断类型
|
|
if (metadata) {
|
|
if (metadata.backupType) {
|
|
type = metadata.backupType
|
|
} else if (metadata.tables && metadata.tables.length > 1) {
|
|
type = 'full'
|
|
}
|
|
}
|
|
}
|
|
|
|
backups.push({
|
|
filename,
|
|
size: stats.size,
|
|
createdAt: stats.mtime,
|
|
metadata,
|
|
type,
|
|
isValid: metadata !== null
|
|
})
|
|
} catch (error) {
|
|
console.error(`处理备份文件 ${filename} 失败:`, error)
|
|
}
|
|
}
|
|
|
|
// 按创建时间倒序排列
|
|
backups.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
|
|
|
|
return {
|
|
success: true,
|
|
backups,
|
|
total: backups.length
|
|
}
|
|
} catch (error) {
|
|
console.error('获取备份文件列表失败:', error)
|
|
throw createError({
|
|
statusCode: 500,
|
|
statusMessage: '获取备份文件列表失败'
|
|
})
|
|
}
|
|
})
|