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; } } #region 预警检查结果日志 nLogManager.GetLogger($"库存预警-{shop.ShopName}").Info($"sku {sku}, 周期 {skuStockNumCycleType}, 7天总销量 {_7dItemTotal}, 7天日均销量 {_7dAvgItemTotal}, 总库存 {totalStockNum}, 预估销售剩余天数 {lessDay}, 是否触发预警 {isWarning}"); #endregion 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 = $"{shop.DingDingWebHook}×tamp={timestamp}&sign={sign}"; var result = restApiService.SendRequest(url, string.Empty, new { msgtype = "text", text = new { content = dingdingContentBuilder.ToString() } }, null, HttpMethod.Post); if (result.StatusCode != System.Net.HttpStatusCode.OK) throw new Exception($"{shop.ShopName} 发送钉钉库存预警失败 {result.Content}"); } } catch (Exception ex) { nLogManager.GetLogger($"库存预警-{shop.ShopName}").Error(ex, "查询sku库存失败"); } } /// /// Base64 SHA256 /// /// 待加密数据 /// 密钥 /// private 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); } } } }