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