Files
KJD/TEMUKJD/BLL/ImportConverters/ConverterWXToPddV3.cs
T
2026-08-24 15:29:01 +08:00

367 lines
9.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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; // 转换为元
}
}
}