From ce8a36fa0a908915ddc4bf8aba92473d8624ede5 Mon Sep 17 00:00:00 2001 From: shanji <18996038927@163.com> Date: Mon, 15 May 2023 14:32:01 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=9F=A5=E8=AF=A2=E9=87=87?= =?UTF-8?q?=E8=B4=AD=E6=96=B9=E6=A1=88=E4=B8=AD=E9=87=87=E8=B4=ADSku?= =?UTF-8?q?=E7=9A=84=E5=9F=BA=E6=9C=AC=E4=BF=A1=E6=81=AF=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 4 + BBWYB.Client/BBWYB.Client.csproj | 4 + BBWYB.Common/Extensions/CopyExtensions.cs | 12 + .../Controllers/PurchaseSchemeController.cs | 11 + BBWYB.Server.API/Program.cs | 4 + .../BBWYB.Server.Business.csproj | 1 + .../PurchaseProductAPIService.cs | 285 ++++++++++++++++++ .../PurchaseScheme/PurchaseSchemeBusiness.cs | 17 +- .../PurcasheSkuBasicInfoRequest.cs | 16 + .../PurchaseProductBasicInfoResponse.cs | 53 ++++ BBWYB.Server.Model/Enums.cs | 9 + bbwyb.sln | 5 + 12 files changed, 420 insertions(+), 1 deletion(-) create mode 100644 .editorconfig create mode 100644 BBWYB.Common/Extensions/CopyExtensions.cs create mode 100644 BBWYB.Server.Business/PurchaseScheme/PurchaseProductAPIService.cs create mode 100644 BBWYB.Server.Model/Dto/Request/PurchaseScheme/PurcasheSkuBasicInfoRequest.cs create mode 100644 BBWYB.Server.Model/Dto/Response/PurchaseScheme/PurchaseProductBasicInfoResponse.cs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3eb20a9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +[*.cs] + +# CS8603: 可能返回 null 引用。 +dotnet_diagnostic.CS8603.severity = none diff --git a/BBWYB.Client/BBWYB.Client.csproj b/BBWYB.Client/BBWYB.Client.csproj index 2131c0a..1cbe17b 100644 --- a/BBWYB.Client/BBWYB.Client.csproj +++ b/BBWYB.Client/BBWYB.Client.csproj @@ -21,6 +21,10 @@ + + + + diff --git a/BBWYB.Common/Extensions/CopyExtensions.cs b/BBWYB.Common/Extensions/CopyExtensions.cs new file mode 100644 index 0000000..2066f23 --- /dev/null +++ b/BBWYB.Common/Extensions/CopyExtensions.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace BBWYB.Common.Extensions +{ + public static class CopyExtensions + { + public static T Copy(this T p) + { + return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(p)); + } + } +} diff --git a/BBWYB.Server.API/Controllers/PurchaseSchemeController.cs b/BBWYB.Server.API/Controllers/PurchaseSchemeController.cs index 186b6b8..3d3b984 100644 --- a/BBWYB.Server.API/Controllers/PurchaseSchemeController.cs +++ b/BBWYB.Server.API/Controllers/PurchaseSchemeController.cs @@ -68,5 +68,16 @@ namespace BBWYB.Server.API.Controllers { return purchaseSchemeBusiness.GetSharePurchaser(querySchemeRequest); } + + /// + /// 查询采购方案中采购Sku的基本信息 + /// + /// + /// + [HttpPost] + public PurchaseSkuBasicInfoResponse GetPurchaseSkuBasicInfo([FromBody] PurchaseSkuBasicInfoRequest request) + { + return purchaseSchemeBusiness.GetPurchaseSkuBasicInfo(request); + } } } diff --git a/BBWYB.Server.API/Program.cs b/BBWYB.Server.API/Program.cs index f2f2e90..1da4090 100644 --- a/BBWYB.Server.API/Program.cs +++ b/BBWYB.Server.API/Program.cs @@ -45,6 +45,10 @@ services.AddMemoryCache(); services.AddControllers(); services.AddHttpContextAccessor(); services.AddHttpClient(); +services.AddHttpClient("gzip").ConfigurePrimaryHttpMessageHandler(handler => new HttpClientHandler() +{ + AutomaticDecompression = System.Net.DecompressionMethods.GZip +}); services.AddCors(options => { options.AddPolicy("cors", p => diff --git a/BBWYB.Server.Business/BBWYB.Server.Business.csproj b/BBWYB.Server.Business/BBWYB.Server.Business.csproj index 2a079df..65902f9 100644 --- a/BBWYB.Server.Business/BBWYB.Server.Business.csproj +++ b/BBWYB.Server.Business/BBWYB.Server.Business.csproj @@ -10,6 +10,7 @@ + diff --git a/BBWYB.Server.Business/PurchaseScheme/PurchaseProductAPIService.cs b/BBWYB.Server.Business/PurchaseScheme/PurchaseProductAPIService.cs new file mode 100644 index 0000000..4cf950c --- /dev/null +++ b/BBWYB.Server.Business/PurchaseScheme/PurchaseProductAPIService.cs @@ -0,0 +1,285 @@ +using BBWYB.Common.Extensions; +using BBWYB.Common.Http; +using BBWYB.Common.Models; +using BBWYB.Server.Model; +using BBWYB.Server.Model.Db; +using BBWYB.Server.Model.Dto; +using Microsoft.Extensions.Caching.Memory; +using Newtonsoft.Json.Linq; +using System.Text.RegularExpressions; + +namespace BBWYB.Server.Business +{ + public class PurchaseProductAPIService : IDenpendency + { + private RestApiService restApiService; + private IMemoryCache memoryCache; + private string oneBoundKey = "t5060712539"; + private string oneBoundSecret = "20211103"; + + //private string qtAppId = "BBWY2023022001"; + //private string qtAppSecret = "908e131365d5448ca651ba20ed7ddefe"; + + private TimeSpan purchaseProductCacheTimeSpan; + //private TimeSpan _1688SessionIdTimeSpan; + + //private ConcurrentDictionary purchaseSchemeProductSkus)> productChaches; + + private IDictionary _1688ProductDetailRequestHeader; + + public PurchaseProductAPIService(RestApiService restApiService, IMemoryCache memoryCache) + { + this.restApiService = restApiService; + _1688ProductDetailRequestHeader = new Dictionary() + { + { "Host","detail.1688.com"}, + { "User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.102 Safari/537.36 Edg/104.0.1293.70"}, + { "Accept","text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"}, + { "Accept-Encoding","gzip, deflate, br"}, + { "Accept-Language","zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6"} + }; + purchaseProductCacheTimeSpan = TimeSpan.FromDays(1); + } + + public PurchaseSkuBasicInfoResponse GetProductInfo(PurchaseSkuBasicInfoRequest request) + { + var cacheKey = $"{request.PurchaseProductId}_{request.PriceMode}"; + if (memoryCache.TryGetValue(cacheKey, out var tuple)) + return tuple.Copy(); + + PurchaseSkuBasicInfoResponse response = null; + + if (request.FirstApiMode == Enums.PurchaseProductAPIMode.Spider) + { + response = LoadFromSpider(request); + if (response == null) + response = LoadFromOneBound(request); + } + else if (request.FirstApiMode == Enums.PurchaseProductAPIMode.OneBound) + { + response = LoadFromOneBound(request); + if (response == null) + response = LoadFromSpider(request); + } + + if (response != null) + { + try + { + memoryCache.Set(cacheKey, response, purchaseProductCacheTimeSpan); + } + catch { } + } + + return response?.Copy(); + } + + private PurchaseSkuBasicInfoResponse LoadFromOneBound(PurchaseSkuBasicInfoRequest request) + { + try + { + string platformStr = string.Empty; + if (request.Platform == Enums.Platform.阿里巴巴) + platformStr = "1688"; + + if (string.IsNullOrEmpty(platformStr)) + return null; + + var result = restApiService.SendRequest("https://api-gw.onebound.cn/", $"{platformStr}/item_get", $"key={oneBoundKey}&secret={oneBoundSecret}&num_iid={request.PurchaseProductId}&lang=zh-CN&cache=no&agent={(request.PriceMode == Enums.PurchaseOrderMode.批发 ? 0 : 1)}", null, HttpMethod.Get, paramPosition: ParamPosition.Query, enableRandomTimeStamp: true); + if (result.StatusCode != System.Net.HttpStatusCode.OK) + throw new Exception($"{result.StatusCode} {result.Content}"); + + var jobject = JObject.Parse(result.Content); + var isOK = jobject.Value("error_code") == "0000"; + if (isOK) + { + var skuJArray = (JArray)jobject["item"]["skus"]["sku"]; + if (skuJArray.Count == 0) + { + //errorMsg = $"商品{purchaseSchemeProduct.PurchaseProductId}缺少sku信息"; + return null; + } + + var list = skuJArray.Select(j => new PurchaseSkuItemBasicInfoResponse() + { + PurchaseProductId = request.PurchaseProductId, + Price = j.Value("price"), + PurchaseSkuId = j.Value("sku_id"), + PurchaseSkuSpecId = j.Value("spec_id"), + Title = j.Value("properties_name"), + Logo = GetOneBoundSkuLogo(j, (JArray)jobject["item"]["prop_imgs"]["prop_img"]) + }).ToList(); + + var purchaserId = jobject["item"]["seller_info"].Value("user_num_id"); + var purchaserName = jobject["item"]["seller_info"].Value("title"); + if (string.IsNullOrEmpty(purchaserName)) + purchaserName = jobject["item"]["seller_info"].Value("shop_name"); + var purchaserLocation = jobject["item"].Value("location"); + + return new PurchaseSkuBasicInfoResponse() + { + Purchaser = new Model.Db.Purchaser() + { + Id = purchaserId, + Location = purchaserLocation, + Name = purchaserName, + Platform = request.Platform + }, + ItemList = list + }; + } + } + catch { } + { + return null; + } + } + + private string GetOneBoundSkuLogo(JToken skuJToken, JArray prop_img) + { + if (!prop_img.HasValues) + return "pack://application:,,,/Resources/Images/defaultItem.png"; + var properties = skuJToken.Value("properties").Split(';', StringSplitOptions.RemoveEmptyEntries); + foreach (var p in properties) + { + var imgJToken = prop_img.FirstOrDefault(g => g.Value("properties") == p); + if (imgJToken != null) + return imgJToken.Value("url"); + } + return "pack://application:,,,/Resources/Images/defaultItem.png"; + } + + private PurchaseSkuBasicInfoResponse LoadFromSpider(PurchaseSkuBasicInfoRequest request) + { + switch (request.Platform) + { + case Enums.Platform.阿里巴巴: + return LoadFrom1688Spider(request); + //case Platform.拳探: + // return LoadFromQTSpider(platform, productId, skuId, purchaseProductId, priceMode); + } + return null; + } + + private PurchaseSkuBasicInfoResponse LoadFrom1688Spider(PurchaseSkuBasicInfoRequest request) + { + //https://detail.1688.com/offer/672221374773.html?clickid=65f3772cd5d16f190ce4991414607&sessionid=3de47a0c26dcbfde4692064bd55861&sk=order + + //globalData/tempModel/sellerUserId + //globalData/tempModel/companyName + //data/1081181309101/data/location + + + //data/1081181309582/data/pirceModel/[currentPrices]/[0]price + + try + { + var _1688pageResult = restApiService.SendRequest("https://detail.1688.com", + $"offer/{request.PurchaseProductId}.html", + $"clickid={Guid.NewGuid().ToString().Md5Encrypt()}&sessionid={Guid.NewGuid().ToString().Md5Encrypt()}&sk={(request.PriceMode == Enums.PurchaseOrderMode.批发 ? "order" : "consign")}", + _1688ProductDetailRequestHeader, + HttpMethod.Get, + httpClientName: "gzip"); + + if (_1688pageResult.StatusCode != System.Net.HttpStatusCode.OK) + return null; + + var match = Regex.Match(_1688pageResult.Content, @"(window\.__INIT_DATA=)(.*)(\r*\n*\s*)"); + if (!match.Success) + return null; + + var jsonStr = match.Groups[2].Value; + var jobject = JObject.Parse(jsonStr); + + //16347413030323 + var purchaser = new Purchaser() + { + Id = jobject["globalData"]["tempModel"]["sellerUserId"].ToString(), + Name = jobject["globalData"]["tempModel"]["companyName"].ToString(), + Location = jobject["data"]["1081181309101"] != null ? + jobject["data"]["1081181309101"]["data"]["location"].ToString() : + jobject["data"]["16347413030323"]["data"]["location"].ToString(), + Platform = Enums.Platform.阿里巴巴 + }; + + var colorsProperty = jobject["globalData"]["skuModel"]["skuProps"].FirstOrDefault(j => j.Value("fid") == 3216 || + j.Value("fid") == 1627207 || + j.Value("fid") == 1234 || + j.Value("fid") == 3151)["value"] + .Children() + .Select(j => new + { + name = j.Value("name"), + imageUrl = j.Value("imageUrl") + }).ToList(); + + var firstPrice = jobject["data"]["1081181309582"] != null ? + jobject["data"]["1081181309582"]["data"]["priceModel"]["currentPrices"][0].Value("price") : + jobject["data"]["16347413030316"]["data"]["priceModel"]["currentPrices"][0].Value("price"); + + var list = new List(); + + foreach (var jsku in jobject["globalData"]["skuModel"]["skuInfoMap"].Children()) + { + var jskuProperty = jsku as JProperty; + var name = jskuProperty.Name; + var matchName = name.Contains(">") ? name.Substring(0, name.IndexOf(">")) : name; + var value = jskuProperty.Value; + + var skuPrice = value.Value("price"); + + list.Add(new PurchaseSkuItemBasicInfoResponse() + { + PurchaseProductId = request.PurchaseProductId, + Price = skuPrice == 0M ? firstPrice : skuPrice, + Title = name, + PurchaseSkuId = value.Value("skuId"), + PurchaseSkuSpecId = value.Value("specId"), + Logo = colorsProperty.FirstOrDefault(c => c.name == matchName)?.imageUrl ?? "pack://application:,,,/Resources/Images/defaultItem.png" + }); + } + + return new PurchaseSkuBasicInfoResponse() + { + ItemList = list, + Purchaser = purchaser + }; + } + catch + { + + return null; + } + } + + //private (Purchaser purchaser, IList purchaseSchemeProductSkus)? LoadFromQTSpider(Platform platform, string productId, string skuId, string purchaseProductId, PurchaseOrderMode priceMode) + //{ + // try + // { + // var response = quanTanProductClient.GetProductInfo(purchaseProductId, qtAppId, qtAppSecret); + // if (response.Status != 200) + // return null; + // return (new Purchaser() + // { + // Id = response.Data.Supplier.VenderId, + // Name = response.Data.Supplier.VerdenName, + // Location = response.Data.Supplier.Location + // }, response.Data.ProductSku.Select(qtsku => new PurchaseSchemeProductSku() + // { + // ProductId = productId, + // SkuId = skuId, + // PurchaseProductId = purchaseProductId, + // Price = qtsku.Price, + // Title = qtsku.Title, + // PurchaseSkuId = qtsku.SkuId, + // PurchaseSkuSpecId = string.Empty, + // Logo = qtsku.Logo + // }).ToList()); + // } + // catch + // { + // return null; + // } + //} + } +} diff --git a/BBWYB.Server.Business/PurchaseScheme/PurchaseSchemeBusiness.cs b/BBWYB.Server.Business/PurchaseScheme/PurchaseSchemeBusiness.cs index a8923c7..f7e793f 100644 --- a/BBWYB.Server.Business/PurchaseScheme/PurchaseSchemeBusiness.cs +++ b/BBWYB.Server.Business/PurchaseScheme/PurchaseSchemeBusiness.cs @@ -9,7 +9,12 @@ namespace BBWYB.Server.Business { public class PurchaseSchemeBusiness : BaseBusiness, IDenpendency { - public PurchaseSchemeBusiness(IFreeSql fsql, NLogManager nLogManager, IIdGenerator idGenerator) : base(fsql, nLogManager, idGenerator) { } + private PurchaseProductAPIService purchaseProductAPIService; + + public PurchaseSchemeBusiness(IFreeSql fsql, NLogManager nLogManager, IIdGenerator idGenerator, PurchaseProductAPIService purchaseProductAPIService) : base(fsql, nLogManager, idGenerator) + { + this.purchaseProductAPIService = purchaseProductAPIService; + } private void ExtractNewPurchaser(IList purchaserSchemeList, IList addPurchaserList) where T : InputPurchaseSchemeRequest { @@ -243,5 +248,15 @@ namespace BBWYB.Server.Business fsql.Delete().Where(p => p.SkuPurchaseSchemeId == schemeId).ExecuteAffrows(); }); } + + /// + /// 获取采购Sku的基本信息 + /// + /// + /// + public PurchaseSkuBasicInfoResponse GetPurchaseSkuBasicInfo(PurchaseSkuBasicInfoRequest request) + { + return purchaseProductAPIService.GetProductInfo(request); + } } } diff --git a/BBWYB.Server.Model/Dto/Request/PurchaseScheme/PurcasheSkuBasicInfoRequest.cs b/BBWYB.Server.Model/Dto/Request/PurchaseScheme/PurcasheSkuBasicInfoRequest.cs new file mode 100644 index 0000000..2853415 --- /dev/null +++ b/BBWYB.Server.Model/Dto/Request/PurchaseScheme/PurcasheSkuBasicInfoRequest.cs @@ -0,0 +1,16 @@ +namespace BBWYB.Server.Model.Dto +{ + public class PurchaseSkuBasicInfoRequest + { + public Enums.Platform Platform { get; set; } + + /// + /// 采购商品Id + /// + public string PurchaseProductId { get; set; } + + public Enums.PurchaseOrderMode PriceMode { get; set; } + + public Enums.PurchaseProductAPIMode FirstApiMode { get; set; } + } +} diff --git a/BBWYB.Server.Model/Dto/Response/PurchaseScheme/PurchaseProductBasicInfoResponse.cs b/BBWYB.Server.Model/Dto/Response/PurchaseScheme/PurchaseProductBasicInfoResponse.cs new file mode 100644 index 0000000..86a7100 --- /dev/null +++ b/BBWYB.Server.Model/Dto/Response/PurchaseScheme/PurchaseProductBasicInfoResponse.cs @@ -0,0 +1,53 @@ +using BBWYB.Server.Model.Db; + +namespace BBWYB.Server.Model.Dto +{ + /// + /// 采购Sku基础信息对象 + /// + public class PurchaseSkuBasicInfoResponse + { + /// + /// 采购SKU基础信息列表 + /// + public IList ItemList { get; set; } + + /// + /// 采购商 + /// + public Purchaser Purchaser { get; set; } + } + + /// + /// 采购Sku基础信息对象 + /// + public class PurchaseSkuItemBasicInfoResponse + { + /// + /// 采购SPU + /// + public string PurchaseProductId { get; set; } + + /// + /// 采购SKU + /// + public string PurchaseSkuId { get; set; } + + /// + /// 采购SPEC 1688独有属性 下单需要 + /// + public string PurchaseSkuSpecId { get; set; } + + /// + /// SKU标题 + /// + public string Title { get; set; } + + public string Logo { get; set; } + + /// + /// 单价 + /// + public decimal Price { get; set; } + } +} diff --git a/BBWYB.Server.Model/Enums.cs b/BBWYB.Server.Model/Enums.cs index 8401161..4089b7c 100644 --- a/BBWYB.Server.Model/Enums.cs +++ b/BBWYB.Server.Model/Enums.cs @@ -256,5 +256,14 @@ { 全类型 = 0, 订单管理 = 1, 商品管理 = 2 } + + /// + /// 采购商品API模式 Spider = 0,OneBound = 1 + /// + public enum PurchaseProductAPIMode + { + Spider = 0, + OneBound = 1 + } } } diff --git a/bbwyb.sln b/bbwyb.sln index 0acbb0f..4230397 100644 --- a/bbwyb.sln +++ b/bbwyb.sln @@ -35,6 +35,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_1688.SDK", "_1688.SDK\_168 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JD.SDK", "JD.SDK\JD.SDK.csproj", "{A4C4F802-D298-42DE-B410-50C8C87EFFAA}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{555AC361-7766-4871-BFBB-E3986694E2A0}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU