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
@@ -293,3 +293,97 @@ export async function listWorkProductMatchLogs({
)
return { items: result.rows, total: Number(totalResult.rows[0]?.total || 0) }
}
export type PruneWorkProductMatchLogsOptions = {
maxRows?: number
retentionDays?: number
batchSize?: number
}
export type PruneWorkProductMatchLogsResult = {
deletedByAge: number
deletedByCount: number
totalDeleted: number
remainingTotal: number
}
/** 按条数上限或保留天数分批清理历史匹配日志 */
export async function pruneWorkProductMatchLogs(
options: PruneWorkProductMatchLogsOptions = {},
): Promise<PruneWorkProductMatchLogsResult> {
let deletedByAge = 0
let deletedByCount = 0
const batchSize = Math.min(5000, Math.max(100, options.batchSize || 1000))
// 1. 按保留天数清理过期日志
if (options.retentionDays && options.retentionDays > 0) {
while (true) {
const res = await query(
`
WITH candidates AS (
SELECT ctid
FROM work_product_match_logs
WHERE created_at < NOW() - make_interval(days => $1::int)
LIMIT $2
)
DELETE FROM work_product_match_logs AS target
USING candidates
WHERE target.ctid = candidates.ctid
`,
[options.retentionDays, batchSize],
)
const count = res.rowCount || 0
deletedByAge += count
if (count < batchSize) break
}
}
// 2. 按最大条数清理超出上限的旧日志(保留最新的 maxRows 条)
if (options.maxRows && options.maxRows > 0) {
const cutoffRes = await query<{ id: string }>(
`
SELECT id
FROM work_product_match_logs
ORDER BY id DESC
OFFSET $1
LIMIT 1
`,
[options.maxRows],
)
const firstRow = cutoffRes.rows[0]
if (firstRow) {
const cutoffId = Number(firstRow.id)
while (true) {
const res = await query(
`
WITH candidates AS (
SELECT ctid
FROM work_product_match_logs
WHERE id <= $1
LIMIT $2
)
DELETE FROM work_product_match_logs AS target
USING candidates
WHERE target.ctid = candidates.ctid
`,
[cutoffId, batchSize],
)
const count = res.rowCount || 0
deletedByCount += count
if (count < batchSize) break
}
}
}
const remainingRes = await query<{ total: number }>(
"SELECT COUNT(*)::int AS total FROM work_product_match_logs",
)
const remainingTotal = Number(remainingRes.rows[0]?.total || 0)
return {
deletedByAge,
deletedByCount,
totalDeleted: deletedByAge + deletedByCount,
remainingTotal,
}
}