746 lines
21 KiB
C#
746 lines
21 KiB
C#
using AllBase;
|
||
using AllBase.Helper;
|
||
using AllBase.Model.Result;
|
||
using Newtonsoft.Json;
|
||
using Newtonsoft.Json.Linq;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using Work.Store.Api;
|
||
using System.Threading.Tasks;
|
||
using Work.Api.Wx;
|
||
|
||
namespace Work.Services
|
||
{
|
||
/// <summary>
|
||
/// 云商品数据服务
|
||
/// </summary>
|
||
public class CloudGoodsService
|
||
{
|
||
public static async Task<PageData<CloudGoods>> GetGoods(GoodsSearchRequest request)
|
||
{
|
||
return await StoreApiClient.Instance.CloudGoods.GetGoods(request);
|
||
}
|
||
public static string GetGoodsDetail(long goodsId)
|
||
{
|
||
return StoreApiClient.Instance.CloudGoods.GetGoodsDetail(goodsId);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 在后台解析微信处罚 JSON、匹配店铺商品编码并上报,异常在内部记录。
|
||
/// </summary>
|
||
public static void ReportViolationsFromJson(string json, ShopLoginInfo shop)
|
||
{
|
||
_ = Task.Run(async () =>
|
||
{
|
||
try
|
||
{
|
||
await ReportViolationsFromJsonCore(json, shop).ConfigureAwait(false);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
try
|
||
{
|
||
LogSave.AddFile("解析并上报违规商品失败:" + ex.Message);
|
||
}
|
||
catch
|
||
{
|
||
// 日志失败也不能影响调用方。
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
private static async Task<List<ReportGoodsViolationResult>> ReportViolationsFromJsonCore(string json, ShopLoginInfo shop)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(json))
|
||
{
|
||
throw new ArgumentException("处罚 JSON 文本不能为空。", nameof(json));
|
||
}
|
||
|
||
JObject root;
|
||
try
|
||
{
|
||
root = JObject.Parse(json);
|
||
}
|
||
catch (JsonException ex)
|
||
{
|
||
throw new ArgumentException("处罚 JSON 格式不正确。", nameof(json), ex);
|
||
}
|
||
|
||
var record = root["record"] as JObject;
|
||
if (root["code"]?.ToString() != "0" || record?["code"]?.ToString() != "0")
|
||
{
|
||
throw new ArgumentException("处罚数据的 code 或 record.code 不为 0,或缺少 record。", nameof(json));
|
||
}
|
||
if (!(record["items"] is JArray records))
|
||
{
|
||
throw new ArgumentException("处罚数据缺少 record.items 数组。", nameof(json));
|
||
}
|
||
|
||
if (records.Count == 0)
|
||
return new List<ReportGoodsViolationResult>();
|
||
var items = new List<ReportGoodsViolationItem>();
|
||
foreach (var token in records)
|
||
{
|
||
if (!(token is JObject punishment))
|
||
{
|
||
throw new ArgumentException("record.items 中的处罚记录必须是对象。", nameof(json));
|
||
}
|
||
var objects = punishment["punishObject"];
|
||
if (objects == null || objects.Type == JTokenType.Null)
|
||
{
|
||
continue;
|
||
}
|
||
if (!(objects is JArray products))
|
||
{
|
||
throw new ArgumentException("punishObject 必须是数组。", nameof(json));
|
||
}
|
||
foreach (var product in products)
|
||
{
|
||
if (!(product is JObject target))
|
||
{
|
||
throw new ArgumentException("punishObject 中的处罚对象必须是对象。", nameof(json));
|
||
}
|
||
var id = target["productId"];
|
||
// 订单等非商品处罚对象不包含 productId。
|
||
if (id == null)
|
||
{
|
||
continue;
|
||
}
|
||
if ((id.Type != JTokenType.String && id.Type != JTokenType.Integer)
|
||
|| !long.TryParse(id.ToString(), out var goodsId) || goodsId <= 0)
|
||
{
|
||
throw new ArgumentException("商品 productId 必须是有效的正整数编号。", nameof(json));
|
||
}
|
||
var reason = punishment["punishReason"];
|
||
if (reason?.Type != JTokenType.String || string.IsNullOrWhiteSpace((string)reason))
|
||
{
|
||
throw new ArgumentException($"商品 {goodsId} 缺少有效的 punishReason。", nameof(json));
|
||
}
|
||
items.Add(new ReportGoodsViolationItem { GoodsId = goodsId, Reasons = ((string)reason).Trim() });
|
||
}
|
||
}
|
||
|
||
if (items.Count == 0)
|
||
{
|
||
return new List<ReportGoodsViolationResult>();
|
||
}
|
||
var productCodes = GetWxProductCodes(shop, items.Select(item => item.GoodsId));
|
||
var mappedItems = new List<ReportGoodsViolationItem>();
|
||
foreach (var item in items)
|
||
{
|
||
if (!productCodes.TryGetValue(item.GoodsId, out var code) || string.IsNullOrWhiteSpace(code))
|
||
{
|
||
LogSave.AddFile($"跳过违规商品 {item.GoodsId}:未找到对应的 spuCode。");
|
||
continue;
|
||
}
|
||
if (!TryGetGoodsId(code, out var goodsId))
|
||
{
|
||
LogSave.AddFile($"跳过违规商品 {item.GoodsId}:spuCode 无法转换为有效商品编号。");
|
||
continue;
|
||
}
|
||
mappedItems.Add(new ReportGoodsViolationItem { GoodsId = goodsId, Reasons = item.Reasons });
|
||
}
|
||
var reports = mappedItems.GroupBy(item => new { item.GoodsId, item.Reasons }).Select(group => group.First()).ToList();
|
||
|
||
var results = new List<ReportGoodsViolationResult>();
|
||
for (int offset = 0; offset < reports.Count && GlobalConfig.GlobalSettings.AutoReportViolationProducts; offset += 1000)
|
||
{
|
||
var batch = reports.Skip(offset).Take(1000).ToList();
|
||
results.AddRange(await ReportViolations(batch));
|
||
await RecordReportLogs(shop.ShopId, batch);
|
||
}
|
||
return results;
|
||
}
|
||
|
||
private static async Task RecordReportLogs(string shopId, List<ReportGoodsViolationItem> batch)
|
||
{
|
||
if (!GlobalConfig.GlobalSettings.AutoReportViolationProducts) return;
|
||
Dictionary<string, int> reasonIds;
|
||
try
|
||
{
|
||
// 上报成功后读取字典,才能取得本批新增违规原因的编号。
|
||
reasonIds = (await GetViolationReasons()).ToDictionary(reason => reason.Name, reason => reason.Id);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogSave.AddFile($"店铺 {shopId} 已上报违规商品,读取日志原因字典失败:{ex.Message}");
|
||
return;
|
||
}
|
||
var logs = new List<RecordViolationLogRequest>(batch.Count);
|
||
foreach (var item in batch)
|
||
{
|
||
if (!reasonIds.TryGetValue(item.Reasons, out var reasonId))
|
||
{
|
||
LogSave.AddFile($"店铺 {shopId} 商品 {item.GoodsId} 找不到违规原因编号:{item.Reasons}");
|
||
continue;
|
||
}
|
||
logs.Add(new RecordViolationLogRequest
|
||
{
|
||
ShopId = shopId, GoodsId = item.GoodsId, ReasonId = reasonId, Action = ViolationLogAction.Report
|
||
});
|
||
}
|
||
await TryRecordLogs(logs);
|
||
}
|
||
|
||
private static bool TryGetGoodsId(string code, out long goodsId)
|
||
{
|
||
code = code?.Trim() ?? string.Empty;
|
||
if (code.StartsWith("P1-", StringComparison.Ordinal))
|
||
{
|
||
code = code.Substring(3);
|
||
}
|
||
else if (code.StartsWith("EC1", StringComparison.Ordinal) && code.EndsWith("A", StringComparison.Ordinal))
|
||
{
|
||
code = code.Substring(3, code.Length - 4);
|
||
}
|
||
return long.TryParse(code, out goodsId) && goodsId > 0;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 后台检查当前店铺所有在售商品,云端确认违规后立即下架;异常仅记录日志。
|
||
/// </summary>
|
||
public static void CheckAndOffShelfViolations(ShopLoginInfo shop)
|
||
{
|
||
_ = Task.Run(async () =>
|
||
{
|
||
try
|
||
{
|
||
await CheckAndOffShelfViolationsCore(shop).ConfigureAwait(false);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
try
|
||
{
|
||
LogSave.AddFile($"店铺 {shop?.ShopId} 在售商品违规检查失败:{ex.Message}");
|
||
}
|
||
catch { }
|
||
}
|
||
});
|
||
}
|
||
|
||
private static async Task CheckAndOffShelfViolationsCore(ShopLoginInfo shop)
|
||
{
|
||
// 先读取完整列表,避免下架改变分页位置而漏查后面的商品。
|
||
var products = GetWxProductCodes(shop, status: 5);
|
||
var mappings = new Dictionary<long, long>();
|
||
foreach (var product in products)
|
||
{
|
||
if (TryGetGoodsId(product.Value, out var goodsId))
|
||
{
|
||
mappings.Add(product.Key, goodsId);
|
||
}
|
||
else
|
||
{
|
||
LogSave.AddFile($"店铺 {shop.ShopId} 跳过商品 {product.Key}:spuCode 无法转换为有效商品编号。");
|
||
}
|
||
}
|
||
var goodsIds = mappings.Values.Distinct().ToList();
|
||
for (var offset = 0; offset < goodsIds.Count; offset += 50)
|
||
{
|
||
var batch = goodsIds.Skip(offset).Take(50).ToList();
|
||
var statuses = GetViolationStatus(batch);
|
||
var violations = statuses.Where(item => item != null && item.IsViolation && batch.Contains(item.GoodsId))
|
||
.ToDictionary(item => item.GoodsId);
|
||
var productIds = mappings.Where(item => violations.ContainsKey(item.Value)).Select(item => item.Key).ToList();
|
||
// 同一云商品可能对应多个微信商品,每次下架最多 50 个。
|
||
for (var index = 0; index < productIds.Count; index += 50)
|
||
{
|
||
if (!GlobalConfig.GlobalSettings.AutoDelistViolationProducts)
|
||
return;
|
||
var page = productIds.Skip(index).Take(50).ToArray();
|
||
var ids = string.Join(",", page);
|
||
var response = JObject.Parse(WxProductApi.DownProd(shop, ids));
|
||
if (response["code"]?.ToString() != "0")
|
||
{
|
||
throw new Exception($"微信商品 {ids} 下架失败:{response}");
|
||
}
|
||
LogSave.AddFile($"店铺 {shop.ShopId} 云端检测违规,已下架微信商品:{ids}");
|
||
var downed = page.Select(id => mappings[id]).Distinct().Select(id => violations[id]);
|
||
await RecordShopActionLogs(shop.ShopId, downed, ViolationLogAction.AutoOffShelf);
|
||
}
|
||
}
|
||
}
|
||
|
||
private static Dictionary<long, string> GetWxProductCodes(ShopLoginInfo shop, IEnumerable<long> productIds = null, int status = -1)
|
||
{
|
||
if (shop == null)
|
||
{
|
||
throw new ArgumentNullException(nameof(shop));
|
||
}
|
||
var codes = new Dictionary<long, string>();
|
||
var prodIds = productIds == null ? null : string.Join(",", productIds.Distinct());
|
||
long received = 0;
|
||
for (var page = 1; ; page++)
|
||
{
|
||
var response = JObject.Parse(WxProductApi.SearchProduct(shop, page, pageSize: 50, prodIds: prodIds, status: status));
|
||
if (response["ret"]?.ToString() != "0" || response["success"]?.Type != JTokenType.Boolean
|
||
|| !(bool)response["success"] || !(response["productList"] is JArray products)
|
||
|| !long.TryParse(response["totalNum"]?.ToString(), out var total) || total < 0)
|
||
{
|
||
throw new Exception($"获取微信商品列表第 {page} 页失败。");
|
||
}
|
||
var previousCount = codes.Count;
|
||
foreach (var product in products)
|
||
{
|
||
if (!(product is JObject item) || !long.TryParse(item["productId"]?.ToString(), out var id) || id <= 0)
|
||
{
|
||
throw new Exception("微信商品列表包含无效的 productId。");
|
||
}
|
||
codes[id] = item["spuCode"]?.Type == JTokenType.String ? (string)item["spuCode"] : null;
|
||
}
|
||
received += products.Count;
|
||
if (received >= total)
|
||
{
|
||
return codes;
|
||
}
|
||
if (codes.Count == previousCount)
|
||
{
|
||
throw new Exception("微信商品列表分页未返回新商品,无法获取全部搜索结果。");
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 批量上报违规商品。
|
||
/// </summary>
|
||
public static async Task<List<ReportGoodsViolationResult>> ReportViolations(List<ReportGoodsViolationItem> items)
|
||
{
|
||
if (items == null)
|
||
{
|
||
throw new ArgumentNullException(nameof(items));
|
||
}
|
||
if (items.Any(item => item == null))
|
||
{
|
||
throw new ArgumentException("违规商品列表不能包含空项。", nameof(items));
|
||
}
|
||
|
||
return await StoreApiClient.Instance.CloudGoods.ReportViolations(items);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 批量查询商品违规状态。
|
||
/// </summary>
|
||
public static List<GoodsViolationStatus> GetViolationStatus(List<long> goodsIds)
|
||
{
|
||
if (goodsIds == null)
|
||
{
|
||
throw new ArgumentNullException(nameof(goodsIds));
|
||
}
|
||
|
||
return StoreApiClient.Instance.CloudGoods.GetViolationStatus(goodsIds);
|
||
}
|
||
|
||
/// <summary>逐原因记录店铺动作。日志请求失败不能改变剔除决定或中断后续下架。</summary>
|
||
public static Task RecordShopActionLogs(string shopId, GoodsViolationStatus status, ViolationLogAction action)
|
||
=> RecordShopActionLogs(shopId, new[] { status }, action);
|
||
|
||
private static async Task RecordShopActionLogs(string shopId, IEnumerable<GoodsViolationStatus> statuses, ViolationLogAction action)
|
||
{
|
||
if (!(action == ViolationLogAction.AutoListingExcluded && GlobalConfig.GlobalSettings.AutoExcludeViolationProductsOnUpload
|
||
|| action == ViolationLogAction.AutoOffShelf && GlobalConfig.GlobalSettings.AutoDelistViolationProducts))
|
||
return;
|
||
var logs = new List<RecordViolationLogRequest>();
|
||
foreach (var status in statuses)
|
||
{
|
||
if (status.ReasonIds == null || status.ReasonIds.Length == 0)
|
||
{
|
||
LogSave.AddFile($"店铺 {shopId} 商品 {status.GoodsId} 缺少违规原因编号,无法上传动作 {action} 日志。");
|
||
continue;
|
||
}
|
||
logs.AddRange(status.ReasonIds.Select(id => new RecordViolationLogRequest
|
||
{
|
||
ShopId = shopId, GoodsId = status.GoodsId, ReasonId = id, Action = action
|
||
}));
|
||
}
|
||
await TryRecordLogs(logs).ConfigureAwait(false);
|
||
}
|
||
|
||
private static async Task TryRecordLogs(List<RecordViolationLogRequest> logs)
|
||
{
|
||
if (logs.Count == 0) return;
|
||
try
|
||
{
|
||
await RecordViolationLogs(logs).ConfigureAwait(false);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogSave.AddFile($"店铺 {logs[0].ShopId} 动作 {logs[0].Action} 的 {logs.Count} 条日志上传失败:{ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>记录店铺已完成的违规上报、上货剔除或下架动作。</summary>
|
||
public static Task RecordViolationLog(RecordViolationLogRequest request)
|
||
{
|
||
if (request == null)
|
||
throw new ArgumentNullException(nameof(request));
|
||
return RecordViolationLogs(new[] { request });
|
||
}
|
||
|
||
public static async Task RecordViolationLogs(IReadOnlyList<RecordViolationLogRequest> requests)
|
||
{
|
||
if (requests == null || requests.Count == 0 || requests.Any(item => item == null))
|
||
throw new ArgumentException("请提供有效的违规日志。", nameof(requests));
|
||
await StoreApiClient.Instance.CloudGoods.RecordViolationLogs(requests);
|
||
}
|
||
|
||
/// <summary>读取日志筛选和显示使用的违规原因字典。</summary>
|
||
public static async Task<List<ViolationReason>> GetViolationReasons()
|
||
{
|
||
return await StoreApiClient.Instance.CloudGoods.GetViolationReasons();
|
||
}
|
||
|
||
public class ViolationReason
|
||
{
|
||
public int Id { get; set; }
|
||
public string Name { get; set; }
|
||
public bool IsViolation { get; set; }
|
||
}
|
||
|
||
/// <summary>按日志编号倒序查询,下一页传入上一页最后一条日志的 Id。</summary>
|
||
public static async Task<List<GoodsViolationLogEntity>> GetViolationLogs(string[] shopIds, long? goodsId = null,
|
||
long beforeId = 0, int size = 50)
|
||
{
|
||
if (shopIds == null || shopIds.Length == 0 || shopIds.Any(string.IsNullOrWhiteSpace))
|
||
throw new ArgumentException("请提供有效的店铺编号。", nameof(shopIds));
|
||
var request = new GetViolationLogsRequest { ShopIds = shopIds, GoodsId = goodsId, BeforeId = beforeId, Size = size };
|
||
return await StoreApiClient.Instance.CloudGoods.GetViolationLogs(request);
|
||
}
|
||
|
||
#region user
|
||
/// <summary>
|
||
/// 搜索收藏
|
||
/// </summary>
|
||
/// <param name="request"></param>
|
||
/// <returns></returns>
|
||
/// <exception cref="Exception"></exception>
|
||
public static async Task<PageData<CloudGoods>> SearchFavorites(UserFavoritesGoodsSearchRequest request)
|
||
{
|
||
request.UserName = GetUserName();
|
||
var page = await StoreApiClient.Instance.CloudGoods.SearchFavorites(request);
|
||
foreach (var item in page.List)
|
||
{
|
||
item.DetectionResult = item.IsSafe == null ? "未检测" : item.IsSafe.Value ? "安全" : "违规";
|
||
}
|
||
|
||
return page;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 收藏
|
||
/// </summary>
|
||
/// <param name="goodsIds"></param>
|
||
/// <returns></returns>
|
||
/// <exception cref="Exception"></exception>
|
||
|
||
public static async Task<int> Favorite(List<long> goodsIds)
|
||
{
|
||
return await StoreApiClient.Instance.CloudGoods.Favorite(GetUserName(), goodsIds);
|
||
}
|
||
/// <summary>
|
||
/// 取消收藏
|
||
/// </summary>
|
||
/// <param name="goodsIds"></param>
|
||
/// <returns></returns>
|
||
/// <exception cref="Exception"></exception>
|
||
|
||
public static async Task<int> RemoveFavorites(List<long> goodsIds)
|
||
{
|
||
return await StoreApiClient.Instance.CloudGoods.RemoveFavorites(GetUserName(), goodsIds);
|
||
}
|
||
#endregion
|
||
|
||
public static string GetUserName()
|
||
{
|
||
return string.IsNullOrEmpty(BaseClass.ParentUserName) ? BaseClass.UserName : BaseClass.ParentUserName;
|
||
}
|
||
}
|
||
public enum ViolationLogAction
|
||
{
|
||
Report = 1,
|
||
AutoListingExcluded = 2,
|
||
AutoOffShelf = 3
|
||
}
|
||
|
||
public sealed class RecordViolationLogRequest
|
||
{
|
||
public string ShopId { get; set; } = string.Empty;
|
||
public long GoodsId { get; set; }
|
||
public int ReasonId { get; set; }
|
||
public ViolationLogAction Action { get; set; }
|
||
}
|
||
|
||
public sealed class GetViolationLogsRequest
|
||
{
|
||
public string[] ShopIds { get; set; } = Array.Empty<string>();
|
||
public long? GoodsId { get; set; }
|
||
public long BeforeId { get; set; }
|
||
public int Size { get; set; } = 50;
|
||
}
|
||
|
||
public class GoodsViolationLogEntity
|
||
{
|
||
public long Id { get; set; }
|
||
public string? ShopId { get; set; }
|
||
public long GoodsId { get; set; }
|
||
public int ReasonId { get; set; }
|
||
public ViolationLogAction Action { get; set; }
|
||
public DateTime CreatedTime { get; set; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 违规商品上报项。
|
||
/// </summary>
|
||
public class ReportGoodsViolationItem
|
||
{
|
||
[JsonProperty("goodsId")]
|
||
public long GoodsId { get; set; }
|
||
|
||
[JsonProperty("reasons")]
|
||
public string Reasons { get; set; } = string.Empty;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 单个商品的违规上报结果。
|
||
/// </summary>
|
||
public class ReportGoodsViolationResult
|
||
{
|
||
[JsonProperty("goodsId")]
|
||
public long GoodsId { get; set; }
|
||
|
||
[JsonProperty("isOffShelf")]
|
||
public bool IsOffShelf { get; set; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 商品违规状态。
|
||
/// </summary>
|
||
public class GoodsViolationStatus
|
||
{
|
||
[JsonProperty("reasonIds")]
|
||
public int[] ReasonIds { get; set; }
|
||
|
||
[JsonProperty("goodsId")]
|
||
public long GoodsId { get; set; }
|
||
|
||
[JsonProperty("isViolation")]
|
||
public bool IsViolation { get; set; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 商品搜索请求
|
||
/// </summary>
|
||
public class GoodsSearchRequest
|
||
{
|
||
/// <summary>
|
||
/// 标题(模糊搜索)
|
||
/// </summary>
|
||
public string Title { get; set; } = string.Empty;
|
||
|
||
/// <summary>
|
||
/// 最低价格(元)
|
||
/// </summary>
|
||
public long? MinPrice { get; set; }
|
||
|
||
/// <summary>
|
||
/// 最高价格(元)
|
||
/// </summary>
|
||
public long? MaxPrice { get; set; }
|
||
|
||
/// <summary>
|
||
/// 一级类目ID
|
||
/// </summary>
|
||
public int? CatId1 { get; set; }
|
||
|
||
/// <summary>
|
||
/// 二级类目ID
|
||
/// </summary>
|
||
public int? CatId2 { get; set; }
|
||
|
||
/// <summary>
|
||
/// 三级类目ID
|
||
/// </summary>
|
||
public int? CatId3 { get; set; }
|
||
|
||
/// <summary>
|
||
/// 四级类目ID
|
||
/// </summary>
|
||
public int? CatId4 { get; set; }
|
||
|
||
/// <summary>
|
||
/// 最低销量
|
||
/// </summary>
|
||
public int? MinSoldQuantity { get; set; }
|
||
|
||
/// <summary>
|
||
/// 最高销量
|
||
/// </summary>
|
||
public int? MaxSoldQuantity { get; set; }
|
||
|
||
/// <summary>
|
||
/// 平台类型
|
||
/// </summary>
|
||
public PlatformType Platform { get; set; }
|
||
/// <summary>安全状态:0 全部(默认),1 安全,2 违规,3 未检测。</summary>
|
||
public int SafeStatus { get; set; }
|
||
|
||
/// <summary>
|
||
/// 页码(默认1)
|
||
/// </summary>
|
||
public int PageIndex { get; set; } = 1;
|
||
|
||
/// <summary>
|
||
/// 每页数量(默认20)
|
||
/// </summary>
|
||
public int PageSize { get; set; } = 20;
|
||
|
||
}
|
||
public class UserFavoritesGoodsSearchRequest : GoodsSearchRequest
|
||
{
|
||
public string UserName { get; set; }
|
||
}
|
||
/// <summary>
|
||
/// 商品搜索返回结果
|
||
/// </summary>
|
||
public class GoodsSearchResponse
|
||
{
|
||
/// <summary>
|
||
/// 商品列表
|
||
/// </summary>
|
||
[JsonProperty("list")]
|
||
public List<CloudGoods> List { get; set; } = new List<CloudGoods>();
|
||
|
||
/// <summary>
|
||
/// 总记录数
|
||
/// </summary>
|
||
[JsonProperty("total")]
|
||
public int Total { get; set; }
|
||
|
||
/// <summary>
|
||
/// 当前页码
|
||
/// </summary>
|
||
[JsonProperty("pageIndex")]
|
||
public int PageIndex { get; set; }
|
||
|
||
/// <summary>
|
||
/// 每页数量
|
||
/// </summary>
|
||
[JsonProperty("pageSize")]
|
||
public int PageSize { get; set; }
|
||
|
||
/// <summary>
|
||
/// 总页数
|
||
/// </summary>
|
||
[JsonProperty("totalPages")]
|
||
public int TotalPages { get; set; }
|
||
}
|
||
public class CloudGoods : BindableBase
|
||
{
|
||
|
||
public bool IsSelected
|
||
{
|
||
get => GetValue<bool>();
|
||
set => SetValue(value);
|
||
}
|
||
/// <summary>
|
||
/// 自增ID
|
||
/// </summary>
|
||
public long Id { get; set; }
|
||
|
||
/// <summary>
|
||
/// 平台类型
|
||
/// </summary>
|
||
public byte Platform { get; set; }
|
||
|
||
/// <summary>
|
||
/// 商品编号
|
||
/// </summary>
|
||
public long GoodsId { get; set; }
|
||
|
||
/// <summary>
|
||
/// 标题
|
||
/// </summary>
|
||
public string Title { get; set; } = string.Empty;
|
||
|
||
/// <summary>
|
||
/// 描述
|
||
/// </summary>
|
||
public string? Description { get; set; }
|
||
|
||
/// <summary>
|
||
/// 缩略图
|
||
/// </summary>
|
||
public string? ThumbUrl { get; set; }
|
||
|
||
/// <summary>
|
||
/// 一级类目ID
|
||
/// </summary>
|
||
public int? CatId1 { get; set; }
|
||
|
||
/// <summary>
|
||
/// 二级类目ID
|
||
/// </summary>
|
||
public int? CatId2 { get; set; }
|
||
|
||
/// <summary>
|
||
/// 三级类目ID
|
||
/// </summary>
|
||
public int? CatId3 { get; set; }
|
||
|
||
/// <summary>
|
||
/// 四级类目ID
|
||
/// </summary>
|
||
public int? CatId4 { get; set; }
|
||
|
||
/// <summary>
|
||
/// 最低价快照(元),导入时计算
|
||
/// </summary>
|
||
public decimal MinPrice { get; set; }
|
||
|
||
/// <summary>
|
||
/// 最高价快照(元),导入时计算
|
||
/// </summary>
|
||
public decimal MaxPrice { get; set; }
|
||
public string Price => $"{MinPrice / 100m:N2}";
|
||
|
||
/// <summary>
|
||
/// 销量
|
||
/// </summary>
|
||
public int SoldQuantity { get; set; }
|
||
|
||
/// <summary>
|
||
/// 状态:0正常 1已删除
|
||
/// </summary>
|
||
public byte Status { get; set; }
|
||
|
||
/// <summary>
|
||
/// 创建时间
|
||
/// </summary>
|
||
public DateTime CreatedTime { get; set; }
|
||
|
||
/// <summary>
|
||
/// 更新时间(自动更新)
|
||
/// </summary>
|
||
public DateTime UpdatedTime { get; set; }
|
||
|
||
/// <summary>
|
||
/// 索引同步状态:0未同步 1已同步
|
||
/// </summary>
|
||
public byte IndexStatus { get; set; }
|
||
|
||
/// <summary>
|
||
/// 安全检测
|
||
/// </summary>
|
||
public bool? IsSafe { get; set; }
|
||
|
||
|
||
[JsonIgnore]
|
||
public string? DetectionResult
|
||
{
|
||
get => GetValue<string>();
|
||
set => SetValue(value);
|
||
}
|
||
}
|
||
}
|