feat(cleanup): 解耦 request_candidates 与 provider_api_keys 生命周期

- 移除 request_candidates.key_id 对 provider_api_keys 的外键约束(含迁移脚本)
- 删除 Key 时不再级联删除候选记录,改为独立按保留天数定时清理
- 新增 request_candidates_retention_days / request_candidates_cleanup_batch_size 配置项
- batch_delete_task 增加 lock_timeout 及超时自动降批重试机制
- cleanup_key_references 提取阶段化清理流程,移除 RequestCandidate 联动删除
- 前端 CleanupPolicySection 新增候选记录保留天数和清理批次配置

Closes #227

Co-authored-by: Entropy-Xu <entropy.xu@cloudhabitatsh.com>
This commit is contained in:
fawney19
2026-03-14 01:31:39 +08:00
parent bdfe4adc98
commit 776dd2f8ea
14 changed files with 516 additions and 70 deletions

View File

@@ -99,6 +99,8 @@
:log-retention-days="systemConfig.log_retention_days"
:cleanup-batch-size="systemConfig.cleanup_batch_size"
:audit-log-retention-days="systemConfig.audit_log_retention_days"
:request-candidates-retention-days="systemConfig.request_candidates_retention_days"
:request-candidates-cleanup-batch-size="systemConfig.request_candidates_cleanup_batch_size"
:loading="cleanupConfigLoading"
:has-changes="hasCleanupConfigChanges"
@save="saveCleanupConfig"
@@ -109,6 +111,8 @@
@update:log-retention-days="systemConfig.log_retention_days = $event"
@update:cleanup-batch-size="systemConfig.cleanup_batch_size = $event"
@update:audit-log-retention-days="systemConfig.audit_log_retention_days = $event"
@update:request-candidates-retention-days="systemConfig.request_candidates_retention_days = $event"
@update:request-candidates-cleanup-batch-size="systemConfig.request_candidates_cleanup_batch_size = $event"
/>
<!-- 定时任务 -->

View File

@@ -152,6 +152,46 @@
超过后删除审计日志记录
</p>
</div>
<div>
<Label
for="request-candidates-retention-days"
class="block text-sm font-medium"
>
候选记录保留天数
</Label>
<Input
id="request-candidates-retention-days"
:model-value="requestCandidatesRetentionDays"
type="number"
placeholder="30"
class="mt-1"
@update:model-value="$emit('update:requestCandidatesRetentionDays', Number($event))"
/>
<p class="mt-1 text-xs text-muted-foreground">
过期后按时间窗口批量清理 request_candidates 审计记录
</p>
</div>
<div>
<Label
for="request-candidates-cleanup-batch-size"
class="block text-sm font-medium"
>
候选记录清理批次
</Label>
<Input
id="request-candidates-cleanup-batch-size"
:model-value="requestCandidatesCleanupBatchSize"
type="number"
placeholder="5000"
class="mt-1"
@update:model-value="$emit('update:requestCandidatesCleanupBatchSize', Number($event))"
/>
<p class="mt-1 text-xs text-muted-foreground">
独立控制候选记录大表清理节奏不再跟随 Key 删除联动
</p>
</div>
</div>
<!-- 清理策略说明 -->
@@ -164,7 +204,7 @@
<p>2. <strong>压缩日志阶段</strong>: body 字段被压缩存储节省空间</p>
<p>3. <strong>统计阶段</strong>: 仅保留 tokens成本等统计信息</p>
<p>4. <strong>归档删除</strong>: 超过保留期限后完全删除记录</p>
<p>5. <strong>候选记录</strong>: 与详细记录同步清理请求候选记录request_candidates</p>
<p>5. <strong>候选记录</strong>: 独立按保留天数清理 request_candidates 审计记录不再跟随 Key 删除联动</p>
<p>6. <strong>审计日志</strong>: 独立清理记录用户登录操作等安全事件</p>
</div>
</div>
@@ -186,6 +226,8 @@ defineProps<{
logRetentionDays: number
cleanupBatchSize: number
auditLogRetentionDays: number
requestCandidatesRetentionDays: number
requestCandidatesCleanupBatchSize: number
loading: boolean
hasChanges: boolean
}>()
@@ -199,5 +241,7 @@ defineEmits<{
'update:logRetentionDays': [value: number]
'update:cleanupBatchSize': [value: number]
'update:auditLogRetentionDays': [value: number]
'update:requestCandidatesRetentionDays': [value: number]
'update:requestCandidatesCleanupBatchSize': [value: number]
}>()
</script>

View File

@@ -32,6 +32,8 @@ export interface SystemConfig {
log_retention_days: number
cleanup_batch_size: number
audit_log_retention_days: number
request_candidates_retention_days: number
request_candidates_cleanup_batch_size: number
// 定时任务
enable_provider_checkin: boolean
provider_checkin_time: string
@@ -66,6 +68,8 @@ const CONFIG_KEYS = [
'log_retention_days',
'cleanup_batch_size',
'audit_log_retention_days',
'request_candidates_retention_days',
'request_candidates_cleanup_batch_size',
// 定时任务
'enable_provider_checkin',
'provider_checkin_time',
@@ -101,6 +105,8 @@ function createDefaultConfig(): SystemConfig {
log_retention_days: 365,
cleanup_batch_size: 1000,
audit_log_retention_days: 30,
request_candidates_retention_days: 30,
request_candidates_cleanup_batch_size: 5000,
// 定时任务
enable_provider_checkin: true,
provider_checkin_time: '01:05',
@@ -171,7 +177,11 @@ export function useSystemConfig() {
systemConfig.value.log_retention_days !== originalConfig.value.log_retention_days ||
systemConfig.value.cleanup_batch_size !== originalConfig.value.cleanup_batch_size ||
systemConfig.value.audit_log_retention_days !==
originalConfig.value.audit_log_retention_days
originalConfig.value.audit_log_retention_days ||
systemConfig.value.request_candidates_retention_days !==
originalConfig.value.request_candidates_retention_days ||
systemConfig.value.request_candidates_cleanup_batch_size !==
originalConfig.value.request_candidates_cleanup_batch_size
)
})
@@ -421,6 +431,16 @@ export function useSystemConfig() {
value: systemConfig.value.audit_log_retention_days,
description: '审计日志保留天数',
},
{
key: 'request_candidates_retention_days',
value: systemConfig.value.request_candidates_retention_days,
description: '请求候选记录保留天数',
},
{
key: 'request_candidates_cleanup_batch_size',
value: systemConfig.value.request_candidates_cleanup_batch_size,
description: '请求候选记录每批次清理条数',
},
]
await Promise.all(
@@ -438,6 +458,10 @@ export function useSystemConfig() {
originalConfig.value.cleanup_batch_size = systemConfig.value.cleanup_batch_size
originalConfig.value.audit_log_retention_days =
systemConfig.value.audit_log_retention_days
originalConfig.value.request_candidates_retention_days =
systemConfig.value.request_candidates_retention_days
originalConfig.value.request_candidates_cleanup_batch_size =
systemConfig.value.request_candidates_cleanup_batch_size
}
success('请求记录清理配置已保存')
} catch (err) {