From 72a758fd7c3c00d02c845a73d60556d3efb67a45 Mon Sep 17 00:00:00 2001 From: zhaoxing <2558691827@qq.com> Date: Thu, 22 Oct 2026 15:32:26 +0800 Subject: [PATCH] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8D=E9=97=AE=E9=A2=98?= =?UTF-8?q?=E6=8E=A5=E5=8D=95=E5=B9=B3=E5=8F=B0=E4=BD=BF=E7=94=A8=E6=B8=B8?= =?UTF-8?q?=E6=88=8FID=E6=9F=A5=E8=AF=A2=E4=B8=8D=E5=88=B0=E6=95=B0?= =?UTF-8?q?=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/repositories/worker-platform-repo.test.ts | 11 +++++++---- .../worker-platform/work-order-query-repo.ts | 9 ++++++++- .../src/pages/admin/panels/WorkOrdersPanel.tsx | 2 +- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/apps/backend/src/repositories/worker-platform-repo.test.ts b/apps/backend/src/repositories/worker-platform-repo.test.ts index 4b798f18..be70e943 100644 --- a/apps/backend/src/repositories/worker-platform-repo.test.ts +++ b/apps/backend/src/repositories/worker-platform-repo.test.ts @@ -139,17 +139,20 @@ test('空白搜索词不生成全表 LIKE 条件', () => { assert.deepEqual(query.params, []) }) -test('平台单号检索只生成可走索引的等值/前缀条件,不得出现前置通配符', () => { +test('纯数字检索同时覆盖平台单号与游戏字段,且不出现非索引通配符', () => { const query = buildWorkOrderWhere({ keyword: '2627401680611492' }) + // 纯数字既可能是平台单号,也可能是游戏 ID:等值/前缀单号分支必须保留。 assert.match( query.whereClause, /\(wo\.platform_order_id = \$1 OR wo\.platform_order_id LIKE \$1 \|\| '%'\)/, ) - // 前置通配符非 sargable:一旦混进来,整条查询会退化成 work_orders 全表扫, - // 生产实测每次读 211MB / 398ms,列表与统计并行触发一次打两遍。 + // 游戏 ID(快手等 10 位纯数字)也走同一关键词:必须并上统一搜索表达式。 + assert.match(query.whereClause, /material_json->'collect'->'fields'->>'gameId'/) + assert.match(query.whereClause, /LIKE LOWER\(\$2\)/) + // 不允许 `platform_order_id ILIKE '%...%'` 这种非 sargable 写法;游戏字段走 073 的 trgm 表达式索引。 assert.doesNotMatch(query.whereClause, /ILIKE\s*'%'/) - assert.deepEqual(query.params, ['2627401680611492']) + assert.deepEqual(query.params, ['2627401680611492', '%2627401680611492%']) }) test('工单号检索大小写不敏感且只走索引,不得出现前置通配符(含 W0/WK 前缀容错)', () => { diff --git a/apps/backend/src/repositories/worker-platform/work-order-query-repo.ts b/apps/backend/src/repositories/worker-platform/work-order-query-repo.ts index ca6e376c..60d7f2d5 100644 --- a/apps/backend/src/repositories/worker-platform/work-order-query-repo.ts +++ b/apps/backend/src/repositories/worker-platform/work-order-query-repo.ts @@ -1271,8 +1271,15 @@ export function buildWorkOrderWhere({ if (isPlatformOrderIdLookupKeyword(normalizedKeyword)) { params.push(normalizedKeyword) const paramIndex = `$${params.length}` + // 纯数字关键词不能只当平台单号查:游戏 ID(快手等普遍 10 位左右纯数字)同样是纯数字, + // 只匹配 platform_order_id 会让「按游戏 ID 搜索」永远空结果(生产已复现)。 + // 这里再并上统一搜索表达式(含各版本 material 里的 gameId / 游戏昵称 / 账号), + // 该表达式的 trigram GIN 索引(073)让这一支走 Bitmap Index Scan, + // 15.6 万行实测约 8ms,不会退化成全表扫。 + params.push(`%${normalizedKeyword}%`) + const keywordParam = `$${params.length}` filters.push( - `(wo.platform_order_id = ${paramIndex} OR wo.platform_order_id LIKE ${paramIndex} || '%')`, + `((wo.platform_order_id = ${paramIndex} OR wo.platform_order_id LIKE ${paramIndex} || '%') OR ${workOrderSearchExpression()} LIKE LOWER(${keywordParam}))`, ) } else if (isWorkOrderNoLookupKeyword(normalizedKeyword)) { // 工单号同时匹配 `WK…`(现役)/`WO…`(历史)/`W0…`(把 O 看成 0)三种写法: diff --git a/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx b/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx index 15949767..6803c29e 100644 --- a/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx +++ b/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx @@ -1150,7 +1150,7 @@ export default function WorkOrdersPanel() { cleared.push('定位工单') } if (cleared.length > 0) { - message.info(`已按单号精准检索,自动忽略:${cleared.join(' / ')}`) + message.info(`已按单号 / 游戏 ID 精准检索,自动忽略:${cleared.join(' / ')}`) } }