From 6c4183427406e563224980ff98f01a1360edcf38 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Tue, 15 Sep 2026 11:51:32 +0800 Subject: [PATCH] =?UTF-8?q?fix(delivery):=20=E4=B9=90=E7=8E=A9=E4=B8=8D?= =?UTF-8?q?=E5=8F=AF=E7=94=A8=E6=97=B6=E5=9B=9E=E9=80=80=20DLC=EF=BC=8C?= =?UTF-8?q?=E9=81=BF=E5=85=8D=E6=8F=90=E4=BA=A4=20UID=20=E7=BB=91=E5=AE=9A?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit legacy 双渠道配置升为 priority;显式 lewan 且具备 DLC 时允许回退,缓解 LEWAN_* 未注入导致的 bind 失败。 Co-authored-by: Cursor --- backend/internal/service/provider.go | 25 ++++++++++++++++------- backend/internal/service/provider_test.go | 24 +++++++++++++++++++++- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/backend/internal/service/provider.go b/backend/internal/service/provider.go index f858f74..eb117a2 100644 --- a/backend/internal/service/provider.go +++ b/backend/internal/service/provider.go @@ -35,7 +35,9 @@ func ParseProductRoutingConfig(fulfillmentConfig string, sku string) ProductRout _ = json.Unmarshal([]byte(fulfillmentConfig), &cfg) } - // 兼容老配置或单 provider 字段 + // 兼容老配置:只抽取字段,不提前固化 routing_mode, + // 避免仅有 lewan_product_id 时在补齐内置 DLC 前被锁成 lewan-only。 + legacyProvider := "" if cfg.RoutingMode == "" { var legacy struct { Provider string `json:"provider"` @@ -45,11 +47,11 @@ func ParseProductRoutingConfig(fulfillmentConfig string, sku string) ProductRout if strings.TrimSpace(fulfillmentConfig) != "" { _ = json.Unmarshal([]byte(fulfillmentConfig), &legacy) } - if legacy.Provider == ProviderLewan || legacy.LewanProductID != "" { - cfg.RoutingMode = RoutingModeLewan + legacyProvider = strings.TrimSpace(legacy.Provider) + if cfg.LewanProductID == "" && legacy.LewanProductID != "" { cfg.LewanProductID = legacy.LewanProductID - } else if legacy.Provider == ProviderDLC { - cfg.RoutingMode = RoutingModeDLC + } + if cfg.DLCGoodID == "" && legacy.DLCGoodID != "" { cfg.DLCGoodID = legacy.DLCGoodID } } @@ -60,13 +62,15 @@ func ParseProductRoutingConfig(fulfillmentConfig string, sku string) ProductRout cfg.DLCGoodID = builtinDLC } - // 若未配置 RoutingMode + // 若未配置 RoutingMode:双渠道优先 priority,单渠道按已有 ID / legacy provider 决定 if cfg.RoutingMode == "" { if cfg.LewanProductID != "" && cfg.DLCGoodID != "" { cfg.RoutingMode = RoutingModePriority cfg.Priority = []string{ProviderLewan, ProviderDLC} - } else if cfg.LewanProductID != "" { + } else if cfg.LewanProductID != "" || legacyProvider == ProviderLewan { cfg.RoutingMode = RoutingModeLewan + } else if legacyProvider == ProviderDLC { + cfg.RoutingMode = RoutingModeDLC } else { cfg.RoutingMode = RoutingModeDLC } @@ -184,6 +188,13 @@ func (r *FulfillmentRouter) ResolveProviders(order *model.FulfillmentOrder) ([]F if p, ok := r.providers[ProviderLewan]; ok { list = append(list, p) } + // 显式 lewan 但商品同时具备 DLC 配置时,乐玩不可用(密钥未配/上游失败)允许回退 DLC, + // 避免「套装-浪漫天命」等双渠道商品在 LEWAN_* 未注入时提交 UID 直接失败。 + if cfg.DLCGoodID != "" { + if p, ok := r.providers[ProviderDLC]; ok { + list = append(list, p) + } + } case RoutingModeDLC: if p, ok := r.providers[ProviderDLC]; ok { list = append(list, p) diff --git a/backend/internal/service/provider_test.go b/backend/internal/service/provider_test.go index 4fa8170..32dbd54 100644 --- a/backend/internal/service/provider_test.go +++ b/backend/internal/service/provider_test.go @@ -39,12 +39,22 @@ func TestParseProductRoutingConfig(t *testing.T) { t.Fatalf("mismatched IDs: dlc=%s lewan=%s", cfg3.DLCGoodID, cfg3.LewanProductID) } - // Case 4: 兼容历史 legacy 单 provider 字段 + // Case 4: 兼容历史 legacy 单 provider 字段(无内置 DLC 时仍为 lewan-only) cfg4JSON := `{"provider":"lewan","lewan_product_id":"uuid-999"}` cfg4 := ParseProductRoutingConfig(cfg4JSON, "any_sku") if cfg4.RoutingMode != RoutingModeLewan || cfg4.LewanProductID != "uuid-999" { t.Fatalf("legacy lewan format failed: %+v", cfg4) } + + // Case 5: legacy 仅有 lewan_product_id,但 SKU 有内置 DLC → 应升为 priority + cfg5JSON := `{"lewan_product_id":"b1fff090-e389-4c6c-9c51-32751582239d"}` + cfg5 := ParseProductRoutingConfig(cfg5JSON, "suit_romantic_destiny") + if cfg5.RoutingMode != RoutingModePriority { + t.Fatalf("expected priority for dual-capable legacy config, got %s", cfg5.RoutingMode) + } + if cfg5.DLCGoodID != "682ef39ca8f40c4234c59f38" { + t.Fatalf("expected builtin DLC good id, got %s", cfg5.DLCGoodID) + } } func TestFulfillmentRouterResolution(t *testing.T) { @@ -73,6 +83,18 @@ func TestFulfillmentRouterResolution(t *testing.T) { t.Fatalf("expected Lewan provider, got %d providers", len(providers)) } + // Case 2b: 显式 lewan 但 SKU 有内置 DLC → 应附带 DLC 回退 + orderLewanWithDLC := &model.FulfillmentOrder{ + ProductSKU: "suit_romantic_destiny", + MerchantProduct: &model.MerchantProduct{ + FulfillmentConfig: `{"routing_mode":"lewan","lewan_product_id":"b1fff090-e389-4c6c-9c51-32751582239d","dlc_good_id":""}`, + }, + } + providers, _ = router.ResolveProviders(orderLewanWithDLC) + if len(providers) != 2 || providers[0].Name() != ProviderLewan || providers[1].Name() != ProviderDLC { + t.Fatalf("expected [lewan, dlc] fallback, got %v", providerNames(providers)) + } + // Case 3: 优先级路由 [lewan, dlc] orderPriority := &model.FulfillmentOrder{ ProductSKU: "shared_item",