diff --git a/TEMUKJD/BLL/ImportConverters/ConvertToPddFactory.cs b/TEMUKJD/BLL/ImportConverters/ConvertToPddFactory.cs index 5256600..4c6dd63 100644 --- a/TEMUKJD/BLL/ImportConverters/ConvertToPddFactory.cs +++ b/TEMUKJD/BLL/ImportConverters/ConvertToPddFactory.cs @@ -15,6 +15,7 @@ namespace TemuKJD.BLL.ImportConverts new ConverterToPddV5(), new ConverterWXToPddV1(), new ConverterWXToPddV2(), + new ConverterWXToPddV3(), }; public static PDDFrontProductInfo5 Convert(string filePath) { diff --git a/TEMUKJD/BLL/ImportConverters/ConverterWXToPddV3.cs b/TEMUKJD/BLL/ImportConverters/ConverterWXToPddV3.cs new file mode 100644 index 0000000..5f8d7bc --- /dev/null +++ b/TEMUKJD/BLL/ImportConverters/ConverterWXToPddV3.cs @@ -0,0 +1,366 @@ +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using NaiBa.Util.Extension; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TemuKJD.BLL.ImportConverters +{ + internal class ConverterWXToPddV3 : GoodsConverterBase + { + private JObject jsonRoot; + + public override string ConverterName => "微信转拼多多数据源V3"; + + public override PlatformType Platform => PlatformType.WX; + + + protected override bool IsMatchProvider() + { + try + { + jsonRoot = JsonConvert.DeserializeObject(RawData); + if (jsonRoot == null) return false; + + // V3 特征:真实商品数据被包裹在 body 节点下 + var body = jsonRoot.SelectToken("$.body"); + if (body == null || body.Type != JTokenType.Object) return false; + + // 1. 检查 body.extra_service 是否存在且为对象类型 + var extraService = body.SelectToken("$.extra_service"); + if (extraService == null || extraService.Type != JTokenType.Object) return false; + + // 2. 检查 body.out_product_id 是否存在且为字符串类型(非空) + var outProductId = body.SelectToken("$.out_product_id")?.ToString(); + if (string.IsNullOrEmpty(outProductId)) return false; + + // 3. 检查 body.skus 是否存在且为数组,且第一个元素包含 out_sku_id + var skus = body.SelectToken("$.skus"); + if (skus == null) return true; + if (skus.Type != JTokenType.Array) return false; + var skusArray = skus as JArray; + if (skusArray.Count == 0) return true; + + var outSkuId = skusArray[0]?.SelectToken("$.out_sku_id")?.ToString(); + if (string.IsNullOrEmpty(outSkuId)) return false; + + // 四个条件全部通过(比 V1 多了 body 包装校验) + return true; + } + catch + { + return false; + } + } + + protected override PDDFrontProductInfo5 ConverterProvider() + { + var product = new PDDFrontProductInfo5(); + + product.Platform = Platform; + + // 解析JSON数据 + var jsonData = JsonConvert.DeserializeObject(RawData); + var body = jsonData["body"] as JObject ?? new JObject(); + var notes = jsonData["notes"] as JObject; + var source = notes?["source"] as JObject; + + // 商品ID:优先用文件名解析,解析不到再退化使用 body.out_product_id / notes.source.product_id + product.GoodsId = Path.GetFileNameWithoutExtension(FilePath).ParseToLong(); + if (product.GoodsId <= 0) + { + var fallbackId = body["out_product_id"]?.Value() ?? source?["product_id"]?.Value(); + product.GoodsId = fallbackId.ParseToLong(); + } + + // 商品基本信息 + product.Title = body["title"]?.Value() ?? source?["title"]?.Value() ?? ""; + // 新格式没有 short_title,退化取 desc_info.desc + product.Description = body["short_title"]?.Value() + ?? body["desc_info"]?["desc"]?.Value() + ?? ""; + + // 品牌ID - 转换为long + product.BrandId = body["brand_id"]?.Value() ?? 0; + + // 类目信息 + product.CategoryId = GetCategoryIdFromCats(body); + product.CategoryPath = GetCategoryPathFromCats(body); + + // 主图 + product.MainImages = GetMainImagesFromNewFormat(body); + + // 详情图 + product.DetailImages = GetDetailImagesFromNewFormat(body); + + // 商品属性 + product.Propertys = GetPropertiesFromNewFormat(body); + + // SKU和规格信息(SKU 会结合 notes.sku_price / stock_num 做补全) + product.Skus = GetSkusFromNewFormat(body, notes); + product.Specs = GetSpecsFromNewFormat(body); + + // 其他默认值 + product.ThumbUrl = body["head_imgs"]?[0]?.Value() ?? ""; + product.MallId = 0; // body 中没有 mallID + product.Price = GetMinPrice(body); // 从SKU中获取最低价 + product.Videos = new List(); // body 中没有视频信息 + + return product; + } + + + /// + /// 从cats_v2中获取类目ID + /// + private int GetCategoryIdFromCats(JObject body) + { + var cats = body["cats_v2"] as JArray; + if (cats == null || cats.Count == 0) + return 0; + + // 获取最后一个类目(最细分类目) + var lastCat = cats.Last; + return lastCat?["cat_id"]?.Value() ?? 0; + } + + /// + /// 从cats_v2中获取类目路径 + /// + private List GetCategoryPathFromCats(JObject body) + { + var result = new List(); + var cats = body["cats_v2"] as JArray; + + if (cats == null) + return result; + + foreach (var cat in cats) + { + var catId = cat["cat_id"]?.Value(); + if (catId.HasValue && catId.Value > 0) + result.Add(catId.Value); + } + + return result; + } + + /// + /// 获取主图 + /// + private List GetMainImagesFromNewFormat(JObject body) + { + var result = new List(); + var headImgs = body["head_imgs"] as JArray; + + if (headImgs == null) + return result; + + foreach (var img in headImgs) + { + var url = img?.Value(); + if (!string.IsNullOrEmpty(url)) + result.Add(url); + } + + return result; + } + + /// + /// 获取详情图 + /// + private List GetDetailImagesFromNewFormat(JObject body) + { + var result = new List(); + var descInfo = body["desc_info"]; + + if (descInfo == null) + return result; + + var imgs = descInfo["imgs"] as JArray; + if (imgs == null) + return result; + + foreach (var img in imgs) + { + var url = img?.Value(); + if (!string.IsNullOrEmpty(url)) + result.Add(url); + } + + return result; + } + + /// + /// 获取商品属性 + /// + private List GetPropertiesFromNewFormat(JObject body) + { + var result = new List(); + var attrs = body["attrs"] as JArray; + + if (attrs == null) + return result; + + foreach (var attr in attrs) + { + var key = attr["attr_key"]?.Value(); + var value = attr["attr_value"]?.Value(); + + if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value)) + { + result.Add(new Property + { + Key = key, + Value = value + }); + } + } + + return result; + } + + /// + /// 获取SKU。 + /// 相比 V1: + /// 1. 库存优先取 body.skus[].stock_num(新格式已带该字段),取不到才用默认值 999; + /// 2. 划线价/原价优先取 notes.sku_price 中按 out_sku_id 匹配到的 origin 字段(单位:分), + /// 取不到则退化为 sale_price 本身。 + /// + private List GetSkusFromNewFormat(JObject body, JObject notes) + { + var result = new List(); + var skus = body["skus"] as JArray; + + if (skus == null) + return result; + + // 按 out_sku_id 建索引,方便用 notes.sku_price 补全划线价 + var skuPriceMap = new Dictionary(); + var skuPriceList = notes?["sku_price"] as JArray; + if (skuPriceList != null) + { + foreach (var item in skuPriceList) + { + if (item is JObject jo) + { + var outSkuId = jo["out_sku_id"]?.Value(); + if (!string.IsNullOrEmpty(outSkuId)) + skuPriceMap[outSkuId] = jo; + } + } + } + + foreach (var sku in skus) + { + var skuAttrs = sku["sku_attrs"] as JArray; + var specValues = new List(); + + if (skuAttrs != null) + { + foreach (var attr in skuAttrs) + { + var key = attr["attr_key"]?.Value(); + var value = attr["attr_value"]?.Value(); + + if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value)) + specValues.Add($"{key}:{value}"); + } + } + + var outSkuId = sku["out_sku_id"]?.Value() ?? ""; + + // sale_price 是分,需要转换为元 + var salePriceInCents = sku["sale_price"]?.Value() ?? 0; + var salePriceInYuan = salePriceInCents / 100; + + // 划线价:优先用 notes.sku_price[].origin(分),取不到就退化用 sale_price + var originPriceInCents = salePriceInCents; + if (skuPriceMap.TryGetValue(outSkuId, out var priceInfo)) + { + originPriceInCents = priceInfo["origin"]?.Value() ?? salePriceInCents; + } + + // 库存:新格式 body.skus 已自带 stock_num,取不到才用默认值 + var stock = sku["stock_num"]?.Value() ?? 999; + + result.Add(new SkuItem + { + SkuId = outSkuId, + Spec = string.Join(";", specValues), + Price = salePriceInYuan, + OriginalPrice = originPriceInCents.ToString(), // 保留划线价(分) + Stock = stock, + SpecImage = sku["thumb_img"]?.Value() ?? "" + }); + } + + return result; + } + + /// + /// 获取规格 + /// + private List GetSpecsFromNewFormat(JObject body) + { + var result = new List(); + var skus = body["skus"] as JArray; + + if (skus == null) + return result; + + foreach (var sku in skus) + { + var skuAttrs = sku["sku_attrs"] as JArray; + if (skuAttrs == null) + continue; + + foreach (var attr in skuAttrs) + { + var specKey = attr["attr_key"]?.Value() ?? ""; + var specValue = attr["attr_value"]?.Value() ?? ""; + + // 去重 + if (result.Any(x => x.SpecKey == specKey && x.SpecValue == specValue)) + continue; + + result.Add(new SpecItem + { + SpecKey = specKey, + SpecValue = specValue, + SpecKeyId = 0, // 新格式中没有ID + SpecValueId = 0 + }); + } + } + + return result; + } + + /// + /// 获取最低价格 + /// + private decimal GetMinPrice(JObject body) + { + var skus = body["skus"] as JArray; + if (skus == null || skus.Count == 0) + return 0; + + var minPrice = decimal.MaxValue; + foreach (var sku in skus) + { + var price = sku["sale_price"]?.Value() ?? 0; + if (price > 0 && price < minPrice) + minPrice = price; + } + + return minPrice > 0 ? minPrice / 100 : 0; // 转换为元 + } + } +} diff --git a/TEMUKJD/KJD.csproj b/TEMUKJD/KJD.csproj index ddf5aff..e394b39 100644 --- a/TEMUKJD/KJD.csproj +++ b/TEMUKJD/KJD.csproj @@ -148,6 +148,7 @@ + diff --git a/test/KJD.Test/UnitTest1.cs b/test/KJD.Test/UnitTest1.cs index 37a8796..017bfab 100644 --- a/test/KJD.Test/UnitTest1.cs +++ b/test/KJD.Test/UnitTest1.cs @@ -100,6 +100,14 @@ namespace KJD.Test var frontProdInfo = ConverterToPddFactory.Convert(filePath); var isMatch = frontProdInfo != null; } + + [Test] + public void TestWXToPDDV3() + { + string filePath = @"C:\Users\chaih\Documents\xwechat_files\hongfei357_a567\msg\file\2026-08\10000528322732(1).json"; + var frontProdInfo = ConverterToPddFactory.Convert(filePath); + var isMatch = frontProdInfo != null; + } } }