mirror of
https://github.com/MarSeventh/CloudFlare-ImgBed.git
synced 2026-01-31 09:03:19 +08:00
fix:优化备份和恢复功能
This commit is contained in:
157
functions/api/debug/restore-check.js
Normal file
157
functions/api/debug/restore-check.js
Normal file
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* 恢复功能检查工具
|
||||
*/
|
||||
|
||||
import { getDatabase } from '../../utils/databaseAdapter.js';
|
||||
|
||||
export async function onRequest(context) {
|
||||
var env = context.env;
|
||||
var url = new URL(context.request.url);
|
||||
var action = url.searchParams.get('action') || 'status';
|
||||
|
||||
try {
|
||||
var db = getDatabase(env);
|
||||
var results = {
|
||||
action: action,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
if (action === 'status') {
|
||||
// 检查当前数据库状态
|
||||
|
||||
// 统计文件数量
|
||||
var fileCount = 0;
|
||||
var cursor = null;
|
||||
while (true) {
|
||||
var response = await db.listFiles({
|
||||
limit: 1000,
|
||||
cursor: cursor
|
||||
});
|
||||
|
||||
if (!response || !response.keys || !Array.isArray(response.keys)) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (var item of response.keys) {
|
||||
if (!item.name.startsWith('manage@') && !item.name.startsWith('chunk_')) {
|
||||
if (item.metadata && item.metadata.TimeStamp) {
|
||||
fileCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cursor = response.cursor;
|
||||
if (!cursor) break;
|
||||
}
|
||||
|
||||
// 统计设置数量
|
||||
var settingsResponse = await db.listSettings({});
|
||||
var settingsCount = 0;
|
||||
if (settingsResponse && settingsResponse.keys) {
|
||||
settingsCount = settingsResponse.keys.length;
|
||||
}
|
||||
|
||||
// 检查关键设置
|
||||
var keySettings = {};
|
||||
var settingKeys = ['manage@sysConfig@page', 'manage@sysConfig@security'];
|
||||
for (var key of settingKeys) {
|
||||
try {
|
||||
var value = await db.get(key);
|
||||
keySettings[key] = {
|
||||
exists: !!value,
|
||||
length: value ? value.length : 0
|
||||
};
|
||||
} catch (error) {
|
||||
keySettings[key] = {
|
||||
exists: false,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
results.status = {
|
||||
fileCount: fileCount,
|
||||
settingsCount: settingsCount,
|
||||
keySettings: keySettings
|
||||
};
|
||||
|
||||
} else if (action === 'test') {
|
||||
// 测试恢复一个简单的设置
|
||||
var testKey = 'test_restore_' + Date.now();
|
||||
var testValue = 'test_value_' + Date.now();
|
||||
|
||||
try {
|
||||
// 写入测试数据
|
||||
await db.put(testKey, testValue);
|
||||
|
||||
// 读取验证
|
||||
var retrieved = await db.get(testKey);
|
||||
|
||||
// 清理测试数据
|
||||
await db.delete(testKey);
|
||||
|
||||
results.test = {
|
||||
success: true,
|
||||
valueMatch: retrieved === testValue,
|
||||
testKey: testKey,
|
||||
testValue: testValue,
|
||||
retrievedValue: retrieved
|
||||
};
|
||||
} catch (error) {
|
||||
results.test = {
|
||||
success: false,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
|
||||
} else if (action === 'sample') {
|
||||
// 提供样本恢复数据
|
||||
results.sampleData = {
|
||||
timestamp: Date.now(),
|
||||
version: "2.0.2",
|
||||
data: {
|
||||
fileCount: 1,
|
||||
files: {
|
||||
"test_file_" + Date.now(): {
|
||||
metadata: {
|
||||
FileName: "test.jpg",
|
||||
FileType: "image/jpeg",
|
||||
FileSize: "0.1",
|
||||
TimeStamp: Date.now(),
|
||||
Channel: "Test",
|
||||
ListType: "None"
|
||||
},
|
||||
value: null
|
||||
}
|
||||
},
|
||||
settings: {
|
||||
"test_setting_" + Date.now(): "test_value_" + Date.now()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
results.instructions = {
|
||||
usage: "Use this sample data to test restore functionality",
|
||||
endpoint: "/api/manage/sysConfig/backup",
|
||||
method: "POST with action=restore"
|
||||
};
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify(results, null, 2), {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
return new Response(JSON.stringify({
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
}), {
|
||||
status: 500,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/**
|
||||
* 恢复功能测试工具
|
||||
*/
|
||||
|
||||
import { getDatabase } from '../../utils/databaseAdapter.js';
|
||||
|
||||
export async function onRequest(context) {
|
||||
var env = context.env;
|
||||
var request = context.request;
|
||||
|
||||
try {
|
||||
if (request.method !== 'POST') {
|
||||
return new Response('Method not allowed', { status: 405 });
|
||||
}
|
||||
|
||||
var testData = await request.json();
|
||||
var db = getDatabase(env);
|
||||
|
||||
var results = {
|
||||
settings: [],
|
||||
files: [],
|
||||
errors: []
|
||||
};
|
||||
|
||||
// 测试恢复设置
|
||||
if (testData.settings) {
|
||||
for (var key in testData.settings) {
|
||||
try {
|
||||
var value = testData.settings[key];
|
||||
await db.put(key, value);
|
||||
|
||||
// 验证是否成功保存
|
||||
var retrieved = await db.get(key);
|
||||
results.settings.push({
|
||||
key: key,
|
||||
saved: true,
|
||||
retrieved: retrieved !== null,
|
||||
valueMatch: retrieved === value,
|
||||
originalLength: value.length,
|
||||
retrievedLength: retrieved ? retrieved.length : 0
|
||||
});
|
||||
} catch (error) {
|
||||
results.errors.push({
|
||||
type: 'setting',
|
||||
key: key,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 测试恢复文件
|
||||
if (testData.files) {
|
||||
for (var fileId in testData.files) {
|
||||
try {
|
||||
var fileData = testData.files[fileId];
|
||||
|
||||
if (fileData.value) {
|
||||
await db.put(fileId, fileData.value, {
|
||||
metadata: fileData.metadata
|
||||
});
|
||||
} else if (fileData.metadata) {
|
||||
await db.put(fileId, '', {
|
||||
metadata: fileData.metadata
|
||||
});
|
||||
}
|
||||
|
||||
// 验证是否成功保存
|
||||
var retrieved = await db.getWithMetadata(fileId);
|
||||
results.files.push({
|
||||
fileId: fileId,
|
||||
saved: true,
|
||||
retrieved: retrieved !== null,
|
||||
hasMetadata: retrieved && retrieved.metadata !== null
|
||||
});
|
||||
} catch (error) {
|
||||
results.errors.push({
|
||||
type: 'file',
|
||||
fileId: fileId,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify(results, null, 2), {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
return new Response(JSON.stringify({
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
}), {
|
||||
status: 500,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -42,23 +42,53 @@ async function handleBackup(context) {
|
||||
}
|
||||
};
|
||||
|
||||
// 首先从索引中读取所有文件信息
|
||||
const indexResult = await readIndex(context, {
|
||||
count: -1, // 获取所有文件
|
||||
start: 0,
|
||||
includeSubdirFiles: true // 包含子目录下的文件
|
||||
});
|
||||
backupData.data.fileCount = indexResult.files.length;
|
||||
// 直接从数据库读取所有文件信息,不依赖索引
|
||||
const db = getDatabase(env);
|
||||
let allFiles = [];
|
||||
let cursor = null;
|
||||
|
||||
// 分批获取所有文件
|
||||
while (true) {
|
||||
const response = await db.listFiles({
|
||||
limit: 1000,
|
||||
cursor: cursor
|
||||
});
|
||||
|
||||
if (!response || !response.keys || !Array.isArray(response.keys)) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (const item of response.keys) {
|
||||
// 跳过管理相关的键和分块数据
|
||||
if (item.name.startsWith('manage@') || item.name.startsWith('chunk_')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 跳过没有元数据的文件
|
||||
if (!item.metadata || !item.metadata.TimeStamp) {
|
||||
continue;
|
||||
}
|
||||
|
||||
allFiles.push({
|
||||
id: item.name,
|
||||
metadata: item.metadata
|
||||
});
|
||||
}
|
||||
|
||||
cursor = response.cursor;
|
||||
if (!cursor) break;
|
||||
}
|
||||
|
||||
backupData.data.fileCount = allFiles.length;
|
||||
|
||||
// 备份文件数据
|
||||
for (const file of indexResult.files) {
|
||||
for (const file of allFiles) {
|
||||
const fileId = file.id;
|
||||
const metadata = file.metadata;
|
||||
|
||||
// 对于TelegramNew渠道且IsChunked为true的文件,需要从数据库读取其值
|
||||
if (metadata.Channel === 'TelegramNew' && metadata.IsChunked === true) {
|
||||
try {
|
||||
const db = getDatabase(env);
|
||||
const fileData = await db.getWithMetadata(fileId);
|
||||
backupData.data.files[fileId] = {
|
||||
metadata: metadata,
|
||||
@@ -82,7 +112,7 @@ async function handleBackup(context) {
|
||||
}
|
||||
|
||||
// 备份系统设置
|
||||
const db = getDatabase(env);
|
||||
// db 已经在上面定义了
|
||||
|
||||
// 备份所有设置,不仅仅是manage@开头的
|
||||
const allSettingsList = await db.listSettings({});
|
||||
@@ -149,27 +179,42 @@ async function handleRestore(request, env) {
|
||||
|
||||
// 恢复文件数据
|
||||
const db = getDatabase(env);
|
||||
for (const [key, fileData] of Object.entries(backupData.data.files)) {
|
||||
try {
|
||||
if (fileData.value) {
|
||||
// 对于有value的文件(如telegram分块文件),恢复完整数据
|
||||
await db.put(key, fileData.value, {
|
||||
metadata: fileData.metadata
|
||||
});
|
||||
} else if (fileData.metadata) {
|
||||
// 只恢复元数据
|
||||
await db.put(key, '', {
|
||||
metadata: fileData.metadata
|
||||
});
|
||||
const fileEntries = Object.entries(backupData.data.files);
|
||||
const batchSize = 50; // 批量处理,避免超时
|
||||
|
||||
for (let i = 0; i < fileEntries.length; i += batchSize) {
|
||||
const batch = fileEntries.slice(i, i + batchSize);
|
||||
|
||||
for (const [key, fileData] of batch) {
|
||||
try {
|
||||
if (fileData.value) {
|
||||
// 对于有value的文件(如telegram分块文件),恢复完整数据
|
||||
await db.put(key, fileData.value, {
|
||||
metadata: fileData.metadata
|
||||
});
|
||||
} else if (fileData.metadata) {
|
||||
// 只恢复元数据
|
||||
await db.put(key, '', {
|
||||
metadata: fileData.metadata
|
||||
});
|
||||
}
|
||||
restoredFiles++;
|
||||
} catch (error) {
|
||||
console.error(`恢复文件 ${key} 失败:`, error);
|
||||
}
|
||||
restoredFiles++;
|
||||
} catch (error) {
|
||||
console.error(`恢复文件 ${key} 失败:`, error);
|
||||
}
|
||||
|
||||
// 每批处理后短暂暂停,避免过载
|
||||
if (i + batchSize < fileEntries.length) {
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
}
|
||||
}
|
||||
|
||||
// 恢复系统设置
|
||||
for (const [key, value] of Object.entries(backupData.data.settings)) {
|
||||
const settingEntries = Object.entries(backupData.data.settings);
|
||||
console.log(`开始恢复 ${settingEntries.length} 个设置`);
|
||||
|
||||
for (const [key, value] of settingEntries) {
|
||||
try {
|
||||
console.log(`恢复设置: ${key}, 长度: ${value.length}`);
|
||||
await db.put(key, value);
|
||||
@@ -181,6 +226,7 @@ async function handleRestore(request, env) {
|
||||
console.log(`设置 ${key} 恢复成功`);
|
||||
} else {
|
||||
console.error(`设置 ${key} 恢复后验证失败`);
|
||||
console.error(`原始长度: ${value.length}, 检索长度: ${retrieved ? retrieved.length : 'null'}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`恢复设置 ${key} 失败:`, error);
|
||||
|
||||
Reference in New Issue
Block a user