更新框架,增加云库搜索模块

This commit is contained in:
chfei committed 2026-09-18 18:40:10 +08:00
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();
}
}