feat(worker-platform): 支持后台配置匹配日志保留上限与天数并提供一键清理历史多余记录
1. 扩展商品匹配配置支持 maxMatchLogs (条数上限) 与 matchLogRetentionDays (保留天数); 2. 新增分批物理清理逻辑 pruneWorkProductMatchLogs,通过索引高效分批删除超出限制的记录; 3. 集成定时任务数据保留清理 data-retention-job,并在保存配置与写入时自动滚动清理; 4. 匹配诊断日志面板新增可视化保留策略设置栏,支持管理员自定义上限、一键保存并提供一键清理超出记录按钮。
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user