diff --git a/SBF.API/Controllers/ExecuteTrusteeshipPolicyController.cs b/SBF.API/Controllers/ExecuteTrusteeshipPolicyController.cs new file mode 100644 index 0000000..48d427b --- /dev/null +++ b/SBF.API/Controllers/ExecuteTrusteeshipPolicyController.cs @@ -0,0 +1,87 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using SBF.Business; +using SBF.Model.Dto; + +namespace SBF.API.Controllers +{ + /// + /// 执行托管策略 + /// + public class ExecuteTrusteeshipPolicyController : BaseApiController + { + + private ExecuteTrusteeshipPolicyBusiness executeTrusteeshipPolicyBusiness; + + public ExecuteTrusteeshipPolicyController(IHttpContextAccessor httpContextAccessor, ExecuteTrusteeshipPolicyBusiness executeTrusteeshipPolicyBusiness) : base(httpContextAccessor) + { + this.executeTrusteeshipPolicyBusiness = executeTrusteeshipPolicyBusiness; + } + + /// + /// 批量上传店铺预算数据(回调地址) + /// + /// + [HttpPost] + [AllowAnonymous] + public async Task BatchUpdateBudget(BatchUpdateBudgetRequest[] requestList) + { + await executeTrusteeshipPolicyBusiness.BatchUpdateBudget(requestList); + } + + + /// + /// 批量上传店铺单元出价数据(回调地址) + /// + /// + [HttpPost] + [AllowAnonymous] + public async Task BatchUpdateBidPrice(BatchUpdateBidPriceRequest[] requestList) + { + await executeTrusteeshipPolicyBusiness.BatchUpdateBidPrice(requestList); + } + + /// + /// 更新策略数据(当日预算. 出价 ,当前时间花费情况) 9:00 + /// + [HttpGet] + [AllowAnonymous] + public async Task UpdateTrusteeshipPolicyData() + { + await executeTrusteeshipPolicyBusiness.UpdateTrusteeshipPolicyData(); + } + + /// + /// 判断 预算耗尽 更新出价 花费 预算 + /// + /// + [HttpGet] + [AllowAnonymous] + public async Task JudgeBudgetCostOutUpdateTrusteeshipPolicyData() + { + await executeTrusteeshipPolicyBusiness.JudgeBudgetCostOutUpdateTrusteeshipPolicyData(); + } + + + /// + /// 运行成长期调高出价策略 + /// + /// + [HttpGet] + [AllowAnonymous] + public async Task RunGrowthPeriodIncreaseBidPriceTrusteeshipPolicy() + { + await executeTrusteeshipPolicyBusiness.RunGrowthPeriodIncreaseBidPriceTrusteeshipPolicy(); + } + /// + /// 运行成长期降低出价策略 + /// + /// + [HttpGet] + [AllowAnonymous] + public async Task RunGrowthPeriodLowerBidPriceTrusteeshipPolicy() + { + await executeTrusteeshipPolicyBusiness.RunGrowthPeriodLowerBidPriceTrusteeshipPolicy(); + } + } +} diff --git a/SBF.API/Extentions/HostExtentions.cs b/SBF.API/Extentions/HostExtentions.cs new file mode 100644 index 0000000..ffc65e7 --- /dev/null +++ b/SBF.API/Extentions/HostExtentions.cs @@ -0,0 +1,60 @@ +using CSRedis; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.Redis; +using System.Security.AccessControl; + + +namespace SBF.API.Extentions +{ + public static class HostExtentions + { + /// + /// 使用缓存 + /// + /// 建造者 + /// + public static IHostBuilder UseCache(this IHostBuilder hostBuilder) + { + + + hostBuilder.ConfigureServices((buidlerContext, services) => + { + var cacheOption = buidlerContext.Configuration.GetSection("Cache").Get(); + switch (cacheOption.CacheType) + { + case CacheType.Memory: services.AddDistributedMemoryCache(); break; + case CacheType.Redis: + { + var csredis = new CSRedisClient(cacheOption.RedisEndpoint); + RedisHelper.Initialization(csredis); + services.AddSingleton(csredis); + services.AddSingleton(new CSRedisCache(RedisHelper.Instance)); + }; break; + default: throw new Exception("缓存类型无效"); + } + }); + + return hostBuilder; + } + + } + internal class CacheOptions + { + public CacheType CacheType { get; set; } + public string RedisEndpoint { get; set; } + } /// + /// 缓存类型 + /// + public enum CacheType + { + /// + /// 使用内存缓存(不支持分布式) + /// + Memory, + + /// + /// 使用Redis缓存(支持分布式) + /// + Redis + } +} diff --git a/SBF.API/Program.cs b/SBF.API/Program.cs index 7ee8d88..786ddac 100644 --- a/SBF.API/Program.cs +++ b/SBF.API/Program.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.OpenApi.Models; using Newtonsoft.Json.Serialization; +using SBF.API.Extentions; using SBF.API.Filters; using SBF.API.Middlewares; using SBF.Business; @@ -13,9 +14,14 @@ using System.Reflection; using Yitter.IdGenerator; var builder = WebApplication.CreateBuilder(args); +builder.Host.UseCache(); + var services = builder.Services; var configuration = builder.Configuration; + + + services.AddMemoryCache(); var idOption = new IdGeneratorOptions(1); var idGenerator = new DefaultIdGenerator(idOption); diff --git a/SBF.API/SBF.API.csproj b/SBF.API/SBF.API.csproj index 0f5e3ae..663998e 100644 --- a/SBF.API/SBF.API.csproj +++ b/SBF.API/SBF.API.csproj @@ -8,6 +8,7 @@ + diff --git a/SBF.API/appsettings.json b/SBF.API/appsettings.json index 5306809..c8a7ec0 100644 --- a/SBF.API/appsettings.json +++ b/SBF.API/appsettings.json @@ -5,6 +5,10 @@ "Microsoft.AspNetCore": "Warning" } }, + "Cache": { + "CacheType": "Redis", + "RedisEndpoint": "116.62.61.68:6379" + }, "AllowedHosts": "*", "Secret": "D96BFA5B-F2AF-45BC-9342-5A55C3F9BBB0", "ConnectionStrings": { diff --git a/SBF.Business/ExecuteTrusteeshipPolicyBusiness.cs b/SBF.Business/ExecuteTrusteeshipPolicyBusiness.cs new file mode 100644 index 0000000..456586a --- /dev/null +++ b/SBF.Business/ExecuteTrusteeshipPolicyBusiness.cs @@ -0,0 +1,586 @@ +using Google.Protobuf.WellKnownTypes; +using Microsoft.Extensions.Caching.Distributed; +using Newtonsoft.Json; +using NLog; +using SBF.Common.Log; +using SBF.Common.Models; +using SBF.Model.Db; +using SBF.Model.Db.Trusteeship; +using SBF.Model.Dto; +using SiNan.Business; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using Yitter.IdGenerator; + +namespace SBF.Business +{ + /// + /// 执行托管策略 + /// + public class ExecuteTrusteeshipPolicyBusiness : BaseBusiness, IDenpendency + { + + ILogger logger; + public ExecuteTrusteeshipPolicyBusiness(IFreeSql fsql, NLogManager nLogManager, IIdGenerator idGenerator, IDistributedCache cache) : base(fsql, nLogManager, idGenerator) + { + logger = nLogManager.GetLogger("执行托管策略"); + _cache = cache; + + } + + + readonly IDistributedCache _cache; + + string host = "http://api.sbf.qiyue666.com/"; + + /// + /// 回调出价和花费 + /// + /// + /// + /// + public async Task BatchUpdateBidPrice(BatchUpdateBidPriceRequest[] requestList) + { + + + var AdgroupIdList = requestList.Select(r => long.Parse(r.AdgroupId)).ToList(); + var trusteeshipTaskList = fsql.Select().Where(s => s.AdGroupId != null && AdgroupIdList.Contains(s.AdGroupId.Value)).ToList(); + foreach (var trusteeshipTask in trusteeshipTaskList) + { + var newData = requestList.FirstOrDefault(t => t.AdgroupId == trusteeshipTask.AdGroupId?.ToString()); + + if (newData != null) + { + trusteeshipTask.BidPrice = newData.SearchFee;//当前出价 + trusteeshipTask.NowCost = newData.TotalCost;//当前总花费 + continue; + } + + } + if (trusteeshipTaskList.Any()) + await fsql.Update(trusteeshipTaskList).ExecuteUpdatedAsync(); + + + logger.Info($"回调出价和花费成功!回调消息:{JsonConvert.SerializeObject(requestList)}"); + + } + + /// + /// 回调预算 + /// + /// + /// + /// + public async Task BatchUpdateBudget(BatchUpdateBudgetRequest[] requestList) + { + + var CampaignIdList = requestList.Select(r => long.Parse(r.CampaignId)).ToList(); + var trusteeshipTaskList = fsql.Select().Where(s => s.CampaignId != null && CampaignIdList.Contains(s.CampaignId.Value)).ToList(); + + + foreach (var trusteeshipTask in trusteeshipTaskList) + { + var newData = requestList.FirstOrDefault(t => t.CampaignId == trusteeshipTask.CampaignId?.ToString()); + + if (newData != null) + { + trusteeshipTask.Budget = newData.Budget; + continue; + } + + } + if (trusteeshipTaskList.Any()) + await fsql.Update(trusteeshipTaskList).ExecuteUpdatedAsync(); + + logger.Info($"回调预算成功!回调消息:{JsonConvert.SerializeObject(requestList)}"); + } + + + + + /// + /// 提交 + /// + /// + /// + public async Task UpdateTrusteeshipPolicyData() + { + //获取当前待执行列表的数据 + var searchTrusteeshipTaskList = FirstWeekUpdateTrusteeshipPolicyData(); + searchTrusteeshipTaskList.AddRange(OverFirstWeekUpdateTrusteeshipPolicyData()); + await UpdateTrusteeshipPolicyData(searchTrusteeshipTaskList); + + await ThreeWeekUpdateBudget();//第三周或三周以上 每天初始化预算 + } + + + private async Task UpdateTrusteeshipPolicyData(List searchTrusteeshipTaskList) + { + var shopIds = searchTrusteeshipTaskList.Select(s => s.ShopId).Distinct().ToList(); + + + + + foreach (var shopId in shopIds) + { + var CampaignIds = searchTrusteeshipTaskList.Where(s => s.ShopId == shopId).Select(s => s.CampaignId).Distinct().ToArray(); + + + /* + { +"UserPin": "string", +"ShopId": "string", +"Start": "2023-12-02T06:08:15.492Z", +"End": "2023-12-02T06:08:15.492Z", +"ApiUrl": "string", +"CampaignIds": [ +"string" +] +} + */ + string url = "http://txapi.sbf.qiyue666.com/SanBanFu/Api/SendGetBudgetByCampaignList"; + HttpClient client = new HttpClient(); + client.Timeout = TimeSpan.FromMinutes(1); + + var postData = JsonConvert.SerializeObject(new + { + ShopId = shopId.ToString(), + Start = DateTime.Now.ToString(), + End = DateTime.Now.ToString(), + ApiUrl = host + "api/ExecuteTrusteeshipPolicy/BatchUpdateBudget", + CampaignIds = CampaignIds + }); + + HttpContent content = new StringContent(postData); + + var res = await client.PostAsync(url, content); + if (res.IsSuccessStatusCode) + { + //发送成功 + logger.Info($"请求预算接口成功! 店铺id:{shopId},请求参数{postData}"); + + //todo: redis + //await _cache.SetStringAsync("SBF:GetBudget:shopId", "0");//0未处理 三板斧批量获取预算 + + } + else + { + logger.Error($"请求预算接口失败,返回消息:{res?.Content?.ReadAsStringAsync()?.Result}, 店铺id:{shopId},请求参数{postData}"); + } + Thread.Sleep(500); + /* + { + "UserPin": "string", + "ShopId": "string", + "Start": "2023-12-02T06:59:26.115Z", + "End": "2023-12-02T06:59:26.115Z", + "ApiUrl": "string", + "AdgroupIds": [ + "string" + ] + } + */ + + var adgroupIds = searchTrusteeshipTaskList.Where(s => s.ShopId == shopId).Select(s => s.AdGroupId).Distinct().ToArray(); + url = "http://txapi.sbf.qiyue666.com/SanBanFu/Api/SendGetFeeByAdgroupList"; + postData = JsonConvert.SerializeObject(new + { + ShopId = shopId.ToString(), + Start = DateTime.Now.ToString(), + End = DateTime.Now.ToString(), + ApiUrl = host + "api/ExecuteTrusteeshipPolicy/BatchUpdateBudget", + AdgroupIds = adgroupIds + }); + content = new StringContent(postData); + res = await client.PostAsync("https://cactus.jd.com/request_algo?g_ty=ajax", content); + if (res.IsSuccessStatusCode) + { + //发送成功 + logger.Info($"请求出价接口成功! 店铺id:{shopId},请求参数{postData}"); + + } + else + { + logger.Error($"请求出价接口失败,返回消息:{res?.Content?.ReadAsStringAsync()?.Result}, 店铺id:{shopId},请求参数{postData}"); + } + Thread.Sleep(500); + } + } + + /// + /// 第一周 只需更新一次 + /// + /// + private List FirstWeekUpdateTrusteeshipPolicyData() + { + List excuteDate = new List(); + DateTime nowDate = DateTime.Now.Date; + + excuteDate.Add(nowDate.AddDays(4)); + excuteDate.Add(nowDate.AddDays(7)); + return fsql.Select().Where(s => s.IsEnd == false && s.PolicyType == Model.Enums.PolicyType.成长期策略包 && s.StartTrusteeshipDate != null && excuteDate.Contains(s.StartTrusteeshipDate.Value)).ToList();// + } + /// + /// 两周以上的(12:00 18:00 23:00要更新) + /// + /// + private List OverFirstWeekUpdateTrusteeshipPolicyData() + { + List excuteDate = new List(); + DateTime nowDate = DateTime.Now.Date; + + excuteDate.Add(nowDate.AddDays(11)); + excuteDate.Add(nowDate.AddDays(14)); + return fsql.Select().Where(s => s.IsEnd == false && s.PolicyType == Model.Enums.PolicyType.成长期策略包 && s.StartTrusteeshipDate != null && (excuteDate.Contains(s.StartTrusteeshipDate.Value) || nowDate.AddDays(-15) >= s.StartTrusteeshipDate.Value)).ToList(); + } + + /// + /// 更新 + /// + private async Task ThreeWeekUpdateBudget() + { + DateTime nowDate = DateTime.Now.Date; + //第三周 或者三周以上的数据 + var trusteeshipTaskList = fsql.Select().Where(s => s.IsEnd == false && s.PolicyType == Model.Enums.PolicyType.成长期策略包 && s.StartTrusteeshipDate != null && (nowDate.AddDays(-15) >= s.StartTrusteeshipDate.Value)).ToList(); + var primarySkuIdList = trusteeshipTaskList.Select(d => d.PrimarySkuId).Distinct().ToList(); + List excuteDate = new List + { + nowDate.AddDays(-1), + nowDate.AddDays(-2), + nowDate.AddDays(-3) + }; + + var primarySkuDailyList = fsql.Select().Where(a => primarySkuIdList.Contains(a.SkuId) && a.Date != null && excuteDate.Contains(a.Date.Value)).ToList(); + List AdjustLogList = new List(); + + var update = fsql.Update(); + foreach (var trusteeshipTask in trusteeshipTaskList) + { + var primarySkuProfit = primarySkuDailyList.Where(p => p.SkuId == trusteeshipTask.PrimarySkuId).Select(p => p.ProductLevelProfit - p.Cost).Average();//3天日均盈利 + + if (trusteeshipTask.AnchorBudget == null) + { + //todo: 提醒设置锚定预算 + continue; + } + decimal newBudget = primarySkuProfit.Value + trusteeshipTask.AnchorBudget.Value; + + + string adjustContent = string.Empty; + if (primarySkuProfit < 0) + { + newBudget = trusteeshipTask.AnchorBudget.Value; + + } + adjustContent = $"主Sku:{trusteeshipTask.PrimarySkuId},近三天日均收益:{primarySkuProfit}"; + decimal oldBudget = trusteeshipTask.Budget.Value;// + + + trusteeshipTask.Budget = newBudget;//更新预算 + + update.Where(u => u.Id == trusteeshipTask.Id).Set(u=>u.Budget, newBudget); + + //修改预算 + + //更新快车的 出价 + string url = "http://txapi.sbf.qiyue666.com/SanBanFu/Api/SendBudgetUpdate"; + HttpClient client = new HttpClient(); + client.Timeout = TimeSpan.FromMinutes(1); + + var postData = JsonConvert.SerializeObject(new + { + ShopId = trusteeshipTask.ShopId?.ToString(), + CampaignId = trusteeshipTask.CampaignId?.ToString(), + DayBudget = newBudget + + }); + + HttpContent content = new StringContent(postData); + + var res = await client.PostAsync("https://cactus.jd.com/request_algo?g_ty=ajax", content); + if (res.IsSuccessStatusCode) + { + //发送成功 + logger.Info($"请求变更出价接口成功! 店铺id:{trusteeshipTask.ShopId},请求参数{postData}"); + + AdjustLogList.Add(new Sbf_AdjustLog + { + AdjustType = Model.Enums.AdjustType.出价, + AdjustWay = Model.Enums.AdjustWay.调高, + CreateTime = DateTime.Now, + PolicyType = Model.Enums.PolicyType.成长期策略包, + TrusteeshipTaskId = trusteeshipTask.Id, + OperateContent = $"{adjustContent},原预算:{oldBudget},调整后预算:{newBudget}" + }); + + } + else + { + logger.Error($"请求变更出价接口失败,返回消息:{res?.Content?.ReadAsStringAsync()?.Result}, 店铺id:{trusteeshipTask.ShopId},请求参数{postData}"); + } + Thread.Sleep(500); + } + + await update.ExecuteUpdatedAsync();//批量更新 + + + if (AdjustLogList.Any()) + await fsql.Insert(AdjustLogList).ExecuteInsertedAsync(); + + } + + + + + + /// + /// 更新第二周 4 7天 和三周以上的数据 + /// + /// + public async Task JudgeBudgetCostOutUpdateTrusteeshipPolicyData() + { + var searchTrusteeshipTaskList = OverFirstWeekUpdateTrusteeshipPolicyData(); + await UpdateTrusteeshipPolicyData(searchTrusteeshipTaskList); + } + + + /// + /// 调高出价策略 + /// + /// + /// + public async Task RunGrowthPeriodIncreaseBidPriceTrusteeshipPolicy() + { + /* + 4 7 11 14 14天以上 + */ + //获取当前待执行列表的数据 + var searchTrusteeshipTaskList = FirstWeekUpdateTrusteeshipPolicyData();// 4 7天 + searchTrusteeshipTaskList.AddRange(OverFirstWeekUpdateTrusteeshipPolicyData());//满足条件的数据 + + var adgroupIds = searchTrusteeshipTaskList.Select(s => s.AdGroupId).Distinct().ToArray(); + + + + + //获取满足条件的所有单元 前三天花费的数据 + + List excuteDate = new List(); + DateTime nowDate = DateTime.Now.Date; + + excuteDate.Add(nowDate.AddDays(-1)); + excuteDate.Add(nowDate.AddDays(-2)); + excuteDate.Add(nowDate.AddDays(-3)); + + + + + //获取前三天花费 + var adgroupCostList = fsql.Select().Where(s => adgroupIds.Contains(s.AdGroupId) && s.Date != null && excuteDate.Contains(s.Date.Value)).ToList(); + + + List AdjustLogList = new List(); + var update = fsql.Update(); + foreach (var trusteeshipTask in searchTrusteeshipTaskList) + { + var beforeThreeDayMaxCost = adgroupCostList.Where(a => a.AdGroupId == trusteeshipTask.AdGroupId).Select(s => s.Cost).Max();//前三天花费最高 + if (trusteeshipTask.BidPrice == null || trusteeshipTask.BidPrice == 0) + { + logger.Error($"出价未获取,或者获取的出价为0,店铺id:{trusteeshipTask.ShopId},任务Id{trusteeshipTask.Id}"); + continue; + } + if (trusteeshipTask.Budget == null || trusteeshipTask.Budget == 0) + { + logger.Error($"预算未获取,或者获取的预算为0,店铺id:{trusteeshipTask.ShopId},任务Id{trusteeshipTask.Id}"); + continue; + } + decimal newBidPrice = 0m; + string adjustContent = string.Empty; + if (beforeThreeDayMaxCost < trusteeshipTask.Budget.Value * 0.3m)//前三天花费最高的一天低于预算的30% 出价调高50% + { + newBidPrice = Math.Round(trusteeshipTask.BidPrice.Value * (1 + 0.5m), 2); + adjustContent = "出价调高50%"; + } + else if (beforeThreeDayMaxCost < trusteeshipTask.Budget.Value * 0.5m)//前三天花费最高的一天低于预算的50% 出价调高35% + { + newBidPrice = Math.Round(trusteeshipTask.BidPrice.Value * (1 + 0.35m), 2); + adjustContent = "出价调高35%"; + } + else if (beforeThreeDayMaxCost < trusteeshipTask.Budget.Value * 0.7m)//前三天花费最高的一天低于预算的70% 出价调高20% + { + newBidPrice = Math.Round(trusteeshipTask.BidPrice.Value * (1 + 0.2m), 2); + adjustContent = "出价调高20%"; + } + else + { + logger.Info($"任务Id:{trusteeshipTask.Id},三天最高花费:{beforeThreeDayMaxCost},预算:{trusteeshipTask.BidPrice}"); + continue; + } + decimal oldBidPrice = trusteeshipTask.BidPrice.Value; + + trusteeshipTask.BidPrice = newBidPrice;//表里更新 新的出价 + update.Where(u => u.Id == trusteeshipTask.Id).Set(u => u.BidPrice, newBidPrice); + //更新快车的 出价 + string url = "http://txapi.sbf.qiyue666.com/SanBanFu/Api/SendFeeUpdate"; + HttpClient client = new HttpClient(); + client.Timeout = TimeSpan.FromMinutes(1); + + var postData = JsonConvert.SerializeObject(new + { + ShopId = trusteeshipTask.ShopId?.ToString(), + AdgroupId = trusteeshipTask.AdGroupId?.ToString(), + SearchFee = newBidPrice + + }); + + HttpContent content = new StringContent(postData); + + var res = await client.PostAsync("https://cactus.jd.com/request_algo?g_ty=ajax", content); + if (res.IsSuccessStatusCode) + { + //发送成功 + logger.Info($"请求变更出价接口成功! 店铺id:{trusteeshipTask.ShopId},请求参数{postData}"); + + AdjustLogList.Add(new Sbf_AdjustLog + { + AdjustType = Model.Enums.AdjustType.出价, + AdjustWay = Model.Enums.AdjustWay.调高, + CreateTime = DateTime.Now, + PolicyType = Model.Enums.PolicyType.成长期策略包, + TrusteeshipTaskId = trusteeshipTask.Id, + OperateContent = $"{adjustContent},原出价:{oldBidPrice},调整后出价:{newBidPrice}" + }); + + } + else + { + logger.Error($"请求变更出价接口失败,返回消息:{res?.Content?.ReadAsStringAsync()?.Result}, 店铺id:{trusteeshipTask.ShopId},请求参数{postData}"); + } + Thread.Sleep(500); + + } + await update.ExecuteUpdatedAsync(); + + if (AdjustLogList.Any()) + await fsql.Insert(AdjustLogList).ExecuteInsertedAsync(); + + } + + /// + /// 降低出价策略 + /// + /// + /// + public async Task RunGrowthPeriodLowerBidPriceTrusteeshipPolicy() + { + var searchTrusteeshipTaskList = OverFirstWeekUpdateTrusteeshipPolicyData();//获取两周的数据 + + + + List AdjustLogList = new List(); + var hour = DateTime.Now.Hour; + + List historyAdjustLogList = new List(); + if (hour != 12) + { + //获取当天的降低出价日志记录 + + historyAdjustLogList = fsql.Select().Where(l => l.CreateTime.Value.Date == DateTime.Now.Date && l.PolicyType == Model.Enums.PolicyType.成长期策略包 + && l.AdjustType == Model.Enums.AdjustType.出价 && l.AdjustWay == Model.Enums.AdjustWay.降低).ToList(); + } + + var update = fsql.Update(); + foreach (var trusteeshipTask in searchTrusteeshipTaskList) + { + if (trusteeshipTask.BidPrice == null || trusteeshipTask.BidPrice == 0) + { + logger.Error($"出价未获取,或者获取的出价为0,店铺id:{trusteeshipTask.ShopId},任务Id{trusteeshipTask.Id}"); + continue; + } + if (trusteeshipTask.Budget == null || trusteeshipTask.Budget == 0) + { + logger.Error($"预算未获取,或者获取的预算为0,店铺id:{trusteeshipTask.ShopId},任务Id{trusteeshipTask.Id}"); + continue; + } + if (trusteeshipTask.Budget == trusteeshipTask.NowCost)//花费耗尽 + { + decimal newBidPrice = 0m; + string adjustContent = string.Empty; + if (hour == 12)//预算早于12:00前花完 降低出价30% + { + newBidPrice = Math.Round(trusteeshipTask.BidPrice.Value * (1 - 0.3m), 2); + adjustContent = "降低出价30%"; + } + else if (hour == 18)//预算早于12:00前花完 降低出价20% + { + if (historyAdjustLogList.Any(h => h.TrusteeshipTaskId == trusteeshipTask.Id))//当天已经降低出价过 + continue; + + adjustContent = "降低出价20%"; + newBidPrice = Math.Round(trusteeshipTask.BidPrice.Value * (1 - 0.2m), 2); + } + else if (hour == 23)//预算早于12:00前花完 降低出价10% + { + if (historyAdjustLogList.Any(h => h.TrusteeshipTaskId == trusteeshipTask.Id))//当天已经降低出价过 + continue; + adjustContent = "降低出价10%"; + newBidPrice = Math.Round(trusteeshipTask.BidPrice.Value * (1 - 0.1m), 2); + } + else + {//出错 + continue; + } + decimal oldBidPrice = trusteeshipTask.BidPrice.Value; + + trusteeshipTask.BidPrice = newBidPrice;//表里更新 新的出价 + update.Where(u => u.Id == trusteeshipTask.Id).Set(u => u.BidPrice, newBidPrice); + //更新快车的 出价 + string url = "http://txapi.sbf.qiyue666.com/SanBanFu/Api/SendFeeUpdate"; + HttpClient client = new HttpClient(); + client.Timeout = TimeSpan.FromMinutes(1); + + var postData = JsonConvert.SerializeObject(new + { + ShopId = trusteeshipTask.ShopId?.ToString(), + AdgroupId = trusteeshipTask.AdGroupId?.ToString(), + SearchFee = newBidPrice + + }); + + HttpContent content = new StringContent(postData); + + var res = await client.PostAsync("https://cactus.jd.com/request_algo?g_ty=ajax", content); + if (res.IsSuccessStatusCode) + { + //发送成功 + logger.Info($"请求变更出价接口成功! 店铺id:{trusteeshipTask.ShopId},请求参数{postData}"); + + AdjustLogList.Add(new Sbf_AdjustLog + { + AdjustType = Model.Enums.AdjustType.出价, + AdjustWay = Model.Enums.AdjustWay.调高, + CreateTime = DateTime.Now, + PolicyType = Model.Enums.PolicyType.成长期策略包, + TrusteeshipTaskId = trusteeshipTask.Id, + OperateContent = $"{adjustContent},原出价:{oldBidPrice},调整后出价:{newBidPrice}" + }); + + } + else + { + logger.Error($"请求变更出价接口失败,返回消息:{res?.Content?.ReadAsStringAsync()?.Result}, 店铺id:{trusteeshipTask.ShopId},请求参数{postData}"); + } + Thread.Sleep(500); + + } + } + await update.ExecuteAffrowsAsync(); + if (AdjustLogList.Any()) + await fsql.Insert(AdjustLogList).ExecuteInsertedAsync(); + } + } +} diff --git a/SBF.Business/SBF.Business.csproj b/SBF.Business/SBF.Business.csproj index baf0b83..537ad9c 100644 --- a/SBF.Business/SBF.Business.csproj +++ b/SBF.Business/SBF.Business.csproj @@ -10,6 +10,7 @@ + diff --git a/SBF.Common/Extensions/ConverterExtensions.cs b/SBF.Common/Extensions/ConverterExtensions.cs index 31df3ef..1db29f2 100644 --- a/SBF.Common/Extensions/ConverterExtensions.cs +++ b/SBF.Common/Extensions/ConverterExtensions.cs @@ -26,4 +26,6 @@ } } } + + } diff --git a/SBF.Model/Db/Trusteeship/Sbf_AdjustLog.cs b/SBF.Model/Db/Trusteeship/Sbf_AdjustLog.cs new file mode 100644 index 0000000..cb976ac --- /dev/null +++ b/SBF.Model/Db/Trusteeship/Sbf_AdjustLog.cs @@ -0,0 +1,52 @@ +using FreeSql.DataAnnotations; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SBF.Model.Db.Trusteeship +{ + public class Sbf_AdjustLog + { + [Column(DbType = "bigint", IsPrimary = true)] + public long Id { get; set; } + + + /// + /// 托管任务Id + /// + [Column(DbType = "bigint")] + public long TrusteeshipTaskId { get; set; } + + /// + /// 创建时间 + /// + [Column(DbType = "datetime")] + public DateTime? CreateTime { get; set; } + + /// + /// 策略类型 成长期策略包 = 0, 冲击主力策略包 = 1, 稳定期策略包 = 2, 主力期策略包 = 3 + /// + [Column(MapType = typeof(int?))] + public Enums.PolicyType? PolicyType { get; set; } = Enums.PolicyType.成长期策略包; + + /// + /// 调整类型(出价=0,预算=1) + /// + [Column(MapType = typeof(int?))] + public Enums.AdjustType AdjustType { get; set; } + + /// + /// 调整方式(调高 = 0, 降低 = 1, 指定值 = 2) + /// + [Column(MapType = typeof(int?))] + public Enums.AdjustWay AdjustWay { get; set; } + + + /// + /// 操作内容 + /// + public string OperateContent { get; set; } + } +} diff --git a/SBF.Model/Db/Trusteeship/Sbf_TrusteeshipTask.cs b/SBF.Model/Db/Trusteeship/Sbf_TrusteeshipTask.cs index 85456fd..c412dd8 100644 --- a/SBF.Model/Db/Trusteeship/Sbf_TrusteeshipTask.cs +++ b/SBF.Model/Db/Trusteeship/Sbf_TrusteeshipTask.cs @@ -52,6 +52,12 @@ namespace SBF.Model.Db [Column(DbType = "decimal(18,2)")] public decimal? Budget { get; set; } = 0.00M; + /// + /// 当天花费 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? NowCost{ get; set; } = 0.00M; + /// /// 业务类型/渠道 快车=2,智能投放=134217728 /// @@ -111,6 +117,18 @@ namespace SBF.Model.Db [Column(MapType = typeof(int?))] public Enums.PolicyType? PolicyType { get; set; } = Enums.PolicyType.成长期策略包; + /// + /// 主SkuId + /// + [Column(StringLength = 50)] + public string PrimarySkuId { get; set; } + + /// + /// 锚定预算 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? AnchorBudget { get; set; } + #region Product [Column(IsIgnore = true)] public DateTime? ProductCreateTime { get; set; } diff --git a/SBF.Model/Dto/Request/ExecuteTrusteeshipPolicy/BatchUpdateBidPriceRequest.cs b/SBF.Model/Dto/Request/ExecuteTrusteeshipPolicy/BatchUpdateBidPriceRequest.cs new file mode 100644 index 0000000..df87faa --- /dev/null +++ b/SBF.Model/Dto/Request/ExecuteTrusteeshipPolicy/BatchUpdateBidPriceRequest.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SBF.Model.Dto +{ + public class BatchUpdateBidPriceRequest + { + /* [{AdgroupId:'',SearchFee:0,End:2022-01-01,Start:2022-01-01,ShopId:123,TotalCost:1,RenQunCost:1,KeyWordCost:1,ShopCost:1}] */ + + /// + /// 单元Id + /// + public string AdgroupId { get; set; } + + /// + /// 出价 + /// + public decimal SearchFee { get; set; } + + /// + /// 开始时间 + /// + public DateTime? Start { get; set; } + /// + /// 结束时间 + /// + public DateTime? End { get; set; } + + /// + /// 店铺id + /// + public long ShopId { get; set; } + + /// + /// 单元总花费 + /// + public decimal TotalCost { get; set; } + + + /// + /// 人群花费 + /// + public decimal? RenQunCost { get; set; } + + + /// + /// 关键词花费 + /// + public decimal? KeyWordCost { get; set; } + + + /// + /// 店铺商品花费 + /// + public decimal? ShopCost { get; set; } + } +} diff --git a/SBF.Model/Dto/Request/ExecuteTrusteeshipPolicy/BatchUpdateBudgetRequest.cs b/SBF.Model/Dto/Request/ExecuteTrusteeshipPolicy/BatchUpdateBudgetRequest.cs new file mode 100644 index 0000000..9638809 --- /dev/null +++ b/SBF.Model/Dto/Request/ExecuteTrusteeshipPolicy/BatchUpdateBudgetRequest.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SBF.Model.Dto +{ + public class BatchUpdateBudgetRequest + { + + /* {CampaignId:'',Budget:0,End:2022-01-01,Start:2022-01-01,ShopId:123,TotalCost:1} */ + + /// + /// 计划Id + /// + public string CampaignId { get; set; } + + /// + /// 预算 + /// + public decimal? Budget { get; set; } + + /// + /// 开始时间 + /// + public DateTime? Start { get; set; } + /// + /// 结束时间 + /// + public DateTime? End { get; set; } + + /// + /// 店铺id + /// + public long? ShopId { get; set; } + + /// + /// 计划总花费 + /// + public decimal? TotalCost { get; set; } + } +} diff --git a/SBF.Model/Enums.cs b/SBF.Model/Enums.cs index 5c23f3a..8fe6def 100644 --- a/SBF.Model/Enums.cs +++ b/SBF.Model/Enums.cs @@ -404,5 +404,21 @@ { 成长期策略包 = 0, 冲击主力策略包 = 1, 稳定期策略包 = 2, 主力期策略包 = 3 } + + /// + /// 调整类型(出价=0,预算=1) + /// + public enum AdjustType + { + 出价 = 0, 预算 = 1 + } + + /// + /// 调整方式(调高 = 0, 降低 = 1, 指定值 = 2) + /// + public enum AdjustWay + { + 调高 = 0, 降低 = 1, 指定值 = 2 + } } } diff --git a/SBF.Model/SBF.Model.csproj b/SBF.Model/SBF.Model.csproj index 5d8b3da..926bb4a 100644 --- a/SBF.Model/SBF.Model.csproj +++ b/SBF.Model/SBF.Model.csproj @@ -14,6 +14,7 @@ +