mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-03-01 16:20:25 +00:00
feat(webui): 新增配置全量备份与恢复功能。 (#1571)
* feat(webui): 新增配置全量备份与恢复功能。 * chore: Remove dependencies "archiver" * feat(webui): 增加上传文件大小限制配置并优化上传处理 * Use memory-based zip import/export and multer Replace disk-based zip handling with in-memory streaming to avoid temp files: remove unzipper/@types(unzipper) deps from package.json; update BackupConfig to stream-export configs with compressing.zip.Stream and to import by extracting uploaded zip buffer via compressing.zip.UncompressStream into in-memory Buffers. Backup of existing config is kept in-memory instead of copying to tmp, and imported files are written with path normalization checks. Router changed to use multer.memoryStorage() for uploads (remove dynamic tmp/disk upload logic and uploadSizeLimit usage). Also remove uploadSizeLimit from config schema. * Revert "chore: Remove dependencies "archiver"" This reverts commit 733ec4c421dcb530d872a5ae1eb41bcf3bec78d5. * Regenerate pnpm-lock.yaml (prune entries) Regenerated pnpm-lock.yaml to reflect the current dependency resolution. This update prunes many removed/unused lock entries (notably archiver, unzipper and related @types, older/deprecated packages such as rimraf v2/fstream/bluebird, etc.) and removes platform 'libc' metadata from several platform-specific packages. There are no package.json changes; run `pnpm install` to sync your local node_modules with the updated lockfile. --------- Co-authored-by: 手瓜一十雪 <nanaeonn@outlook.com>
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
import { Button } from '@heroui/button';
|
||||
import toast from 'react-hot-toast';
|
||||
import { LuDownload, LuUpload } from 'react-icons/lu';
|
||||
import { requestServerWithFetch } from '@/utils/request';
|
||||
|
||||
// 导入配置
|
||||
const handleImportConfig = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
// 检查文件类型
|
||||
if (!file.name.endsWith('.zip')) {
|
||||
toast.error('请选择zip格式的配置文件');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('configFile', file);
|
||||
|
||||
const response = await requestServerWithFetch('/OB11Config/ImportConfig', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.message || '导入配置失败');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
// 检查是否成功导入
|
||||
if (result.code === 0) {
|
||||
toast.success(result.data?.message || '配置导入成功。');
|
||||
} else {
|
||||
toast.error(`配置导入失败: ${result.data?.message || '未知错误'}`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
const msg = (error as Error).message;
|
||||
toast.error(`导入配置失败: ${msg}`);
|
||||
} finally {
|
||||
// 重置文件输入
|
||||
event.target.value = '';
|
||||
}
|
||||
};
|
||||
|
||||
// 导出配置
|
||||
const handleExportConfig = async () => {
|
||||
try {
|
||||
const response = await requestServerWithFetch('/OB11Config/ExportConfig', {
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('导出配置失败');
|
||||
}
|
||||
|
||||
// 创建下载链接
|
||||
const blob = await response.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
const fileName = response.headers.get('Content-Disposition')?.split('=')[1]?.replace(/"/g, '') || 'config_backup.zip';
|
||||
a.download = fileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(a);
|
||||
|
||||
toast.success('配置导出成功');
|
||||
} catch (error) {
|
||||
const msg = (error as Error).message;
|
||||
|
||||
toast.error(`导出配置失败: ${msg}`);
|
||||
}
|
||||
};
|
||||
|
||||
const BackupConfigCard: React.FC = () => {
|
||||
return (
|
||||
<div className='space-y-6'>
|
||||
<div>
|
||||
<h3 className='text-lg font-medium mb-4'>备份与恢复</h3>
|
||||
<p className='text-sm text-default-500 mb-4'>
|
||||
您可以通过导入/导出配置文件来备份和恢复NapCat的所有设置
|
||||
</p>
|
||||
|
||||
<div className='flex flex-wrap gap-3'>
|
||||
<Button
|
||||
isIconOnly
|
||||
className="bg-primary hover:bg-primary/90 text-white"
|
||||
radius='full'
|
||||
onPress={handleExportConfig}
|
||||
title="导出配置"
|
||||
>
|
||||
<LuDownload size={20} />
|
||||
</Button>
|
||||
<label className="cursor-pointer">
|
||||
<input
|
||||
type="file"
|
||||
accept=".zip"
|
||||
onChange={handleImportConfig}
|
||||
className="hidden"
|
||||
/>
|
||||
<Button
|
||||
isIconOnly
|
||||
className="bg-primary hover:bg-primary/90 text-white"
|
||||
radius='full'
|
||||
as="span"
|
||||
title="导入配置"
|
||||
>
|
||||
<LuUpload size={20} />
|
||||
</Button>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className='mt-4 p-3 bg-warning/10 border border-warning/20 rounded-lg'>
|
||||
<div className='flex items-start gap-2'>
|
||||
<p className='text-sm text-warning'>
|
||||
导入配置会覆盖当前所有设置,请谨慎操作。导入前建议先导出当前配置作为备份。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BackupConfigCard;
|
||||
@@ -13,6 +13,7 @@ import ServerConfigCard from './server';
|
||||
import SSLConfigCard from './ssl';
|
||||
import ThemeConfigCard from './theme';
|
||||
import WebUIConfigCard from './webui';
|
||||
import BackupConfigCard from './backup';
|
||||
|
||||
export interface ConfigPageProps {
|
||||
children?: React.ReactNode;
|
||||
@@ -108,6 +109,11 @@ export default function ConfigPage () {
|
||||
<ThemeConfigCard />
|
||||
</ConfigPageItem>
|
||||
</Tab>
|
||||
<Tab title='备份与恢复' key='backup'>
|
||||
<ConfigPageItem>
|
||||
<BackupConfigCard />
|
||||
</ConfigPageItem>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</section>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user