feat: 日志自动清理 — 系统设置+定时任务+手动操作

1. 系统设置新增 cleanup 分组(6项配置):
   - 启用自动清理(开关)
   - 清理执行时间(默认凌晨3点)
   - 工单保留天数(默认90天)
   - 审计日志保留天数(默认180天)
   - 查询历史保留天数(默认30天)
   - 定时任务日志保留天数(默认30天)

2. log_cleanup.py: 自动清理服务
   - 只清理已完成/已拒绝的工单
   - 按保留天数过滤
   - 0天=不清理

3. APScheduler cron job: 每天03:00自动清理

4. API endpoints:
   - POST /settings/cleanup/run — 手动触发清理
   - GET /settings/cleanup/preview — 预览待清理数量

5. 前端 PlatformConfigPage:
   - GROUP_META 新增 cleanup 分组
   - CleanupActions 子组件: 预览+手动清理按钮

6. 清空所有历史工单数据(tickets/ticket_approvals/audit_logs/query_histories)
This commit is contained in:
logan committed 2026-05-25 00:19:38 +08:00
1 parent a8c92fe7ae
commit 00db0e5bca
5 files changed
+352

No files matched your search

+67
View File
@@ -15,11 +15,75 @@ interface SettingItem {
type SettingsMap = Record<string, SettingItem[]>;
/* ── 清理操作子组件 ── */
function CleanupActions() {
const [preview, setPreview] = useState<Record<string, number> | null>(null);
const [loading, setLoading] = useState(false);
const [cleaning, setCleaning] = useState(false);
const [result, setResult] = useState<string>('');
const handlePreview = async () => {
setLoading(true);
setResult('');
try {
const res = await api.get('/settings/cleanup/preview') as Record<string, number>;
setPreview(res);
} catch {
setResult('预览失败');
} finally {
setLoading(false);
}
};
const handleCleanup = async () => {
setCleaning(true);
setResult('');
try {
const res = await api.post('/settings/cleanup/run') as { message: string; stats: Record<string, number> };
const total = Object.values(res.stats).reduce((a, b) => a + b, 0);
setResult(`清理完成,共删除 ${total} 条记录`);
setPreview(null);
} catch {
setResult('清理失败');
} finally {
setCleaning(false);
}
};
return (
<div className="px-5 pb-4 pt-2 space-y-3">
<div className="flex items-center gap-3">
<button onClick={handlePreview} disabled={loading}
className="px-4 py-1.5 rounded-lg text-sm bg-[var(--dg-bg-tertiary)] text-[var(--dg-text-primary)] hover:bg-[var(--dg-bg-hover)] disabled:opacity-50 transition-colors">
{loading ? '查询中...' : '预览待清理数量'}
</button>
{preview && (
<button onClick={handleCleanup} disabled={cleaning}
className="px-4 py-1.5 rounded-lg text-sm bg-orange-500 hover:bg-orange-600 text-white disabled:opacity-50 transition-colors">
{cleaning ? '清理中...' : '立即清理'}
</button>
)}
</div>
{preview && (
<div className="text-xs text-[var(--dg-text-muted)] space-y-1">
{Object.entries(preview).map(([k, v]) => (
<div key={k}>{k}: <span className="text-[var(--dg-text-primary)] font-medium">{v}</span> 条</div>
))}
</div>
)}
{result && (
<div className="text-xs text-emerald-500">{result}</div>
)}
</div>
);
}
/* ── 分组元信息 ── */
const GROUP_META: Record<string, { label: string; icon: string; desc: string }> = {
query: { label: '查询控制', icon: '🔍', desc: '查询权限、LIMIT 上限、超时设置' },
security: { label: '安全策略', icon: '🔒', desc: '登录安全、密码策略、审计日志' },
ticket: { label: '工单流程', icon: '📋', desc: '审批流程、回滚控制' },
cleanup: { label: '日志清理', icon: '🧹', desc: '数据变更工单、审计日志、查询历史的自动清理策略' },
};
export default function PlatformConfigPage() {
@@ -179,6 +243,9 @@ export default function PlatformConfigPage() {
);
})}
</div>
{/* 日志清理分组专属操作区 */}
{group === 'cleanup' && <CleanupActions />}
</div>
);
})}