更新框架,增加云库搜索模块
This commit is contained in:
1 parent
8526e5e0d2
commit
f2caec6083
192 files changed
+3626
-804
No files matched your search
@@ -0,0 +1,58 @@
|
||||
using Store.Modules.CloudProduct.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Store.Modules.CloudProduct.ImportConverters
|
||||
{
|
||||
/// <summary>
|
||||
/// 导入商品转换工厂:按顺序尝试各转换器,返回标准商品模型。
|
||||
/// </summary>
|
||||
public class ConverterToGoodsFactory
|
||||
{
|
||||
private static readonly List<IGoodsConverter> _converters = new List<IGoodsConverter>
|
||||
{
|
||||
new ConverterV1(),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 根据本地 JSON 文件路径转换。
|
||||
/// </summary>
|
||||
public static ProductEntity? Convert(string filePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
throw new FileNotFoundException("找不到导入文件", filePath);
|
||||
|
||||
string content = File.ReadAllText(filePath);
|
||||
return ConvertContent(content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据 JSON 内容字符串转换。
|
||||
/// </summary>
|
||||
public static ProductEntity? ConvertContent(string content)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(content))
|
||||
throw new ArgumentNullException(nameof(content));
|
||||
|
||||
foreach (IGoodsConverter item in _converters)
|
||||
{
|
||||
item.ReadContent(content);
|
||||
if (item.IsMatch())
|
||||
{
|
||||
var goods = item.Convert();
|
||||
if (goods != null)
|
||||
return goods;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
|
||||
using Store.Modules.CloudProduct.Entities;
|
||||
using Store.Modules.CloudProduct.Enums;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace Store.Modules.CloudProduct.ImportConverters
|
||||
{
|
||||
/// <summary>
|
||||
/// PDD 前端商品详情数据转换器(数据源格式见 data.txt)
|
||||
/// </summary>
|
||||
internal class ConverterV1 : GoodsConverterBase
|
||||
{
|
||||
public override string ConverterName => "ConverterV1";
|
||||
|
||||
public override PlatformType Platform => PlatformType.PDD;
|
||||
|
||||
protected override bool IsMatchProvider()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (JsonNode.Parse(RawData) is not JsonObject root)
|
||||
return false;
|
||||
|
||||
// data.txt:顶层包含 goods.goods_id
|
||||
if (root["goods"] is JsonObject goodsObj && goodsObj["goods_id"] != null)
|
||||
return true;
|
||||
|
||||
// 也兼容顶层直接是商品对象的情况
|
||||
if (root["goods_id"] != null && (root["goods_name"] != null || root["title"] != null))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override ProductEntity ConverterProvider()
|
||||
{
|
||||
if (JsonNode.Parse(RawData) is not JsonObject root)
|
||||
throw new Exception("无法解析 PDD 商品数据");
|
||||
|
||||
var goodsNode = root["goods"] as JsonObject ?? root;
|
||||
var priceNode = root["price"] as JsonObject;
|
||||
|
||||
var goods = new ProductEntity
|
||||
{
|
||||
Platform = (byte)PlatformType.PDD,
|
||||
GoodsId = GetLong(goodsNode, "goods_id"),
|
||||
Title = GetString(goodsNode, "goods_name")
|
||||
?? GetString(goodsNode, "short_name")
|
||||
?? string.Empty,
|
||||
Description = GetString(goodsNode, "share_desc") ?? GetString(goodsNode, "short_name"),
|
||||
ThumbUrl = GetString(goodsNode, "hd_url") ?? GetString(goodsNode, "thumb_url") ?? GetString(goodsNode, "hd_thumb_url") ?? GetString(goodsNode, "image_url"),
|
||||
CatId1 = GetIntOrNull(goodsNode, "cat_id_1"),
|
||||
CatId2 = GetIntOrNull(goodsNode, "cat_id_2"),
|
||||
CatId3 = GetIntOrNull(goodsNode, "cat_id_3"),
|
||||
CatId4 = GetIntOrNull(goodsNode, "cat_id_4"),
|
||||
SoldQuantity = GetInt(goodsNode, "sold_quantity"),
|
||||
Status = 0,
|
||||
};
|
||||
|
||||
var price = GetPriceRange(priceNode, root);
|
||||
goods.MinPrice = price.Min;
|
||||
goods.MaxPrice = price.Max;
|
||||
|
||||
return goods;
|
||||
}
|
||||
|
||||
private static string? GetString(JsonObject? obj, string key)
|
||||
{
|
||||
if (obj == null || !obj.TryGetPropertyValue(key, out var node) || node == null)
|
||||
return null;
|
||||
|
||||
if (node is JsonValue value && value.TryGetValue<string>(out var str))
|
||||
return str;
|
||||
|
||||
return node.ToJsonString();
|
||||
}
|
||||
|
||||
private static long GetLong(JsonObject? obj, string key)
|
||||
{
|
||||
if (obj == null || !obj.TryGetPropertyValue(key, out var node) || node == null)
|
||||
return 0;
|
||||
|
||||
if (node is JsonValue value)
|
||||
{
|
||||
if (value.TryGetValue<long>(out var l)) return l;
|
||||
if (value.TryGetValue<decimal>(out var d)) return (long)d;
|
||||
if (long.TryParse(value.ToString(), out var l2)) return l2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int GetInt(JsonObject? obj, string key)
|
||||
{
|
||||
if (obj == null || !obj.TryGetPropertyValue(key, out var node) || node == null)
|
||||
return 0;
|
||||
|
||||
if (node is JsonValue value)
|
||||
{
|
||||
if (value.TryGetValue<int>(out var i)) return i;
|
||||
if (value.TryGetValue<long>(out var l)) return (int)l;
|
||||
if (int.TryParse(value.ToString(), out var i2)) return i2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int? GetIntOrNull(JsonObject? obj, string key)
|
||||
{
|
||||
if (obj == null || !obj.TryGetPropertyValue(key, out var node) || node == null)
|
||||
return null;
|
||||
|
||||
if (node is JsonValue value)
|
||||
{
|
||||
if (value.TryGetValue<int>(out var i) && i > 0) return i;
|
||||
if (value.TryGetValue<long>(out var l) && l > 0) return (int)l;
|
||||
if (int.TryParse(value.ToString(), out var i2) && i2 > 0) return i2;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static long? GetLongOrNull(JsonObject? obj, string key)
|
||||
{
|
||||
if (obj == null || !obj.TryGetPropertyValue(key, out var node) || node == null)
|
||||
return null;
|
||||
|
||||
if (node is JsonValue value)
|
||||
{
|
||||
if (value.TryGetValue<long>(out var d)) return d;
|
||||
if (value.TryGetValue<long>(out var l)) return l;
|
||||
if (long.TryParse(value.ToString(), out var d2)) return d2;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算价格区间。优先使用顶层 price 节点,其次遍历 sku。
|
||||
/// </summary>
|
||||
private static (long? Min, long? Max) GetPriceRange(JsonObject? priceNode, JsonObject root)
|
||||
{
|
||||
long? min = null;
|
||||
long? max = null;
|
||||
|
||||
if (priceNode != null)
|
||||
{
|
||||
min = GetLongOrNull(priceNode, "min_group_price");
|
||||
max = GetLongOrNull(priceNode, "max_group_price");
|
||||
if (min.HasValue || max.HasValue)
|
||||
return (min, max);
|
||||
}
|
||||
|
||||
if (root["sku"] is JsonArray skuArray && skuArray.Count > 0)
|
||||
{
|
||||
foreach (var item in skuArray.OfType<JsonObject>())
|
||||
{
|
||||
var price = GetLongOrNull(item, "group_price") ?? GetLongOrNull(item, "normal_price");
|
||||
if (price == null) continue;
|
||||
|
||||
min = min.HasValue ? Math.Min(min.Value, price.Value) : price;
|
||||
max = max.HasValue ? Math.Max(max.Value, price.Value) : price;
|
||||
}
|
||||
|
||||
if (min.HasValue || max.HasValue)
|
||||
return (min, max);
|
||||
}
|
||||
|
||||
return (null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
using Store.Modules.CloudProduct.Entities;
|
||||
using Store.Modules.CloudProduct.Enums;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Store.Modules.CloudProduct.ImportConverters
|
||||
{
|
||||
/// <summary>
|
||||
/// 导入商品数据转换器
|
||||
/// </summary>
|
||||
public interface IGoodsConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// 原始数据
|
||||
/// </summary>
|
||||
string RawData { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取转换器名称
|
||||
/// </summary>
|
||||
string ConverterName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 支持的平台类型
|
||||
/// </summary>
|
||||
PlatformType Platform { get; }
|
||||
/// <summary>
|
||||
/// 读取内容
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
void ReadContent(string content);
|
||||
/// <summary>
|
||||
/// 判断是否匹配当前转换器
|
||||
/// </summary>
|
||||
/// <returns>是否匹配</returns>
|
||||
bool IsMatch();
|
||||
|
||||
/// <summary>
|
||||
/// 转换为标准商品模型
|
||||
/// </summary>
|
||||
ProductEntity Convert();
|
||||
}
|
||||
|
||||
public abstract class GoodsConverterBase : IGoodsConverter
|
||||
{
|
||||
private bool _isMatch = false;
|
||||
public string RawData { get; private set; }
|
||||
public abstract string ConverterName { get; }
|
||||
public abstract PlatformType Platform { get; }
|
||||
|
||||
void IGoodsConverter.ReadContent(string filePath)
|
||||
{
|
||||
ReadContent(filePath);
|
||||
}
|
||||
protected virtual void ReadContent(string filePath)
|
||||
{
|
||||
RawData = filePath;
|
||||
}
|
||||
|
||||
|
||||
public virtual bool IsMatch()
|
||||
{
|
||||
_isMatch = true;
|
||||
return IsMatchProvider();
|
||||
}
|
||||
protected abstract bool IsMatchProvider();
|
||||
|
||||
/// <summary>
|
||||
/// 带校验的转换(基类统一实现)
|
||||
/// </summary>
|
||||
public virtual ProductEntity Convert()
|
||||
{
|
||||
if (_isMatch == false) throw new Exception("未校验数据");
|
||||
try
|
||||
{
|
||||
JsonNode.Parse(RawData);
|
||||
}
|
||||
catch { throw new Exception("数据解析失败,不是有效的JSON格式"); }
|
||||
|
||||
|
||||
var product = ConverterProvider();
|
||||
if (product == null)
|
||||
throw new Exception("转换失败");
|
||||
// 基础校验
|
||||
if (product.GoodsId == 0)
|
||||
throw new Exception("商品ID不能为空");
|
||||
|
||||
return product;
|
||||
}
|
||||
|
||||
protected abstract ProductEntity ConverterProvider();
|
||||
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user