From d4ef428b3d16ed39b8a1782adfb1619f3a8574ba Mon Sep 17 00:00:00 2001 From: shanj <18996038927@163.com> Date: Sun, 20 Nov 2022 01:56:04 +0800 Subject: [PATCH] 1 --- .../JD/JDStockNumWarningBusiness.cs | 255 ++++++++++++++++++ .../JD/JDStoreHouseWarningBusiness.cs | 88 ------ BBWY.Server.Business/TaskSchedulerManager.cs | 4 + BBWY.Server.Business/YunDingBusiness.cs | 2 +- BBWY.Server.Model/Enums.cs | 11 + BBWY.Test/DingDingAPITest.cs | 95 +++++++ BBWY.Test/Program.cs | 18 +- 7 files changed, 368 insertions(+), 105 deletions(-) create mode 100644 BBWY.Server.Business/EarlyWarning/JD/JDStockNumWarningBusiness.cs delete mode 100644 BBWY.Server.Business/EarlyWarning/JD/JDStoreHouseWarningBusiness.cs create mode 100644 BBWY.Test/DingDingAPITest.cs diff --git a/BBWY.Server.Business/EarlyWarning/JD/JDStockNumWarningBusiness.cs b/BBWY.Server.Business/EarlyWarning/JD/JDStockNumWarningBusiness.cs new file mode 100644 index 00000000..1fe3d16f --- /dev/null +++ b/BBWY.Server.Business/EarlyWarning/JD/JDStockNumWarningBusiness.cs @@ -0,0 +1,255 @@ +using BBWY.Common.Extensions; +using BBWY.Common.Http; +using BBWY.Common.Models; +using BBWY.Server.Model; +using BBWY.Server.Model.Db; +using BBWY.Server.Model.Dto; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Yitter.IdGenerator; + +namespace BBWY.Server.Business +{ + public class JDStockNumWarningBusiness : BaseSyncBusiness, IDenpendency + { + private IList validStorageTypeList; + + public JDStockNumWarningBusiness(RestApiService restApiService, IOptions options, NLogManager nLogManager, IFreeSql fsql, IIdGenerator idGenerator, TaskSchedulerManager taskSchedulerManager, VenderBusiness venderBusiness, YunDingBusiness yunDingBusiness) : base(restApiService, options, nLogManager, fsql, idGenerator, taskSchedulerManager, venderBusiness, yunDingBusiness) + { + validStorageTypeList = new List() { + Enums.StorageType.云仓, + Enums.StorageType.京仓, + Enums.StorageType.本地自发 + }; + } + + public void StartCheckStockNum() + { + var storeHouseList = fsql.Select().Where(s => s.Platform == Enums.Platform.京东).ToList(); + var shopList = venderBusiness.GetShopList(platform: Enums.Platform.京东, filterTurnoverDays: true); + foreach (var shop in shopList) + { + Task.Factory.StartNew(() => CheckStockNum(shop, storeHouseList), CancellationToken.None, TaskCreationOptions.LongRunning, taskSchedulerManager.StockNumWarningTaskScheduler); + } + } + + private void CheckStockNum(ShopResponse shop, IList storeHouseList) + { + try + { + long shopId = long.Parse(shop.ShopId); + var yesterDayDate = DateTime.Now.Date.AddDays(-1); + var ysterDayTime = DateTime.Now.Date.AddSeconds(-1); + + //查询符合条件的sku (昨日 && 非赠品 && 销量>取消 && 非代发和刷单) + var yesterDaySkuIds = fsql.Select() + .InnerJoin((s, osku, o) => s.Sku == osku.SkuId) + .InnerJoin((s, osku, o) => osku.OrderId == o.Id) + .Where((s, osku, o) => s.ShopId == shopId && + s.Date == yesterDayDate && + s.IsGift == false && + s.ItemTotal > s.CancelItemTotal && + o.StartTime >= yesterDayDate && + o.StartTime <= ysterDayTime && + validStorageTypeList.Contains(o.StorageType.Value)) + .Distinct() + .ToList((s, osku, o) => s.Sku); + + //查询近9天的销量(三个周期) + var allCycleStartDate = yesterDayDate.AddDays(-8); + var skuSaleDailyList = fsql.Select() + .Where(s => s.Date >= allCycleStartDate && s.Date <= yesterDayDate) + .Where(s => yesterDaySkuIds.Contains(s.Sku)) + .ToList(); + + var firstCycleStartDate = allCycleStartDate; + var firstCycleEndDate = allCycleStartDate.AddDays(2); + + var secondCycleStartDate = firstCycleEndDate.AddDays(1); + var secondCycleEndDate = secondCycleStartDate.AddDays(2); + + var thirdCycleStartDate = secondCycleEndDate.AddDays(1); + var thirdCycleEndDate = thirdCycleStartDate.AddDays(2); + + var _7dAvgStartDate = DateTime.Now.Date.AddDays(-7); + + bool isSendDingTalk = false; + var dingdingContentBuilder = new StringBuilder(); + dingdingContentBuilder.Append(shop.ShopName); + dingdingContentBuilder.AppendLine(); + + foreach (var sku in yesterDaySkuIds) + { + //第一周期销量 + var firstCycleSaleList = skuSaleDailyList.Where(s => s.Sku == sku && s.Date >= firstCycleStartDate && s.Date <= firstCycleEndDate); + var firstCycleItemTotal = firstCycleSaleList.Count() > 0 ? firstCycleSaleList.Sum(s => s.ItemTotal - s.CancelItemTotal) : 0; + + //第二周期销量 + var secondCycleSaleList = skuSaleDailyList.Where(s => s.Sku == sku && s.Date >= secondCycleStartDate && s.Date <= secondCycleEndDate); + var secondCycleItemTotal = secondCycleSaleList.Count() > 0 ? secondCycleSaleList.Sum(s => s.ItemTotal - s.CancelItemTotal) : 0; + + //第三周期销量 + var thirdCycleSaleList = skuSaleDailyList.Where(s => s.Sku == sku && s.Date >= thirdCycleStartDate && s.Date <= thirdCycleEndDate); + var thirdCycleItemTotal = thirdCycleSaleList.Count() > 0 ? thirdCycleSaleList.Sum(s => s.ItemTotal - s.CancelItemTotal) : 0; + + //计算周期增幅 + var _2Ratio = firstCycleItemTotal == 0 ? 0 : 1.0 * secondCycleItemTotal / firstCycleItemTotal - 1; + var _3Ratio = secondCycleItemTotal == 0 ? 0 : 1.0 * thirdCycleItemTotal / secondCycleItemTotal - 1; + + Enums.SkuStockNumCycleType skuStockNumCycleType = Enums.SkuStockNumCycleType.暂无周期; + if (_2Ratio >= 0.2 && _3Ratio >= 0.2) + skuStockNumCycleType = Enums.SkuStockNumCycleType.增长期; + else if (_2Ratio >= -0.2 && _2Ratio <= 0.2 && _3Ratio >= -0.2 && _3Ratio <= 0.2) + skuStockNumCycleType = Enums.SkuStockNumCycleType.稳定期; + else if (_2Ratio < -0.2 && _3Ratio < -0.2) + skuStockNumCycleType = Enums.SkuStockNumCycleType.衰退期; + + if (skuStockNumCycleType == Enums.SkuStockNumCycleType.暂无周期) + continue; + + Thread.Sleep(1000); + var restApiResult = restApiService.SendRequest(GetPlatformRelayAPIHost(shop.PlatformId), "api/platformsdk/GetStockNumBySku", new SearchProductSkuRequest() + { + AppKey = shop.AppKey, + AppSecret = shop.AppSecret, + AppToken = shop.AppToken, + Platform = shop.PlatformId, + Sku = sku + }, GetYunDingRequestHeader(), HttpMethod.Post); + if (restApiResult.StatusCode != System.Net.HttpStatusCode.OK) + throw new Exception($"{sku} {restApiResult.Content}"); + var response = JsonConvert.DeserializeObject>(restApiResult.Content); + if (response.Data == null || response.Data.Count() == 0) + continue; + var skuStockNumList = response.Data.Select(j => new + { + StockNum = j.Value("stockNum"), + Store = storeHouseList.FirstOrDefault(s => s.Id == j.Value("storeId")), + StoreId = j.Value("storeId"), + SkuId = sku + }); + + var _7dAvgSaleList = skuSaleDailyList.Where(s => s.Sku == sku && s.Date >= _7dAvgStartDate && s.Date <= yesterDayDate); + var _7dItemTotal = _7dAvgSaleList.Sum(s => s.ItemTotal - s.CancelItemTotal); + var _7dAvgItemTotal = 1.0 * _7dItemTotal / 7; //近7天日均销量 + var totalStockNum = skuStockNumList.Sum(s => s.StockNum); //总库存 + + var lessDay = _7dAvgItemTotal == 0 ? 0 : totalStockNum / _7dAvgItemTotal; //剩余天数 + bool isWarning = false; //是否触发提醒 + int suggestStockNum = 0; //建议备货量 + if (skuStockNumCycleType == Enums.SkuStockNumCycleType.增长期) + { + if (lessDay < 15) + { + isWarning = true; + if (shop.SkuSafeTurnoverDays == 28) + suggestStockNum = (int)Math.Ceiling(_7dItemTotal * 4 * 1.5); + else if (shop.SkuSafeTurnoverDays == 21) + suggestStockNum = (int)Math.Ceiling(_7dItemTotal * 3 * 1.5); + else if (shop.SkuSafeTurnoverDays == 14) + suggestStockNum = (int)Math.Ceiling(_7dItemTotal * 2 * 1.5); + } + } + else if (skuStockNumCycleType == Enums.SkuStockNumCycleType.稳定期) + { + if (lessDay < 8) + { + isWarning = true; + if (shop.SkuSafeTurnoverDays == 28) + suggestStockNum = _7dItemTotal * 4; + else if (shop.SkuSafeTurnoverDays == 21) + suggestStockNum = _7dItemTotal * 3; + else if (shop.SkuSafeTurnoverDays == 14) + suggestStockNum = _7dItemTotal * 2; + } + } + else if (skuStockNumCycleType == Enums.SkuStockNumCycleType.衰退期) + { + if (lessDay < 8) + { + isWarning = true; + suggestStockNum = 0; + } + } + + if (isWarning) + { + isSendDingTalk = true; + #region 拼接提醒内容 + dingdingContentBuilder.Append($"SKU:{sku}\n"); + dingdingContentBuilder.Append($"商品状态:{skuStockNumCycleType}\n"); + dingdingContentBuilder.Append($"近7天销量:{_7dItemTotal}\n"); + foreach (var stockNumInfo in skuStockNumList) + { + if (stockNumInfo.Store != null) + dingdingContentBuilder.Append($"{stockNumInfo.Store.Name}:{stockNumInfo.StockNum}件\n"); + else + dingdingContentBuilder.Append($"{stockNumInfo.StoreId}:{stockNumInfo.StockNum}件\n"); + } + if (skuStockNumCycleType == Enums.SkuStockNumCycleType.增长期 || skuStockNumCycleType == Enums.SkuStockNumCycleType.稳定期) + dingdingContentBuilder.Append($"低于安全周转天数,建议备货{suggestStockNum}件"); + else if (skuStockNumCycleType == Enums.SkuStockNumCycleType.衰退期) + dingdingContentBuilder.Append("商品进入衰退期,建议暂停备货,采购代发"); + #endregion + } + } + + if (isSendDingTalk) + { + var secret = shop.DingDingKey; + var timestamp = DateTime.Now.DateTimeToStamp(); + var stringToSign = timestamp + "\n" + secret; + var sign = EncryptWithSHA256(stringToSign, secret); + var url = ""; + restApiService.SendRequest(url, string.Empty, new + { + msgtype = "text", + text = new + { + content = dingdingContentBuilder.ToString() + } + }, null, HttpMethod.Post); + } + } + catch (Exception ex) + { + nLogManager.Default().Error(ex, "查询sku库存失败"); + } + } + + /// + /// Base64 SHA256 + /// + /// 待加密数据 + /// 密钥 + /// + public static string EncryptWithSHA256(string data, string secret) + { + secret = secret ?? ""; + + // 1、string 转换成 utf-8 的byte[] + var encoding = Encoding.UTF8; + byte[] keyByte = encoding.GetBytes(secret); + byte[] dataBytes = encoding.GetBytes(data); + + // 2、 HMACSHA256加密 + using (var hmac256 = new HMACSHA256(keyByte)) + { + byte[] hashData = hmac256.ComputeHash(dataBytes); + // 3、转换成base64 + var base64Str = Convert.ToBase64String(hashData); + // 4、urlEncode编码 + return System.Web.HttpUtility.UrlEncode(base64Str, Encoding.UTF8); + } + } + } +} diff --git a/BBWY.Server.Business/EarlyWarning/JD/JDStoreHouseWarningBusiness.cs b/BBWY.Server.Business/EarlyWarning/JD/JDStoreHouseWarningBusiness.cs deleted file mode 100644 index a37e357c..00000000 --- a/BBWY.Server.Business/EarlyWarning/JD/JDStoreHouseWarningBusiness.cs +++ /dev/null @@ -1,88 +0,0 @@ -using BBWY.Common.Http; -using BBWY.Common.Models; -using BBWY.Server.Model; -using BBWY.Server.Model.Db; -using BBWY.Server.Model.Dto; -using Microsoft.Extensions.Options; -using System; -using System.Collections.Generic; -using System.Linq; -using Yitter.IdGenerator; - -namespace BBWY.Server.Business -{ - public class JDStoreHouseWarningBusiness : BaseSyncBusiness, IDenpendency - { - private IList validStorageTypeList; - - public JDStoreHouseWarningBusiness(RestApiService restApiService, IOptions options, NLogManager nLogManager, IFreeSql fsql, IIdGenerator idGenerator, TaskSchedulerManager taskSchedulerManager, VenderBusiness venderBusiness, YunDingBusiness yunDingBusiness) : base(restApiService, options, nLogManager, fsql, idGenerator, taskSchedulerManager, venderBusiness, yunDingBusiness) - { - validStorageTypeList = new List() { - Enums.StorageType.云仓, - Enums.StorageType.京仓, - Enums.StorageType.本地自发 - }; - } - - public void StartCheckStockNum() - { - var shopList = venderBusiness.GetShopList(platform: Enums.Platform.京东, filterTurnoverDays: true); - foreach (var shop in shopList) - { - CheckStockNum(shop); - } - } - - private void CheckStockNum(ShopResponse shop) - { - long shopId = long.Parse(shop.ShopId); - var yesterDayDate = DateTime.Now.Date.AddDays(-1); - var ysterDayTime = DateTime.Now.Date.AddSeconds(-1); - var yesterDaySkuIds = fsql.Select() - .InnerJoin((s, osku, o) => s.Sku == osku.SkuId) - .InnerJoin((s, osku, o) => osku.OrderId == o.Id) - .Where((s, osku, o) => s.ShopId == shopId && - s.Date == yesterDayDate && - s.IsGift == false && - s.ItemTotal > s.CancelItemTotal && - o.StartTime >= yesterDayDate && - o.StartTime <= ysterDayTime && - validStorageTypeList.Contains(o.StorageType.Value)) - .Distinct() - .ToList((s, osku, o) => s.Sku); - - var queryStartDate = yesterDayDate.AddDays(-8); - var skuSaleDailyList = fsql.Select() - .Where(s => s.Date >= queryStartDate && s.Date <= yesterDayDate) - .Where(s => yesterDaySkuIds.Contains(s.Sku)) - .ToList(); - - var firstCycleStartDate = queryStartDate; - var firstCycleEndDate = queryStartDate.AddDays(2); - - var secondCycleStartDate = firstCycleEndDate.AddDays(1); - var secondCycleEndDate = secondCycleStartDate.AddDays(2); - - var thirdCycleStartDate = secondCycleEndDate.AddDays(1); - var thirdCycleEndDate = thirdCycleStartDate.AddDays(2); - - foreach (var sku in yesterDaySkuIds) - { - //第一周期销量 - var firstCycleSaleList = skuSaleDailyList.Where(s => s.Sku == sku && s.Date >= firstCycleStartDate && s.Date <= firstCycleEndDate); - var firstCycleItemTotal = firstCycleSaleList.Count() > 0 ? firstCycleSaleList.Sum(s => s.ItemTotal - s.CancelItemTotal) : 0; - - //第二周期销量 - var secondCycleSaleList = skuSaleDailyList.Where(s => s.Sku == sku && s.Date >= secondCycleStartDate && s.Date <= secondCycleEndDate); - var secondCycleItemTotal = secondCycleSaleList.Count() > 0 ? secondCycleSaleList.Sum(s => s.ItemTotal - s.CancelItemTotal) : 0; - - //第三周期销量 - var thirdCycleSaleList = skuSaleDailyList.Where(s => s.Sku == sku && s.Date >= thirdCycleStartDate && s.Date <= thirdCycleEndDate); - var thirdCycleItemTotal = thirdCycleSaleList.Count() > 0 ? thirdCycleSaleList.Sum(s => s.ItemTotal - s.CancelItemTotal) : 0; - - //判断周期 - - } - } - } -} diff --git a/BBWY.Server.Business/TaskSchedulerManager.cs b/BBWY.Server.Business/TaskSchedulerManager.cs index 91af950e..c9e68705 100644 --- a/BBWY.Server.Business/TaskSchedulerManager.cs +++ b/BBWY.Server.Business/TaskSchedulerManager.cs @@ -18,6 +18,8 @@ namespace BBWY.Server.Business public LimitedConcurrencyLevelTaskScheduler StoreHouseTaskScheduler { get; private set; } + public LimitedConcurrencyLevelTaskScheduler StockNumWarningTaskScheduler { get; private set; } + public TaskSchedulerManager() { SyncOrderTaskScheduler = new LimitedConcurrencyLevelTaskScheduler(10); @@ -28,6 +30,8 @@ namespace BBWY.Server.Business JDPopularizeTaskScheduler = new LimitedConcurrencyLevelTaskScheduler(10); StoreHouseTaskScheduler = new LimitedConcurrencyLevelTaskScheduler(2); + + StockNumWarningTaskScheduler = new LimitedConcurrencyLevelTaskScheduler(10); } } } diff --git a/BBWY.Server.Business/YunDingBusiness.cs b/BBWY.Server.Business/YunDingBusiness.cs index 02ad221a..92216892 100644 --- a/BBWY.Server.Business/YunDingBusiness.cs +++ b/BBWY.Server.Business/YunDingBusiness.cs @@ -9,7 +9,7 @@ namespace BBWY.Server.Business { private IMemoryCache memoryCache; private IFreeSql fsql; - private TimeSpan expirationTimeSpan = TimeSpan.FromDays(2); + private TimeSpan expirationTimeSpan = TimeSpan.FromDays(1000); public YunDingBusiness(IMemoryCache memoryCache, IFreeSql fsql) { this.memoryCache = memoryCache; diff --git a/BBWY.Server.Model/Enums.cs b/BBWY.Server.Model/Enums.cs index 1e14a12a..de33c121 100644 --- a/BBWY.Server.Model/Enums.cs +++ b/BBWY.Server.Model/Enums.cs @@ -212,5 +212,16 @@ { 暂停 = 0, 使用 = 1 } + + /// + /// SKU库存周期 暂无周期=0,增长期=1,稳定期=2,衰退期=3 + /// + public enum SkuStockNumCycleType + { + 暂无周期 = 0, + 增长期 = 1, + 稳定期 = 2, + 衰退期 = 3 + } } } diff --git a/BBWY.Test/DingDingAPITest.cs b/BBWY.Test/DingDingAPITest.cs new file mode 100644 index 00000000..beb60298 --- /dev/null +++ b/BBWY.Test/DingDingAPITest.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Security.Cryptography; +using System.Text; +using BBWY.Common.Extensions; +using Newtonsoft.Json; + +namespace BBWY.Test +{ + public class DingDingAPITest + { + public DingDingAPITest() + { + + } + + public void Send() + { + var contentStrBuilder = new StringBuilder(); + contentStrBuilder.Append("店铺名:布莱特玩具专营店"); + contentStrBuilder.AppendLine(); + for (var i = 0; i < 3; i++) + { + //contentStrBuilder.Append("店铺名:布莱特玩具专营店\n"); + contentStrBuilder.Append("SKU:123456\n"); + contentStrBuilder.Append("商品状态:稳定期\n"); + contentStrBuilder.Append("近7天销量:100件\n"); + contentStrBuilder.Append("XX仓库存:20件\n"); + contentStrBuilder.Append("XXX仓库存:50件\n"); + contentStrBuilder.Append("低于安全周转天数,建议备货500件"); + contentStrBuilder.AppendLine(); + } + + + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + + + var secret = "SEC48f0a928dda2c97eee97c1c89b41253900699df351f35900ad5d32a8b050167e"; + var timestamp = DateTime.Now.DateTimeToStamp(); + var stringToSign = timestamp + "\n" + secret; + var sign = EncryptWithSHA256(stringToSign, secret); + + var url = new Uri($"https://oapi.dingtalk.com/robot/send?access_token=f99578b3ba45dc53edf4355419a6f080ed02787813ab7e9a4df02206e8b0b1ea×tamp={timestamp}&sign={sign}"); + + using (var httpClient = new HttpClient()) + { + using (var request = new HttpRequestMessage(HttpMethod.Post, url)) + { + request.Content = new StringContent(JsonConvert.SerializeObject(new + { + msgtype = "text", + text = new + { + content = contentStrBuilder.ToString() + } + }), Encoding.UTF8, "application/json"); ; + + using (var response = httpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead).Result) + { + var resultStr = response.Content.ReadAsStringAsync().Result; + Console.Write(resultStr); + } + } + } + } + + + /// + /// Base64 SHA256 + /// + /// 待加密数据 + /// 密钥 + /// + public static string EncryptWithSHA256(string data, string secret) + { + secret = secret ?? ""; + + // 1、string 转换成 utf-8 的byte[] + var encoding = Encoding.UTF8; + byte[] keyByte = encoding.GetBytes(secret); + byte[] dataBytes = encoding.GetBytes(data); + + // 2、 HMACSHA256加密 + using (var hmac256 = new HMACSHA256(keyByte)) + { + byte[] hashData = hmac256.ComputeHash(dataBytes); + // 3、转换成base64 + var base64Str = Convert.ToBase64String(hashData); + // 4、urlEncode编码 + return System.Web.HttpUtility.UrlEncode(base64Str, Encoding.UTF8); + } + } + } +} diff --git a/BBWY.Test/Program.cs b/BBWY.Test/Program.cs index bb04095d..21a024af 100644 --- a/BBWY.Test/Program.cs +++ b/BBWY.Test/Program.cs @@ -30,23 +30,9 @@ namespace BBWY.Test IJdClient client = GetJdClient(appkey, appSecret); var test1 = new JDBaoBiaoAPITest(); - //test1.Test_FindSku(client, token); - test1.Test_获取事业部编码(client, token); - Console.WriteLine(); - Console.WriteLine(); - test1.Test_查询仓库(client, token); - Console.WriteLine(); - Console.WriteLine(); - //test1.Test2(client, token); - - test1.Test_查询京仓库存(client, token); - Console.WriteLine(); - Console.WriteLine(); - - test1.Test_FindSku(client, token); - Console.WriteLine(); - Console.WriteLine(); + var ddTest = new DingDingAPITest(); + ddTest.Send(); Console.ReadKey(); }