新增透传:91 编码与 affiliate_dash sku 一致时自动命中

- resolveAffiliateDashSkuByProductNo 改 async,匹配两级:①指定映射(skuMapping 归一命中) ②透传(productNo == affiliate_dash 商品 sku)
- 商品列表内存缓存 5 分钟,拉取失败返回上次缓存/空集合,匹配 miss 走其他通道不阻塞下单
- 两层命中均触发指定匹配最高优先(preferredExecutorKey)
- 测试 +3(224 全绿);线上验证 suit_alan_walker/suit_cloud_bear 透传命中,乱码回退
This commit is contained in:
yml2213
2026-08-05 17:40:13 +08:00
parent 769d41655a
commit 0f81380d9c
3 changed files with 102 additions and 10 deletions
@@ -3,6 +3,7 @@ import test from 'node:test'
import {
matchAffiliateDashSku,
matchAffiliateDashSkuPassthrough,
normalizeAffiliateDashMappingKey,
} from './product-resolution-service.js'
@@ -32,3 +33,21 @@ test('normalizeAffiliateDashMappingKey NFKC + 去空白 + 小写', () => {
assert.equal(normalizeAffiliateDashMappingKey(' 幸运币90个 '), '幸运币90个')
assert.equal(normalizeAffiliateDashMappingKey('M416-仓鼠灰灰'), 'm416-仓鼠灰灰')
})
test('matchAffiliateDashSkuPassthrough sku 一致时命中', () => {
const skus = new Set(['suit_alan_walker', 'lucky_coin_x90'])
assert.equal(matchAffiliateDashSkuPassthrough('suit_alan_walker', skus), true)
assert.equal(matchAffiliateDashSkuPassthrough('lucky_coin_x90', skus), true)
})
test('matchAffiliateDashSkuPassthrough 不在商品列表时未命中', () => {
const skus = new Set(['suit_alan_walker'])
assert.equal(matchAffiliateDashSkuPassthrough('not_a_real_sku', skus), false)
assert.equal(matchAffiliateDashSkuPassthrough('', skus), false)
assert.equal(matchAffiliateDashSkuPassthrough('suit_alan_walker', null), false)
assert.equal(matchAffiliateDashSkuPassthrough('suit_alan_walker', undefined), false)
})
test('matchAffiliateDashSkuPassthrough 空集合未命中', () => {
assert.equal(matchAffiliateDashSkuPassthrough('suit_alan_walker', new Set()), false)
})
@@ -12,6 +12,7 @@ import {
type FulfillmentRouteResult,
} from './routing-config-service.js'
import { getAffiliateDashConfig } from '../platforms/affiliate-dash/config.js'
import { listAllAffiliateDashProducts } from '../platforms/affiliate-dash/product-service.js'
import type { AffiliateDashSkuMapping } from '../../types/runtime-config.js'
import { FULFILLMENT_EXECUTOR_KEYS } from './executors/types.js'
@@ -42,7 +43,7 @@ export type AffiliateDashSkuMatch = {
sku: string
productNo: string
skuName: string
matchMode: 'affiliate_dash_sku_mapping'
matchMode: 'affiliate_dash_sku_mapping' | 'affiliate_dash_sku_passthrough'
}
type FulfillmentItemCandidate = {
@@ -234,7 +235,7 @@ async function resolveConfiguredItemCandidate({
if (isOpen91KuaishouOrder(provider, platform)) {
const cloudtentaclesNameMatch = await resolveCloudtentaclesSkuByProductName(externalSkuName)
const kuaishouFeifeiMatch = resolveKuaishouFeifeiProductByName(externalSkuName)
const affiliateDashMatch = resolveAffiliateDashSkuByProductNo(item)
const affiliateDashMatch = await resolveAffiliateDashSkuByProductNo(item)
// 指定匹配(显式映射)命中时优先走 affiliate_dash(可配置关闭,默认开)
const affiliateDashConfig = getAffiliateDashConfig()
const preferredExecutorKey =
@@ -310,7 +311,9 @@ async function resolveConfiguredItemCandidate({
}
}
function resolveAffiliateDashSkuByProductNo(item: FulfillmentItem): AffiliateDashSkuMatch | null {
async function resolveAffiliateDashSkuByProductNo(
item: FulfillmentItem,
): Promise<AffiliateDashSkuMatch | null> {
const config = getAffiliateDashConfig()
if (config.enabled === false || !config.baseUrl) {
return null
@@ -322,16 +325,62 @@ function resolveAffiliateDashSkuByProductNo(item: FulfillmentItem): AffiliateDas
item.externalSkuCode,
item.externalItemId,
])
const sku = productNo ? matchAffiliateDashSku(config.skuMapping, String(productNo)) : ''
if (!productNo || !sku) {
if (!productNo) {
return null
}
return {
sku,
productNo: String(productNo),
skuName: String(item.externalSkuName || '').trim(),
matchMode: 'affiliate_dash_sku_mapping',
// ① 指定映射(运营显式配置)
const mappedSku = matchAffiliateDashSku(config.skuMapping, String(productNo))
if (mappedSku) {
return {
sku: mappedSku,
productNo: String(productNo),
skuName: String(item.externalSkuName || '').trim(),
matchMode: 'affiliate_dash_sku_mapping',
}
}
// ② 透传:91 商品编码 == affiliate_dash 商品 sku 时直接命中(需商品列表校验,缓存 5 分钟)
const rawProductNo = String(productNo).trim()
const skuSet = await getAffiliateDashSkuSet()
if (matchAffiliateDashSkuPassthrough(rawProductNo, skuSet)) {
return {
sku: rawProductNo,
productNo: rawProductNo,
skuName: String(item.externalSkuName || '').trim(),
matchMode: 'affiliate_dash_sku_passthrough',
}
}
return null
}
/** 透传判定:productNo 与 affiliate_dash 商品 sku 完全一致。 */
export function matchAffiliateDashSkuPassthrough(
productNo: string,
availableSkus: ReadonlySet<string> | null | undefined,
): boolean {
const raw = String(productNo || '').trim()
return Boolean(raw && availableSkus?.has(raw))
}
const AFFILIATE_DASH_SKU_CACHE_TTL_MS = 5 * 60 * 1000
let affiliateDashSkuCache: { skus: Set<string>; fetchedAt: number } | null = null
async function getAffiliateDashSkuSet(): Promise<Set<string>> {
const now = Date.now()
if (affiliateDashSkuCache && now - affiliateDashSkuCache.fetchedAt < AFFILIATE_DASH_SKU_CACHE_TTL_MS) {
return affiliateDashSkuCache.skus
}
try {
const result = await listAllAffiliateDashProducts()
const skus = new Set<string>(result.list.map((product) => String(product.sku || '').trim()).filter(Boolean))
affiliateDashSkuCache = { skus, fetchedAt: now }
return skus
} catch {
// 拉取失败:返回上次缓存(即使过期)或空集合,匹配 miss 走其他通道,不阻塞下单
return affiliateDashSkuCache?.skus || new Set<string>()
}
}