新增 WX to PDD V3

This commit is contained in:
chfei committed 2026-08-24 15:29:01 +08:00
1 parent 610e75dfce
commit 9e71c54414
4 files changed
+376

No files matched your search

@@ -15,6 +15,7 @@ namespace TemuKJD.BLL.ImportConverts
new ConverterToPddV5(),
new ConverterWXToPddV1(),
new ConverterWXToPddV2(),
new ConverterWXToPddV3(),
};
public static PDDFrontProductInfo5 Convert(string filePath)
{
@@ -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<JObject>(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<JObject>(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<string>() ?? source?["product_id"]?.Value<string>();
product.GoodsId = fallbackId.ParseToLong();
}
// 商品基本信息
product.Title = body["title"]?.Value<string>() ?? source?["title"]?.Value<string>() ?? "";
// 新格式没有 short_title,退化取 desc_info.desc
product.Description = body["short_title"]?.Value<string>()
?? body["desc_info"]?["desc"]?.Value<string>()
?? "";
// 品牌ID - 转换为long
product.BrandId = body["brand_id"]?.Value<long>() ?? 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<string>() ?? "";
product.MallId = 0; // body 中没有 mallID
product.Price = GetMinPrice(body); // 从SKU中获取最低价
product.Videos = new List<string>(); // body 中没有视频信息
return product;
}
/// <summary>
/// 从cats_v2中获取类目ID
/// </summary>
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<int>() ?? 0;
}
/// <summary>
/// 从cats_v2中获取类目路径
/// </summary>
private List<int> GetCategoryPathFromCats(JObject body)
{
var result = new List<int>();
var cats = body["cats_v2"] as JArray;
if (cats == null)
return result;
foreach (var cat in cats)
{
var catId = cat["cat_id"]?.Value<int>();
if (catId.HasValue && catId.Value > 0)
result.Add(catId.Value);
}
return result;
}
/// <summary>
/// 获取主图
/// </summary>
private List<string> GetMainImagesFromNewFormat(JObject body)
{
var result = new List<string>();
var headImgs = body["head_imgs"] as JArray;
if (headImgs == null)
return result;
foreach (var img in headImgs)
{
var url = img?.Value<string>();
if (!string.IsNullOrEmpty(url))
result.Add(url);
}
return result;
}
/// <summary>
/// 获取详情图
/// </summary>
private List<string> GetDetailImagesFromNewFormat(JObject body)
{
var result = new List<string>();
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<string>();
if (!string.IsNullOrEmpty(url))
result.Add(url);
}
return result;
}
/// <summary>
/// 获取商品属性
/// </summary>
private List<Property> GetPropertiesFromNewFormat(JObject body)
{
var result = new List<Property>();
var attrs = body["attrs"] as JArray;
if (attrs == null)
return result;
foreach (var attr in attrs)
{
var key = attr["attr_key"]?.Value<string>();
var value = attr["attr_value"]?.Value<string>();
if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value))
{
result.Add(new Property
{
Key = key,
Value = value
});
}
}
return result;
}
/// <summary>
/// 获取SKU。
/// 相比 V1:
/// 1. 库存优先取 body.skus[].stock_num(新格式已带该字段),取不到才用默认值 999;
/// 2. 划线价/原价优先取 notes.sku_price 中按 out_sku_id 匹配到的 origin 字段(单位:分),
/// 取不到则退化为 sale_price 本身。
/// </summary>
private List<SkuItem> GetSkusFromNewFormat(JObject body, JObject notes)
{
var result = new List<SkuItem>();
var skus = body["skus"] as JArray;
if (skus == null)
return result;
// 按 out_sku_id 建索引,方便用 notes.sku_price 补全划线价
var skuPriceMap = new Dictionary<string, JObject>();
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<string>();
if (!string.IsNullOrEmpty(outSkuId))
skuPriceMap[outSkuId] = jo;
}
}
}
foreach (var sku in skus)
{
var skuAttrs = sku["sku_attrs"] as JArray;
var specValues = new List<string>();
if (skuAttrs != null)
{
foreach (var attr in skuAttrs)
{
var key = attr["attr_key"]?.Value<string>();
var value = attr["attr_value"]?.Value<string>();
if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value))
specValues.Add($"{key}:{value}");
}
}
var outSkuId = sku["out_sku_id"]?.Value<string>() ?? "";
// sale_price 是分,需要转换为元
var salePriceInCents = sku["sale_price"]?.Value<decimal>() ?? 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<decimal>() ?? salePriceInCents;
}
// 库存:新格式 body.skus 已自带 stock_num,取不到才用默认值
var stock = sku["stock_num"]?.Value<int>() ?? 999;
result.Add(new SkuItem
{
SkuId = outSkuId,
Spec = string.Join(";", specValues),
Price = salePriceInYuan,
OriginalPrice = originPriceInCents.ToString(), // 保留划线价(分)
Stock = stock,
SpecImage = sku["thumb_img"]?.Value<string>() ?? ""
});
}
return result;
}
/// <summary>
/// 获取规格
/// </summary>
private List<SpecItem> GetSpecsFromNewFormat(JObject body)
{
var result = new List<SpecItem>();
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<string>() ?? "";
var specValue = attr["attr_value"]?.Value<string>() ?? "";
// 去重
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;
}
/// <summary>
/// 获取最低价格
/// </summary>
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<decimal>() ?? 0;
if (price > 0 && price < minPrice)
minPrice = price;
}
return minPrice > 0 ? minPrice / 100 : 0; // 转换为元
}
}
}
+1
View File
@@ -148,6 +148,7 @@
<Compile Include="BLL\HttpResponseWxAiTalkBLL.cs" />
<Compile Include="BLL\ImportConverters\ConverterWXToPddV1.cs" />
<Compile Include="BLL\ImportConverters\ConverterWXToPddV2.cs" />
<Compile Include="BLL\ImportConverters\ConverterWXToPddV3.cs" />
<Compile Include="BLL\ImportConverters\ConvertToPddFactory.cs" />
<Compile Include="BLL\ImportConverters\ConverterToPddV5.cs" />
<Compile Include="BLL\ImportConverters\IGoodsConverter.cs" />
+8
View File
@@ -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;
}
}
}