12 changed files with 420 additions and 1 deletions
@ -0,0 +1,4 @@ |
|||||
|
[*.cs] |
||||
|
|
||||
|
# CS8603: 可能返回 null 引用。 |
||||
|
dotnet_diagnostic.CS8603.severity = none |
@ -0,0 +1,12 @@ |
|||||
|
using Newtonsoft.Json; |
||||
|
|
||||
|
namespace BBWYB.Common.Extensions |
||||
|
{ |
||||
|
public static class CopyExtensions |
||||
|
{ |
||||
|
public static T Copy<T>(this T p) |
||||
|
{ |
||||
|
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(p)); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -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<string, (Purchaser purchaser, IList<PurchaseSchemeProductSku> purchaseSchemeProductSkus)> productChaches;
|
||||
|
|
||||
|
private IDictionary<string, string> _1688ProductDetailRequestHeader; |
||||
|
|
||||
|
public PurchaseProductAPIService(RestApiService restApiService, IMemoryCache memoryCache) |
||||
|
{ |
||||
|
this.restApiService = restApiService; |
||||
|
_1688ProductDetailRequestHeader = new Dictionary<string, string>() |
||||
|
{ |
||||
|
{ "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<PurchaseSkuBasicInfoResponse>(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<string>("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<decimal>("price"), |
||||
|
PurchaseSkuId = j.Value<string>("sku_id"), |
||||
|
PurchaseSkuSpecId = j.Value<string>("spec_id"), |
||||
|
Title = j.Value<string>("properties_name"), |
||||
|
Logo = GetOneBoundSkuLogo(j, (JArray)jobject["item"]["prop_imgs"]["prop_img"]) |
||||
|
}).ToList(); |
||||
|
|
||||
|
var purchaserId = jobject["item"]["seller_info"].Value<string>("user_num_id"); |
||||
|
var purchaserName = jobject["item"]["seller_info"].Value<string>("title"); |
||||
|
if (string.IsNullOrEmpty(purchaserName)) |
||||
|
purchaserName = jobject["item"]["seller_info"].Value<string>("shop_name"); |
||||
|
var purchaserLocation = jobject["item"].Value<string>("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<string>("properties").Split(';', StringSplitOptions.RemoveEmptyEntries); |
||||
|
foreach (var p in properties) |
||||
|
{ |
||||
|
var imgJToken = prop_img.FirstOrDefault(g => g.Value<string>("properties") == p); |
||||
|
if (imgJToken != null) |
||||
|
return imgJToken.Value<string>("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*</script>)"); |
||||
|
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<int>("fid") == 3216 || |
||||
|
j.Value<int>("fid") == 1627207 || |
||||
|
j.Value<int>("fid") == 1234 || |
||||
|
j.Value<int>("fid") == 3151)["value"] |
||||
|
.Children() |
||||
|
.Select(j => new |
||||
|
{ |
||||
|
name = j.Value<string>("name"), |
||||
|
imageUrl = j.Value<string>("imageUrl") |
||||
|
}).ToList(); |
||||
|
|
||||
|
var firstPrice = jobject["data"]["1081181309582"] != null ? |
||||
|
jobject["data"]["1081181309582"]["data"]["priceModel"]["currentPrices"][0].Value<decimal>("price") : |
||||
|
jobject["data"]["16347413030316"]["data"]["priceModel"]["currentPrices"][0].Value<decimal>("price"); |
||||
|
|
||||
|
var list = new List<PurchaseSkuItemBasicInfoResponse>(); |
||||
|
|
||||
|
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<decimal>("price"); |
||||
|
|
||||
|
list.Add(new PurchaseSkuItemBasicInfoResponse() |
||||
|
{ |
||||
|
PurchaseProductId = request.PurchaseProductId, |
||||
|
Price = skuPrice == 0M ? firstPrice : skuPrice, |
||||
|
Title = name, |
||||
|
PurchaseSkuId = value.Value<string>("skuId"), |
||||
|
PurchaseSkuSpecId = value.Value<string>("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<PurchaseSchemeProductSku> 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;
|
||||
|
// }
|
||||
|
//}
|
||||
|
} |
||||
|
} |
@ -0,0 +1,16 @@ |
|||||
|
namespace BBWYB.Server.Model.Dto |
||||
|
{ |
||||
|
public class PurchaseSkuBasicInfoRequest |
||||
|
{ |
||||
|
public Enums.Platform Platform { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 采购商品Id
|
||||
|
/// </summary>
|
||||
|
public string PurchaseProductId { get; set; } |
||||
|
|
||||
|
public Enums.PurchaseOrderMode PriceMode { get; set; } |
||||
|
|
||||
|
public Enums.PurchaseProductAPIMode FirstApiMode { get; set; } |
||||
|
} |
||||
|
} |
@ -0,0 +1,53 @@ |
|||||
|
using BBWYB.Server.Model.Db; |
||||
|
|
||||
|
namespace BBWYB.Server.Model.Dto |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 采购Sku基础信息对象
|
||||
|
/// </summary>
|
||||
|
public class PurchaseSkuBasicInfoResponse |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 采购SKU基础信息列表
|
||||
|
/// </summary>
|
||||
|
public IList<PurchaseSkuItemBasicInfoResponse> ItemList { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 采购商
|
||||
|
/// </summary>
|
||||
|
public Purchaser Purchaser { get; set; } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 采购Sku基础信息对象
|
||||
|
/// </summary>
|
||||
|
public class PurchaseSkuItemBasicInfoResponse |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 采购SPU
|
||||
|
/// </summary>
|
||||
|
public string PurchaseProductId { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 采购SKU
|
||||
|
/// </summary>
|
||||
|
public string PurchaseSkuId { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 采购SPEC 1688独有属性 下单需要
|
||||
|
/// </summary>
|
||||
|
public string PurchaseSkuSpecId { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// SKU标题
|
||||
|
/// </summary>
|
||||
|
public string Title { get; set; } |
||||
|
|
||||
|
public string Logo { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 单价
|
||||
|
/// </summary>
|
||||
|
public decimal Price { get; set; } |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue