feat(admin): 添加 S3 备份列表查看、下载和删除功能
- 在数据管理页面添加“查看备份”按钮,用于打开备份列表弹窗 - 新增 S3BackupModal 组件,展示备份文件列表,支持下载和删除操作 - 扩展 S3 工具类,支持列出、获取和删除 S3 对象 - 新增后端 API 接口:获取备份列表、删除备份、下载备份文件 - 为所有支持的语言添加相应的国际化文本
This commit is contained in:
@@ -5,13 +5,27 @@ import { S3Client } from '../../utils/s3';
|
||||
import { getConfigs } from './exportConfig';
|
||||
import { getStatsData } from './exportStats';
|
||||
|
||||
async function getS3Client(c: Context<{ Bindings: Bindings }>): Promise<{ s3: S3Client; settings: any } | Response> {
|
||||
const settings = await loadS3Settings(c.env);
|
||||
if (!settings.endpoint || !settings.bucket || !settings.accessKeyId || !settings.secretAccessKey) {
|
||||
return c.json({ message: 'S3 配置不完整,请先配置 S3 信息' }, 400);
|
||||
}
|
||||
const s3 = new S3Client({
|
||||
endpoint: settings.endpoint,
|
||||
accessKeyId: settings.accessKeyId,
|
||||
secretAccessKey: settings.secretAccessKey,
|
||||
bucket: settings.bucket,
|
||||
region: settings.region,
|
||||
});
|
||||
return { s3, settings };
|
||||
}
|
||||
|
||||
export async function triggerS3Backup(c: Context<{ Bindings: Bindings }>) {
|
||||
try {
|
||||
// 1. Load S3 Settings
|
||||
const settings = await loadS3Settings(c.env);
|
||||
if (!settings.endpoint || !settings.bucket || !settings.accessKeyId || !settings.secretAccessKey) {
|
||||
return c.json({ message: 'S3 配置不完整,请先配置 S3 信息' }, 400);
|
||||
}
|
||||
const result = await getS3Client(c);
|
||||
if (result instanceof Response) return result;
|
||||
const { s3 } = result;
|
||||
|
||||
// 2. Gather Backup Data (Logic from exportBackup.ts)
|
||||
const { results: comments } = await c.env.CWD_DB.prepare('SELECT * FROM Comment ORDER BY priority DESC, created DESC').all();
|
||||
@@ -33,14 +47,6 @@ export async function triggerS3Backup(c: Context<{ Bindings: Bindings }>) {
|
||||
const fileName = `cwd-backup-${dateStr}-${Date.now()}.json`;
|
||||
|
||||
// 3. Upload to S3
|
||||
const s3 = new S3Client({
|
||||
endpoint: settings.endpoint,
|
||||
accessKeyId: settings.accessKeyId,
|
||||
secretAccessKey: settings.secretAccessKey,
|
||||
bucket: settings.bucket,
|
||||
region: settings.region,
|
||||
});
|
||||
|
||||
await s3.putObject(fileName, jsonString);
|
||||
|
||||
return c.json({
|
||||
@@ -52,3 +58,62 @@ export async function triggerS3Backup(c: Context<{ Bindings: Bindings }>) {
|
||||
return c.json({ message: e.message || 'S3 备份失败' }, 500);
|
||||
}
|
||||
}
|
||||
|
||||
export async function listS3Backups(c: Context<{ Bindings: Bindings }>) {
|
||||
try {
|
||||
const result = await getS3Client(c);
|
||||
if (result instanceof Response) return result;
|
||||
const { s3 } = result;
|
||||
|
||||
const files = await s3.listObjects('cwd-backup-');
|
||||
return c.json({ files });
|
||||
} catch (e: any) {
|
||||
console.error('S3 List Backups Error:', e);
|
||||
return c.json({ message: e.message || '获取备份列表失败' }, 500);
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteS3BackupHandler(c: Context<{ Bindings: Bindings }>) {
|
||||
try {
|
||||
const key = c.req.query('key');
|
||||
if (!key) {
|
||||
return c.json({ message: '缺少 key 参数' }, 400);
|
||||
}
|
||||
|
||||
const result = await getS3Client(c);
|
||||
if (result instanceof Response) return result;
|
||||
const { s3 } = result;
|
||||
|
||||
await s3.deleteObject(key);
|
||||
return c.json({ message: '删除成功' });
|
||||
} catch (e: any) {
|
||||
console.error('S3 Delete Backup Error:', e);
|
||||
return c.json({ message: e.message || '删除备份失败' }, 500);
|
||||
}
|
||||
}
|
||||
|
||||
export async function downloadS3BackupHandler(c: Context<{ Bindings: Bindings }>) {
|
||||
try {
|
||||
const key = c.req.query('key');
|
||||
if (!key) {
|
||||
return c.json({ message: '缺少 key 参数' }, 400);
|
||||
}
|
||||
|
||||
const result = await getS3Client(c);
|
||||
if (result instanceof Response) return result;
|
||||
const { s3 } = result;
|
||||
|
||||
const s3Response = await s3.getObject(key);
|
||||
const body = await s3Response.arrayBuffer();
|
||||
|
||||
// Set headers for file download
|
||||
c.header('Content-Type', 'application/json');
|
||||
c.header('Content-Disposition', `attachment; filename="${key}"`);
|
||||
c.header('Content-Length', String(body.byteLength));
|
||||
|
||||
return c.body(body);
|
||||
} catch (e: any) {
|
||||
console.error('S3 Download Backup Error:', e);
|
||||
return c.json({ message: e.message || '下载备份失败' }, 500);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user