702 lines
19 KiB
C#
702 lines
19 KiB
C#
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;
|
||
|
||
namespace TemuKJD.BLL.ImportConverters
|
||
{
|
||
/// <summary>
|
||
/// 微信转拼多多数据源 V4版本(兼容增强版)
|
||
/// 针对微信商品详情API(/channels/ec/product/get)返回的 detail.product 结构
|
||
///
|
||
/// 兼容的数据样例:
|
||
/// - 10000031453165(女装针织衫,含视频、资质、完整size_chart)
|
||
/// - 10000182390569(不锈钢调味球,无视频、无资质列表、简化size_chart)
|
||
///
|
||
/// 兼容策略:
|
||
/// 1. 所有可选字段使用安全访问,缺失时回退
|
||
/// 2. 属性解析优先 param_v2,回退 categorys
|
||
/// 3. 规格解析优先 value_v2,回退 value
|
||
/// 4. SKU价格优先 sale_price,回退 sku_list.sku_info.price
|
||
/// 5. SKU库存优先 product_skus,回退 spu_total_stock 均分
|
||
/// </summary>
|
||
internal class ConverterWXToPddV4 : GoodsConverterBase
|
||
{
|
||
private JObject _jsonRoot;
|
||
private JObject _productNode; // detail.product
|
||
private JObject _infoNode; // detail.product.info
|
||
private JArray _skuList; // detail.product.info.sku_list.sku_info(价格兜底)
|
||
private JArray _productSkus; // detail.product.product_skus(完整SKU)
|
||
private JArray _saleAttrs; // detail.product.info.product_sale_attrs
|
||
|
||
public override string ConverterName => "微信转拼多多数据源 V4";
|
||
|
||
public override PlatformType Platform => PlatformType.WX;
|
||
|
||
protected override bool IsMatchProvider()
|
||
{
|
||
try
|
||
{
|
||
_jsonRoot = JsonConvert.DeserializeObject<JObject>(RawData);
|
||
if (_jsonRoot == null) return false;
|
||
|
||
// 1. 检查 detail.product 结构
|
||
var detail = _jsonRoot.SelectToken("$.detail") as JObject;
|
||
if (detail == null) return false;
|
||
|
||
var product = detail.SelectToken("$.product") as JObject;
|
||
if (product == null) return false;
|
||
|
||
// 2. 检查 product_key.product_id
|
||
var productKey = product.SelectToken("$.product_key") as JObject;
|
||
if (productKey == null) return false;
|
||
|
||
var productId = productKey.SelectToken("$.product_id")?.ToString();
|
||
if (string.IsNullOrEmpty(productId)) return false;
|
||
|
||
// 3. 检查 info 节点
|
||
var info = product.SelectToken("$.info") as JObject;
|
||
if (info == null) return false;
|
||
|
||
// 4. 检查标题
|
||
var title = info.SelectToken("$.title")?.ToString();
|
||
if (string.IsNullOrEmpty(title)) return false;
|
||
|
||
// 5. 检查SKU(兼容两种:product_skus 优先,sku_list.sku_info 兜底)
|
||
var productSkus = product.SelectToken("$.product_skus") as JArray;
|
||
var skuList = info.SelectToken("$.sku_list.sku_info") as JArray;
|
||
|
||
if ((productSkus == null || productSkus.Count == 0) &&
|
||
(skuList == null || skuList.Count == 0))
|
||
return false;
|
||
|
||
// 缓存节点
|
||
_productNode = product;
|
||
_infoNode = info;
|
||
_productSkus = productSkus;
|
||
_skuList = skuList;
|
||
_saleAttrs = info.SelectToken("$.product_sale_attrs") as JArray;
|
||
|
||
return true;
|
||
}
|
||
catch
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
protected override PDDFrontProductInfo5 ConverterProvider()
|
||
{
|
||
var product = new PDDFrontProductInfo5
|
||
{
|
||
Platform = Platform,
|
||
GoodsId = ParseGoodsId()
|
||
};
|
||
|
||
// ========== 1. 商品基本信息 ==========
|
||
product.Title = GetStringValue(_infoNode, "title");
|
||
product.Description = GetStringValue(_infoNode, "short_title");
|
||
if (string.IsNullOrEmpty(product.Description))
|
||
product.Description = GetStringValue(_infoNode, "ai_gen_short_title");
|
||
|
||
// ========== 2. 品牌信息 ==========
|
||
product.BrandId = GetLongValue(_infoNode, "brand_id");
|
||
|
||
// ========== 3. 类目信息 ==========
|
||
product.CategoryId = GetCategoryId();
|
||
product.CategoryPath = GetCategoryPath();
|
||
|
||
// ========== 4. 主图 ==========
|
||
product.MainImages = GetMainImages();
|
||
|
||
// ========== 5. 详情图 ==========
|
||
product.DetailImages = GetDetailImages();
|
||
|
||
// ========== 6. 商品属性 ==========
|
||
product.Propertys = GetProperties();
|
||
|
||
// ========== 7. SKU和规格信息 ==========
|
||
var skuResult = GetSkusAndSpecs();
|
||
product.Skus = skuResult.Item1;
|
||
product.Specs = skuResult.Item2;
|
||
|
||
// ========== 8. 其他信息 ==========
|
||
product.ThumbUrl = product.MainImages.FirstOrDefault() ?? string.Empty;
|
||
product.MallId = 0;
|
||
product.Price = GetMinPrice();
|
||
product.Videos = GetVideoUrls();
|
||
|
||
return product;
|
||
}
|
||
|
||
#region 核心字段解析
|
||
|
||
/// <summary>
|
||
/// 解析商品ID(兼容 out_product_id 存在与否)
|
||
/// </summary>
|
||
private long ParseGoodsId()
|
||
{
|
||
// 优先 product_id(所有情况都存在)
|
||
var productId = _productNode.SelectToken("$.product_key.product_id")?.ToString();
|
||
if (long.TryParse(productId, out var result))
|
||
return result;
|
||
|
||
// 回退文件路径
|
||
return Path.GetFileNameWithoutExtension(FilePath).ParseToLong();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取类目ID(兼容 category 数组,取最细分类目)
|
||
/// </summary>
|
||
private int GetCategoryId()
|
||
{
|
||
var category = _infoNode["category"] as JArray;
|
||
if (category == null || category.Count == 0)
|
||
return 0;
|
||
|
||
var lastCat = category.Last as JObject;
|
||
return lastCat?["cat_id"]?.Value<int>() ?? 0;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取类目路径
|
||
/// </summary>
|
||
private List<int> GetCategoryPath()
|
||
{
|
||
var result = new List<int>();
|
||
var category = _infoNode["category"] as JArray;
|
||
if (category == null)
|
||
return result;
|
||
|
||
foreach (var cat in category)
|
||
{
|
||
var catId = cat["cat_id"]?.Value<int>();
|
||
if (catId.HasValue && catId.Value > 0)
|
||
result.Add(catId.Value);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取主图列表
|
||
/// </summary>
|
||
private List<string> GetMainImages()
|
||
{
|
||
var result = new List<string>();
|
||
|
||
// 优先 head_img
|
||
var headImg = _infoNode["head_img"] as JArray;
|
||
if (headImg != null && headImg.Count > 0)
|
||
{
|
||
foreach (var img in headImg)
|
||
{
|
||
var url = img?.Value<string>();
|
||
if (!string.IsNullOrEmpty(url))
|
||
result.Add(url);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
// 回退 head_img_infos
|
||
var headImgInfos = _infoNode["head_img_infos"] as JArray;
|
||
if (headImgInfos != null)
|
||
{
|
||
foreach (var img in headImgInfos)
|
||
{
|
||
var url = img["img_url"]?.Value<string>();
|
||
if (!string.IsNullOrEmpty(url))
|
||
result.Add(url);
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取详情图列表
|
||
/// </summary>
|
||
private List<string> GetDetailImages()
|
||
{
|
||
var result = new List<string>();
|
||
var detail = _infoNode["detail"] as JObject;
|
||
if (detail == null)
|
||
return result;
|
||
|
||
var detailImg = detail["detail_img"] as JArray;
|
||
if (detailImg == null)
|
||
return result;
|
||
|
||
foreach (var img in detailImg)
|
||
{
|
||
var url = img?.Value<string>();
|
||
if (!string.IsNullOrEmpty(url))
|
||
result.Add(url);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取商品属性列表(从 info.param 解析)
|
||
/// 兼容两种结构:
|
||
/// - 10000031453165:categorys[0].name = key, categorys[1].name = value
|
||
/// - 10000182390569:categorys[0].name = key, categorys[1].name = value(相同)
|
||
/// </summary>
|
||
private List<Property> GetProperties()
|
||
{
|
||
var result = new List<Property>();
|
||
var param = _infoNode["param"] as JArray;
|
||
if (param == null)
|
||
return result;
|
||
|
||
foreach (var item in param)
|
||
{
|
||
var categorys = item["categorys"] as JArray;
|
||
if (categorys == null || categorys.Count < 2)
|
||
continue;
|
||
|
||
var key = categorys[0]?["name"]?.Value<string>();
|
||
var value = categorys[1]?["name"]?.Value<string>();
|
||
|
||
if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value))
|
||
{
|
||
if (!result.Any(p => p.Key == key))
|
||
{
|
||
result.Add(new Property
|
||
{
|
||
Key = key,
|
||
Value = value
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取视频URL列表(兼容无视频的情况)
|
||
/// </summary>
|
||
private List<string> GetVideoUrls()
|
||
{
|
||
var result = new List<string>();
|
||
var headVideoInfo = _infoNode["head_video_info"] as JArray;
|
||
if (headVideoInfo == null)
|
||
return result;
|
||
|
||
foreach (var video in headVideoInfo)
|
||
{
|
||
var url = video["video_url"]?.Value<string>();
|
||
if (!string.IsNullOrEmpty(url))
|
||
result.Add(url);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region SKU和规格解析
|
||
|
||
/// <summary>
|
||
/// 获取SKU和规格信息
|
||
/// 兼容两种数据:
|
||
/// - product_skus:包含 sku_key.sku_id、sale_param、sale_price、stock_info
|
||
/// - sku_list.sku_info:包含 sku_id、price、estimate_price(无规格、无库存)
|
||
/// </summary>
|
||
private Tuple<List<SkuItem> , List<SpecItem>> GetSkusAndSpecs()
|
||
{
|
||
var skuList = new List<SkuItem>();
|
||
var specList = new List<SpecItem>();
|
||
|
||
// 构建 sku_list.sku_info 的价格字典(作为兜底)
|
||
var priceFallbackDict = BuildPriceFallbackDict();
|
||
|
||
// 构建 product_sale_attrs 的规格字典(作为兜底)
|
||
var attrFallbackDict = BuildAttrFallbackDict();
|
||
|
||
// 优先处理 product_skus(完整信息)
|
||
if (_productSkus != null && _productSkus.Count > 0)
|
||
{
|
||
foreach (var sku in _productSkus)
|
||
{
|
||
var skuObj = sku as JObject;
|
||
if (skuObj == null) continue;
|
||
|
||
var skuItem = ParseProductSku(skuObj, priceFallbackDict, attrFallbackDict);
|
||
if (skuItem != null)
|
||
skuList.Add(skuItem);
|
||
}
|
||
}
|
||
// 回退到 sku_list.sku_info(仅价格)
|
||
else if (_skuList != null && _skuList.Count > 0)
|
||
{
|
||
foreach (var sku in _skuList)
|
||
{
|
||
var skuObj = sku as JObject;
|
||
if (skuObj == null) continue;
|
||
|
||
var skuItem = ParseSkuInfoOnly(skuObj, attrFallbackDict);
|
||
if (skuItem != null)
|
||
skuList.Add(skuItem);
|
||
}
|
||
}
|
||
|
||
// 构建规格列表(优先 product_sale_attrs)
|
||
BuildSpecList(specList, skuList);
|
||
|
||
return new Tuple<List<SkuItem>, List<SpecItem>>(skuList, specList);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 构建 sku_list.sku_info 的价格兜底字典
|
||
/// </summary>
|
||
private Dictionary<string, decimal> BuildPriceFallbackDict()
|
||
{
|
||
var dict = new Dictionary<string, decimal>();
|
||
if (_skuList == null) return dict;
|
||
|
||
foreach (var sku in _skuList)
|
||
{
|
||
var skuObj = sku as JObject;
|
||
if (skuObj == null) continue;
|
||
|
||
var skuId = skuObj["sku_id"]?.Value<string>();
|
||
if (string.IsNullOrEmpty(skuId)) continue;
|
||
|
||
// 优先 estimate_price(优惠后),回退 price
|
||
var estimatePrice = skuObj["estimate_price"]?.Value<decimal>() ?? 0;
|
||
var price = skuObj["price"]?.Value<decimal>() ?? 0;
|
||
var actualPrice = estimatePrice > 0 ? estimatePrice : price;
|
||
|
||
dict[skuId] = actualPrice;
|
||
}
|
||
|
||
return dict;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 构建 product_sale_attrs 的规格兜底字典(skuId -> spec文本)
|
||
/// 兼容两种结构:
|
||
/// - 10000031453165:items[].value_v2, items[].sku_ids[]
|
||
/// - 10000182390569:items[].value, items[].sku_ids[]
|
||
/// </summary>
|
||
private Dictionary<string, string> BuildAttrFallbackDict()
|
||
{
|
||
var dict = new Dictionary<string, string>();
|
||
if (_saleAttrs == null) return dict;
|
||
|
||
foreach (var attr in _saleAttrs)
|
||
{
|
||
var key = attr["key"]?.Value<string>() ?? string.Empty;
|
||
var items = attr["items"] as JArray;
|
||
if (items == null) continue;
|
||
|
||
foreach (var item in items)
|
||
{
|
||
// 兼容 value_v2 和 value
|
||
var value = item["value_v2"]?.Value<string>();
|
||
if (string.IsNullOrEmpty(value))
|
||
value = item["value"]?.Value<string>();
|
||
|
||
if (string.IsNullOrEmpty(value)) continue;
|
||
|
||
var skuIds = item["sku_ids"] as JArray;
|
||
if (skuIds == null) continue;
|
||
|
||
var specText = $"{key}:{value}";
|
||
foreach (var skuId in skuIds)
|
||
{
|
||
var id = skuId?.Value<string>();
|
||
if (!string.IsNullOrEmpty(id))
|
||
dict[id] = specText;
|
||
}
|
||
}
|
||
}
|
||
|
||
return dict;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 解析 product_skus 中的单个SKU
|
||
/// </summary>
|
||
private SkuItem ParseProductSku(
|
||
JObject skuObj,
|
||
Dictionary<string, decimal> priceFallbackDict,
|
||
Dictionary<string, string> attrFallbackDict)
|
||
{
|
||
var skuKey = skuObj["sku_key"] as JObject;
|
||
var skuInfo = skuObj["product_sku_info"] as JObject;
|
||
if (skuKey == null || skuInfo == null) return null;
|
||
|
||
var skuId = skuKey["sku_id"]?.Value<string>() ?? string.Empty;
|
||
var outSkuId = skuKey["out_sku_id"]?.Value<string>() ?? string.Empty;
|
||
if (string.IsNullOrEmpty(skuId)) return null;
|
||
|
||
// 解析规格
|
||
var specText = ParseSkuSpec(skuInfo, skuId, attrFallbackDict);
|
||
|
||
// 解析价格
|
||
var salePrice = skuInfo["sale_price"]?.Value<decimal>() ?? 0;
|
||
if (salePrice <= 0 && priceFallbackDict.TryGetValue(skuId, out var fallbackPrice))
|
||
salePrice = fallbackPrice;
|
||
|
||
// 解析库存
|
||
var stockNum = ParseStockNum(skuInfo);
|
||
|
||
// 缩略图
|
||
var thumbImg = skuInfo["thumb_img"]?.Value<string>() ?? string.Empty;
|
||
|
||
return new SkuItem
|
||
{
|
||
SkuId = !string.IsNullOrEmpty(outSkuId) ? outSkuId : skuId,
|
||
Spec = specText,
|
||
Price = salePrice / 100, // 分转元
|
||
OriginalPrice = salePrice.ToString(),
|
||
Stock = stockNum,
|
||
SpecImage = thumbImg
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 解析 sku_list.sku_info 中的单个SKU(仅价格)
|
||
/// </summary>
|
||
private SkuItem ParseSkuInfoOnly(
|
||
JObject skuObj,
|
||
Dictionary<string, string> attrFallbackDict)
|
||
{
|
||
var skuId = skuObj["sku_id"]?.Value<string>() ?? string.Empty;
|
||
if (string.IsNullOrEmpty(skuId)) return null;
|
||
|
||
// 规格从 attrFallbackDict 获取
|
||
var specText = attrFallbackDict.TryGetValue(skuId, out var spec) ? spec : string.Empty;
|
||
|
||
// 价格
|
||
var estimatePrice = skuObj["estimate_price"]?.Value<decimal>() ?? 0;
|
||
var price = skuObj["price"]?.Value<decimal>() ?? 0;
|
||
var actualPrice = estimatePrice > 0 ? estimatePrice : price;
|
||
|
||
return new SkuItem
|
||
{
|
||
SkuId = skuId,
|
||
Spec = specText,
|
||
Price = actualPrice / 100,
|
||
OriginalPrice = actualPrice.ToString(),
|
||
Stock = 0,
|
||
SpecImage = string.Empty
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 解析SKU规格
|
||
/// 兼容两种结构:
|
||
/// - 10000031453165:sale_param[].param_v2.attr_name / attr_value_name
|
||
/// - 10000182390569:sale_param[].categorys[0].name / categorys[1].name
|
||
/// </summary>
|
||
private string ParseSkuSpec(
|
||
JObject skuInfo,
|
||
string skuId,
|
||
Dictionary<string, string> attrFallbackDict)
|
||
{
|
||
var specValues = new List<string>();
|
||
var saleParams = skuInfo["sale_param"] as JArray;
|
||
|
||
if (saleParams != null)
|
||
{
|
||
foreach (var param in saleParams)
|
||
{
|
||
// 优先 param_v2
|
||
var paramV2 = param["param_v2"] as JObject;
|
||
if (paramV2 != null)
|
||
{
|
||
var attrName = paramV2["attr_name"]?.Value<string>() ?? string.Empty;
|
||
var attrValue = paramV2["attr_value_name"]?.Value<string>() ?? string.Empty;
|
||
|
||
if (!string.IsNullOrEmpty(attrName) && !string.IsNullOrEmpty(attrValue))
|
||
{
|
||
specValues.Add($"{attrName}:{attrValue}");
|
||
continue;
|
||
}
|
||
}
|
||
|
||
// 回退 categorys
|
||
var categorys = param["categorys"] as JArray;
|
||
if (categorys != null && categorys.Count >= 2)
|
||
{
|
||
var attrName = categorys[0]?["name"]?.Value<string>() ?? string.Empty;
|
||
var attrValue = categorys[1]?["name"]?.Value<string>() ?? string.Empty;
|
||
|
||
if (!string.IsNullOrEmpty(attrName) && !string.IsNullOrEmpty(attrValue))
|
||
{
|
||
specValues.Add($"{attrName}:{attrValue}");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果无法从 sale_param 解析,从 attrFallbackDict 兜底
|
||
if (specValues.Count == 0 && attrFallbackDict.TryGetValue(skuId, out var fallbackSpec))
|
||
{
|
||
specValues.Add(fallbackSpec);
|
||
}
|
||
|
||
return string.Join(";", specValues);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 解析库存(兼容字符串/数字格式)
|
||
/// </summary>
|
||
private int ParseStockNum(JObject skuInfo)
|
||
{
|
||
var stockInfo = skuInfo["stock_info"] as JObject;
|
||
if (stockInfo == null) return 0;
|
||
|
||
var stockToken = stockInfo["stock_num"];
|
||
if (stockToken == null) return 0;
|
||
|
||
if (stockToken.Type == JTokenType.String)
|
||
{
|
||
return int.TryParse(stockToken.Value<string>(), out var result) ? result : 0;
|
||
}
|
||
|
||
return stockToken.Value<int>();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 构建规格列表
|
||
/// 优先从 product_sale_attrs 构建,回退从SKU的spec文本解析
|
||
/// </summary>
|
||
private void BuildSpecList(List<SpecItem> specList, List<SkuItem> skus)
|
||
{
|
||
var specDict = new Dictionary<string, HashSet<string>>();
|
||
|
||
// 优先从 product_sale_attrs 构建
|
||
if (_saleAttrs != null && _saleAttrs.Count > 0)
|
||
{
|
||
foreach (var attr in _saleAttrs)
|
||
{
|
||
var key = attr["key"]?.Value<string>() ?? string.Empty;
|
||
var items = attr["items"] as JArray;
|
||
if (items == null) continue;
|
||
|
||
foreach (var item in items)
|
||
{
|
||
// 兼容 value_v2 和 value
|
||
var value = item["value_v2"]?.Value<string>();
|
||
if (string.IsNullOrEmpty(value))
|
||
value = item["value"]?.Value<string>();
|
||
|
||
if (string.IsNullOrEmpty(value)) continue;
|
||
|
||
if (!specDict.ContainsKey(key))
|
||
specDict[key] = new HashSet<string>();
|
||
specDict[key].Add(value);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果 product_sale_attrs 不存在,从SKU的spec文本解析
|
||
if (specDict.Count == 0 && skus.Count > 0)
|
||
{
|
||
foreach (var sku in skus)
|
||
{
|
||
if (string.IsNullOrEmpty(sku.Spec)) continue;
|
||
|
||
foreach (var part in sku.Spec.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
|
||
{
|
||
var kv = part.Split(':');
|
||
if (kv.Length == 2)
|
||
{
|
||
var key = kv[0].Trim();
|
||
var value = kv[1].Trim();
|
||
if (!specDict.ContainsKey(key))
|
||
specDict[key] = new HashSet<string>();
|
||
specDict[key].Add(value);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 构建 SpecItem 列表
|
||
foreach (var spec in specDict)
|
||
{
|
||
foreach (var value in spec.Value)
|
||
{
|
||
specList.Add(new SpecItem
|
||
{
|
||
SpecKey = spec.Key,
|
||
SpecValue = value,
|
||
SpecKeyId = 0,
|
||
SpecValueId = 0
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取最低价格(元)
|
||
/// </summary>
|
||
private decimal GetMinPrice()
|
||
{
|
||
// 优先从 product_skus 获取
|
||
if (_productSkus != null && _productSkus.Count > 0)
|
||
{
|
||
var minPrice = decimal.MaxValue;
|
||
foreach (var sku in _productSkus)
|
||
{
|
||
var skuObj = sku as JObject;
|
||
if (skuObj == null) continue;
|
||
|
||
var skuInfo = skuObj["product_sku_info"] as JObject;
|
||
if (skuInfo == null) continue;
|
||
|
||
var price = skuInfo["sale_price"]?.Value<decimal>() ?? 0;
|
||
if (price > 0 && price < minPrice)
|
||
minPrice = price;
|
||
}
|
||
|
||
if (minPrice > 0 && minPrice != decimal.MaxValue)
|
||
return minPrice / 100;
|
||
}
|
||
|
||
// 回退 sku_list.min_price
|
||
var skuListInfo = _infoNode["sku_list"] as JObject;
|
||
if (skuListInfo != null)
|
||
{
|
||
var minPriceToken = skuListInfo["min_price"]?.Value<decimal>() ?? 0;
|
||
if (minPriceToken > 0)
|
||
return minPriceToken / 100;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 辅助方法
|
||
|
||
private string GetStringValue(JObject obj, string path)
|
||
{
|
||
return obj?.SelectToken(path)?.Value<string>() ?? string.Empty;
|
||
}
|
||
|
||
private long GetLongValue(JObject obj, string path)
|
||
{
|
||
var token = obj?.SelectToken(path);
|
||
if (token == null) return 0;
|
||
|
||
if (token.Type == JTokenType.String)
|
||
return long.TryParse(token.Value<string>(), out var result) ? result : 0;
|
||
|
||
return token.Value<long>();
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
} |