7 changed files with 368 additions and 105 deletions
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
@ -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; |
|||
|
|||
//判断周期
|
|||
|
|||
} |
|||
} |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
/// <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); |
|||
} |
|||
} |
|||
} |
|||
} |
Loading…
Reference in new issue