diff --git a/SBF.API/.config/dotnet-tools.json b/SBF.API/.config/dotnet-tools.json new file mode 100644 index 0000000..b0e38ab --- /dev/null +++ b/SBF.API/.config/dotnet-tools.json @@ -0,0 +1,5 @@ +{ + "version": 1, + "isRoot": true, + "tools": {} +} \ No newline at end of file diff --git a/SBF.API/Controllers/BaseApiController.cs b/SBF.API/Controllers/BaseApiController.cs new file mode 100644 index 0000000..524f1db --- /dev/null +++ b/SBF.API/Controllers/BaseApiController.cs @@ -0,0 +1,36 @@ +using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Primitives; + +namespace SBF.API.Controllers +{ + [Produces("application/json")] + [Route("Api/[Controller]/[Action]")] + [ApiController] + [EnableCors("cors")] + public class BaseApiController : ControllerBase + { + protected IHttpContextAccessor httpContextAccessor; + public BaseApiController(IHttpContextAccessor httpContextAccessor) + { + this.httpContextAccessor = httpContextAccessor; + } + + protected string GetUserId() + { + return httpContextAccessor?.HttpContext?.User.Claims.Where(x => x.Type == "userId")?.FirstOrDefault()?.Value; + } + + protected string GetToken() + { + httpContextAccessor.HttpContext.Request.Headers.TryGetValue("Authorization", out StringValues token); + return token; + } + + protected string GetClientCode() + { + httpContextAccessor.HttpContext.Request.Headers.TryGetValue("ClientCode", out StringValues clientCode); + return clientCode; + } + } +} diff --git a/SBF.API/Controllers/TrusteeshipController.cs b/SBF.API/Controllers/TrusteeshipController.cs new file mode 100644 index 0000000..5bcc785 --- /dev/null +++ b/SBF.API/Controllers/TrusteeshipController.cs @@ -0,0 +1,49 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using SBF.Business; +using SBF.Model.Dto; + +namespace SBF.API.Controllers +{ + + public class TrusteeshipController : BaseApiController + { + private TrusteeshipBusiness trusteeshipBusiness; + public TrusteeshipController(IHttpContextAccessor httpContextAccessor, TrusteeshipBusiness trusteeshipBusiness) : base(httpContextAccessor) + { + this.trusteeshipBusiness = trusteeshipBusiness; + } + + /// + /// 搜索Sku参与的推广渠道 + /// + /// + /// + [HttpPost] + public IList SearchSkuJoinPopularizeChannel([FromBody] SearchSkuJoinPopularizeChannelRequest request) + { + return trusteeshipBusiness.SearchSkuJoinPopularizeChannel(request); + } + + /// + /// 查询托管任务列表 + /// + /// + /// + [HttpPost] + public ListResponse QueryTrusteeship([FromBody] QueryTrusteeshipRequest request) + { + return trusteeshipBusiness.QueryTrusteeship(request); + } + + /// + /// 创建托管任务 + /// + /// + [HttpPost] + public void CreateTrusteeship([FromBody] CreateTrusteeshipRequest request) + { + trusteeshipBusiness.CreateTrusteeship(request); + } + } +} diff --git a/SBF.API/Filters/ResultFilter.cs b/SBF.API/Filters/ResultFilter.cs new file mode 100644 index 0000000..9739f92 --- /dev/null +++ b/SBF.API/Filters/ResultFilter.cs @@ -0,0 +1,30 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using SBF.Common.Models; + +namespace SBF.API.Filters +{ + public class ResultFilter : IResultFilter + { + public void OnResultExecuted(ResultExecutedContext context) + { + + } + + public void OnResultExecuting(ResultExecutingContext context) + { + if (context.Result is ObjectResult) + { + var objectResult = context.Result as ObjectResult; + if (!(objectResult.Value is ApiResponse)) + { + objectResult.Value = new ApiResponse() { Data = objectResult.Value }; + } + } + else if (context.Result is EmptyResult) + { + context.Result = new ObjectResult(new ApiResponse()); + } + } + } +} diff --git a/SBF.API/Middlewares/ClientVersionValidationMiddleWare.cs b/SBF.API/Middlewares/ClientVersionValidationMiddleWare.cs new file mode 100644 index 0000000..78a15f2 --- /dev/null +++ b/SBF.API/Middlewares/ClientVersionValidationMiddleWare.cs @@ -0,0 +1,55 @@ +using Microsoft.Extensions.Options; +using Microsoft.Extensions.Primitives; +using SBF.Common.Models; + +namespace SBF.API.Middlewares +{ + public class ClientVersionValidationMiddleWare + { + /// + /// 管道请求委托 + /// + private RequestDelegate _next; + + private IDictionary apiVersionDictionary; + + private IOptionsMonitor> _monitor; + + public ClientVersionValidationMiddleWare(RequestDelegate requestDelegate, IOptionsMonitor> monitor) + { + _next = requestDelegate; + _monitor = monitor; + apiVersionDictionary = new Dictionary(); + } + + public async Task Invoke(HttpContext context) + { + try + { + Console.WriteLine(context.Request.Path); + var apiRequirement = _monitor.CurrentValue.FirstOrDefault(x => x.Api.Equals(context.Request.Path, StringComparison.CurrentCultureIgnoreCase)); + if (apiRequirement != null) + { + if (!context.Request.Headers.TryGetValue("ClientVersion", out StringValues clientVersionStr)) + throw new BusinessException("缺少版本信息,请更新司南"); + if (!int.TryParse(clientVersionStr, out int clientVersion)) + throw new BusinessException("版本信息不正确,请更新司南"); + if (clientVersion < apiRequirement.MinimumVersion) + throw new BusinessException("当前请求需更新司南"); + } + await _next(context); //调用管道执行下一个中间件 + } + catch + { + throw; + } + } + } + + public class ClientVersionValidationModel + { + public string Api { get; set; } + + public int MinimumVersion { get; set; } + } +} diff --git a/SBF.API/Middlewares/CustomExceptionMiddleWare.cs b/SBF.API/Middlewares/CustomExceptionMiddleWare.cs new file mode 100644 index 0000000..ac949ac --- /dev/null +++ b/SBF.API/Middlewares/CustomExceptionMiddleWare.cs @@ -0,0 +1,86 @@ +using Newtonsoft.Json; +using SBF.Common.Log; +using SBF.Common.Models; +using System.Text; + +namespace SBF.API.Middlewares +{ + public class CustomExceptionMiddleWare + { + /// + /// 管道请求委托 + /// + private RequestDelegate _next; + + /// + /// 需要处理的状态码字典 + /// + private IDictionary _exceptionStatusCodeDic; + + //private NLogManager nLogManager; + + private NLogManager nLogManager; + + public CustomExceptionMiddleWare(RequestDelegate next, NLogManager nLogManager) + { + _next = next; + //this.logger = logger; + this.nLogManager = nLogManager; + _exceptionStatusCodeDic = new Dictionary + { + { 401, "未授权的请求" }, + { 404, "找不到该资源" }, + { 403, "访问被拒绝" }, + { 500, "服务器发生意外的错误" }, + { 503, "服务不可用" } + //其余状态自行扩展 + }; + } + + public async Task Invoke(HttpContext context) + { + try + { + await _next(context); //调用管道执行下一个中间件 + } + catch (Exception ex) + { + if (ex is BusinessException) + { + var busEx = ex as BusinessException; + context.Response.StatusCode = 200; //业务异常时将Http状态码改为200 + await ErrorHandle(context, busEx.Code, busEx.Message); + } + else + { + context.Response.Clear(); + context.Response.StatusCode = 500; //发生未捕获的异常,手动设置状态码 + //logger.Error(ex); //记录错误 + nLogManager.Default().Error(ex); + } + } + finally + { + if (_exceptionStatusCodeDic.TryGetValue(context.Response.StatusCode, out string exMsg)) + { + await ErrorHandle(context, context.Response.StatusCode, exMsg); + } + } + } + + /// + /// 处理方式:返回Json格式 + /// + /// + /// + /// + /// + private async Task ErrorHandle(HttpContext context, int code, string exMsg) + { + var apiResponse = ApiResponse.Error(code, exMsg); + var serialzeStr = JsonConvert.SerializeObject(apiResponse); + context.Response.ContentType = "application/json"; + await context.Response.WriteAsync(serialzeStr, Encoding.UTF8); + } + } +} diff --git a/SBF.API/Program.cs b/SBF.API/Program.cs new file mode 100644 index 0000000..7ee8d88 --- /dev/null +++ b/SBF.API/Program.cs @@ -0,0 +1,131 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.OpenApi.Models; +using Newtonsoft.Json.Serialization; +using SBF.API.Filters; +using SBF.API.Middlewares; +using SBF.Business; +using SBF.Common.Extensions; +using SBF.Common.Http; +using SBF.Common.Log; +using SBF.Common.Models; +using SBF.Model; +using System.Reflection; +using Yitter.IdGenerator; + +var builder = WebApplication.CreateBuilder(args); +var services = builder.Services; +var configuration = builder.Configuration; + +services.AddMemoryCache(); +var idOption = new IdGeneratorOptions(1); +var idGenerator = new DefaultIdGenerator(idOption); +services.AddSingleton(typeof(IIdGenerator), idGenerator); + +var fsql = new FreeSql.FreeSqlBuilder().UseConnectionString(FreeSql.DataType.MySql, configuration.GetConnectionString("BBWYCDB")).Build(); +services.AddSingleton(typeof(IFreeSql), fsql); +var fsql2 = new FreeSql.FreeSqlBuilder().UseConnectionString(FreeSql.DataType.MySql, configuration.GetConnectionString("MDSDB")).Build(); +var fsql3 = new FreeSql.FreeSqlBuilder().UseConnectionString(FreeSql.DataType.MySql, configuration.GetConnectionString("XXDB")).Build(); + +services.AddSingleton(new FreeSqlMultiDBManager() +{ + MDSfsql = fsql2, + BBWYCfsql = fsql, + XXfsql = fsql3 +}); + +services.AddSingleton(); +services.AddSingleton(); +services.AddSingleton(); +services.BatchRegisterServices(new Assembly[] { Assembly.Load("SBF.Business") }, typeof(IDenpendency), ServiceLifetime.Singleton); +services.AddMemoryCache(); +services.AddControllers(); +services.AddHttpContextAccessor(); +services.AddHttpClient(); +services.AddHttpClient("gzip").ConfigurePrimaryHttpMessageHandler(handler => new HttpClientHandler() +{ + AutomaticDecompression = System.Net.DecompressionMethods.GZip +}); +services.AddCors(options => +{ + options.AddPolicy("cors", p => + { + p.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader(); + }); +}); +services.AddControllers(c => +{ + c.Filters.Add(); + c.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true; +}).AddNewtonsoftJson(setupAction => +{ + setupAction.SerializerSettings.ContractResolver = new DefaultContractResolver(); + setupAction.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss"; + //setupAction.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore; + //setupAction.SerializerSettings.DefaultValueHandling = Newtonsoft.Json.DefaultValueHandling.Include; +}); + +services.AddMapper(new MappingProfiles()); +services.AddEndpointsApiExplorer(); +services.AddSwaggerGen(c => +{ + c.SwaggerDoc("v1", new OpenApiInfo + { + Version = "v1.0.0", + Title = "SBF API", + Description = "ע\r\n1.زƲôշ\r\n2.ApiResponseΪض(Code,Data,Message),ӿеķֵData\r\n3.Code=200" + }); + //JWT֤ + c.AddSecurityDefinition(JwtBearerDefaults.AuthenticationScheme, new OpenApiSecurityScheme + { + Scheme = JwtBearerDefaults.AuthenticationScheme, + BearerFormat = "JWT", + Type = SecuritySchemeType.ApiKey, + Name = "Authorization", + In = ParameterLocation.Header, + Description = "Authorization:Bearer {your JWT token}
", + }); + c.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme{Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = JwtBearerDefaults.AuthenticationScheme + } + }, + new string[] { } + } + }); + + var executingAssembly = Assembly.GetExecutingAssembly(); + var assemblyNames = executingAssembly.GetReferencedAssemblies().Union(new AssemblyName[] { executingAssembly.GetName() }).ToArray(); + Array.ForEach(assemblyNames, (assemblyName) => + { + //var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; + var xmlFile = $"{assemblyName.Name}.xml"; + var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); + if (!File.Exists(xmlPath)) + return; + c.IncludeXmlComments(xmlPath, true); + }); +}); + + + +var app = builder.Build(); + +app.UseSwagger(c => c.SerializeAsV2 = true) + .UseSwaggerUI(c => + { + c.SwaggerEndpoint("/swagger/v1/swagger.json", "SiNan API"); + c.RoutePrefix = string.Empty; + }); + +app.UseMiddleware(); +app.UseRouting(); +app.UseCors("cors"); +app.UseMiddleware(); +app.UseAuthorization(); +app.MapControllers(); + +app.Run(); diff --git a/SBF.API/Properties/launchSettings.json b/SBF.API/Properties/launchSettings.json new file mode 100644 index 0000000..fcb6328 --- /dev/null +++ b/SBF.API/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:13038", + "sslPort": 44318 + } + }, + "profiles": { + "SBF.API": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "weatherforecast", + "applicationUrl": "https://localhost:7055;http://localhost:5055", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "weatherforecast", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/SBF.API/SBF.API.csproj b/SBF.API/SBF.API.csproj new file mode 100644 index 0000000..0f5e3ae --- /dev/null +++ b/SBF.API/SBF.API.csproj @@ -0,0 +1,28 @@ + + + + net6.0 + enable + enable + True + + + + + + + + + + + + + + + + + + + + + diff --git a/SBF.API/appsettings.Development.json b/SBF.API/appsettings.Development.json new file mode 100644 index 0000000..770d3e9 --- /dev/null +++ b/SBF.API/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "DetailedErrors": true, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/SBF.API/appsettings.json b/SBF.API/appsettings.json new file mode 100644 index 0000000..5306809 --- /dev/null +++ b/SBF.API/appsettings.json @@ -0,0 +1,16 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "Secret": "D96BFA5B-F2AF-45BC-9342-5A55C3F9BBB0", + "ConnectionStrings": { + //"DB": "data source=rm-bp1508okrh23710yfao.mysql.rds.aliyuncs.com;port=3306;user id=qyroot;password=kaicn1132+-;initial catalog=bbwy;charset=utf8;sslmode=none;" + "BBWYCDB": "data source=rm-bp1508okrh23710yfao.mysql.rds.aliyuncs.com;port=3306;user id=qyroot;password=kaicn1132+-;initial catalog=bbwy_test;charset=utf8;sslmode=none;", + "MDSDB": "data source=rm-bp1508okrh23710yfao.mysql.rds.aliyuncs.com;port=3306;user id=qyroot;password=kaicn1132+-;initial catalog=mds;charset=utf8;sslmode=none;", + "XXDB": "data source=rm-bp1508okrh23710yfao.mysql.rds.aliyuncs.com;user id=qyroot;password=kaicn1132+-;initial catalog=jdxx;charset=utf8;sslmode=none;" + } +} diff --git a/SBF.Business/BaseBusiness.cs b/SBF.Business/BaseBusiness.cs new file mode 100644 index 0000000..336e2dd --- /dev/null +++ b/SBF.Business/BaseBusiness.cs @@ -0,0 +1,19 @@ +using SBF.Common.Log; +using Yitter.IdGenerator; + +namespace SiNan.Business +{ + public class BaseBusiness + { + protected IFreeSql fsql; + protected NLogManager nLogManager; + protected IIdGenerator idGenerator; + + public BaseBusiness(IFreeSql fsql, NLogManager nLogManager, IIdGenerator idGenerator) + { + this.fsql = fsql; + this.nLogManager = nLogManager; + this.idGenerator = idGenerator; + } + } +} diff --git a/SBF.Business/FreeSqlMultiDBManager.cs b/SBF.Business/FreeSqlMultiDBManager.cs new file mode 100644 index 0000000..445627f --- /dev/null +++ b/SBF.Business/FreeSqlMultiDBManager.cs @@ -0,0 +1,13 @@ +namespace SBF.Business +{ + public class FreeSqlMultiDBManager + { + //public IFreeSql BBWYBfsql { get; set; } + + public IFreeSql MDSfsql { get; set; } + + public IFreeSql BBWYCfsql { get; set; } + + public IFreeSql XXfsql { get; set; } + } +} diff --git a/SBF.Business/SBF.Business.csproj b/SBF.Business/SBF.Business.csproj new file mode 100644 index 0000000..baf0b83 --- /dev/null +++ b/SBF.Business/SBF.Business.csproj @@ -0,0 +1,24 @@ + + + + net6.0 + enable + enable + True + + + + + + + + + + + + + + + + + diff --git a/SBF.Business/TaskSchedulerManager.cs b/SBF.Business/TaskSchedulerManager.cs new file mode 100644 index 0000000..9c1d764 --- /dev/null +++ b/SBF.Business/TaskSchedulerManager.cs @@ -0,0 +1,21 @@ +using System.Threading.Tasks.Schedulers; + +namespace SBF.Business +{ + public class TaskSchedulerManager + { + public LimitedConcurrencyLevelTaskScheduler AggregationSpuGOIScheduler { get; private set; } + + + public LimitedConcurrencyLevelTaskScheduler AggregationCampaignGOIScheduler { get; private set; } + + public LimitedConcurrencyLevelTaskScheduler AggregationAdGroupGOIScheduler { get; private set; } + + public TaskSchedulerManager() + { + AggregationSpuGOIScheduler = new LimitedConcurrencyLevelTaskScheduler(5); + AggregationCampaignGOIScheduler = new LimitedConcurrencyLevelTaskScheduler(5); + AggregationAdGroupGOIScheduler = new LimitedConcurrencyLevelTaskScheduler(5); + } + } +} diff --git a/SBF.Business/TrusteeshipBusiness.cs b/SBF.Business/TrusteeshipBusiness.cs new file mode 100644 index 0000000..c5904dd --- /dev/null +++ b/SBF.Business/TrusteeshipBusiness.cs @@ -0,0 +1,253 @@ +using SBF.Common.Extensions; +using SBF.Common.Log; +using SBF.Common.Models; +using SBF.Model.Db; +using SBF.Model.Dto; +using SiNan.Business; +using Yitter.IdGenerator; + +namespace SBF.Business +{ + public class TrusteeshipBusiness : BaseBusiness, IDenpendency + { + public TrusteeshipBusiness(IFreeSql fsql, NLogManager nLogManager, IIdGenerator idGenerator) : base(fsql, nLogManager, idGenerator) + { + } + + /// + /// 搜索SKU参与的推广渠道 + /// + /// + /// + public IList SearchSkuJoinPopularizeChannel(SearchSkuJoinPopularizeChannelRequest request) + { + if (request.ShopId == null || request.ShopId == 0) + throw new BusinessException("缺少ShopId"); + + + var skuList = new List(); + if (request.SkuList != null && request.SkuList.Count() > 0) + { + request.Sku = string.Empty; + request.Spu = string.Empty; + skuList.AddRange(request.SkuList); + } + + if (!string.IsNullOrEmpty(request.Sku)) + { + request.Spu = string.Empty; + skuList.Add(request.Sku); + } + + if (!string.IsNullOrEmpty(request.Spu)) + { + skuList.AddRange(fsql.Select() + .InnerJoin((ps, p) => ps.ProductId == p.Id) + .Where((ps, p) => ps.State == 1 && p.State == 8) + .WhereIf(!string.IsNullOrEmpty(request.Spu), (ps, p) => ps.ProductId == request.Spu) + .ToList((ps, p) => ps.Id)); + } + + if (skuList.Count == 0) + throw new BusinessException("缺少sku信息"); + + var yesterDay = DateTime.Now.Date.AddDays(-1); + var list = fsql.Select() + .LeftJoin((ads, c, ad) => ads.CampaignId == c.CampaignId && ads.Date == c.Date) + .LeftJoin((ads, c, ad) => ads.AdGroupId == ad.AdGroupId && ads.Date == ad.Date) + .Where((ads, c, ad) => ads.ShopId == request.ShopId && ads.Date == yesterDay) + .WhereIf(skuList.Count() > 1, (ads, c, ad) => skuList.Contains(ads.Sku)) + .WhereIf(skuList.Count() == 1, (ads, c, ad) => ads.Sku == skuList[0]) + .Where((ads, c, ad) => !fsql.Select() + .Where(s => s.ShopId == request.ShopId && s.CampaignId == ads.CampaignId && s.SkuId == ads.Sku) + .Any()) + .GroupBy((ads, c, ad) => new { ads.BusinessType, ads.CampaignId, c.CampaignName, ads.AdGroupId, ad.AdGroupName, ads.AdId, ads.AdName, ads.Sku }) + .ToList(g => new SkuJoinPopularizeChannelResponse + { + BusinessType = g.Key.BusinessType.Value, + CampaignId = g.Key.CampaignId, + CampaignName = g.Key.CampaignName, + AdGroupId = g.Key.AdGroupId, + AdGroupName = g.Key.AdGroupName, + AdId = g.Key.AdId, + AdName = g.Key.AdName, + Sku = g.Key.Sku + }); + return list; + } + + + /// + /// 查询托管任务 + /// + /// + /// + public ListResponse QueryTrusteeship(QueryTrusteeshipRequest request) + { + if (request.ShopId == null || request.ShopId == 0) + throw new BusinessException("缺少ShopId"); + + var list = fsql.Select() + .InnerJoin((t, p, ps) => t.SpuId == p.Id) + .InnerJoin((t, p, ps) => t.SkuId == ps.Id) + .Where((t, p, ps) => t.ShopId == request.ShopId) + .WhereIf(request.Stage != null, (t, p, ps) => p.Stage == request.Stage) + .WhereIf(!string.IsNullOrEmpty(request.Spu), (t, p, ps) => t.SpuId == request.Spu) + .WhereIf(!string.IsNullOrEmpty(request.Sku), (t, p, ps) => t.SkuId == request.Sku) + .WhereIf(!string.IsNullOrEmpty(request.Title), (t, p, ps) => p.Title.StartsWith(request.Title)) + .OrderByDescending((t, p, ps) => t.CreateTime) + .Page(request.PageIndex, request.PageSize) + .Count(out var count) + .ToList((t, p, ps) => new Sbf_TrusteeshipTask() + { + Id = t.Id, + ShopId = t.ShopId, + SpuId = t.SpuId, + SkuId = t.SkuId, + ActualAmountInTrusteeship = t.ActualAmountInTrusteeship, + AdGroupId = t.AdGroupId, + AdGroupName = t.AdGroupName, + AdId = t.AdId, + AdName = t.AdName, + BidPrice = t.BidPrice, + Budget = t.Budget, + BusinessType = t.BusinessType, + CampaginName = t.CampaginName, + CampaignId = t.CampaignId, + CostInTrusteeship = t.CostInTrusteeship, + CreateTime = t.CreateTime, + EndTime = t.EndTime, + IsEnd = t.IsEnd, + StartTrusteeshipDate = t.StartTrusteeshipDate, + + Logo = ps.Logo, + Price = ps.Price, + SkuTitle = ps.Title, + SkuState = ps.State, + SkuCreateTime = ps.CreateTime, + CategoryId = ps.CategoryId, + CategoryName = ps.CategoryName, + + MainSkuId = p.MainSkuId, + ProductCreateTime = p.CreateTime, + ProductItemNum = p.ProductItemNum, + ProductState = p.State, + ProductTitle = p.Title, + Stage = p.Stage, + Platform = p.Platform + }) + .Map>(); + + var startDate = DateTime.Now.Date.AddDays(-7); + var endDate = DateTime.Now.Date.AddDays(-1); + + var skuIdList = list.Select(x => x.SkuId).Distinct().ToList(); + var spuIdList = list.Select(x => x.SpuId).Distinct().ToList(); + + + #region 推广花费 + var costList = fsql.Select() + .Where(x => x.ShopId == request.ShopId && + x.Date >= startDate && x.Date <= endDate && + skuIdList.Contains(x.SkuId)) + .ToList(x => new + { + x.Date, + x.SkuId, + x.CampaignId, + x.BusinessType, + x.Cost + }); + //.GroupBy(x => new { x.Date, x.SkuId, x.CampaignId, x.BusinessType }) + //.ToList(g => new + //{ + // Date = g.Key.Date, + // SkuId = g.Key.SkuId, + // CampaignId = g.Key.CampaignId, + // BusinessType = g.Key.BusinessType, + // Cost = g.Sum(g.Value.Cost) + //}); + #endregion + + #region 商品营业额 + var actualAmountList = fsql.Select() + .Where(x => x.ShopId == request.ShopId && + x.Date >= startDate && x.Date <= endDate && + spuIdList.Contains(x.ProductId)) + .ToList(x => new + { + x.Date, + x.ProductId, + x.Cost, + x.ActualAmount + }); + + #endregion + + #region 免费访客量 + + #endregion + + foreach (var task in list) + { + task.CostByDateList = costList.Where(x => x.SkuId == task.SkuId && x.CampaignId == task.CampaignId && x.BusinessType == task.BusinessType) + .OrderBy(x => x.Date) + .Select(x => new NumberByDate() + { + Date = x.Date, + Value = x.Cost + }).ToList(); + + task.ActualAmountByDateList = actualAmountList.Where(x => x.ProductId == task.SpuId) + .OrderBy(x => x.Date) + .Select(x => new NumberByDate() + { + Date = x.Date, + Value = x.ActualAmount + }).ToList(); + } + + return new ListResponse() { ItemList = list, Count = count }; + } + + public void CreateTrusteeship(CreateTrusteeshipRequest request) + { + if (request.SkuList == null || request.SkuList.Count() == 0) + throw new BusinessException("缺少SkuList"); + var joinList = SearchSkuJoinPopularizeChannel(new SearchSkuJoinPopularizeChannelRequest() + { + ShopId = request.ShopId, + SkuList = request.SkuList + }); + + var insertList = joinList.Select(x => new Sbf_TrusteeshipTask() + { + Id = idGenerator.NewLong(), + ActualAmountInTrusteeship = 0M, + AdGroupId = x.AdGroupId, + AdGroupName = x.AdGroupName, + AdId = x.AdId, + AdName = x.AdName, + BidPrice = 0M, + Budget = 0M, + BusinessType = x.BusinessType, + CampaginName = x.CampaignName, + CampaignId = x.CampaignId, + CostInTrusteeship = 0M, + CreateTime = DateTime.Now, + EndTime = null, + IsEnd = false, + StartTrusteeshipDate = DateTime.Now.Date.AddDays(1), + ShopId = request.ShopId, + SkuId = x.Sku + }).ToList(); + + var skuIdList = insertList.Select(x => x.SkuId).Distinct().ToList(); + var psList = fsql.Select(skuIdList).ToList(); + foreach (var insertTask in insertList) + insertTask.SpuId = psList.FirstOrDefault(ps => ps.Id == insertTask.SkuId)?.ProductId; + + fsql.Insert(insertList).ExecuteAffrows(); + } + } +} diff --git a/SBF.Common/Extensions/ConverterExtensions.cs b/SBF.Common/Extensions/ConverterExtensions.cs new file mode 100644 index 0000000..31df3ef --- /dev/null +++ b/SBF.Common/Extensions/ConverterExtensions.cs @@ -0,0 +1,29 @@ +namespace SBF.Common.Extensions +{ + public static class ConverterExtensions + { + public static long? ToInt64(this object? o) + { + try + { + return Convert.ToInt64(o); + } + catch + { + return null; + } + } + + public static int? ToInt32(this object? o) + { + try + { + return Convert.ToInt32(o); + } + catch + { + return null; + } + } + } +} diff --git a/SBF.Common/Extensions/CopyExtensions.cs b/SBF.Common/Extensions/CopyExtensions.cs new file mode 100644 index 0000000..e4c9867 --- /dev/null +++ b/SBF.Common/Extensions/CopyExtensions.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace SBF.Common.Extensions +{ + public static class CopyExtensions + { + public static T Copy(this T p) + { + return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(p)); + } + } +} diff --git a/SBF.Common/Extensions/DateTimeExtension.cs b/SBF.Common/Extensions/DateTimeExtension.cs new file mode 100644 index 0000000..8c5e241 --- /dev/null +++ b/SBF.Common/Extensions/DateTimeExtension.cs @@ -0,0 +1,99 @@ +using System.Runtime.InteropServices; + +namespace SBF.Common.Extensions +{ + public static class DateTimeExtension + { + private static readonly DateTime beginTime = new DateTime(1970, 1, 1, 0, 0, 0, 0); + + /// + /// 时间戳转时间 + /// + /// 时间 + /// true:13, false:10 + /// + [Obsolete] + public static DateTime StampToDateTime(this long timeStamp) + { + DateTime dt = TimeZone.CurrentTimeZone.ToLocalTime(beginTime); + return timeStamp.ToString().Length == 13 ? dt.AddMilliseconds(timeStamp) : dt.AddSeconds(timeStamp); + } + + /// + /// 时间转时间戳 + /// + /// 时间 + /// true:13, false:10 + /// + public static long DateTimeToStamp(this DateTime time, bool len13 = true) + { + TimeSpan ts = time.ToUniversalTime() - beginTime; + if (len13) + return Convert.ToInt64(ts.TotalMilliseconds); + else + return Convert.ToInt64(ts.TotalSeconds); + } + + /// + /// 将秒数转换为时分秒形式 + /// + /// + /// + public static string FormatToHHmmss(long second) + { + if (second < 60) + return $"00:00:{(second >= 10 ? $"{second}" : $"0{second}")}"; + if (second < 3600) + { + var minute = second / 60; + var s = second % 60; + return $"00:{(minute >= 10 ? $"{minute}" : $"0{minute}")}:{(s >= 10 ? $"{s}" : $"0{s}")}"; + } + else + { + var hour = second / 3600; + var minute = (second - (hour * 3600)) / 60; + var s = (second - ((hour * 3600) + minute * 60)) % 60; + return $"{(hour >= 10 ? $"{hour}" : $"0{hour}")}:{(minute >= 10 ? $"{minute}" : $"0{minute}")}:{(s >= 10 ? $"{s}" : $"0{s}")}"; + } + } + + #region SetLocalTime + [DllImport("Kernel32.dll")] + private static extern bool SetLocalTime(ref SYSTEMTIME lpSystemTime); + + [StructLayout(LayoutKind.Sequential)] + private struct SYSTEMTIME + { + public ushort wYear; + public ushort wMonth; + public ushort wDayOfWeek; + public ushort wDay; + public ushort wHour; + public ushort wMinute; + public ushort wSecond; + public ushort wMilliseconds; + } + + /// + /// 修改系统时间(需要管理员权限) + /// + /// + public static void SetSystemTime(DateTime date) + { + SYSTEMTIME lpTime = new SYSTEMTIME(); + lpTime.wYear = Convert.ToUInt16(date.Year); + lpTime.wMonth = Convert.ToUInt16(date.Month); + lpTime.wDayOfWeek = Convert.ToUInt16(date.DayOfWeek); + lpTime.wDay = Convert.ToUInt16(date.Day); + DateTime time = date; + lpTime.wHour = Convert.ToUInt16(time.Hour); + lpTime.wMinute = Convert.ToUInt16(time.Minute); + lpTime.wSecond = Convert.ToUInt16(time.Second); + lpTime.wMilliseconds = Convert.ToUInt16(time.Millisecond); + var r = SetLocalTime(ref lpTime); + Console.WriteLine($"修改系统时间 {r}"); + } + #endregion + } +} diff --git a/SBF.Common/Extensions/EncryptionExtension.cs b/SBF.Common/Extensions/EncryptionExtension.cs new file mode 100644 index 0000000..025f8e5 --- /dev/null +++ b/SBF.Common/Extensions/EncryptionExtension.cs @@ -0,0 +1,82 @@ +using System.Security.Cryptography; +using System.Text; + +namespace SBF.Common.Extensions +{ + public static class EncryptionExtension + { + + public static string Md5Encrypt(this string originStr) + { + using (var md5 = MD5.Create()) + { + return string.Join(string.Empty, md5.ComputeHash(Encoding.UTF8.GetBytes(originStr)).Select(x => x.ToString("x2"))); + } + } + + //AES加密 传入,要加密的串和, 解密key + public static string AESEncrypt(this string input) + { + var key = "dataplatform2019"; + var ivStr = "1012132405963708"; + + var encryptKey = Encoding.UTF8.GetBytes(key); + var iv = Encoding.UTF8.GetBytes(ivStr); //偏移量,最小为16 + using (var aesAlg = Aes.Create()) + { + using (var encryptor = aesAlg.CreateEncryptor(encryptKey, iv)) + { + using (var msEncrypt = new MemoryStream()) + { + using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, + CryptoStreamMode.Write)) + + using (var swEncrypt = new StreamWriter(csEncrypt)) + { + swEncrypt.Write(input); + } + var decryptedContent = msEncrypt.ToArray(); + + return Convert.ToBase64String(decryptedContent); + } + } + } + } + + public static string AESDecrypt(this string cipherText) + { + var fullCipher = Convert.FromBase64String(cipherText); + + var ivStr = "1012132405963708"; + var key = "dataplatform2019"; + + var iv = Encoding.UTF8.GetBytes(ivStr); + var decryptKey = Encoding.UTF8.GetBytes(key); + + using (var aesAlg = Aes.Create()) + { + using (var decryptor = aesAlg.CreateDecryptor(decryptKey, iv)) + { + string result; + using (var msDecrypt = new MemoryStream(fullCipher)) + { + using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)) + { + using (var srDecrypt = new StreamReader(csDecrypt)) + { + result = srDecrypt.ReadToEnd(); + } + } + } + + return result; + } + } + } + + public static string Base64Encrypt(this string originStr) + { + return Convert.ToBase64String(Encoding.UTF8.GetBytes(originStr)); + } + } +} diff --git a/SBF.Common/Extensions/MapperExtension.cs b/SBF.Common/Extensions/MapperExtension.cs new file mode 100644 index 0000000..70f0464 --- /dev/null +++ b/SBF.Common/Extensions/MapperExtension.cs @@ -0,0 +1,59 @@ +using AutoMapper; +using Microsoft.Extensions.DependencyInjection; + +namespace SBF.Common.Extensions +{ + public static class MapperExtension + { + private static IMapper _mapper; + + /// + /// 注册对象映射器 + /// + /// + /// + /// + public static IServiceCollection AddMapper(this IServiceCollection services, Profile profile) + { + var config = new MapperConfiguration(cfg => + { + cfg.AddProfile(profile); + }); + _mapper = config.CreateMapper(); + return services; + } + + /// + /// 设置对象映射执行者 + /// + /// 映射执行者 + public static void SetMapper(IMapper mapper) + { + _mapper = mapper; + } + + /// + /// 将对象映射为指定类型 + /// + /// 要映射的目标类型 + /// 源对象 + /// 目标类型的对象 + public static TTarget Map(this object source) + { + return _mapper.Map(source); + } + + /// + /// 使用源类型的对象更新目标类型的对象 + /// + /// 源类型 + /// 目标类型 + /// 源对象 + /// 待更新的目标对象 + /// 更新后的目标类型对象 + public static TTarget Map(this TSource source, TTarget target) + { + return _mapper.Map(source, target); + } + } +} diff --git a/SBF.Common/Extensions/StartupExtension.cs b/SBF.Common/Extensions/StartupExtension.cs new file mode 100644 index 0000000..24174c4 --- /dev/null +++ b/SBF.Common/Extensions/StartupExtension.cs @@ -0,0 +1,34 @@ +using Microsoft.Extensions.DependencyInjection; +using System.Reflection; + +namespace SBF.Common.Extensions +{ + public static class StartupExtension + { + public static void BatchRegisterServices(this IServiceCollection serviceCollection, Assembly[] assemblys, Type baseType, ServiceLifetime serviceLifetime = ServiceLifetime.Singleton, bool registerSelf = true) + { + List typeList = new List(); //所有符合注册条件的类集合 + foreach (var assembly in assemblys) + { + var types = assembly.GetTypes().Where(t => t.IsClass && !t.IsSealed && !t.IsAbstract && baseType.IsAssignableFrom(t)); + typeList.AddRange(types); + } + + if (typeList.Count() == 0) + return; + + foreach (var instanceType in typeList) + { + if (registerSelf) + { + serviceCollection.Add(new ServiceDescriptor(instanceType, instanceType, serviceLifetime)); + continue; + } + var interfaces = instanceType.GetInterfaces(); + if (interfaces != null && interfaces.Length > 0) + foreach (var interfaceType in interfaces) + serviceCollection.Add(new ServiceDescriptor(interfaceType, instanceType, serviceLifetime)); + } + } + } +} diff --git a/SBF.Common/Http/HttpDownloader.cs b/SBF.Common/Http/HttpDownloader.cs new file mode 100644 index 0000000..a8fc576 --- /dev/null +++ b/SBF.Common/Http/HttpDownloader.cs @@ -0,0 +1,452 @@ +using System.Net; + +namespace SBF.Common.Http +{ + /// + /// Http下载器 + /// + public class HttpDownloader + { + + private IHttpClientFactory httpClientFactory; + + public HttpDownloader(IHttpClientFactory httpClientFactory, DownloadSetting dwSetting = null) + { + this.httpClientFactory = httpClientFactory; + complateArgs = new DownloadCompletedEventArgs(); + changeArgs = new DownloadProgressChangedEventArgs(); + if (dwSetting == null) + dwSetting = new DownloadSetting(); + downloadSetting = dwSetting; + buffer = new byte[downloadSetting.BufferSize]; + } + + ~HttpDownloader() + { + this.buffer = null; + this.downloadSetting = null; + this.complateArgs.Error = null; + this.complateArgs = null; + this.changeArgs = null; + } + + #region Property + + /// + /// 下载进度变化参数 + /// + private DownloadProgressChangedEventArgs changeArgs; + + /// + /// 下载完成参数 + /// + private DownloadCompletedEventArgs complateArgs; + + /// + /// 下载参数配置类 + /// + private DownloadSetting downloadSetting; + + /// + /// 下载缓冲区 + /// + private byte[] buffer; + + #endregion + + #region Delegate + public delegate void DownloadProgressChangedEventHandler(object sender, DownloadProgressChangedEventArgs e); + + public delegate void DownloadCompletedEventHandler(object sender, DownloadCompletedEventArgs e); + + public delegate void ReDownloadEventHandler(object sender, DownloadCompletedEventArgs e); + #endregion + + #region Event + /// + /// 下载进度变化事件 + /// + public event DownloadProgressChangedEventHandler OnDownloadProgressChanged; + + /// + /// 下载完成事件 + /// + public event DownloadCompletedEventHandler OnDownloadComplated; + + /// + /// 自动重下事件 + /// + public event ReDownloadEventHandler OnReDownload; + #endregion + + #region Method + /// + /// 设置下载参数 + /// + /// + public void SetDownloadSetting(DownloadSetting dwSetting) + { + if (dwSetting != null) + downloadSetting = dwSetting; + } + + private void DoProcessChangedEvent(DownloadProgressChangedEventArgs args) + { + OnDownloadProgressChanged?.Invoke(this, args); + } + + private void DoCompletedEvent(DownloadCompletedEventArgs args) + { + OnDownloadComplated?.Invoke(this, args); + } + + private void DoReDownloadEvent(DownloadCompletedEventArgs args) + { + OnReDownload?.Invoke(this, args); + } + + private void ArgsInit() + { + changeArgs.Cancel = false; + complateArgs.Cancelled = false; + complateArgs.Error = null; + } + + /// + /// 获取文件总大小 + /// + /// + /// + public long GetTotalLength(string url) + { + long length = 0; + try + { + using (var httpClient = httpClientFactory.CreateClient()) + { + var requestMessage = new HttpRequestMessage(HttpMethod.Head, url); + var httpResponse = httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead).Result; + if (httpResponse.IsSuccessStatusCode) + length = httpResponse.Content.Headers.ContentLength ?? 0; + } + + //var req = (HttpWebRequest)WebRequest.Create(new Uri(url)); + //req.Method = "HEAD"; + //req.Timeout = 5000; + //var res = (HttpWebResponse)req.GetResponse(); + //if (res.StatusCode == HttpStatusCode.OK) + //{ + // length = res.ContentLength; + //} + //res.Close(); + return length; + } + catch (WebException wex) + { + throw wex; + } + } + + + private bool DownloadFile(string url, string savePath, long startPosition, long totalSize) + { + long currentReadSize = 0; //当前已经读取的字节数 + if (startPosition != 0) + currentReadSize = startPosition; + using (var httpClient = httpClientFactory.CreateClient()) + { + try + { + httpClient.Timeout = new TimeSpan(0, 0, 20); + if (currentReadSize != 0 && currentReadSize == totalSize) + { + changeArgs.CurrentSize = changeArgs.TotalSize = totalSize; + return true; + } + if (currentReadSize != 0) + { + try + { + httpClient.DefaultRequestHeaders.Range = new System.Net.Http.Headers.RangeHeaderValue(currentReadSize, totalSize); + } + catch (Exception ex) + { + //http 416 + currentReadSize = 0; + } + } + + using (var response = httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead).Result) + { + using (var responseStream = response.Content.ReadAsStreamAsync().Result) + { + changeArgs.TotalSize = totalSize; + using (var fs = new FileStream(savePath, currentReadSize == 0 ? FileMode.Create : FileMode.Append, FileAccess.Write)) + { + //判断服务器是否支持断点续下 + if (currentReadSize != 0 && + response.StatusCode == HttpStatusCode.PartialContent && + response.Content.Headers.ContentLength != totalSize) + fs.Seek(startPosition, SeekOrigin.Begin); //支持,从本地文件流末尾进行写入 + else + currentReadSize = 0; //不支持,从本地文件流开始进行写入 + + if (buffer.Length != downloadSetting.BufferSize) + buffer = new byte[downloadSetting.BufferSize]; + int size = responseStream.Read(buffer, 0, buffer.Length); + while (size != 0) + { + fs.Write(buffer, 0, size); + if (buffer.Length != downloadSetting.BufferSize) + buffer = new byte[downloadSetting.BufferSize]; + size = responseStream.Read(buffer, 0, buffer.Length); + currentReadSize += size; //累计下载大小 + //执行下载进度变化事件 + changeArgs.CurrentSize = currentReadSize; + DoProcessChangedEvent(changeArgs); + //判断挂起状态 + if (changeArgs.Cancel) + return true; + } + + //执行下载进度变化事件 + changeArgs.CurrentSize = changeArgs.TotalSize; + DoProcessChangedEvent(changeArgs); + fs.Flush(true); + } + + //验证远程服务器文件字节数和本地文件字节数是否一致 + long localFileSize = 0; + try + { + localFileSize = new FileInfo(savePath).Length; + } + catch (Exception ex) + { + localFileSize = changeArgs.CurrentSize; + } + + if (totalSize != localFileSize) + { + complateArgs.Error = new Exception(string.Format("远程文件字节:[{0}]与本地文件字节:[{1}]不一致", totalSize, localFileSize)); + } + } + } + } + catch (IOException ex) + { + complateArgs.Error = ex; + } + catch (WebException ex) + { + complateArgs.Error = ex; + } + catch (Exception ex) + { + complateArgs.Error = ex; + } + } + return complateArgs.Error == null; + } + + + public bool DownloadFile(string url, string saveFolderPath, string saveFileName, long? totalSize) + { + ArgsInit(); + var result = false; + //验证文件夹 + try + { + if (!Directory.Exists(saveFolderPath)) + Directory.CreateDirectory(saveFolderPath); + } + catch (Exception ex) + { + complateArgs.Error = ex; + DoCompletedEvent(complateArgs); + return result; + } + + if (totalSize == null || totalSize.Value == 0) + { + try + { + totalSize = GetTotalLength(url); + } + catch (Exception ex) + { + Console.WriteLine("获取远程服务器字节数失败 {0}", ex.Message); + complateArgs.Error = ex; + DoCompletedEvent(complateArgs); + return result; + } + } + + string saveFilePath = Path.Combine(saveFolderPath, saveFileName); + long startPosition = 0; + for (var i = 1; i <= downloadSetting.TryCount; i++) + { + if (File.Exists(saveFilePath)) + { + try + { + startPosition = new FileInfo(saveFilePath).Length; + } + catch + { + startPosition = 0; + } + } + + result = DownloadFile(url, saveFilePath, startPosition, totalSize.Value); + if (result) + { + break; + } + else + { + if (i != downloadSetting.TryCount) + { + complateArgs.TryCount = i + 1; + DoReDownloadEvent(complateArgs); + Thread.Sleep(downloadSetting.SleepTime); + if (complateArgs.Cancelled) + break; + ArgsInit(); + } + } + if (complateArgs.Cancelled) + break; + } + DoCompletedEvent(complateArgs); + return result; + } + + public byte[] DwonloadBytes(string url) + { + ArgsInit(); + using (var httpClient = httpClientFactory.CreateClient()) + { + return httpClient.GetByteArrayAsync(url).Result; + } + + } + + /// + /// 取消/暂停下载 + /// + public void CancelDownload() + { + this.changeArgs.Cancel = true; + this.complateArgs.Cancelled = true; + } + #endregion + } + + + /// + /// 下载进度变化参数 + /// + public class DownloadProgressChangedEventArgs + { + public DownloadProgressChangedEventArgs() + { + ProgressPercentage = 0.0; + Cancel = false; + } + /// + /// 下载进度百分比 + /// + public double ProgressPercentage; + + + private long currentSize; + + /// + /// 当前下载总大小 + /// + public long CurrentSize + { + get { return currentSize; } + set + { + currentSize = value; + this.ProgressPercentage = Math.Round((CurrentSize * 1.0 / TotalSize) * 100, 2); + } + } + + /// + /// 文件总大小 + /// + public long TotalSize; + + /// + /// 取消状态 + /// + public bool Cancel; + } + + /// + /// 下载完成参数 + /// + public class DownloadCompletedEventArgs + { + public DownloadCompletedEventArgs() + { + Cancelled = false; + Error = null; + } + /// + /// 下载异常 + /// + public Exception Error; + + /// + /// 重试次数 + /// + public int TryCount; + + /// + /// 下载操作是否被取消 【取消则为true;默认false】 + /// + public bool Cancelled; + } + + public class DownloadSetting + { + public DownloadSetting() + { + this.BufferSize = 1024 * 1024 * 1; + this.TryCount = 5; + this.SleepTime = 5000; + } + + /// + /// + /// + /// + /// + /// 毫秒 + public DownloadSetting(int bufferSize, int tryCount, int sleepTime) + { + this.BufferSize = bufferSize; + this.TryCount = tryCount; + this.SleepTime = sleepTime; + } + + /// + /// 下载缓冲区大小 + /// + public int BufferSize; + + /// + /// 重试次数 + /// + public int TryCount; + + /// + /// 自动重下休眠时间, 毫秒 + /// + public int SleepTime; + } +} diff --git a/SBF.Common/Http/RestAPIService.cs b/SBF.Common/Http/RestAPIService.cs new file mode 100644 index 0000000..dda3910 --- /dev/null +++ b/SBF.Common/Http/RestAPIService.cs @@ -0,0 +1,117 @@ +using Newtonsoft.Json; +using SBF.Common.Extensions; +using System.Net; +using System.Net.Http.Headers; +using System.Text; + +namespace SBF.Common.Http +{ + public class RestApiService + { + public const string ContentType_Json = "application/json"; + public const string ContentType_Form = "application/x-www-form-urlencoded"; + public TimeSpan TimeOut { get; set; } = new TimeSpan(0, 0, 40); + + private IHttpClientFactory httpClientFactory; + + public RestApiService(IHttpClientFactory httpClientFactory) + { + this.httpClientFactory = httpClientFactory; + } + + /// + /// 发送请求 + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public RestApiResult SendRequest(string apiHost, + string apiPath, + object param, + IDictionary requestHeaders, + HttpMethod httpMethod, + string contentType = ContentType_Json, + ParamPosition paramPosition = ParamPosition.Body, + bool enableRandomTimeStamp = false, + bool getResponseHeader = false, + HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead, + string httpClientName = "", + int timeOutSeconds = 0) + { + //Get和Delete强制使用QueryString形式传参 + if (httpMethod == HttpMethod.Get) + paramPosition = ParamPosition.Query; + + //拼接Url + + var url = $"{apiHost}{(apiHost.EndsWith("/") ? string.Empty : (string.IsNullOrEmpty(apiPath) ? string.Empty : "/"))}{(apiPath.StartsWith("/") ? apiPath.Substring(1) : apiPath)}"; + + var isCombineParam = false; + if (paramPosition == ParamPosition.Query && param != null) + { + url = $"{url}{(param.ToString().StartsWith("?") ? string.Empty : "?")}{param}"; + isCombineParam = true; + } + + //使用时间戳绕过CDN + if (enableRandomTimeStamp) + url = $"{url}{(isCombineParam ? "&" : "?")}t={DateTime.Now.DateTimeToStamp()}"; + + using (var httpClient = string.IsNullOrEmpty(httpClientName) ? httpClientFactory.CreateClient() : httpClientFactory.CreateClient(httpClientName)) + { + if (timeOutSeconds == 0) + httpClient.Timeout = TimeOut; + else + httpClient.Timeout = TimeSpan.FromSeconds(timeOutSeconds); + using (var request = new HttpRequestMessage(httpMethod, url)) + { + if (requestHeaders != null && requestHeaders.Count > 0) + foreach (var key in requestHeaders.Keys) + request.Headers.Add(key, requestHeaders[key]); + + if (paramPosition == ParamPosition.Body && param != null) + request.Content = new StringContent(contentType == ContentType_Json ? JsonConvert.SerializeObject(param) : param.ToString(), Encoding.UTF8, contentType); + + using (var response = httpClient.SendAsync(request, httpCompletionOption).Result) + { + return new RestApiResult() + { + StatusCode = response.StatusCode, + Content = httpCompletionOption == HttpCompletionOption.ResponseContentRead ? response.Content.ReadAsStringAsync().Result : + string.Empty, + Headers = getResponseHeader ? response.Headers : null + }; + } + } + } + } + } + + public class RestApiResult + { + public HttpStatusCode StatusCode { get; set; } + + public string Content { get; set; } + + public HttpResponseHeaders Headers { get; set; } + } + + /// + /// 参数传递位置 + /// + public enum ParamPosition + { + Query, + Body + } +} diff --git a/SBF.Common/Models/ApiResponse.cs b/SBF.Common/Models/ApiResponse.cs new file mode 100644 index 0000000..5bb593f --- /dev/null +++ b/SBF.Common/Models/ApiResponse.cs @@ -0,0 +1,34 @@ +using System; + +namespace SBF.Common.Models +{ + public class ApiResponse + { + public bool Success { get { return Code == 200; } } + + /// + /// 接口调用状态 + /// + public int Code { get; set; } = 200; + + /// + /// 返回消息 + /// + public string Msg { get; set; } + + /// + /// 数据内容 + /// + public T Data { get; set; } + + public static ApiResponse Error(int code, string msg) + { + return new ApiResponse() { Code = code, Msg = msg }; + } + } + + public class ApiResponse : ApiResponse + { + + } +} diff --git a/SBF.Common/Models/BusinessException.cs b/SBF.Common/Models/BusinessException.cs new file mode 100644 index 0000000..546518f --- /dev/null +++ b/SBF.Common/Models/BusinessException.cs @@ -0,0 +1,18 @@ +namespace SBF.Common.Models +{ + /// + /// 业务异常 + /// + public class BusinessException : Exception + { + public BusinessException(string message) : base(message) + { + + } + + /// + /// 错误代码 + /// + public int Code { get; set; } + } +} diff --git a/SBF.Common/Models/IDenpendency.cs b/SBF.Common/Models/IDenpendency.cs new file mode 100644 index 0000000..afc5578 --- /dev/null +++ b/SBF.Common/Models/IDenpendency.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SBF.Common.Models +{ + public interface IDenpendency + { + } +} diff --git a/SBF.Common/SBF.Common.csproj b/SBF.Common/SBF.Common.csproj new file mode 100644 index 0000000..8ccdeae --- /dev/null +++ b/SBF.Common/SBF.Common.csproj @@ -0,0 +1,18 @@ + + + + net6.0 + enable + enable + True + + + + + + + + + + + diff --git a/SBF.Common/TaskSchedulers/DelayTrigger.cs b/SBF.Common/TaskSchedulers/DelayTrigger.cs new file mode 100644 index 0000000..032bf66 --- /dev/null +++ b/SBF.Common/TaskSchedulers/DelayTrigger.cs @@ -0,0 +1,66 @@ +namespace SBF.Common.Trigger +{ + /// + /// 延迟触发组件 + /// + public class DelayTrigger + { + public DelayTrigger(int delayTime = 1000) + { + if (delayTime < 1000) + delayTime = 1000; + this.delayTime = delayTime; + } + /// + /// 延迟执行时间(ms) + /// + private int delayTime; + + /// + /// 关键字 + /// + private string currentKey; + + /// + /// 是否可以执行 + /// + private volatile bool canExecute; + + /// + /// 是否正在延迟响应中 + /// + private volatile bool isDelaying; + + + public Action OnExecute; + + + public void SetKey(string key) + { + currentKey = key; + if (isDelaying) + { + canExecute = false; + return; + } + Task.Factory.StartNew(delegate + { + isDelaying = true; + while (true) + { + canExecute = true; + Thread.Sleep(delayTime); + if (canExecute) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"DelayTrigger {currentKey} Execute at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:ffff")}"); + Console.ResetColor(); + OnExecute?.Invoke(currentKey); + isDelaying = false; + break; + } + } + }); + } + } +} diff --git a/SBF.Common/TaskSchedulers/LimitedConcurrencyLevelTaskScheduler.cs b/SBF.Common/TaskSchedulers/LimitedConcurrencyLevelTaskScheduler.cs new file mode 100644 index 0000000..05a2dcc --- /dev/null +++ b/SBF.Common/TaskSchedulers/LimitedConcurrencyLevelTaskScheduler.cs @@ -0,0 +1,153 @@ +//-------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// File: LimitedConcurrencyTaskScheduler.cs +// +//-------------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Linq; + +namespace System.Threading.Tasks.Schedulers +{ + /// + /// Provides a task scheduler that ensures a maximum concurrency level while + /// running on top of the ThreadPool. + /// + public class LimitedConcurrencyLevelTaskScheduler : TaskScheduler + { + /// Whether the current thread is processing work items. + [ThreadStatic] + private static bool _currentThreadIsProcessingItems; + /// The list of tasks to be executed. + private readonly LinkedList _tasks = new LinkedList(); // protected by lock(_tasks) + /// The maximum concurrency level allowed by this scheduler. + private readonly int _maxDegreeOfParallelism; + /// Whether the scheduler is currently processing work items. + private int _delegatesQueuedOrRunning = 0; // protected by lock(_tasks) + + /// + /// 最大并发数 + /// + private readonly int maxConcurrencyCountOfSystem = Environment.ProcessorCount * 2; + + /// + /// Initializes an instance of the LimitedConcurrencyLevelTaskScheduler class with the + /// specified degree of parallelism. + /// + /// The maximum degree of parallelism provided by this scheduler. + public LimitedConcurrencyLevelTaskScheduler(int maxDegreeOfParallelism) + { + if (maxDegreeOfParallelism < 1) throw new ArgumentOutOfRangeException("maxDegreeOfParallelism"); + if (maxDegreeOfParallelism > maxConcurrencyCountOfSystem) + maxDegreeOfParallelism = maxConcurrencyCountOfSystem; + _maxDegreeOfParallelism = maxDegreeOfParallelism; + } + + /// Queues a task to the scheduler. + /// The task to be queued. + protected sealed override void QueueTask(Task task) + { + // Add the task to the list of tasks to be processed. If there aren't enough + // delegates currently queued or running to process tasks, schedule another. + lock (_tasks) + { + _tasks.AddLast(task); + if (_delegatesQueuedOrRunning < _maxDegreeOfParallelism) + { + ++_delegatesQueuedOrRunning; + NotifyThreadPoolOfPendingWork(); + } + } + } + + /// + /// Informs the ThreadPool that there's work to be executed for this scheduler. + /// + private void NotifyThreadPoolOfPendingWork() + { + ThreadPool.UnsafeQueueUserWorkItem(_ => + { + // Note that the current thread is now processing work items. + // This is necessary to enable inlining of tasks into this thread. + //new Thread(() => + //{ + _currentThreadIsProcessingItems = true; + try + { + // Process all available items in the queue. + while (true) + { + Task item; + lock (_tasks) + { + // When there are no more items to be processed, + // note that we're done processing, and get out. + if (_tasks.Count == 0) + { + --_delegatesQueuedOrRunning; + break; + } + + // Get the next item from the queue + item = _tasks.First.Value; + _tasks.RemoveFirst(); + } + + // Execute the task we pulled out of the queue + base.TryExecuteTask(item); + } + } + // We're done processing items on the current thread + finally { _currentThreadIsProcessingItems = false; } + //}) + //{ IsBackground = true }.Start(); + }, null); + } + + /// Attempts to execute the specified task on the current thread. + /// The task to be executed. + /// + /// Whether the task could be executed on the current thread. + protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) + { + // If this thread isn't already processing a task, we don't support inlining + if (!_currentThreadIsProcessingItems) return false; + + // If the task was previously queued, remove it from the queue + if (taskWasPreviouslyQueued) TryDequeue(task); + + // Try to run the task. + return base.TryExecuteTask(task); + } + + /// Attempts to remove a previously scheduled task from the scheduler. + /// The task to be removed. + /// Whether the task could be found and removed. + protected sealed override bool TryDequeue(Task task) + { + lock (_tasks) return _tasks.Remove(task); + } + + /// Gets the maximum concurrency level supported by this scheduler. + public sealed override int MaximumConcurrencyLevel { get { return _maxDegreeOfParallelism; } } + + /// Gets an enumerable of the tasks currently scheduled on this scheduler. + /// An enumerable of the tasks currently scheduled. + protected sealed override IEnumerable GetScheduledTasks() + { + bool lockTaken = false; + try + { + Monitor.TryEnter(_tasks, ref lockTaken); + if (lockTaken) return _tasks.ToArray(); + else throw new NotSupportedException(); + } + finally + { + if (lockTaken) Monitor.Exit(_tasks); + } + } + } +} \ No newline at end of file diff --git a/SBF.Model/Db/Aggregation/AggregationJDPopularizeAdGroup.cs b/SBF.Model/Db/Aggregation/AggregationJDPopularizeAdGroup.cs new file mode 100644 index 0000000..3f9d652 --- /dev/null +++ b/SBF.Model/Db/Aggregation/AggregationJDPopularizeAdGroup.cs @@ -0,0 +1,95 @@ +using FreeSql.DataAnnotations; + +namespace SBF.Model.Db +{ + + [Table(Name = "aggregationjdpopularizeadgroup", DisableSyncStructure = true)] + public partial class AggregationJDPopularizeAdGroup + { + /// + /// 单元Id + /// + [Column(DbType = "bigint", IsPrimary = true)] + public long Id { get; set; } + + + public string AdGroupName { get; set; } + + /// + /// 计划Id + /// + [Column(DbType = "bigint")] + public long? CampaignId { get; set; } + + [Column(DbType = "datetime")] + public DateTime? CreateTime { get; set; } + + /// + /// 数据日期 + /// + [Column(DbType = "datetime")] + public DateTime? Date { get; set; } + + /// + /// 近30天推广花费 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent30dCost { get; set; } + + /// + /// 近30天推广维度GOI + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent30dPopularizeLevelGOI { get; set; } + + /// + /// 近30天推广维度毛利 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent30dPopularizeLevelProfit { get; set; } + + /// + /// 近7天推广花费 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent7dCost { get; set; } + + /// + /// 近7天推广维度GOI + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent7dPopularizeLevelGOI { get; set; } + + /// + /// 近7天推广维度毛利 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent7dPopularizeLevelProfit { get; set; } + + [Column(DbType = "bigint")] + public long? ShopId { get; set; } + + [Column(DbType = "datetime")] + public DateTime? UpdateTime { get; set; } + + /// + /// 昨天推广花费 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? YestodayCost { get; set; } + + /// + /// 昨天推广维度GOI + /// + [Column(DbType = "decimal(18,2)")] + public decimal? YestodayPopularizeLevelGOI { get; set; } + + /// + /// 昨天推广维度毛利 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? YestodayPopularizeLevelProfit { get; set; } + + } + +} diff --git a/SBF.Model/Db/Aggregation/AggregationJDPopularizeAdGroupDaily.cs b/SBF.Model/Db/Aggregation/AggregationJDPopularizeAdGroupDaily.cs new file mode 100644 index 0000000..163b0e5 --- /dev/null +++ b/SBF.Model/Db/Aggregation/AggregationJDPopularizeAdGroupDaily.cs @@ -0,0 +1,57 @@ +using FreeSql.DataAnnotations; + +namespace SBF.Model.Db +{ + + [Table(Name = "aggregationjdpopularizeadgroupdaily", DisableSyncStructure = true)] + public partial class AggregationJDPopularizeAdGroupDaily + { + + [Column(DbType = "bigint", IsPrimary = true)] + public long Id { get; set; } + + /// + /// 单元Id + /// + [Column(DbType = "bigint")] + public long? AdGroupId { get; set; } + + + public string AdGroupName { get; set; } + + /// + /// 计划Id + /// + [Column(DbType = "bigint")] + public long? CampaignId { get; set; } + + /// + /// 推广花费 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Cost { get; set; } + + [Column(DbType = "datetime")] + public DateTime? CreateTime { get; set; } + + [Column(DbType = "datetime")] + public DateTime? Date { get; set; } + + /// + /// 推广维度GOI + /// + [Column(DbType = "decimal(18,2)")] + public decimal? PopularizeLevelGOI { get; set; } + + /// + /// 推广维度毛利 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? PopularizeLevelProfit { get; set; } + + [Column(DbType = "bigint")] + public long? ShopId { get; set; } + + } + +} diff --git a/SBF.Model/Db/Aggregation/AggregationJDPopularizeAdSku.cs b/SBF.Model/Db/Aggregation/AggregationJDPopularizeAdSku.cs new file mode 100644 index 0000000..4aedec1 --- /dev/null +++ b/SBF.Model/Db/Aggregation/AggregationJDPopularizeAdSku.cs @@ -0,0 +1,117 @@ +using FreeSql.DataAnnotations; + +namespace SBF.Model.Db +{ + + [Table(Name = "aggregationjdpopularizeadsku", DisableSyncStructure = true)] + public partial class AggregationJDPopularizeAdSku + { + + /// + /// 聚合Id + /// + [Column(DbType = "bigint", IsPrimary = true)] + public long Id { get; set; } + + /// + /// 单元Id + /// + [Column(DbType = "bigint")] + public long? AdGroupId { get; set; } + + /// + /// 创意Id + /// + [Column(DbType = "bigint")] + public long? AdId { get; set; } + + + public string AdName { get; set; } + + /// + /// 业务线(快车:2 京速推:134217728) + /// + [Column(DbType = "int")] + public int? BusinessType { get; set; } + + /// + /// 计划Id + /// + [Column(DbType = "bigint")] + public long? CampaignId { get; set; } + + [Column(DbType = "datetime")] + public DateTime? CreateTime { get; set; } + + /// + /// 近30天推广花费 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent30dCost { get; set; } + + /// + /// 近30天推广维度GOI + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent30dPopularizeLevelGOI { get; set; } + + /// + /// 近30天推广维度毛利 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent30dPopularizeLevelProfit { get; set; } + + /// + /// 近7天推广花费 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent7dCost { get; set; } + + /// + /// 近7天推广维度GOI + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent7dPopularizeLevelGOI { get; set; } + + /// + /// 近7天推广维度毛利 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent7dPopularizeLevelProfit { get; set; } + + [Column(DbType = "bigint")] + public long? ShopId { get; set; } + + [Column(StringLength = 50)] + public string SkuId { get; set; } + + [Column(DbType = "datetime")] + public DateTime? UpdateTime { get; set; } + + /// + /// 昨天推广花费 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? YestodayCost { get; set; } + + /// + /// 昨天推广维度GOI + /// + [Column(DbType = "decimal(18,2)")] + public decimal? YestodayPopularizeLevelGOI { get; set; } + + /// + /// 昨天推广维度毛利 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? YestodayPopularizeLevelProfit { get; set; } + + /// + /// 数据日期 + /// + [Column(DbType = "datetime")] + public DateTime? Date { get; set; } + + } + +} diff --git a/SBF.Model/Db/Aggregation/AggregationJDPopularizeAdSkuDaily.cs b/SBF.Model/Db/Aggregation/AggregationJDPopularizeAdSkuDaily.cs new file mode 100644 index 0000000..4fbf5f0 --- /dev/null +++ b/SBF.Model/Db/Aggregation/AggregationJDPopularizeAdSkuDaily.cs @@ -0,0 +1,72 @@ +using FreeSql.DataAnnotations; + +namespace SBF.Model.Db +{ + + [Table(Name = "aggregationjdpopularizeadskudaily", DisableSyncStructure = true)] + public partial class AggregationJDPopularizeAdSkuDaily + { + + /// + /// 聚合Id + /// + [Column(DbType = "bigint", IsPrimary = true)] + public long Id { get; set; } + + /// + /// 单元Id + /// + [Column(DbType = "bigint")] + public long? AdGroupId { get; set; } + + [Column(DbType = "bigint")] + public long? AdId { get; set; } + + + public string AdName { get; set; } + + /// + /// 业务线(快车:2 京速推:134217728) + /// + [Column(DbType = "int")] + public int? BusinessType { get; set; } + + /// + /// 计划Id + /// + [Column(DbType = "bigint")] + public long? CampaignId { get; set; } + + /// + /// 推广花费 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Cost { get; set; } + + [Column(DbType = "datetime")] + public DateTime? CreateTime { get; set; } + + [Column(DbType = "datetime")] + public DateTime? Date { get; set; } + + /// + /// 推广维度GOI + /// + [Column(DbType = "decimal(18,2)")] + public decimal? PopularizeLevelGOI { get; set; } + + /// + /// 推广维度毛利 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? PopularizeLevelProfit { get; set; } + + [Column(DbType = "bigint")] + public long? ShopId { get; set; } + + [Column(StringLength = 50)] + public string SkuId { get; set; } + + } + +} diff --git a/SBF.Model/Db/Aggregation/AggregationJDPopularizeCampaign.cs b/SBF.Model/Db/Aggregation/AggregationJDPopularizeCampaign.cs new file mode 100644 index 0000000..7709cfe --- /dev/null +++ b/SBF.Model/Db/Aggregation/AggregationJDPopularizeCampaign.cs @@ -0,0 +1,96 @@ +using FreeSql.DataAnnotations; + +namespace SBF.Model.Db +{ + + [Table(Name = "aggregationjdpopularizecampaign", DisableSyncStructure = true)] + public partial class AggregationJDPopularizeCampaign + { + + /// + /// 计划Id + /// + [Column(DbType = "bigint", IsPrimary = true)] + public long Id { get; set; } + + /// + /// 业务线(快车:2 京速推:134217728) + /// + [Column(DbType = "int")] + public int? BusinessType { get; set; } + + + public string CampaignName { get; set; } + + [Column(DbType = "datetime")] + public DateTime? CreateTime { get; set; } + + /// + /// 近30天推广花费 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent30dCost { get; set; } + + /// + /// 近30天推广维度GOI + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent30dPopularizeLevelGOI { get; set; } + + /// + /// 近30天推广维度毛利 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent30dPopularizeLevelProfit { get; set; } + + /// + /// 近7天推广花费 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent7dCost { get; set; } + + /// + /// 近7天推广维度GOI + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent7dPopularizeLevelGOI { get; set; } + + /// + /// 近7天推广维度毛利 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent7dPopularizeLevelProfit { get; set; } + + [Column(DbType = "bigint")] + public long? ShopId { get; set; } + + [Column(DbType = "datetime")] + public DateTime? UpdateTime { get; set; } + + /// + /// 昨天推广花费 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? YestodayCost { get; set; } + + /// + /// 昨天推广维度GOI + /// + [Column(DbType = "decimal(18,2)")] + public decimal? YestodayPopularizeLevelGOI { get; set; } + + /// + /// 昨天推广维度毛利 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? YestodayPopularizeLevelProfit { get; set; } + + /// + /// 数据日期 + /// + [Column(DbType = "datetime")] + public DateTime? Date { get; set; } + + } + +} diff --git a/SBF.Model/Db/Aggregation/AggregationJDPopularizeCampaignDaily.cs b/SBF.Model/Db/Aggregation/AggregationJDPopularizeCampaignDaily.cs new file mode 100644 index 0000000..77e6ac1 --- /dev/null +++ b/SBF.Model/Db/Aggregation/AggregationJDPopularizeCampaignDaily.cs @@ -0,0 +1,57 @@ +using FreeSql.DataAnnotations; + +namespace SBF.Model.Db +{ + + [Table(Name = "aggregationjdpopularizecampaigndaily", DisableSyncStructure = true)] + public partial class AggregationJDPopularizeCampaignDaily + { + + [Column(DbType = "bigint", IsPrimary = true)] + public long Id { get; set; } + + /// + /// 业务线(快车:2 京速推:134217728) + /// + [Column(DbType = "int")] + public int? BusinessType { get; set; } + + /// + /// 计划Id + /// + [Column(DbType = "bigint")] + public long? CampaignId { get; set; } + + + public string CampaignName { get; set; } + + /// + /// 推广花费 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Cost { get; set; } + + [Column(DbType = "datetime")] + public DateTime? CreateTime { get; set; } + + [Column(DbType = "datetime")] + public DateTime? Date { get; set; } + + /// + /// 推广维度GOI + /// + [Column(DbType = "decimal(18,2)")] + public decimal? PopularizeLevelGOI { get; set; } + + /// + /// 推广维度毛利 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? PopularizeLevelProfit { get; set; } + + [Column(DbType = "bigint")] + public long? ShopId { get; set; } + + } + +} diff --git a/SBF.Model/Db/Aggregation/AggregationJDPopularizeSku.cs b/SBF.Model/Db/Aggregation/AggregationJDPopularizeSku.cs new file mode 100644 index 0000000..a29eab6 --- /dev/null +++ b/SBF.Model/Db/Aggregation/AggregationJDPopularizeSku.cs @@ -0,0 +1,125 @@ +using FreeSql.DataAnnotations; + +namespace SBF.Model.Db +{ + + [Table(Name = "aggregationjdpopularizesku", DisableSyncStructure = true)] + public partial class AggregationJDPopularizeSku + { + /// + /// sku + /// + [Column(StringLength = 50, IsPrimary = true, IsNullable = false)] + public string Id { get; set; } + + [Column(DbType = "datetime")] + public DateTime? CreateTime { get; set; } + + [Column(StringLength = 50)] + public string ProductId { get; set; } + + /// + /// 近30天推广花费 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent30dCost { get; set; } + + /// + /// 近30天推广维度GOI + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent30dPopularizeLevelGOI { get; set; } + + /// + /// 近30天推广维度毛利 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent30dPopularizeLevelProfit { get; set; } + + /// + /// 近30天产品维度GOI + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent30dProductLevelGOI { get; set; } + + /// + /// 近30天产品维度毛利 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent30dProductLevelProfit { get; set; } + + /// + /// 近7天推广花费 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent7dCost { get; set; } + + /// + /// 近7天推广维度GOI + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent7dPopularizeLevelGOI { get; set; } + + /// + /// 近7天推广维度毛利 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent7dPopularizeLevelProfit { get; set; } + + /// + /// 近7天产品维度GOI + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent7dProductLevelGOI { get; set; } + + /// + /// 近7天产品维度毛利 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent7dProductLevelProfit { get; set; } + + [Column(DbType = "bigint")] + public long? ShopId { get; set; } + + [Column(DbType = "datetime")] + public DateTime? UpdateTime { get; set; } + + /// + /// 昨天推广花费 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? YestodayCost { get; set; } + + /// + /// 昨天推广维度GOI + /// + [Column(DbType = "decimal(18,2)")] + public decimal? YestodayPopularizeLevelGOI { get; set; } + + /// + /// 昨天推广维度毛利 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? YestodayPopularizeLevelProfit { get; set; } + + /// + /// 昨天产品维度GOI + /// + [Column(DbType = "decimal(18,2)")] + public decimal? YestodayProductLevelGOI { get; set; } + + /// + /// 昨天产品维度毛利 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? YestodayProductLevelProfit { get; set; } + + /// + /// 数据日期 + /// + [Column(DbType = "datetime")] + public DateTime? Date { get; set; } + + } + +} diff --git a/SBF.Model/Db/Aggregation/AggregationJDPopularizeSkuDaily.cs b/SBF.Model/Db/Aggregation/AggregationJDPopularizeSkuDaily.cs new file mode 100644 index 0000000..f463a69 --- /dev/null +++ b/SBF.Model/Db/Aggregation/AggregationJDPopularizeSkuDaily.cs @@ -0,0 +1,66 @@ +using FreeSql.DataAnnotations; + +namespace SBF.Model.Db +{ + + [Table(Name = "aggregationjdpopularizeskudaily", DisableSyncStructure = true)] + public partial class AggregationJDPopularizeSkuDaily + { + + [Column(DbType = "bigint", IsPrimary = true)] + public long Id { get; set; } + + /// + /// 推广花费 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Cost { get; set; } + + [Column(DbType = "datetime")] + public DateTime? CreateTime { get; set; } + + [Column(DbType = "datetime")] + public DateTime? Date { get; set; } + + /// + /// 推广维度GOI + /// + [Column(DbType = "decimal(18,2)")] + public decimal? PopularizeLevelGOI { get; set; } + + /// + /// 推广维度毛利 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? PopularizeLevelProfit { get; set; } + + /// + /// spu + /// + [Column(StringLength = 50)] + public string ProductId { get; set; } + + /// + /// 产品维度GOI + /// + [Column(DbType = "decimal(18,2)")] + public decimal? ProductLevelGOI { get; set; } + + /// + /// 产品维度毛利 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? ProductLevelProfit { get; set; } + + [Column(DbType = "bigint")] + public long? ShopId { get; set; } + + /// + /// sku + /// + [Column(StringLength = 50)] + public string SkuId { get; set; } + + } + +} diff --git a/SBF.Model/Db/Aggregation/AggregationJDPopularizeSpu.cs b/SBF.Model/Db/Aggregation/AggregationJDPopularizeSpu.cs new file mode 100644 index 0000000..35c6360 --- /dev/null +++ b/SBF.Model/Db/Aggregation/AggregationJDPopularizeSpu.cs @@ -0,0 +1,122 @@ +using FreeSql.DataAnnotations; + +namespace SBF.Model.Db +{ + + [Table(Name = "aggregationjdpopularizespu", DisableSyncStructure = true)] + public partial class AggregationJDPopularizeSpu + { + /// + /// spu + /// + [Column(StringLength = 50, IsPrimary = true, IsNullable = false)] + public string Id { get; set; } + + [Column(DbType = "datetime")] + public DateTime? CreateTime { get; set; } + + /// + /// 近30天推广花费 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent30dCost { get; set; } + + /// + /// 近30天推广维度GOI + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent30dPopularizeLevelGOI { get; set; } + + /// + /// 近30天推广维度毛利 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent30dPopularizeLevelProfit { get; set; } + + /// + /// 近30天产品维度GOI + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent30dProductLevelGOI { get; set; } + + /// + /// 近30天产品维度毛利 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent30dProductLevelProfit { get; set; } + + /// + /// 近7天推广花费 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent7dCost { get; set; } + + /// + /// 近7天推广维度GOI + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent7dPopularizeLevelGOI { get; set; } + + /// + /// 近7天推广维度毛利 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent7dPopularizeLevelProfit { get; set; } + + /// + /// 近7天产品维度GOI + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent7dProductLevelGOI { get; set; } + + /// + /// 近7天产品维度毛利 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Recent7dProductLevelProfit { get; set; } + + [Column(DbType = "bigint")] + public long? ShopId { get; set; } + + [Column(DbType = "datetime")] + public DateTime? UpdateTime { get; set; } + + /// + /// 昨天推广花费 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? YestodayCost { get; set; } + + /// + /// 昨天推广维度GOI + /// + [Column(DbType = "decimal(18,2)")] + public decimal? YestodayPopularizeLevelGOI { get; set; } + + /// + /// 昨天推广维度毛利 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? YestodayPopularizeLevelProfit { get; set; } + + /// + /// 昨天产品维度GOI + /// + [Column(DbType = "decimal(18,2)")] + public decimal? YestodayProductLevelGOI { get; set; } + + /// + /// 昨天产品维度毛利 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? YestodayProductLevelProfit { get; set; } + + /// + /// 数据日期 + /// + [Column(DbType = "datetime")] + public DateTime? Date { get; set; } + + } + +} diff --git a/SBF.Model/Db/Aggregation/AggregationJDPopularizeSpuDaily.cs b/SBF.Model/Db/Aggregation/AggregationJDPopularizeSpuDaily.cs new file mode 100644 index 0000000..57d92ea --- /dev/null +++ b/SBF.Model/Db/Aggregation/AggregationJDPopularizeSpuDaily.cs @@ -0,0 +1,65 @@ +using FreeSql.DataAnnotations; + +namespace SBF.Model.Db +{ + + [Table(Name = "aggregationjdpopularizespudaily", DisableSyncStructure = true)] + public partial class AggregationJDPopularizeSpuDaily + { + [Column(DbType = "bigint", IsPrimary = true)] + public long Id { get; set; } + + /// + /// 推广花费 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Cost { get; set; } + + [Column(DbType = "datetime")] + public DateTime? CreateTime { get; set; } + + [Column(DbType = "datetime")] + public DateTime? Date { get; set; } + + /// + /// 推广维度GOI + /// + [Column(DbType = "decimal(18,2)")] + public decimal? PopularizeLevelGOI { get; set; } + + /// + /// 推广维度毛利 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? PopularizeLevelProfit { get; set; } + + /// + /// spu + /// + [Column(StringLength = 50)] + public string ProductId { get; set; } + + /// + /// 产品维度GOI + /// + [Column(DbType = "decimal(18,2)")] + public decimal? ProductLevelGOI { get; set; } + + /// + /// 产品维度毛利 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? ProductLevelProfit { get; set; } + + [Column(DbType = "bigint")] + public long? ShopId { get; set; } + + /// + /// 商品营业额(SKU实收之和) + /// + [Column(DbType = "decimal(18,2)")] + public decimal? ActualAmount { get; set; } = 0.00M; + + } + +} diff --git a/SBF.Model/Db/JD/JDOrderPopularizeRelation.cs b/SBF.Model/Db/JD/JDOrderPopularizeRelation.cs new file mode 100644 index 0000000..754829e --- /dev/null +++ b/SBF.Model/Db/JD/JDOrderPopularizeRelation.cs @@ -0,0 +1,75 @@ +using FreeSql.DataAnnotations; +using System; + +namespace SBF.Model.Db +{ + + /// + /// 京东订单推广归属关系表 + /// + [Table(Name = "jdorderpopularizerelation", DisableSyncStructure = true)] + public partial class JDOrderPopularizeRelation { + + [ Column(IsPrimary = true)] + public long Id { get; set; } + + /// + /// 单元Id + /// + + public long? AdGroupId { get; set; } + + /// + /// 创意Id + /// + + public long? AdId { get; set; } + + /// + /// 业务线(快车:2 京速推:134217728) + /// + + public int? BusinessType { get; set; } + + /// + /// 计划Id + /// + + public long? CampaignId { get; set; } + + [Column(DbType = "datetime")] + public DateTime? CreateTime { get; set; } + + [Column(DbType = "datetime")] + public DateTime? OrderTime { get; set; } + + /// + /// 点击时间 + /// + [Column(DbType = "datetime")] + public DateTime? CookieTime { get; set; } + + /// + /// 订单Id + /// + [Column(StringLength = 50)] + public string OrderId { get; set; } + + /// + /// 下单Sku + /// + [Column(StringLength = 50)] + public string PlaceOrderSku { get; set; } + + /// + /// 推广Sku + /// + [Column(StringLength = 50)] + public string PopularizeSku { get; set; } + + + public long? ShopId { get; set; } + + } + +} diff --git a/SBF.Model/Db/JD/JDPopularizeAdGroup.cs b/SBF.Model/Db/JD/JDPopularizeAdGroup.cs new file mode 100644 index 0000000..c62926c --- /dev/null +++ b/SBF.Model/Db/JD/JDPopularizeAdGroup.cs @@ -0,0 +1,88 @@ +using FreeSql.DataAnnotations; +using System; + +namespace SBF.Model.Db +{ + + /// + /// 京东推广单元表 + /// + [Table(Name = "jdpopularizeadgroup", DisableSyncStructure = true)] + public partial class JDPopularizeAdGroup + { + + [Column(IsPrimary = true)] + public long Id { get; set; } + + /// + /// 单元Id + /// + + public long? AdGroupId { get; set; } + + [Column(StringLength = 100)] + public string AdGroupName { get; set; } + + /// + /// 业务线(快车:2 京速推:134217728) + /// + + public int? BusinessType { get; set; } + + /// + /// 计划Id + /// + + public long? CampaignId { get; set; } + + /// + /// 点击数 + /// + [Column(Name = "clicks")] + public int? Clicks { get; set; } + + /// + /// 总花费 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Cost { get; set; } + + [Column(DbType = "datetime")] + public DateTime? CreateTime { get; set; } + + /// + /// 计费日期 + /// + [Column(DbType = "datetime")] + public DateTime? Date { get; set; } + + /// + /// 展现次数 + /// + [Column(Name = "impressions")] + public int? Impressions { get; set; } + + /// + /// 账号归属 + /// + [Column(Name = "pin")] + public string Pin { get; set; } + + + public long? ShopId { get; set; } + + /// + /// 总加购人数 + /// + [Column(Name = "totalCartCnt")] + public int? TotalCartCnt { get; set; } + + /// + /// 总订单数 + /// + [Column(Name = "totalOrderCnt")] + public int? TotalOrderCnt { get; set; } + + } + +} diff --git a/SBF.Model/Db/JD/JDPopularizeAdSku.cs b/SBF.Model/Db/JD/JDPopularizeAdSku.cs new file mode 100644 index 0000000..44e2e75 --- /dev/null +++ b/SBF.Model/Db/JD/JDPopularizeAdSku.cs @@ -0,0 +1,111 @@ +using FreeSql.DataAnnotations; +using System; + +namespace SBF.Model.Db +{ + + /// + /// 京东推广SKU创意表 + /// + [Table(Name = "jdpopularizeadsku", DisableSyncStructure = true)] + public partial class JDPopularizeAdSku + { + + [Column(IsPrimary = true)] + public long Id { get; set; } + + /// + /// 单元Id + /// + + public long? AdGroupId { get; set; } + + /// + /// 创意Id + /// + + public long? AdId { get; set; } + + [Column(StringLength = 100)] + public string AdName { get; set; } + + /// + /// 业务线(快车:2 京速推:134217728) + /// + + public int? BusinessType { get; set; } + + /// + /// 计划Id + /// + + public long? CampaignId { get; set; } + + /// + /// 点击数 + /// + [Column(Name = "clicks")] + public int? Clicks { get; set; } + + /// + /// 总花费 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Cost { get; set; } + + [Column(DbType = "datetime")] + public DateTime? CreateTime { get; set; } + + /// + /// 计费日期 + /// + [Column(DbType = "datetime")] + public DateTime? Date { get; set; } + + /// + /// 展现次数 + /// + [Column(Name = "impressions")] + public int? Impressions { get; set; } + + /// + /// 账号归属 + /// + [Column(Name = "pin")] + public string Pin { get; set; } + + + public long? ShopId { get; set; } + + [Column(StringLength = 50)] + public string Sku { get; set; } + + [Column(StringLength = 50)] + public string ProductId { get; set; } + + /// + /// 总加购人数 + /// + [Column(Name = "totalCartCnt")] + public int? TotalCartCnt { get; set; } + + /// + /// 总订单数 + /// + [Column(Name = "totalOrderCnt")] + public int? TotalOrderCnt { get; set; } + + /// + /// 总订单金额 + /// + [Column(Name = "totalOrderSum")] + public decimal TotalOrderSum { get; set; } + + /// + /// 访客数 + /// + [Column(Name = "visitorCnt")] + public int VisitorCnt { get; set; } + } + +} diff --git a/SBF.Model/Db/JD/JDPopularizeCampaign.cs b/SBF.Model/Db/JD/JDPopularizeCampaign.cs new file mode 100644 index 0000000..39b2e98 --- /dev/null +++ b/SBF.Model/Db/JD/JDPopularizeCampaign.cs @@ -0,0 +1,81 @@ +using FreeSql.DataAnnotations; +using System; + +namespace SBF.Model.Db +{ + + /// + /// 京东推广计划表 + /// + [Table(Name = "jdpopularizecampaign", DisableSyncStructure = true)] + public partial class JDPopularizeCampaign + { + + [Column(IsPrimary = true)] + public long Id { get; set; } + + /// + /// 业务线(快车:2 京速推:134217728) + /// + + public int? BusinessType { get; set; } + + /// + /// 计划Id + /// + public long? CampaignId { get; set; } + + [Column(StringLength = 100)] + public string CampaignName { get; set; } + + /// + /// 点击数 + /// + [Column(Name = "clicks")] + public int? Clicks { get; set; } + + /// + /// 总花费 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Cost { get; set; } + + [Column(DbType = "datetime")] + public DateTime? CreateTime { get; set; } + + /// + /// 计费日期 + /// + [Column(DbType = "datetime")] + public DateTime? Date { get; set; } + + /// + /// 展现次数 + /// + [Column(Name = "impressions")] + public int? Impressions { get; set; } + + /// + /// 账号归属 + /// + [Column(Name = "pin")] + public string Pin { get; set; } + + + public long? ShopId { get; set; } + + /// + /// 总加购人数 + /// + [Column(Name = "totalCartCnt")] + public int? TotalCartCnt { get; set; } + + /// + /// 总订单数 + /// + [Column(Name = "totalOrderCnt")] + public int? TotalOrderCnt { get; set; } + + } + +} diff --git a/SBF.Model/Db/JD/JDPublishOrder.cs b/SBF.Model/Db/JD/JDPublishOrder.cs new file mode 100644 index 0000000..6392908 --- /dev/null +++ b/SBF.Model/Db/JD/JDPublishOrder.cs @@ -0,0 +1,21 @@ +using FreeSql.DataAnnotations; +using System; + +namespace SBF.Model.Db +{ + + [Table(Name = "jdpublishorder", DisableSyncStructure = true)] + public partial class JdPublishOrder { + + [Column(StringLength = 100, IsPrimary = true, IsNullable = false)] + public string Id { get; set; } + + [Column(DbType = "datetime")] + public DateTime? CreateTime { get; set; } + + [Column(DbType = "bigint")] + public long? ShopId { get; set; } + + } + +} diff --git a/SBF.Model/Db/Product/Product.cs b/SBF.Model/Db/Product/Product.cs new file mode 100644 index 0000000..8f72293 --- /dev/null +++ b/SBF.Model/Db/Product/Product.cs @@ -0,0 +1,57 @@ +using FreeSql.DataAnnotations; + +namespace SBF.Model.Db +{ + + [Table(Name = "product", DisableSyncStructure = true)] + public partial class Product + { + + /// + /// SPU + /// + [Column(StringLength = 50, IsPrimary = true, IsNullable = false)] + public string Id { get; set; } + + [Column(DbType = "datetime")] + public DateTime? CreateTime { get; set; } + + [Column(DbType = "int(1)", MapType = typeof(int))] + public Enums.Platform Platform { get; set; } + + /// + /// 货号 + /// + [Column(StringLength = 100)] + public string ProductItemNum { get; set; } + + + public long? ShopId { get; set; } + + /// + /// 标题 + /// + + public string Title { get; set; } + + /// + /// 主SkuId + /// + [Column(StringLength = 50)] + public string MainSkuId { get; set; } + + /// + /// 京东商品状态【-1:删除 1:从未上架 2:自主下架 4:系统下架 8:上架 513:从未上架待审 514:自主下架待审 516:系统下架待审 520:上架待审核 1028:系统下架审核失败】 + /// + public int? State { get; set; } + + /// + /// 商品阶段 新品款=0 成长款=1 日销款=2 TOP款=3 清仓款=4 + /// + [Column(DbType = "int", MapType = typeof(int?))] + public Enums.Stage? Stage { get; set; } = 0; + + + } + +} diff --git a/SBF.Model/Db/Product/ProductSku.cs b/SBF.Model/Db/Product/ProductSku.cs new file mode 100644 index 0000000..addab25 --- /dev/null +++ b/SBF.Model/Db/Product/ProductSku.cs @@ -0,0 +1,56 @@ +using FreeSql.DataAnnotations; + +namespace SBF.Model.Db +{ + + [Table(Name = "productsku", DisableSyncStructure = true)] + public partial class ProductSku + { + + /// + /// SKU + /// + [Column(StringLength = 50, IsPrimary = true, IsNullable = false)] + public string Id { get; set; } + + [Column(DbType = "datetime")] + public DateTime? CreateTime { get; set; } + + + public string Logo { get; set; } + + [Column(DbType = "int(1)", MapType = typeof(int))] + public Enums.Platform Platform { get; set; } + + /// + /// 售价 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Price { get; set; } + + /// + /// SPU + /// + [Column(StringLength = 50)] + public string ProductId { get; set; } + + + public long? ShopId { get; set; } + + + public string Title { get; set; } + + /// + /// 京东Sku状态【1:上架 2:下架 4:删除】 + /// + public int? State { get; set; } + + /// + /// 三级类目Id + /// + public int? CategoryId { get; set; } + + public string CategoryName { get; set; } + } + +} diff --git a/SBF.Model/Dto/Request/CreateTrusteeshipRequest.cs b/SBF.Model/Dto/Request/CreateTrusteeshipRequest.cs new file mode 100644 index 0000000..98f0afa --- /dev/null +++ b/SBF.Model/Dto/Request/CreateTrusteeshipRequest.cs @@ -0,0 +1,9 @@ +namespace SBF.Model.Dto +{ + public class CreateTrusteeshipRequest + { + public long ShopId { get; set; } + + public List SkuList { get; set; } + } +} diff --git a/SBF.Model/Dto/Request/QueryTrusteeshipRequest.cs b/SBF.Model/Dto/Request/QueryTrusteeshipRequest.cs new file mode 100644 index 0000000..d2d68ab --- /dev/null +++ b/SBF.Model/Dto/Request/QueryTrusteeshipRequest.cs @@ -0,0 +1,19 @@ +namespace SBF.Model.Dto +{ + public class QueryTrusteeshipRequest + { + public Enums.Stage? Stage { get; set; } + + public long? ShopId { get; set; } + + public string Spu { get; set; } + + public string Sku { get; set; } + + public string Title { get; set; } + + public int PageIndex { get; set; } + + public int PageSize { get; set; } + } +} diff --git a/SBF.Model/Dto/Request/SearchSkuJoinPopularizeChannelRequest.cs b/SBF.Model/Dto/Request/SearchSkuJoinPopularizeChannelRequest.cs new file mode 100644 index 0000000..dd5fcd3 --- /dev/null +++ b/SBF.Model/Dto/Request/SearchSkuJoinPopularizeChannelRequest.cs @@ -0,0 +1,19 @@ +namespace SBF.Model.Dto +{ + public class SearchSkuJoinPopularizeChannelRequest + { + public long? ShopId { get; set; } + + public string Spu { get; set; } + + /// + /// 有sku条件会忽略spu + /// + public string Sku { get; set; } + + /// + /// 有skuList条件会忽略spu和sku + /// + public List SkuList { get; set; } + } +} diff --git a/SBF.Model/Dto/Response/ListResponse.cs b/SBF.Model/Dto/Response/ListResponse.cs new file mode 100644 index 0000000..c75c46c --- /dev/null +++ b/SBF.Model/Dto/Response/ListResponse.cs @@ -0,0 +1,9 @@ +namespace SBF.Model.Dto +{ + public class ListResponse where T : class + { + public List ItemList { get; set; } + + public long Count { get; set; } + } +} diff --git a/SBF.Model/Dto/Response/NumberByDate.cs b/SBF.Model/Dto/Response/NumberByDate.cs new file mode 100644 index 0000000..9ed74ef --- /dev/null +++ b/SBF.Model/Dto/Response/NumberByDate.cs @@ -0,0 +1,9 @@ +namespace SBF.Model.Dto +{ + public class NumberByDate + { + public decimal? Value { get; set; } + + public DateTime? Date { get; set; } + } +} diff --git a/SBF.Model/Dto/Response/SkuJoinPopularizeChannelResponse.cs b/SBF.Model/Dto/Response/SkuJoinPopularizeChannelResponse.cs new file mode 100644 index 0000000..44e82f6 --- /dev/null +++ b/SBF.Model/Dto/Response/SkuJoinPopularizeChannelResponse.cs @@ -0,0 +1,45 @@ +namespace SBF.Model.Dto +{ + public class SkuJoinPopularizeChannelResponse + { + /// + /// 业务类型/渠道 快车=2,智能投放=134217728 + /// + public int BusinessType { get; set; } + + /// + /// 计划Id + /// + public long? CampaignId { get; set; } + + /// + /// 计划名称 + /// + public string CampaignName { get; set; } + + /// + /// 单元Id + /// + public long? AdGroupId { get; set; } + + /// + /// 单元名称 + /// + public string AdGroupName { get; set; } + + /// + /// 创意Id + /// + public long? AdId { get; set; } + + /// + /// 创意名称 + /// + public string AdName { get; set; } + + /// + /// 所属Sku + /// + public string Sku { get; set; } + } +} diff --git a/SBF.Model/Dto/Response/TrusteeshipTaskResponse.cs b/SBF.Model/Dto/Response/TrusteeshipTaskResponse.cs new file mode 100644 index 0000000..433a53d --- /dev/null +++ b/SBF.Model/Dto/Response/TrusteeshipTaskResponse.cs @@ -0,0 +1,111 @@ +using SBF.Model.Db; + +namespace SBF.Model.Dto +{ + public class TrusteeshipTaskResponse + { + public long Id { get; set; } + + public string SpuId { get; set; } + + public string SkuId { get; set; } + + #region + public ProductSku ProductSku { get; set; } + + public Product Product { get; set; } + + /// + /// 评价数 + /// + public int PevaluationCount { get; set; } + #endregion + + /// + /// 计划Id + /// + public long? CampaignId { get; set; } + + /// + /// 计划名称 + /// + public string CampaginName { get; set; } + + public long? AdGroupId { get; set; } + + /// + /// 单元名称 + /// + public string AdGroupName { get; set; } + + /// + /// 创意Id + /// + public long? AdId { get; set; } + + /// + /// 创意名称 + /// + public string AdName { get; set; } + + /// + /// 预算 + /// + public decimal? Budget { get; set; } + + /// + /// 出价 + /// + public decimal? BidPrice { get; set; } + + /// + /// 托管期间实收金额(营业额) + /// + public decimal? ActualAmountInTrusteeship { get; set; } + + /// + /// 托管期间总花费 + /// + public decimal? CostInTrusteeship { get; set; } + + /// + /// 按日期分组的推广花费 + /// + public IList CostByDateList { get; set; } + + /// + /// 按日期分组的商品营业额 + /// + public IList ActualAmountByDateList { get; set; } + + /// + /// 按日期分组的免费访客量 + /// + public IList FreeCountByDateList { get; set; } + + /// + /// 托管开始时间 + /// + public DateTime CreateTime { get; set; } + + /// + /// 托管结束时间 + /// + public DateTime? EndTime { get; set; } + + /// + /// 是否结束 + /// + public bool IsEnd { get; set; } + + /// + /// 开始计算托管日期 + /// + public DateTime StartTrusteeshipDate { get; set; } + + /// + /// 业务类型/渠道 快车=2,智能投放=134217728 + /// + public int BusinessType { get; set; } + } +} diff --git a/SBF.Model/Enums.cs b/SBF.Model/Enums.cs new file mode 100644 index 0000000..a8034ad --- /dev/null +++ b/SBF.Model/Enums.cs @@ -0,0 +1,400 @@ +namespace SBF.Model +{ + public class Enums + { + /// + /// 电商平台 淘宝 = 0,京东 = 1,阿里巴巴 = 2, 拼多多 = 3,微信 = 4,拳探 = 10 + /// + public enum Platform + { + 淘宝 = 0, + 京东 = 1, + 阿里巴巴 = 2, + 拼多多 = 3, + 微信 = 4, + 拳探 = 10 + } + + /// + /// 采购方式 线上采购 = 0,线下采购 = 1 + /// + public enum PurchaseMethod + { + 线上采购 = 0, + 线下采购 = 1 + } + + /// + /// 采购单模式 批发 = 0,代发 = 1 + /// + public enum PurchaseOrderMode + { + 批发 = 0, + 代发 = 1 + } + + /// + /// 采购商品API模式 Spider = 0,OneBound = 1 + /// + public enum PurchaseProductAPIMode + { + Spider = 0, + OneBound = 1 + } + + /// + /// 仓储类型 + /// + public enum StorageType + { + 京仓 = 0, + 云仓 = 1, + 本地自发 = 2, + 代发 = 3, + SD = 4 + } + + /// + /// 订单类型 + /// + public enum OrderType + { + #region JD订单类型 + SOP = 22, + LOC = 75, + FBP = 21 + #endregion + } + + /// + /// 支付方式 + /// + public enum PayType + { + 货到付款 = 1, + 邮局汇款 = 2, + 自提 = 3, + 在线支付 = 4, + 公司转账 = 5, + 银行卡转账 = 6 + } + + /// + /// 订单状态 + /// + public enum OrderState + { + 待付款 = 0, + 等待采购 = 1, + 待出库 = 2, + 待收货 = 3, + 已完成 = 4, + 锁定 = 5, + 已取消 = 6, + 暂停 = 7, + 已退款 = 8 + } + + /// + /// 刷单类型 + /// + public enum SDType + { + 自刷 = 0, + 其他 = 1, + 京礼金 = 2, + 刷单组 = 3 + } + + /// + /// 订单同步任务状态 + /// + public enum OrderSyncState + { + Running = 0, + End = 1 + } + + public enum PayChannelType + { + 支付宝 = 0, + 微信 = 1, + 银行卡 = 2 + } + + /// + /// 服务单处理结果 + /// + public enum ServiceResult + { + 退货 = 0, + 换新 = 1, + 原返 = 2, + 线下换新 = 3, + 维修 = 4, + 商品补发 = 5, + 仅退款 = 6, + SD退货 = 7 + } + + /// + /// 商品处理方式 + /// + public enum ProductResult + { + 一件代发_退回厂家 = 0, + 退回齐越仓 = 1, + 退回京仓 = 2, + 退回云仓 = 3, + 客户无退货 = 4 + } + + /// + /// 商品情况 + /// + public enum ProductHealth + { + 可二次销售 = 0, + 残次品_无法二次销售 = 1, + 厂家退货退款 = 2, + 客户无退货 = 3, + 破损 = 4 + } + + /// + /// 排序时间类型 + /// + public enum SortTimeType + { + ModifyTime = 0, StartTime = 1 + } + + /// + /// 支付账单类型 + /// + public enum PayBillType + { + 支付宝 = 0, + 微信 = 1, + 银行卡 = 2 + } + + + /// + /// 资金类型 + /// + public enum AuditCapitalType + { + 当月商品采购 = 0, + 当月商品退款 = 1, + 上月商品采购 = 2, + 上月商品退款 = 3, + 批量采购商品 = 4, + 采购运费 = 5, + 入仓运费 = 6, + 售后成本 = 7, + 发票点数 = 8, + 快递单号 = 9, + 诚e赊还款 = 10, + 空单号 = 11, + 购买刷单号 = 12, + 手机费 = 13, + 质检报告 = 14, + 备用金转入 = 15, + 平台补贴 = 16, + 快递赔付 = 17, + 自定义 = 18, + 备用金充值 = 19 + } + + /// + /// 仓库类型(如业务不需要则为null) 商家仓 = 1, 京仓 = 2, 云仓 = 3, 聚水潭齐越仓 = 4, 聚水潭惠安仓 = 5 + /// + public enum StockType + { + 商家仓 = 1, 京仓 = 2, 云仓 = 3, 聚水潭齐越仓 = 4, 聚水潭惠安仓 = 5 + } + + /// + /// 仓库状态 0暂停,1使用 + /// + public enum StockStatus + { + 暂停 = 0, 使用 = 1 + } + + /// + /// SKU库存周期 暂无周期=0,增长期=1,稳定期=2,衰退期=3 + /// + public enum SkuStockNumCycleType + { + 暂无周期 = 0, + 增长期 = 1, + 稳定期 = 2, + 衰退期 = 3 + } + + /// + /// 司南周期 暂无周期 = -1,成长加速期 = 0,成熟利润期 = 1,稳定日销期 = 2,策马奔腾期 = 3 + /// + public enum SBFCycleType + { + 暂无周期 = -1, + 成长加速期 = 0, + 成熟利润期 = 1, + 稳定日销期 = 2, + 策马奔腾期 = 3 + } + + /// + /// 促销任务状态 等待 = 0,进行中 = 1,已完成 = 2, 已停止 = 3 + /// + public enum PromitionTaskStatus + { + 等待 = 0, + 进行中 = 1, + 已完成 = 2, + 已停止 = 3 + } + + /// + /// AppKey类型 全类型 = 0, 订单管理 = 1, 商品管理 = 2 + /// + public enum AppKeyType + { + 全类型 = 0, 订单管理 = 1, 商品管理 = 2 + } + + /// + /// 服务单状态 + /// + public enum ServiceOrderState + { + 待收货 = 10005, + 已取消 = 10011, + 已完成 = 10010 + } + + public enum ReturnDirection + { + 退货仓 = 0, 采购商 = 1, 原返 = 2, 无退货 = 3 + } + + + /// + /// 运输状态 0=待质检,1=已入库,2=派送中,3=运输中 + /// + public enum TransportState + { + 待质检 = 0, 已入库 = 1, 派送中 = 2, 运输中 = 3 + } + + /// + /// 商品健康状态 残次品=0 良品=1 + /// + public enum NewProductHealth + { + 残次品 = 0, 良品 = 1 + } + + /// + /// 产品功能 坏=0 好=1 + /// + public enum ProductFunction + { + 坏 = 0, 好 = 1 + } + + /// + /// 产品外观 (严重损=0 轻微损=1 新=2) + /// + public enum ProductAppearance + { + 严重损 = 0, 轻微损 = 1, 新 = 2 + } + + /// + /// 产品包装(无=0 非新=1 新=2) + /// + public enum ProductPackage + { + 无 = 0, 非新 = 1, 新 = 2 + } + + /// + /// 订单状态 + /// 待付款 = 0 + /// 等待采购 = 1, 部分采购 = 110 + /// 待发货 = 2, 部分发货 = 120 + /// 待收货 = 3, 部分收货 = 130 + /// 已完成 = 4 + /// 已取消 = 6 + /// 待验收 = 140 + /// 待核算 = 150 + /// + public enum PurchaseOrderState + { + 待付款 = 0, + 等待采购 = 1, + 部分采购 = 110, + 待发货 = 2, + 部分发货 = 120, + 待收货 = 3, + 部分收货 = 130, + 已完成 = 4, + 已取消 = 6, + 待验收 = 140, + 待核算 = 150 + } + + + public enum PackState + { + 待发布 = 0, 打包中 = 1, 已完成 = 2 + } + + public enum IsDeleted + { + 正常 = 0, + 删除 = 1 + } + + public enum Settle + { + 未结算 = 0, + 已结清 = 1 + } + + + public enum BillOrigin + { + 店铺 = 0, + 拳探 = 1 + } + + /// + /// 入仓类型 (发回齐越 = 0, 厂商代发入仓 = 1, 其他仓不包装=2) + /// + public enum IntoStoreType + { + 发回齐越 = 0, 厂商代发入仓 = 1, 其他仓不包装 = 2 + } + + + /// + /// 库存类型状态(可销售=1, 待退品=2, 商家预留=3 ,仓库锁定=4, 临期锁定=5 ,盘点锁定=6,内配出锁定=7,在途库存=8,VMI锁定=9,质押类型 =10,过期锁定类型=11,过期锁定类型=12,门店专享类型-b2b2c项目=13,耗材=14,TC在途可售=15,ERP在途可售=16,业务锁定=17,物权变更锁定=18,物权变更中库存=19,流转单在途=20,越库库存类型=21,待质押库存类型=22,采购在途=23) + /// + public enum StockState + { + 可销售 = 1, 待退品 = 2, 商家预留 = 3, 仓库锁定 = 4, 临期锁定 = 5, 盘点锁定 = 6, 内配出锁定 = 7, 在途库存 = 8, VMI锁定 = 9, 质押类型 = 10, 过期锁定类型 = 11, 耗材 = 14, TC在途可售 = 15, ERP在途可售 = 16, 业务锁定 = 17, 物权变更锁定 = 18, 物权变更中库存 = 19, 流转单在途 = 20, 越库库存类型 = 21, 待质押库存类型 = 22, 采购在途 = 23 + } + + /// + /// 商品阶段 新品款 = 0 成长款 = 1 日销款 = 2 TOP款 = 3 清仓款 = 4 + /// + public enum Stage + { + 新品款 = 0, 成长款 = 1, 日销款 = 2, TOP款 = 3, 清仓款 = 4 + } + } +} diff --git a/SBF.Model/MappingProfiles.cs b/SBF.Model/MappingProfiles.cs new file mode 100644 index 0000000..d2d3823 --- /dev/null +++ b/SBF.Model/MappingProfiles.cs @@ -0,0 +1,33 @@ +using AutoMapper; +using SBF.Model.Db; +using SBF.Model.Dto; + +namespace SBF.Model +{ + public class MappingProfiles : Profile + { + public MappingProfiles() + { + CreateMap().ForPath(t => t.Product.Id, opt => opt.MapFrom(f => f.SpuId)) + .ForPath(t => t.Product.MainSkuId, opt => opt.MapFrom(f => f.MainSkuId)) + .ForPath(t => t.Product.Platform, opt => opt.MapFrom(f => f.Platform)) + .ForPath(t => t.Product.ProductItemNum, opt => opt.MapFrom(f => f.ProductItemNum)) + .ForPath(t => t.Product.ShopId, opt => opt.MapFrom(f => f.ShopId)) + .ForPath(t => t.Product.State, opt => opt.MapFrom(f => f.ProductState)) + .ForPath(t => t.Product.CreateTime, opt => opt.MapFrom(f => f.ProductCreateTime)) + .ForPath(t => t.Product.Stage, opt => opt.MapFrom(f => f.Stage)) + .ForPath(t => t.Product.State, opt => opt.MapFrom(f => f.ProductState)) + .ForPath(t => t.Product.Title, opt => opt.MapFrom(f => f.ProductTitle)) + .ForPath(t => t.ProductSku.Id, opt => opt.MapFrom(f => f.SkuId)) + .ForPath(t => t.ProductSku.CategoryName, opt => opt.MapFrom(f => f.CategoryName)) + .ForPath(t => t.ProductSku.ProductId, opt => opt.MapFrom(f => f.SpuId)) + .ForPath(t => t.ProductSku.Price, opt => opt.MapFrom(f => f.Price)) + .ForPath(t => t.ProductSku.ShopId, opt => opt.MapFrom(f => f.ShopId)) + .ForPath(t => t.ProductSku.Logo, opt => opt.MapFrom(f => f.Logo)) + .ForPath(t => t.ProductSku.State, opt => opt.MapFrom(f => f.SkuState)) + .ForPath(t => t.ProductSku.Platform, opt => opt.MapFrom(f => f.Platform)) + .ForPath(t => t.ProductSku.CreateTime, opt => opt.MapFrom(f => f.SkuCreateTime)) + .ForPath(t => t.ProductSku.Title, opt => opt.MapFrom(f => f.SkuTitle)); + } + } +} diff --git a/SBF.Model/SBF.Model.csproj b/SBF.Model/SBF.Model.csproj new file mode 100644 index 0000000..732cb12 --- /dev/null +++ b/SBF.Model/SBF.Model.csproj @@ -0,0 +1,15 @@ + + + + net6.0 + enable + enable + True + + + + + + + + diff --git a/SBF.Model/Sbf_TrusteeshipTask.cs b/SBF.Model/Sbf_TrusteeshipTask.cs new file mode 100644 index 0000000..5562092 --- /dev/null +++ b/SBF.Model/Sbf_TrusteeshipTask.cs @@ -0,0 +1,164 @@ +using FreeSql.DataAnnotations; + +namespace SBF.Model.Db +{ + + [Table(Name = "sbf_trusteeshiptask", DisableSyncStructure = true)] + public partial class Sbf_TrusteeshipTask + { + + [Column(DbType = "bigint", IsPrimary = true)] + public long Id { get; set; } + + /// + /// 托管期间SPU营业额 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? ActualAmountInTrusteeship { get; set; } + + /// + /// 单元Id + /// + [Column(DbType = "bigint")] + public long? AdGroupId { get; set; } + + /// + /// 单元名称 + /// + [Column(StringLength = 100)] + public string AdGroupName { get; set; } + + /// + /// 创意Id + /// + [Column(DbType = "bigint")] + public long? AdId { get; set; } + + /// + /// 创意名称 + /// + [Column(StringLength = 100)] + public string AdName { get; set; } + + /// + /// 出价 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? BidPrice { get; set; } = 0.00M; + + /// + /// 预算 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? Budget { get; set; } = 0.00M; + + /// + /// 业务类型/渠道 快车=2,智能投放=134217728 + /// + [Column(DbType = "int")] + public int? BusinessType { get; set; } + + /// + /// 计划名称 + /// + [Column(StringLength = 100)] + public string CampaginName { get; set; } + + /// + /// 计划Id + /// + [Column(DbType = "bigint")] + public long? CampaignId { get; set; } + + /// + /// 托管期间总花费 + /// + [Column(DbType = "decimal(18,2)")] + public decimal? CostInTrusteeship { get; set; } + + /// + /// 托管任务创建时间 + /// + [Column(DbType = "datetime")] + public DateTime? CreateTime { get; set; } + + /// + /// 托管结束时间 + /// + [Column(DbType = "datetime")] + public DateTime? EndTime { get; set; } + + /// + /// 是否结束托管 + /// + public bool? IsEnd { get; set; } = false; + + [Column(DbType = "bigint")] + public long? ShopId { get; set; } + + [Column(StringLength = 50)] + public string SkuId { get; set; } + + [Column(StringLength = 50)] + public string SpuId { get; set; } + + /// + /// 开始纳入托管时间 + /// + [Column(DbType = "datetime")] + public DateTime? StartTrusteeshipDate { get; set; } + + + #region Product + [Column(IsIgnore = true)] + public DateTime? ProductCreateTime { get; set; } + + [Column(IsIgnore = true)] + public Enums.Platform Platform { get; set; } + + [Column(IsIgnore = true)] + public string ProductItemNum { get; set; } + + + [Column(IsIgnore = true)] + public string ProductTitle { get; set; } + + [Column(IsIgnore = true)] + public string MainSkuId { get; set; } + + [Column(IsIgnore = true)] + public int? ProductState { get; set; } + + + [Column(IsIgnore = true)] + public Enums.Stage? Stage { get; set; } = 0; + #endregion + + #region ProductSku + [Column(IsIgnore = true)] + public string Logo { get; set; } + + /// + /// 售价 + /// + [Column(IsIgnore = true)] + public decimal? Price { get; set; } + + [Column(IsIgnore = true)] + public string SkuTitle { get; set; } + + [Column(IsIgnore = true)] + public int? SkuState { get; set; } + + [Column(IsIgnore = true)] + public int? CategoryId { get; set; } + + [Column(IsIgnore = true)] + public string CategoryName { get; set; } + + [Column(IsIgnore = true)] + public DateTime? SkuCreateTime { get; set; } + #endregion + } + +} diff --git a/SBF.Model/__重新生成.bat b/SBF.Model/__重新生成.bat new file mode 100644 index 0000000..8aae0e9 --- /dev/null +++ b/SBF.Model/__重新生成.bat @@ -0,0 +1,2 @@ + +FreeSql.Generator -Razor 1 -NameOptions 1,0,0,0 -NameSpace SBF.Model.Db -DB "MySql,data source=rm-bp1508okrh23710yfao.mysql.rds.aliyuncs.com;port=3306;user id=qyroot;password=kaicn1132+-;initial catalog=bbwy_test;charset=utf8;sslmode=none;" -FileName "{name}.cs" diff --git a/SBF.sln b/SBF.sln new file mode 100644 index 0000000..d596a01 --- /dev/null +++ b/SBF.sln @@ -0,0 +1,43 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SBF.API", "SBF.API\SBF.API.csproj", "{F7C42964-BE96-45A4-9558-C69CDE177ACC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SBF.Business", "SBF.Business\SBF.Business.csproj", "{78AA0E7C-17F1-423F-8277-9087DAA5F83D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SBF.Model", "SBF.Model\SBF.Model.csproj", "{3278399D-99DD-4697-9DD0-83B0CD48C4E4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SBF.Common", "SBF.Common\SBF.Common.csproj", "{0D350626-0FD4-4AB0-AEAF-2D0249342A87}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F7C42964-BE96-45A4-9558-C69CDE177ACC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F7C42964-BE96-45A4-9558-C69CDE177ACC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F7C42964-BE96-45A4-9558-C69CDE177ACC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F7C42964-BE96-45A4-9558-C69CDE177ACC}.Release|Any CPU.Build.0 = Release|Any CPU + {78AA0E7C-17F1-423F-8277-9087DAA5F83D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {78AA0E7C-17F1-423F-8277-9087DAA5F83D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {78AA0E7C-17F1-423F-8277-9087DAA5F83D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {78AA0E7C-17F1-423F-8277-9087DAA5F83D}.Release|Any CPU.Build.0 = Release|Any CPU + {3278399D-99DD-4697-9DD0-83B0CD48C4E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3278399D-99DD-4697-9DD0-83B0CD48C4E4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3278399D-99DD-4697-9DD0-83B0CD48C4E4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3278399D-99DD-4697-9DD0-83B0CD48C4E4}.Release|Any CPU.Build.0 = Release|Any CPU + {0D350626-0FD4-4AB0-AEAF-2D0249342A87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0D350626-0FD4-4AB0-AEAF-2D0249342A87}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0D350626-0FD4-4AB0-AEAF-2D0249342A87}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0D350626-0FD4-4AB0-AEAF-2D0249342A87}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {0F44D589-862B-41C1-B06D-906D8345ADCD} + EndGlobalSection +EndGlobal