feat(worker-platform): 支持后台配置匹配日志保留上限与天数并提供一键清理历史多余记录

1. 扩展商品匹配配置支持 maxMatchLogs (条数上限) 与 matchLogRetentionDays (保留天数);
2. 新增分批物理清理逻辑 pruneWorkProductMatchLogs,通过索引高效分批删除超出限制的记录;
3. 集成定时任务数据保留清理 data-retention-job,并在保存配置与写入时自动滚动清理;
4. 匹配诊断日志面板新增可视化保留策略设置栏,支持管理员自定义上限、一键保存并提供一键清理超出记录按钮。
This commit is contained in:
yml2213
2026-09-12 16:01:26 +08:00
parent e7b51f09a6
commit 1217da7393
8 changed files with 321 additions and 25 deletions
@@ -1,4 +1,4 @@
import { PlayCircleOutlined, ReloadOutlined, SearchOutlined } from '@ant-design/icons'
import { DeleteOutlined, PlayCircleOutlined, ReloadOutlined, SearchOutlined, SettingOutlined } from '@ant-design/icons'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import {
App,
@@ -6,7 +6,9 @@ import {
Card,
Descriptions,
Input,
InputNumber,
Modal,
Popconfirm,
Select,
Space,
Switch,
@@ -16,7 +18,7 @@ import {
Typography,
} from 'antd'
import type { TableColumnsType } from 'antd'
import { useState } from 'react'
import { useEffect, useState } from 'react'
import { useNavigate } from 'react-router'
import JsonPreview from '@/components/admin/JsonPreview'
@@ -27,6 +29,7 @@ import {
fetchAdminKuaishouMatchSources,
fetchAdminWorkerProductMatchConfig,
fetchAdminWorkProductMatchLogs,
cleanupAdminWorkProductMatchLogs,
saveAdminWorkerProductMatchConfig,
testAdminKuaishouProductMatch,
} from '@/services/admin'
@@ -50,6 +53,11 @@ export default function ProductMatchPanel() {
const [logKeyword, setLogKeyword] = useState('')
const [logCreatedRange, setLogCreatedRange] = useState<[string, string] | null>(null)
const [logPage, setLogPage] = useState(1)
const [maxLogs, setMaxLogs] = useState<number>(10000)
const [retentionDays, setRetentionDays] = useState<number>(30)
const [savingRetention, setSavingRetention] = useState(false)
const [cleaningLogs, setCleaningLogs] = useState(false)
const shopsQuery = useQuery({
queryKey: ['admin-kuaishou-industry-shops', 'product-match'],
@@ -87,6 +95,58 @@ export default function ProductMatchPanel() {
const logsPagination = logsQuery.data?.data.pagination
const shops = shopsQuery.data?.data.shops || []
const matchConfig = matchConfigQuery.data?.data
useEffect(() => {
if (matchConfig) {
if (typeof matchConfig.maxMatchLogs === "number") {
setMaxLogs(matchConfig.maxMatchLogs)
}
if (typeof matchConfig.matchLogRetentionDays === "number") {
setRetentionDays(matchConfig.matchLogRetentionDays)
}
}
}, [matchConfig])
async function handleSaveRetentionConfig() {
setSavingRetention(true)
try {
await saveAdminWorkerProductMatchConfig({
legacyFallbackEnabled: matchConfig?.legacyFallbackEnabled ?? false,
maxMatchLogs: maxLogs,
matchLogRetentionDays: retentionDays,
})
message.success("匹配日志保留策略已保存并已执行清理")
await Promise.all([
queryClient.invalidateQueries({ queryKey: ["admin-worker-platform-product-match-config"] }),
queryClient.invalidateQueries({ queryKey: ["admin-worker-platform-product-match-logs"] }),
])
} catch (error) {
message.error(error instanceof Error ? error.message : "保存配置失败")
} finally {
setSavingRetention(false)
}
}
async function handleCleanupLogs() {
setCleaningLogs(true)
try {
const response = await cleanupAdminWorkProductMatchLogs({
maxRows: maxLogs,
retentionDays: retentionDays,
})
const result = response.data
message.success(
`成功清理 ${result.totalDeleted} 条历史匹配日志,剩余 ${result.remainingTotal} 条`,
)
await Promise.all([
queryClient.invalidateQueries({ queryKey: ["admin-worker-platform-product-match-logs"] }),
])
} catch (error) {
message.error(error instanceof Error ? error.message : "清理日志失败")
} finally {
setCleaningLogs(false)
}
}
const shopNameBySellerId = createShopNameBySellerId(shops)
function bindToTemplate(source: KuaishouMatchSource) {
@@ -391,10 +451,71 @@ export default function ProductMatchPanel() {
>
刷新
</Button>
<Typography.Text type="secondary">
日志全量保留,可按结果、关键词与日期筛选
</Typography.Text>
</Space>
<div
style={{
display: "flex",
flexWrap: "wrap",
justifyContent: "space-between",
alignItems: "center",
gap: 12,
background: "#fafafa",
padding: "8px 12px",
borderRadius: 6,
border: "1px solid #f0f0f0",
}}
>
<Space wrap align="center" size={8}>
<SettingOutlined style={{ color: "#1677ff" }} />
<Typography.Text strong style={{ fontSize: 13 }}>
日志保留策略:
</Typography.Text>
<Typography.Text type="secondary">最多保留</Typography.Text>
<InputNumber
min={100}
max={500000}
step={1000}
value={maxLogs}
onChange={(val) => setMaxLogs(val || 10000)}
addonAfter="条"
style={{ width: 140 }}
/>
<Typography.Text type="secondary">保留天数</Typography.Text>
<InputNumber
min={1}
max={365}
step={7}
value={retentionDays}
onChange={(val) => setRetentionDays(val || 30)}
addonAfter="天"
style={{ width: 115 }}
/>
<Button
size="small"
type="primary"
ghost
loading={savingRetention}
onClick={handleSaveRetentionConfig}
>
保存上限
</Button>
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
(自动滚动保留最新记录,超出自动清理)
</Typography.Text>
</Space>
<Popconfirm
title="清理超出上限的历史日志"
description={`将按当前设置(保留最新 ${maxLogs} 条且不超过 ${retentionDays} 天)立即从数据库清理多余历史记录。是否继续?`}
okText="立即清理"
cancelText="取消"
okButtonProps={{ danger: true }}
onConfirm={handleCleanupLogs}
>
<Button danger size="small" icon={<DeleteOutlined />} loading={cleaningLogs}>
立即清理历史日志
</Button>
</Popconfirm>
</div>
<Table<WorkProductMatchLog>
rowKey="logId"
loading={logsQuery.isLoading}
@@ -343,6 +343,17 @@ export function fetchAdminWorkProductMatchLogs(params?: Record<string, unknown>)
)
}
export function cleanupAdminWorkProductMatchLogs(
payload: { maxRows?: number; retentionDays?: number } = {},
) {
return apiPost<{
deletedByAge: number
deletedByCount: number
totalDeleted: number
remainingTotal: number
}>('/api/v1/admin/worker-platform/product-match-logs/cleanup', payload)
}
export function reprocessAdminKuaishouWorkOrderMatches(limit = 100) {
return apiPost<{ scannedCount: number; createdCount: number; skippedCount: number }>(
'/api/v1/admin/worker-platform/orders/reprocess-matches',
@@ -291,6 +291,8 @@ export type WorkerPlatformNotificationConfig = {
export type WorkerProductMatchConfig = {
legacyFallbackEnabled: boolean
maxMatchLogs?: number
matchLogRetentionDays?: number
}
export type WorkerAnnouncementConfig = {