新增透传: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:
@@ -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,17 +325,63 @@ function resolveAffiliateDashSkuByProductNo(item: FulfillmentItem): AffiliateDas
|
||||
item.externalSkuCode,
|
||||
item.externalItemId,
|
||||
])
|
||||
const sku = productNo ? matchAffiliateDashSku(config.skuMapping, String(productNo)) : ''
|
||||
if (!productNo || !sku) {
|
||||
if (!productNo) {
|
||||
return null
|
||||
}
|
||||
|
||||
// ① 指定映射(运营显式配置)
|
||||
const mappedSku = matchAffiliateDashSku(config.skuMapping, String(productNo))
|
||||
if (mappedSku) {
|
||||
return {
|
||||
sku,
|
||||
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>()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -688,3 +688,27 @@ order_site 处理要求:
|
||||
| 神秘新商品(未映射) | (无)回退 | — |
|
||||
|
||||
**验证**:typecheck 通过;后端全量 221 测试通过(新增 9 个)。
|
||||
|
||||
---
|
||||
|
||||
## 23. 透传优化:91 编码 == affiliate_dash sku 自动命中(v2.8 · 已完成)
|
||||
|
||||
**需求**:91 商品编码可直接设置为英文(= affiliate_dash sku,如 `suit_alan_walker`),映射表未命中时**自动透传**,无需在 order_site 维护映射表。
|
||||
|
||||
### 23.1 匹配层级(`resolveAffiliateDashSkuByProductNo`,改为 async)
|
||||
|
||||
1. **① 指定映射**(`matchAffiliateDashSku`):skuMapping 精确/归一命中 → `matchMode='affiliate_dash_sku_mapping'`
|
||||
2. **② 透传**(`matchAffiliateDashSkuPassthrough`):映射未命中时,productNo 与 affiliate_dash 商品 sku 完全一致 → `matchMode='affiliate_dash_sku_passthrough'`
|
||||
3. 两层任一命中都触发「指定匹配最高优先」(`preferredExecutorKey=affiliate_dash`),规则/全局优先级不抢占
|
||||
|
||||
**商品列表缓存**:`getAffiliateDashSkuSet()` 内存缓存 5 分钟(`listAllAffiliateDashProducts` 拉全量 33 个 sku);拉取失败返回上次缓存或空集合——匹配 miss 走其他通道,**不阻塞下单**。
|
||||
|
||||
### 23.2 行为验证(线上商品列表)
|
||||
|
||||
| 输入(91 编码) | executor | sku | matchMode |
|
||||
| --- | --- | --- | --- |
|
||||
| `suit_alan_walker`(真实商品) | affiliate_dash | suit_alan_walker | affiliate_dash_sku_passthrough |
|
||||
| `suit_cloud_bear`(真实商品) | affiliate_dash | suit_cloud_bear | affiliate_dash_sku_passthrough |
|
||||
| `not_a_real_sku` | (无)回退 | — | — |
|
||||
|
||||
**验证**:typecheck 通过;后端全量 224 测试通过(透传纯函数测试 +3)。
|
||||
|
||||
Reference in New Issue
Block a user