'use client'; import * as React from 'react'; import { useCallback, useEffect, useState } from 'react'; import { toast } from 'sonner'; import { HardDrive, RefreshCw, Trash2 } from 'lucide-react'; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Skeleton } from '@/components/ui/skeleton'; import { Label } from '@/components/ui/label'; import { Input } from '@/components/ui/input'; import { Switch } from '@/components/ui/switch'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import type { CacheStatus } from '@/lib/services/admin'; import services from '@/lib/services'; /** * 格式化字节大小 */ const formatBytes = (bytes: number) => { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }; /** * 格式化数字,每3位加逗号 */ const formatNumber = (num: number | string) => { if (num === undefined || num === null) return '0'; return num.toString().replace(/\B(?=(\d{3})+(?!\B))/g, ','); }; interface CacheManagerProps { refreshTrigger: number; } export function CacheManager({ refreshTrigger }: CacheManagerProps) { const [cacheStatus, setCacheStatus] = useState(null); const [loadingCache, setLoadingCache] = useState(true); const [savingConfig, setSavingConfig] = useState(false); const [clearingCache, setClearingCache] = useState(false); const [showClearConfirm, setShowClearConfirm] = useState(false); // 策略配置表单状态 const [maxSizeMB, setMaxSizeMB] = useState('100'); const [ttlMinutes, setTtlMinutes] = useState('60'); const [lruEnabled, setLruEnabled] = useState(true); // 获取磁盘缓存状态 const fetchCacheStatus = useCallback(async (isSilent = false) => { if (!isSilent) setLoadingCache(true); try { const data = await services.adminCache.getCacheStatus(); setCacheStatus(data); setMaxSizeMB(data.max_size_mb.toString()); setTtlMinutes(data.ttl_minutes.toString()); setLruEnabled(data.lru_enabled); } catch (err) { toast.error('获取磁盘缓存状态失败', { description: err instanceof Error ? err.message : '未知错误', }); } finally { setLoadingCache(false); } }, []); // 保存配置 const handleSaveConfig = async (e: React.FormEvent) => { e.preventDefault(); const size = parseInt(maxSizeMB, 10); const ttl = parseInt(ttlMinutes, 10); if (isNaN(size) || size < 1) { toast.error('保存失败', { description: '最大容量限制必须是大于等于 1 的整数', }); return; } if (isNaN(ttl) || ttl < 0) { toast.error('保存失败', { description: '默认过期时间必须是大于等于 0 的整数', }); return; } setSavingConfig(true); try { await services.adminCache.updateCacheConfig({ max_size_mb: size, ttl_minutes: ttl, lru_enabled: lruEnabled, }); toast.success('保存成功', { description: '磁盘缓存策略已热更新' }); await fetchCacheStatus(true); } catch (err) { toast.error('保存配置失败', { description: err instanceof Error ? err.message : '未知错误', }); } finally { setSavingConfig(false); } }; // 清空磁盘缓存数据 const handleClearCache = async () => { setClearingCache(true); try { await services.adminCache.clearCache(); toast.success('清空成功', { description: '缓存数据已全部清除' }); setShowClearConfirm(false); await fetchCacheStatus(true); } catch (err) { toast.error('清空缓存失败', { description: err instanceof Error ? err.message : '未知错误', }); } finally { setClearingCache(false); } }; // 初始化拉取 useEffect(() => { fetchCacheStatus(); }, [fetchCacheStatus]); // 当外部刷新触发器改变时自动刷新 useEffect(() => { fetchCacheStatus(true); }, [refreshTrigger, fetchCacheStatus]); return (
缓存管理
管理和监控系统级磁盘缓存的资源占用、生命周期及淘汰策略
{loadingCache && !cacheStatus ? (
) : cacheStatus ? (
{/* 左边:状态区 (2/5 cols) */}

运行状态

{/* 已占空间 */}

已用空间

{formatBytes(cacheStatus.total_size)}

{/* Key数量 */}

缓存键数量

{formatNumber(cacheStatus.keys_count)}{' '} 个文件

{/* 存储路径 */}

缓存基准目录

{cacheStatus.base_path}
{/* 右边:配置区 (3/5 cols) */}

策略配置

setMaxSizeMB(e.target.value)} className='h-8 text-xs bg-background/50 border-border/40' placeholder='例如 100' required />

当总大小超出该值时,自动触发淘汰算法

setTtlMinutes(e.target.value)} className='h-8 text-xs bg-background/50 border-border/40' placeholder='例如 60,0 表示永不过期' required />

缓存项的最长存活时间,超期后将失效被清理

在到达最大容量限制时,自动移除最久未被访问的缓存项。关闭该功能仅清理过期项。
) : (
未加载到缓存状态信息
)}
{/* 清除缓存确认对话框 */} 确认清空所有缓存? 该操作将彻底清空磁盘目录下的全部缓存文件(包含临时解压、处理后的图片及各种块文件),重置键数量统计为 0。此操作不可撤销,且可能导致用户拉取资源时出现一过性的响应变慢。
); }