shanji 3 years ago
parent
commit
d4ef428b3d
  1. 255
      BBWY.Server.Business/EarlyWarning/JD/JDStockNumWarningBusiness.cs
  2. 88
      BBWY.Server.Business/EarlyWarning/JD/JDStoreHouseWarningBusiness.cs
  3. 4
      BBWY.Server.Business/TaskSchedulerManager.cs
  4. 2
      BBWY.Server.Business/YunDingBusiness.cs
  5. 11
      BBWY.Server.Model/Enums.cs
  6. 95
      BBWY.Test/DingDingAPITest.cs
  7. 18
      BBWY.Test/Program.cs

255
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<Enums.StorageType> validStorageTypeList;
public JDStockNumWarningBusiness(RestApiService restApiService, IOptions<GlobalConfig> 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.,
Enums.StorageType.
};
}
public void StartCheckStockNum()
{
var storeHouseList = fsql.Select<Storehouse>().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<Storehouse> 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<SkuDailySalesDetail, OrderSku, Order>()
.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<SkuDailySalesDetail>()
.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<ApiResponse<JArray>>(restApiResult.Content);
if (response.Data == null || response.Data.Count() == 0)
continue;
var skuStockNumList = response.Data.Select(j => new
{
StockNum = j.Value<int>("stockNum"),
Store = storeHouseList.FirstOrDefault(s => s.Id == j.Value<string>("storeId")),
StoreId = j.Value<string>("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库存失败");
}
}
/// <summary>
/// Base64 SHA256
/// </summary>
/// <param name="data">待加密数据</param>
/// <param name="secret">密钥</param>
/// <returns></returns>
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);
}
}
}
}

88
BBWY.Server.Business/EarlyWarning/JD/JDStoreHouseWarningBusiness.cs

@ -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<Enums.StorageType> validStorageTypeList;
public JDStoreHouseWarningBusiness(RestApiService restApiService, IOptions<GlobalConfig> 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.,
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<SkuDailySalesDetail, OrderSku, Order>()
.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<SkuDailySalesDetail>()
.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;
//判断周期
}
}
}
}

4
BBWY.Server.Business/TaskSchedulerManager.cs

@ -18,6 +18,8 @@ namespace BBWY.Server.Business
public LimitedConcurrencyLevelTaskScheduler StoreHouseTaskScheduler { get; private set; } public LimitedConcurrencyLevelTaskScheduler StoreHouseTaskScheduler { get; private set; }
public LimitedConcurrencyLevelTaskScheduler StockNumWarningTaskScheduler { get; private set; }
public TaskSchedulerManager() public TaskSchedulerManager()
{ {
SyncOrderTaskScheduler = new LimitedConcurrencyLevelTaskScheduler(10); SyncOrderTaskScheduler = new LimitedConcurrencyLevelTaskScheduler(10);
@ -28,6 +30,8 @@ namespace BBWY.Server.Business
JDPopularizeTaskScheduler = new LimitedConcurrencyLevelTaskScheduler(10); JDPopularizeTaskScheduler = new LimitedConcurrencyLevelTaskScheduler(10);
StoreHouseTaskScheduler = new LimitedConcurrencyLevelTaskScheduler(2); StoreHouseTaskScheduler = new LimitedConcurrencyLevelTaskScheduler(2);
StockNumWarningTaskScheduler = new LimitedConcurrencyLevelTaskScheduler(10);
} }
} }
} }

2
BBWY.Server.Business/YunDingBusiness.cs

@ -9,7 +9,7 @@ namespace BBWY.Server.Business
{ {
private IMemoryCache memoryCache; private IMemoryCache memoryCache;
private IFreeSql fsql; private IFreeSql fsql;
private TimeSpan expirationTimeSpan = TimeSpan.FromDays(2); private TimeSpan expirationTimeSpan = TimeSpan.FromDays(1000);
public YunDingBusiness(IMemoryCache memoryCache, IFreeSql fsql) public YunDingBusiness(IMemoryCache memoryCache, IFreeSql fsql)
{ {
this.memoryCache = memoryCache; this.memoryCache = memoryCache;

11
BBWY.Server.Model/Enums.cs

@ -212,5 +212,16 @@
{ {
= 0, 使 = 1 = 0, 使 = 1
} }
/// <summary>
/// SKU库存周期 暂无周期=0,增长期=1,稳定期=2,衰退期=3
/// </summary>
public enum SkuStockNumCycleType
{
= 0,
= 1,
= 2,
退 = 3
}
} }
} }

95
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&timestamp={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);
}
}
}
}
/// <summary>
/// Base64 SHA256
/// </summary>
/// <param name="data">待加密数据</param>
/// <param name="secret">密钥</param>
/// <returns></returns>
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);
}
}
}
}

18
BBWY.Test/Program.cs

@ -30,23 +30,9 @@ namespace BBWY.Test
IJdClient client = GetJdClient(appkey, appSecret); IJdClient client = GetJdClient(appkey, appSecret);
var test1 = new JDBaoBiaoAPITest(); var test1 = new JDBaoBiaoAPITest();
//test1.Test_FindSku(client, token);
test1.Test_获取事业部编码(client, token);
Console.WriteLine();
Console.WriteLine();
test1.Test_查询仓库(client, token); var ddTest = new DingDingAPITest();
Console.WriteLine(); ddTest.Send();
Console.WriteLine();
//test1.Test2(client, token);
test1.Test_查询京仓库存(client, token);
Console.WriteLine();
Console.WriteLine();
test1.Test_FindSku(client, token);
Console.WriteLine();
Console.WriteLine();
Console.ReadKey(); Console.ReadKey();
} }

Loading…
Cancel
Save