优化后台任务与订单按订单号搜索性能
This commit is contained in:
@@ -75,6 +75,15 @@ const checks: Array<{
|
||||
"material_json->'collect'->'fields'->>'gameId'",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: '任务与订单平台订单号检索索引',
|
||||
file: path.join(BACKEND_ROOT, 'src/db/migrations/074_admin_order_and_task_lookup_indexes.sql'),
|
||||
patterns: [
|
||||
'idx_fulfillment_tasks_platform_order_id',
|
||||
'idx_fulfillment_tasks_task_no_pattern',
|
||||
'idx_orders_platform_order_id',
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const failures: string[] = []
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
-- 优化后台任务列表与订单列表按平台订单号、任务号搜索性能。
|
||||
-- 修复原表仅有复合索引 (provider, platform, platform_order_id),导致单独按订单号查询时无法命中 B-Tree 索引的问题。
|
||||
|
||||
-- 1. 履约任务表:平台订单号独立索引与前缀模式索引
|
||||
CREATE INDEX IF NOT EXISTS idx_fulfillment_tasks_platform_order_id
|
||||
ON fulfillment_tasks(platform_order_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_fulfillment_tasks_platform_order_id_pattern
|
||||
ON fulfillment_tasks(platform_order_id text_pattern_ops);
|
||||
|
||||
-- 2. 履约任务表:任务号独立索引与前缀模式索引
|
||||
CREATE INDEX IF NOT EXISTS idx_fulfillment_tasks_task_no_pattern
|
||||
ON fulfillment_tasks(task_no text_pattern_ops);
|
||||
|
||||
-- 3. 外部订单主表:平台订单号独立索引与前缀模式索引
|
||||
CREATE INDEX IF NOT EXISTS idx_orders_platform_order_id
|
||||
ON orders(platform_order_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_orders_platform_order_id_pattern
|
||||
ON orders(platform_order_id text_pattern_ops);
|
||||
|
||||
COMMENT ON INDEX idx_fulfillment_tasks_platform_order_id IS
|
||||
'支持履约任务按平台订单号快速精确查找';
|
||||
COMMENT ON INDEX idx_fulfillment_tasks_platform_order_id_pattern IS
|
||||
'支持履约任务按平台订单号前缀模式查找';
|
||||
COMMENT ON INDEX idx_fulfillment_tasks_task_no_pattern IS
|
||||
'支持履约任务按任务号前缀模式查找';
|
||||
COMMENT ON INDEX idx_orders_platform_order_id IS
|
||||
'支持外部订单按平台订单号快速精确查找';
|
||||
COMMENT ON INDEX idx_orders_platform_order_id_pattern IS
|
||||
'支持外部订单按平台订单号前缀模式查找';
|
||||
@@ -228,24 +228,35 @@ export async function listOrdersWithExecutor(
|
||||
const params: unknown[] = []
|
||||
|
||||
if (keyword) {
|
||||
params.push(`%${keyword}%`)
|
||||
const keywordParam = `$${params.length}`
|
||||
filters.push(`(
|
||||
(
|
||||
o.platform_order_id || ' ' || o.shop_id || ' ' || o.shop_name || ' ' ||
|
||||
o.buyer_id || ' ' || o.buyer_name || ' ' || o.receiver_contact
|
||||
) ILIKE ${keywordParam}
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM order_items oi
|
||||
WHERE oi.order_id = o.id
|
||||
AND (
|
||||
oi.sku_code || ' ' || oi.sku_name || ' ' ||
|
||||
COALESCE(oi.item_snapshot_json->'kuaishouSendCode'->>'itemTitle', '') || ' ' ||
|
||||
COALESCE(oi.item_snapshot_json->'kuaishouSendCode'->>'skuNick', '')
|
||||
) ILIKE ${keywordParam}
|
||||
const trimmedKeyword = String(keyword).trim()
|
||||
const isPureOrderNo = /^\d{6,}$/.test(trimmedKeyword)
|
||||
|
||||
if (isPureOrderNo) {
|
||||
params.push(trimmedKeyword)
|
||||
const paramIndex = `$${params.length}`
|
||||
filters.push(
|
||||
`(o.platform_order_id = ${paramIndex} OR o.platform_order_id LIKE ${paramIndex} || '%')`,
|
||||
)
|
||||
)`)
|
||||
} else {
|
||||
params.push(`%${keyword}%`)
|
||||
const keywordParam = `$${params.length}`
|
||||
filters.push(`(
|
||||
(
|
||||
o.platform_order_id || ' ' || o.shop_id || ' ' || o.shop_name || ' ' ||
|
||||
o.buyer_id || ' ' || o.buyer_name || ' ' || o.receiver_contact
|
||||
) ILIKE ${keywordParam}
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM order_items oi
|
||||
WHERE oi.order_id = o.id
|
||||
AND (
|
||||
oi.sku_code || ' ' || oi.sku_name || ' ' ||
|
||||
COALESCE(oi.item_snapshot_json->'kuaishouSendCode'->>'itemTitle', '') || ' ' ||
|
||||
COALESCE(oi.item_snapshot_json->'kuaishouSendCode'->>'skuNick', '')
|
||||
) ILIKE ${keywordParam}
|
||||
)
|
||||
)`)
|
||||
}
|
||||
}
|
||||
|
||||
if (provider) {
|
||||
|
||||
@@ -46,3 +46,29 @@ test('任务列表保留旧版分字段筛选参数', () => {
|
||||
assert.match(result.whereClause, /kcts\.role_id.*ctx\.role_id.*ILIKE \$4/s)
|
||||
assert.deepEqual(result.params, ['%26245%', '%DT-1%', 'SKU-1', '%10001%'])
|
||||
})
|
||||
|
||||
test('任务列表关键词为纯数字时智能路由到平台订单号索引条件', () => {
|
||||
const result = buildTaskListFilters({
|
||||
keyword: '20247230034021572',
|
||||
})
|
||||
|
||||
assert.match(
|
||||
result.whereClause,
|
||||
/ft\.platform_order_id = \$1 OR ft\.platform_order_id LIKE \$1 \|\| '%'/s,
|
||||
)
|
||||
assert.doesNotMatch(result.whereClause, /oi\.sku_code/i)
|
||||
assert.doesNotMatch(result.whereClause, /item_snapshot_json/i)
|
||||
assert.doesNotMatch(result.whereClause, /kcts/i)
|
||||
assert.deepEqual(result.params, ['20247230034021572'])
|
||||
})
|
||||
|
||||
test('任务列表关键词为 DT 任务号时智能路由到任务号索引条件', () => {
|
||||
const result = buildTaskListFilters({
|
||||
keyword: 'DT881aa66294cc',
|
||||
})
|
||||
|
||||
assert.match(result.whereClause, /ft\.task_no = \$1 OR ft\.task_no LIKE \$1 \|\| '%'/s)
|
||||
assert.doesNotMatch(result.whereClause, /oi\.sku_code/i)
|
||||
assert.doesNotMatch(result.whereClause, /item_snapshot_json/i)
|
||||
assert.deepEqual(result.params, ['DT881aa66294cc'])
|
||||
})
|
||||
|
||||
@@ -436,26 +436,46 @@ export async function listTasks({
|
||||
const offset = (page - 1) * pageSize
|
||||
const { whereClause, params } = buildTaskListFilters(filters)
|
||||
|
||||
const totalResult = await query<{ [column: string]: unknown; total: number }>(
|
||||
`
|
||||
SELECT COUNT(*)::int AS total
|
||||
FROM fulfillment_tasks ft
|
||||
LEFT JOIN order_items oi ON oi.id = ft.order_item_id
|
||||
LEFT JOIN task_runtime_contexts ctx ON ctx.task_id = ft.id
|
||||
LEFT JOIN kuaishou_cloud_task_states kcts ON kcts.task_id = ft.id
|
||||
${whereClause}
|
||||
`,
|
||||
params,
|
||||
)
|
||||
|
||||
params.push(pageSize)
|
||||
params.push(offset)
|
||||
const itemsParams = [...params, pageSize, offset]
|
||||
const itemsResult = await query<TaskRow>(
|
||||
`${buildTaskSelect('oi.sku_code, oi.sku_name, oi.quantity')}
|
||||
LEFT JOIN order_items oi ON oi.id = ft.order_item_id
|
||||
${whereClause}
|
||||
ORDER BY ft.id DESC
|
||||
LIMIT $${params.length - 1} OFFSET $${params.length}`,
|
||||
LIMIT $${itemsParams.length - 1} OFFSET $${itemsParams.length}`,
|
||||
itemsParams,
|
||||
)
|
||||
|
||||
// 首页且返回记录不满一页时直接得出总数,彻底免除第二次全量 COUNT(*) 扫描
|
||||
if (page === 1 && itemsResult.rows.length < pageSize) {
|
||||
return {
|
||||
items: itemsResult.rows,
|
||||
total: itemsResult.rows.length,
|
||||
}
|
||||
}
|
||||
|
||||
// 仅在过滤条件真正引用子表字段时才进行关联,单表过滤时避免无谓的 3 表 LEFT JOIN 开销
|
||||
const needsJoinOi = whereClause.includes('oi.')
|
||||
const needsJoinCtx = whereClause.includes('ctx.')
|
||||
const needsJoinKcts = whereClause.includes('kcts.')
|
||||
const joinClauses: string[] = []
|
||||
if (needsJoinOi) {
|
||||
joinClauses.push('LEFT JOIN order_items oi ON oi.id = ft.order_item_id')
|
||||
}
|
||||
if (needsJoinCtx) {
|
||||
joinClauses.push('LEFT JOIN task_runtime_contexts ctx ON ctx.task_id = ft.id')
|
||||
}
|
||||
if (needsJoinKcts) {
|
||||
joinClauses.push('LEFT JOIN kuaishou_cloud_task_states kcts ON kcts.task_id = ft.id')
|
||||
}
|
||||
|
||||
const totalResult = await query<{ [column: string]: unknown; total: number }>(
|
||||
`
|
||||
SELECT COUNT(*)::int AS total
|
||||
FROM fulfillment_tasks ft
|
||||
${joinClauses.join('\n')}
|
||||
${whereClause}
|
||||
`,
|
||||
params,
|
||||
)
|
||||
|
||||
@@ -480,17 +500,33 @@ export function buildTaskListFilters({
|
||||
const params: unknown[] = []
|
||||
|
||||
if (keyword) {
|
||||
params.push(`%${keyword}%`)
|
||||
const keywordParam = `$${params.length}`
|
||||
filters.push(`(
|
||||
(ft.task_no || ' ' || ft.platform_order_id) ILIKE ${keywordParam}
|
||||
OR oi.sku_code ILIKE ${keywordParam}
|
||||
OR oi.sku_name ILIKE ${keywordParam}
|
||||
OR COALESCE(oi.item_snapshot_json->'kuaishouSendCode'->>'itemTitle', '') ILIKE ${keywordParam}
|
||||
OR COALESCE(oi.item_snapshot_json->'kuaishouSendCode'->>'skuNick', '') ILIKE ${keywordParam}
|
||||
OR COALESCE(NULLIF(kcts.role_id, ''), ctx.role_id, '') ILIKE ${keywordParam}
|
||||
OR COALESCE(NULLIF(kcts.role_name, ''), NULLIF(ctx.role_name, ''), ctx.nickname, '') ILIKE ${keywordParam}
|
||||
)`)
|
||||
const trimmedKeyword = String(keyword).trim()
|
||||
const isPureOrderNo = /^\d{6,}$/.test(trimmedKeyword)
|
||||
const isTaskNo = /^DT[a-zA-Z0-9_-]+$/i.test(trimmedKeyword)
|
||||
|
||||
if (isPureOrderNo) {
|
||||
params.push(trimmedKeyword)
|
||||
const paramIndex = `$${params.length}`
|
||||
filters.push(
|
||||
`(ft.platform_order_id = ${paramIndex} OR ft.platform_order_id LIKE ${paramIndex} || '%')`,
|
||||
)
|
||||
} else if (isTaskNo) {
|
||||
params.push(trimmedKeyword)
|
||||
const paramIndex = `$${params.length}`
|
||||
filters.push(`(ft.task_no = ${paramIndex} OR ft.task_no LIKE ${paramIndex} || '%')`)
|
||||
} else {
|
||||
params.push(`%${keyword}%`)
|
||||
const keywordParam = `$${params.length}`
|
||||
filters.push(`(
|
||||
(ft.task_no || ' ' || ft.platform_order_id) ILIKE ${keywordParam}
|
||||
OR oi.sku_code ILIKE ${keywordParam}
|
||||
OR oi.sku_name ILIKE ${keywordParam}
|
||||
OR COALESCE(oi.item_snapshot_json->'kuaishouSendCode'->>'itemTitle', '') ILIKE ${keywordParam}
|
||||
OR COALESCE(oi.item_snapshot_json->'kuaishouSendCode'->>'skuNick', '') ILIKE ${keywordParam}
|
||||
OR COALESCE(NULLIF(kcts.role_id, ''), ctx.role_id, '') ILIKE ${keywordParam}
|
||||
OR COALESCE(NULLIF(kcts.role_name, ''), NULLIF(ctx.role_name, ''), ctx.nickname, '') ILIKE ${keywordParam}
|
||||
)`)
|
||||
}
|
||||
}
|
||||
|
||||
if (executorKey) {
|
||||
|
||||
@@ -816,8 +816,17 @@ export function buildWorkOrderWhere({
|
||||
filters.push(`wo.id = $${params.length}`)
|
||||
}
|
||||
if (normalizedKeyword) {
|
||||
params.push(`%${normalizedKeyword}%`)
|
||||
filters.push(`${workOrderSearchExpression()} LIKE LOWER($${params.length})`)
|
||||
const isPureOrderNo = /^\d{6,}$/.test(normalizedKeyword)
|
||||
if (isPureOrderNo) {
|
||||
params.push(normalizedKeyword)
|
||||
const paramIndex = `$${params.length}`
|
||||
filters.push(
|
||||
`(wo.platform_order_id = ${paramIndex} OR wo.platform_order_id LIKE ${paramIndex} || '%')`,
|
||||
)
|
||||
} else {
|
||||
params.push(`%${normalizedKeyword}%`)
|
||||
filters.push(`${workOrderSearchExpression()} LIKE LOWER($${params.length})`)
|
||||
}
|
||||
}
|
||||
if (workerId) {
|
||||
params.push(workerId)
|
||||
|
||||
Reference in New Issue
Block a user