From c62c8c2cad7d3a06e6148368b300513ed14a852f Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sat, 19 Sep 2026 10:40:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(worker-platform):=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=AF=84=E5=8D=96=E5=8C=B9=E9=85=8D=E5=93=81=E7=B1=BB=E9=9A=94?= =?UTF-8?q?=E7=A6=BB=E9=97=A8=E7=A6=81=EF=BC=8C=E6=9D=9C=E7=BB=9D=E6=89=8B?= =?UTF-8?q?=E6=9E=AA=E8=B7=A8=E5=93=81=E7=B1=BB=E8=AF=AF=E5=8C=B9=E9=85=8D?= =?UTF-8?q?=E5=AF=86=E9=92=A5=E7=A4=BC=E7=9B=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 新增 detectItemCategory 与 formatItemCategoryLabel 品类主体识别器(覆盖苏尔南手枪/枪皮、特种兵密钥、爆破型肩炮、专属礼盒、改名卡、点券等类目); 2. 寄卖匹配比对中引入强品类隔离门禁,在宽松模式下亦严禁跨品类误判为非精确匹配; 3. describeConsignmentSpecDifferences 补充品类级差异冲突说明,非同品类订单自动沉底; 4. 补充专项单测并保证全量测试与构建通过。 --- .../consignment-matching-service.test.ts | 43 ++++++ .../consignment-matching-service.ts | 122 ++++++++++++++++++ 2 files changed, 165 insertions(+) diff --git a/apps/backend/src/services/worker-platform/consignment-matching-service.test.ts b/apps/backend/src/services/worker-platform/consignment-matching-service.test.ts index c5a9cb69..a0a2300a 100644 --- a/apps/backend/src/services/worker-platform/consignment-matching-service.test.ts +++ b/apps/backend/src/services/worker-platform/consignment-matching-service.test.ts @@ -600,3 +600,46 @@ test('发布寄卖建议价:号主钻级比后台配置高时,按已配置 null, ) }) + +test('品类隔离门禁:苏尔南手枪现货绝不匹配特种兵密钥、爆破型肩炮、专属礼盒等无关商品', () => { + const invPistol = buildInventory({ + item_name: '苹果QQ区怒嚎3钻2璀璨 苏尔南', + system: '苹果', + server_zone: 'QQ区', + series: '怒嚎', + diamond_level: 3, + brilliance_count: 2, + }) + + // 1. 特种兵密钥 + const keyOrder = extractPeacekeeperSpecs('特种兵密钥1个') + assert.equal(matchPeacekeeperOrderWithInventory(keyOrder, invPistol), null) + assert.equal( + matchPeacekeeperOrderWithInventory(keyOrder, invPistol, { allowUnspecifiedSpecs: true }), + null, + '宽松模式下也必须拒绝跨品类', + ) + + // 2. 爆破型肩炮 + const cannonOrder = extractPeacekeeperSpecs('全新版【金色带特效】爆破型肩炮') + assert.equal(matchPeacekeeperOrderWithInventory(cannonOrder, invPistol), null) + assert.equal( + matchPeacekeeperOrderWithInventory(cannonOrder, invPistol, { allowUnspecifiedSpecs: true }), + null, + ) + + // 3. 专属礼盒 + const boxOrder = extractPeacekeeperSpecs('1个精致荣耀专属礼盒') + assert.equal(matchPeacekeeperOrderWithInventory(boxOrder, invPistol), null) + assert.equal( + matchPeacekeeperOrderWithInventory(boxOrder, invPistol, { allowUnspecifiedSpecs: true }), + null, + ) + + // 4. 正确的苏尔南手枪单子正常命中 + const pistolOrder = extractPeacekeeperSpecs('苹果QQ区怒嚎3钻 苏尔南手枪') + const matched = matchPeacekeeperOrderWithInventory(pistolOrder, invPistol) + assert.ok(matched) + assert.equal(matched.matchType, 'partial') // 2璀璨充0璀璨 +}) + diff --git a/apps/backend/src/services/worker-platform/consignment-matching-service.ts b/apps/backend/src/services/worker-platform/consignment-matching-service.ts index 3121985e..f4e63a1e 100644 --- a/apps/backend/src/services/worker-platform/consignment-matching-service.ts +++ b/apps/backend/src/services/worker-platform/consignment-matching-service.ts @@ -64,6 +64,85 @@ export function normalizePeacekeeperSeries(value: string): string { return extractPeacekeeperSeries(value) || String(value || '').trim() } +/** 常见商品品类主体定义 */ +export type ItemCategoryType = + | 'pistol_skin' // 手枪/苏尔南/枪皮 + | 'key_secret' // 特种兵密钥/钥匙 + | 'cannon' // 爆破型肩炮/肩甲 + | 'gift_box' // 专属礼盒/宝箱/福袋 + | 'rename_card' // 改名卡/更名卡 + | 'diamond_topup' // 钻石/点券/充值 + | 'other' + +export function detectItemCategory(text: string): ItemCategoryType { + const normalized = String(text || '').toLowerCase() + if (!normalized) return 'other' + + // 1. 手枪/枪皮/苏尔南系列 + if ( + normalized.includes('苏尔南') || + normalized.includes('手枪') || + normalized.includes('暗潮') || + normalized.includes('怒嚎') || + normalized.includes('怒号') || + normalized.includes('血宴') || + normalized.includes('枪皮') + ) { + return 'pistol_skin' + } + + // 2. 密钥/特种兵密钥 + if ( + normalized.includes('密钥') || + normalized.includes('密匙') || + normalized.includes('特种兵密钥') || + normalized.includes('钥匙') + ) { + return 'key_secret' + } + + // 3. 肩炮 + if (normalized.includes('肩炮') || normalized.includes('爆破型肩炮')) { + return 'cannon' + } + + // 4. 专属礼盒/宝箱 + if (normalized.includes('礼盒') || normalized.includes('宝箱') || normalized.includes('福袋')) { + return 'gift_box' + } + + // 5. 改名卡 + if (normalized.includes('改名') || normalized.includes('更名')) { + return 'rename_card' + } + + // 6. 钻石/点券充值 + if (normalized.includes('点券') || normalized.includes('代充') || normalized.includes('充值')) { + return 'diamond_topup' + } + + return 'other' +} + +export function formatItemCategoryLabel(type: ItemCategoryType): string { + switch (type) { + case 'pistol_skin': + return '苏尔南手枪/枪皮' + case 'key_secret': + return '特种兵密钥' + case 'cannon': + return '爆破型肩炮' + case 'gift_box': + return '专属礼盒' + case 'rename_card': + return '改名卡' + case 'diamond_topup': + return '点券/充值' + default: + return '其它类目' + } +} + function parseSpecsFromText(rawText: string): Omit { const text = String(rawText || '').trim() @@ -167,6 +246,29 @@ export function matchPeacekeeperOrderWithInventory( return null } + // 1. 品类与物品主体强隔离门禁(防跨品类误判为非精确匹配) + const orderCat = detectItemCategory(order.rawText) + const invCat = detectItemCategory( + `${inventory.item_name} ${inventory.series} ${inventory.category_name || ''}`, + ) + + // 若两端均能识别出品类且不一致(如订单是密钥/肩炮/礼盒,现货是手枪/枪皮),直接拒绝 + if (orderCat !== 'other' && invCat !== 'other' && orderCat !== invCat) { + return null + } + + // 若现货明确为手枪/枪皮类目,订单不能是无任何手枪特征的其他商品 + if (invCat === 'pistol_skin') { + const orderHasPistolSignal = + Boolean(order.series) || + order.rawText.includes('苏尔南') || + order.rawText.includes('手枪') || + order.rawText.includes('枪皮') + if (!orderHasPistolSignal) { + return null + } + } + const invSystem = normalizePeacekeeperSystem(inventory.system) const invZone = normalizePeacekeeperServerZone(inventory.server_zone) const invSeries = normalizePeacekeeperSeries(inventory.series) @@ -281,6 +383,26 @@ export function describeConsignmentSpecDifferences( inventory: ConsignmentInventoryRow, ): string[] { const diffs: string[] = [] + + const orderCat = detectItemCategory(order.rawText) + const invCat = detectItemCategory( + `${inventory.item_name} ${inventory.series} ${inventory.category_name || ''}`, + ) + if (orderCat !== 'other' && invCat !== 'other' && orderCat !== invCat) { + diffs.push( + `品类:订单「${formatItemCategoryLabel(orderCat)}」/ 现货「${formatItemCategoryLabel(invCat)}」`, + ) + } else if (invCat === 'pistol_skin') { + const orderHasPistolSignal = + Boolean(order.series) || + order.rawText.includes('苏尔南') || + order.rawText.includes('手枪') || + order.rawText.includes('枪皮') + if (!orderHasPistolSignal) { + diffs.push(`品类:订单商品非手枪/枪皮 / 现货「苏尔南手枪/枪皮」`) + } + } + const compare = ( label: string, orderValue: string,