Browse Source

init

trusteeshipTask
shanji 2 years ago
parent
commit
d81037ba0d
  1. 5
      SBF.API/.config/dotnet-tools.json
  2. 36
      SBF.API/Controllers/BaseApiController.cs
  3. 49
      SBF.API/Controllers/TrusteeshipController.cs
  4. 30
      SBF.API/Filters/ResultFilter.cs
  5. 55
      SBF.API/Middlewares/ClientVersionValidationMiddleWare.cs
  6. 86
      SBF.API/Middlewares/CustomExceptionMiddleWare.cs
  7. 131
      SBF.API/Program.cs
  8. 31
      SBF.API/Properties/launchSettings.json
  9. 28
      SBF.API/SBF.API.csproj
  10. 9
      SBF.API/appsettings.Development.json
  11. 16
      SBF.API/appsettings.json
  12. 19
      SBF.Business/BaseBusiness.cs
  13. 13
      SBF.Business/FreeSqlMultiDBManager.cs
  14. 24
      SBF.Business/SBF.Business.csproj
  15. 21
      SBF.Business/TaskSchedulerManager.cs
  16. 253
      SBF.Business/TrusteeshipBusiness.cs
  17. 29
      SBF.Common/Extensions/ConverterExtensions.cs
  18. 12
      SBF.Common/Extensions/CopyExtensions.cs
  19. 99
      SBF.Common/Extensions/DateTimeExtension.cs
  20. 82
      SBF.Common/Extensions/EncryptionExtension.cs
  21. 59
      SBF.Common/Extensions/MapperExtension.cs
  22. 34
      SBF.Common/Extensions/StartupExtension.cs
  23. 452
      SBF.Common/Http/HttpDownloader.cs
  24. 117
      SBF.Common/Http/RestAPIService.cs
  25. 34
      SBF.Common/Models/ApiResponse.cs
  26. 18
      SBF.Common/Models/BusinessException.cs
  27. 10
      SBF.Common/Models/IDenpendency.cs
  28. 18
      SBF.Common/SBF.Common.csproj
  29. 66
      SBF.Common/TaskSchedulers/DelayTrigger.cs
  30. 153
      SBF.Common/TaskSchedulers/LimitedConcurrencyLevelTaskScheduler.cs
  31. 95
      SBF.Model/Db/Aggregation/AggregationJDPopularizeAdGroup.cs
  32. 57
      SBF.Model/Db/Aggregation/AggregationJDPopularizeAdGroupDaily.cs
  33. 117
      SBF.Model/Db/Aggregation/AggregationJDPopularizeAdSku.cs
  34. 72
      SBF.Model/Db/Aggregation/AggregationJDPopularizeAdSkuDaily.cs
  35. 96
      SBF.Model/Db/Aggregation/AggregationJDPopularizeCampaign.cs
  36. 57
      SBF.Model/Db/Aggregation/AggregationJDPopularizeCampaignDaily.cs
  37. 125
      SBF.Model/Db/Aggregation/AggregationJDPopularizeSku.cs
  38. 66
      SBF.Model/Db/Aggregation/AggregationJDPopularizeSkuDaily.cs
  39. 122
      SBF.Model/Db/Aggregation/AggregationJDPopularizeSpu.cs
  40. 65
      SBF.Model/Db/Aggregation/AggregationJDPopularizeSpuDaily.cs
  41. 75
      SBF.Model/Db/JD/JDOrderPopularizeRelation.cs
  42. 88
      SBF.Model/Db/JD/JDPopularizeAdGroup.cs
  43. 111
      SBF.Model/Db/JD/JDPopularizeAdSku.cs
  44. 81
      SBF.Model/Db/JD/JDPopularizeCampaign.cs
  45. 21
      SBF.Model/Db/JD/JDPublishOrder.cs
  46. 57
      SBF.Model/Db/Product/Product.cs
  47. 56
      SBF.Model/Db/Product/ProductSku.cs
  48. 9
      SBF.Model/Dto/Request/CreateTrusteeshipRequest.cs
  49. 19
      SBF.Model/Dto/Request/QueryTrusteeshipRequest.cs
  50. 19
      SBF.Model/Dto/Request/SearchSkuJoinPopularizeChannelRequest.cs
  51. 9
      SBF.Model/Dto/Response/ListResponse.cs
  52. 9
      SBF.Model/Dto/Response/NumberByDate.cs
  53. 45
      SBF.Model/Dto/Response/SkuJoinPopularizeChannelResponse.cs
  54. 111
      SBF.Model/Dto/Response/TrusteeshipTaskResponse.cs
  55. 400
      SBF.Model/Enums.cs
  56. 33
      SBF.Model/MappingProfiles.cs
  57. 15
      SBF.Model/SBF.Model.csproj
  58. 164
      SBF.Model/Sbf_TrusteeshipTask.cs
  59. 2
      SBF.Model/__重新生成.bat
  60. 43
      SBF.sln

5
SBF.API/.config/dotnet-tools.json

@ -0,0 +1,5 @@
{
"version": 1,
"isRoot": true,
"tools": {}
}

36
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;
}
}
}

49
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;
}
/// <summary>
/// 搜索Sku参与的推广渠道
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
[HttpPost]
public IList<SkuJoinPopularizeChannelResponse> SearchSkuJoinPopularizeChannel([FromBody] SearchSkuJoinPopularizeChannelRequest request)
{
return trusteeshipBusiness.SearchSkuJoinPopularizeChannel(request);
}
/// <summary>
/// 查询托管任务列表
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
[HttpPost]
public ListResponse<TrusteeshipTaskResponse> QueryTrusteeship([FromBody] QueryTrusteeshipRequest request)
{
return trusteeshipBusiness.QueryTrusteeship(request);
}
/// <summary>
/// 创建托管任务
/// </summary>
/// <param name="request"></param>
[HttpPost]
public void CreateTrusteeship([FromBody] CreateTrusteeshipRequest request)
{
trusteeshipBusiness.CreateTrusteeship(request);
}
}
}

30
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());
}
}
}
}

55
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
{
/// <summary>
/// 管道请求委托
/// </summary>
private RequestDelegate _next;
private IDictionary<string, int> apiVersionDictionary;
private IOptionsMonitor<List<ClientVersionValidationModel>> _monitor;
public ClientVersionValidationMiddleWare(RequestDelegate requestDelegate, IOptionsMonitor<List<ClientVersionValidationModel>> monitor)
{
_next = requestDelegate;
_monitor = monitor;
apiVersionDictionary = new Dictionary<string, int>();
}
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; }
}
}

86
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
{
/// <summary>
/// 管道请求委托
/// </summary>
private RequestDelegate _next;
/// <summary>
/// 需要处理的状态码字典
/// </summary>
private IDictionary<int, string> _exceptionStatusCodeDic;
//private NLogManager nLogManager;
private NLogManager nLogManager;
public CustomExceptionMiddleWare(RequestDelegate next, NLogManager nLogManager)
{
_next = next;
//this.logger = logger;
this.nLogManager = nLogManager;
_exceptionStatusCodeDic = new Dictionary<int, string>
{
{ 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);
}
}
}
/// <summary>
/// 处理方式:返回Json格式
/// </summary>
/// <param name="context"></param>
/// <param name="code"></param>
/// <param name="exMsg"></param>
/// <returns></returns>
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);
}
}
}

131
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<NLogManager>();
services.AddSingleton<RestApiService>();
services.AddSingleton<TaskSchedulerManager>();
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<ResultFilter>();
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}<br/>",
});
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<CustomExceptionMiddleWare>();
app.UseRouting();
app.UseCors("cors");
app.UseMiddleware<ClientVersionValidationMiddleWare>();
app.UseAuthorization();
app.MapControllers();
app.Run();

31
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"
}
}
}
}

28
SBF.API/SBF.API.csproj

@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FreeSql" Version="3.2.805" />
<PackageReference Include="FreeSql.Provider.MySql" Version="3.2.805" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.25" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.25" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="5.2.6" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SBF.Business\SBF.Business.csproj" />
<ProjectReference Include="..\SBF.Model\SBF.Model.csproj" />
</ItemGroup>
</Project>

9
SBF.API/appsettings.Development.json

@ -0,0 +1,9 @@
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

16
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;"
}
}

19
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;
}
}
}

13
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; }
}
}

24
SBF.Business/SBF.Business.csproj

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FreeSql" Version="3.2.805" />
<PackageReference Include="FreeSql.Provider.MySql" Version="3.2.805" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="5.2.6" />
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SBF.Common\SBF.Common.csproj" />
<ProjectReference Include="..\SBF.Model\SBF.Model.csproj" />
</ItemGroup>
</Project>

21
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);
}
}
}

253
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)
{
}
/// <summary>
/// 搜索SKU参与的推广渠道
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public IList<SkuJoinPopularizeChannelResponse> SearchSkuJoinPopularizeChannel(SearchSkuJoinPopularizeChannelRequest request)
{
if (request.ShopId == null || request.ShopId == 0)
throw new BusinessException("缺少ShopId");
var skuList = new List<string>();
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<ProductSku, Product>()
.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<JDPopularizeAdSku, JDPopularizeCampaign, JDPopularizeAdGroup>()
.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<Sbf_TrusteeshipTask>()
.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;
}
/// <summary>
/// 查询托管任务
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public ListResponse<TrusteeshipTaskResponse> QueryTrusteeship(QueryTrusteeshipRequest request)
{
if (request.ShopId == null || request.ShopId == 0)
throw new BusinessException("缺少ShopId");
var list = fsql.Select<Sbf_TrusteeshipTask, Product, ProductSku>()
.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<List<TrusteeshipTaskResponse>>();
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<AggregationJDPopularizeAdSkuDaily>()
.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<AggregationJDPopularizeSpuDaily>()
.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<TrusteeshipTaskResponse>() { 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<ProductSku>(skuIdList).ToList();
foreach (var insertTask in insertList)
insertTask.SpuId = psList.FirstOrDefault(ps => ps.Id == insertTask.SkuId)?.ProductId;
fsql.Insert(insertList).ExecuteAffrows();
}
}
}

29
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;
}
}
}
}

12
SBF.Common/Extensions/CopyExtensions.cs

@ -0,0 +1,12 @@
using Newtonsoft.Json;
namespace SBF.Common.Extensions
{
public static class CopyExtensions
{
public static T Copy<T>(this T p)
{
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(p));
}
}
}

99
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);
/// <summary>
/// 时间戳转时间
/// </summary>
/// <param name="timeStamp">时间</param>
/// <param name="len13">true:13, false:10</param>
/// <returns></returns>
[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);
}
/// <summary>
/// 时间转时间戳
/// </summary>
/// <param name="time">时间</param>
/// <param name="len13">true:13, false:10</param>
/// <returns></returns>
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);
}
/// <summary>
/// 将秒数转换为时分秒形式
/// </summary>
/// <param name="second"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 修改系统时间(需要管理员权限)
/// </summary>
/// <param name="date"></param>
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
}
}

82
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));
}
}
}

59
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;
/// <summary>
/// 注册对象映射器
/// </summary>
/// <param name="services"></param>
/// <param name="profile"></param>
/// <returns></returns>
public static IServiceCollection AddMapper(this IServiceCollection services, Profile profile)
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile(profile);
});
_mapper = config.CreateMapper();
return services;
}
/// <summary>
/// 设置对象映射执行者
/// </summary>
/// <param name="mapper">映射执行者</param>
public static void SetMapper(IMapper mapper)
{
_mapper = mapper;
}
/// <summary>
/// 将对象映射为指定类型
/// </summary>
/// <typeparam name="TTarget">要映射的目标类型</typeparam>
/// <param name="source">源对象</param>
/// <returns>目标类型的对象</returns>
public static TTarget Map<TTarget>(this object source)
{
return _mapper.Map<TTarget>(source);
}
/// <summary>
/// 使用源类型的对象更新目标类型的对象
/// </summary>
/// <typeparam name="TSource">源类型</typeparam>
/// <typeparam name="TTarget">目标类型</typeparam>
/// <param name="source">源对象</param>
/// <param name="target">待更新的目标对象</param>
/// <returns>更新后的目标类型对象</returns>
public static TTarget Map<TSource, TTarget>(this TSource source, TTarget target)
{
return _mapper.Map(source, target);
}
}
}

34
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<Type> typeList = new List<Type>(); //所有符合注册条件的类集合
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));
}
}
}
}

452
SBF.Common/Http/HttpDownloader.cs

@ -0,0 +1,452 @@
using System.Net;
namespace SBF.Common.Http
{
/// <summary>
/// Http下载器
/// </summary>
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
/// <summary>
/// 下载进度变化参数
/// </summary>
private DownloadProgressChangedEventArgs changeArgs;
/// <summary>
/// 下载完成参数
/// </summary>
private DownloadCompletedEventArgs complateArgs;
/// <summary>
/// 下载参数配置类
/// </summary>
private DownloadSetting downloadSetting;
/// <summary>
/// 下载缓冲区
/// </summary>
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
/// <summary>
/// 下载进度变化事件
/// </summary>
public event DownloadProgressChangedEventHandler OnDownloadProgressChanged;
/// <summary>
/// 下载完成事件
/// </summary>
public event DownloadCompletedEventHandler OnDownloadComplated;
/// <summary>
/// 自动重下事件
/// </summary>
public event ReDownloadEventHandler OnReDownload;
#endregion
#region Method
/// <summary>
/// 设置下载参数
/// </summary>
/// <param name="dwSetting"></param>
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;
}
/// <summary>
/// 获取文件总大小
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
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;
}
}
/// <summary>
/// 取消/暂停下载
/// </summary>
public void CancelDownload()
{
this.changeArgs.Cancel = true;
this.complateArgs.Cancelled = true;
}
#endregion
}
/// <summary>
/// 下载进度变化参数
/// </summary>
public class DownloadProgressChangedEventArgs
{
public DownloadProgressChangedEventArgs()
{
ProgressPercentage = 0.0;
Cancel = false;
}
/// <summary>
/// 下载进度百分比
/// </summary>
public double ProgressPercentage;
private long currentSize;
/// <summary>
/// 当前下载总大小
/// </summary>
public long CurrentSize
{
get { return currentSize; }
set
{
currentSize = value;
this.ProgressPercentage = Math.Round((CurrentSize * 1.0 / TotalSize) * 100, 2);
}
}
/// <summary>
/// 文件总大小
/// </summary>
public long TotalSize;
/// <summary>
/// 取消状态
/// </summary>
public bool Cancel;
}
/// <summary>
/// 下载完成参数
/// </summary>
public class DownloadCompletedEventArgs
{
public DownloadCompletedEventArgs()
{
Cancelled = false;
Error = null;
}
/// <summary>
/// 下载异常
/// </summary>
public Exception Error;
/// <summary>
/// 重试次数
/// </summary>
public int TryCount;
/// <summary>
/// 下载操作是否被取消 【取消则为true;默认false】
/// </summary>
public bool Cancelled;
}
public class DownloadSetting
{
public DownloadSetting()
{
this.BufferSize = 1024 * 1024 * 1;
this.TryCount = 5;
this.SleepTime = 5000;
}
/// <summary>
///
/// </summary>
/// <param name="bufferSize"></param>
/// <param name="tryCount"></param>
/// <param name="sleepTime">毫秒</param>
public DownloadSetting(int bufferSize, int tryCount, int sleepTime)
{
this.BufferSize = bufferSize;
this.TryCount = tryCount;
this.SleepTime = sleepTime;
}
/// <summary>
/// 下载缓冲区大小
/// </summary>
public int BufferSize;
/// <summary>
/// 重试次数
/// </summary>
public int TryCount;
/// <summary>
/// 自动重下休眠时间, 毫秒
/// </summary>
public int SleepTime;
}
}

117
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;
}
/// <summary>
/// 发送请求
/// </summary>
/// <param name="apiHost"></param>
/// <param name="apiPath"></param>
/// <param name="param"></param>
/// <param name="requestHeaders"></param>
/// <param name="httpMethod"></param>
/// <param name="contentType"></param>
/// <param name="paramPosition"></param>
/// <param name="enableRandomTimeStamp"></param>
/// <param name="getResponseHeader"></param>
/// <param name="httpCompletionOption"></param>
/// <param name="httpClientName"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public RestApiResult SendRequest(string apiHost,
string apiPath,
object param,
IDictionary<string, string> 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; }
}
/// <summary>
/// 参数传递位置
/// </summary>
public enum ParamPosition
{
Query,
Body
}
}

34
SBF.Common/Models/ApiResponse.cs

@ -0,0 +1,34 @@
using System;
namespace SBF.Common.Models
{
public class ApiResponse<T>
{
public bool Success { get { return Code == 200; } }
/// <summary>
/// 接口调用状态
/// </summary>
public int Code { get; set; } = 200;
/// <summary>
/// 返回消息
/// </summary>
public string Msg { get; set; }
/// <summary>
/// 数据内容
/// </summary>
public T Data { get; set; }
public static ApiResponse<T> Error(int code, string msg)
{
return new ApiResponse<T>() { Code = code, Msg = msg };
}
}
public class ApiResponse : ApiResponse<object>
{
}
}

18
SBF.Common/Models/BusinessException.cs

@ -0,0 +1,18 @@
namespace SBF.Common.Models
{
/// <summary>
/// 业务异常
/// </summary>
public class BusinessException : Exception
{
public BusinessException(string message) : base(message)
{
}
/// <summary>
/// 错误代码
/// </summary>
public int Code { get; set; }
}
}

10
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
{
}
}

18
SBF.Common/SBF.Common.csproj

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="12.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="5.2.6" />
</ItemGroup>
</Project>

66
SBF.Common/TaskSchedulers/DelayTrigger.cs

@ -0,0 +1,66 @@
namespace SBF.Common.Trigger
{
/// <summary>
/// 延迟触发组件
/// </summary>
public class DelayTrigger
{
public DelayTrigger(int delayTime = 1000)
{
if (delayTime < 1000)
delayTime = 1000;
this.delayTime = delayTime;
}
/// <summary>
/// 延迟执行时间(ms)
/// </summary>
private int delayTime;
/// <summary>
/// 关键字
/// </summary>
private string currentKey;
/// <summary>
/// 是否可以执行
/// </summary>
private volatile bool canExecute;
/// <summary>
/// 是否正在延迟响应中
/// </summary>
private volatile bool isDelaying;
public Action<string> 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;
}
}
});
}
}
}

153
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
{
/// <summary>
/// Provides a task scheduler that ensures a maximum concurrency level while
/// running on top of the ThreadPool.
/// </summary>
public class LimitedConcurrencyLevelTaskScheduler : TaskScheduler
{
/// <summary>Whether the current thread is processing work items.</summary>
[ThreadStatic]
private static bool _currentThreadIsProcessingItems;
/// <summary>The list of tasks to be executed.</summary>
private readonly LinkedList<Task> _tasks = new LinkedList<Task>(); // protected by lock(_tasks)
/// <summary>The maximum concurrency level allowed by this scheduler.</summary>
private readonly int _maxDegreeOfParallelism;
/// <summary>Whether the scheduler is currently processing work items.</summary>
private int _delegatesQueuedOrRunning = 0; // protected by lock(_tasks)
/// <summary>
/// 最大并发数
/// </summary>
private readonly int maxConcurrencyCountOfSystem = Environment.ProcessorCount * 2;
/// <summary>
/// Initializes an instance of the LimitedConcurrencyLevelTaskScheduler class with the
/// specified degree of parallelism.
/// </summary>
/// <param name="maxDegreeOfParallelism">The maximum degree of parallelism provided by this scheduler.</param>
public LimitedConcurrencyLevelTaskScheduler(int maxDegreeOfParallelism)
{
if (maxDegreeOfParallelism < 1) throw new ArgumentOutOfRangeException("maxDegreeOfParallelism");
if (maxDegreeOfParallelism > maxConcurrencyCountOfSystem)
maxDegreeOfParallelism = maxConcurrencyCountOfSystem;
_maxDegreeOfParallelism = maxDegreeOfParallelism;
}
/// <summary>Queues a task to the scheduler.</summary>
/// <param name="task">The task to be queued.</param>
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();
}
}
}
/// <summary>
/// Informs the ThreadPool that there's work to be executed for this scheduler.
/// </summary>
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);
}
/// <summary>Attempts to execute the specified task on the current thread.</summary>
/// <param name="task">The task to be executed.</param>
/// <param name="taskWasPreviouslyQueued"></param>
/// <returns>Whether the task could be executed on the current thread.</returns>
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);
}
/// <summary>Attempts to remove a previously scheduled task from the scheduler.</summary>
/// <param name="task">The task to be removed.</param>
/// <returns>Whether the task could be found and removed.</returns>
protected sealed override bool TryDequeue(Task task)
{
lock (_tasks) return _tasks.Remove(task);
}
/// <summary>Gets the maximum concurrency level supported by this scheduler.</summary>
public sealed override int MaximumConcurrencyLevel { get { return _maxDegreeOfParallelism; } }
/// <summary>Gets an enumerable of the tasks currently scheduled on this scheduler.</summary>
/// <returns>An enumerable of the tasks currently scheduled.</returns>
protected sealed override IEnumerable<Task> 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);
}
}
}
}

95
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
{
/// <summary>
/// 单元Id
/// </summary>
[Column(DbType = "bigint", IsPrimary = true)]
public long Id { get; set; }
public string AdGroupName { get; set; }
/// <summary>
/// 计划Id
/// </summary>
[Column(DbType = "bigint")]
public long? CampaignId { get; set; }
[Column(DbType = "datetime")]
public DateTime? CreateTime { get; set; }
/// <summary>
/// 数据日期
/// </summary>
[Column(DbType = "datetime")]
public DateTime? Date { get; set; }
/// <summary>
/// 近30天推广花费
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent30dCost { get; set; }
/// <summary>
/// 近30天推广维度GOI
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent30dPopularizeLevelGOI { get; set; }
/// <summary>
/// 近30天推广维度毛利
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent30dPopularizeLevelProfit { get; set; }
/// <summary>
/// 近7天推广花费
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent7dCost { get; set; }
/// <summary>
/// 近7天推广维度GOI
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent7dPopularizeLevelGOI { get; set; }
/// <summary>
/// 近7天推广维度毛利
/// </summary>
[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; }
/// <summary>
/// 昨天推广花费
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? YestodayCost { get; set; }
/// <summary>
/// 昨天推广维度GOI
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? YestodayPopularizeLevelGOI { get; set; }
/// <summary>
/// 昨天推广维度毛利
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? YestodayPopularizeLevelProfit { get; set; }
}
}

57
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; }
/// <summary>
/// 单元Id
/// </summary>
[Column(DbType = "bigint")]
public long? AdGroupId { get; set; }
public string AdGroupName { get; set; }
/// <summary>
/// 计划Id
/// </summary>
[Column(DbType = "bigint")]
public long? CampaignId { get; set; }
/// <summary>
/// 推广花费
/// </summary>
[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; }
/// <summary>
/// 推广维度GOI
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? PopularizeLevelGOI { get; set; }
/// <summary>
/// 推广维度毛利
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? PopularizeLevelProfit { get; set; }
[Column(DbType = "bigint")]
public long? ShopId { get; set; }
}
}

117
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
{
/// <summary>
/// 聚合Id
/// </summary>
[Column(DbType = "bigint", IsPrimary = true)]
public long Id { get; set; }
/// <summary>
/// 单元Id
/// </summary>
[Column(DbType = "bigint")]
public long? AdGroupId { get; set; }
/// <summary>
/// 创意Id
/// </summary>
[Column(DbType = "bigint")]
public long? AdId { get; set; }
public string AdName { get; set; }
/// <summary>
/// 业务线(快车:2 京速推:134217728)
/// </summary>
[Column(DbType = "int")]
public int? BusinessType { get; set; }
/// <summary>
/// 计划Id
/// </summary>
[Column(DbType = "bigint")]
public long? CampaignId { get; set; }
[Column(DbType = "datetime")]
public DateTime? CreateTime { get; set; }
/// <summary>
/// 近30天推广花费
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent30dCost { get; set; }
/// <summary>
/// 近30天推广维度GOI
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent30dPopularizeLevelGOI { get; set; }
/// <summary>
/// 近30天推广维度毛利
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent30dPopularizeLevelProfit { get; set; }
/// <summary>
/// 近7天推广花费
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent7dCost { get; set; }
/// <summary>
/// 近7天推广维度GOI
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent7dPopularizeLevelGOI { get; set; }
/// <summary>
/// 近7天推广维度毛利
/// </summary>
[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; }
/// <summary>
/// 昨天推广花费
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? YestodayCost { get; set; }
/// <summary>
/// 昨天推广维度GOI
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? YestodayPopularizeLevelGOI { get; set; }
/// <summary>
/// 昨天推广维度毛利
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? YestodayPopularizeLevelProfit { get; set; }
/// <summary>
/// 数据日期
/// </summary>
[Column(DbType = "datetime")]
public DateTime? Date { get; set; }
}
}

72
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
{
/// <summary>
/// 聚合Id
/// </summary>
[Column(DbType = "bigint", IsPrimary = true)]
public long Id { get; set; }
/// <summary>
/// 单元Id
/// </summary>
[Column(DbType = "bigint")]
public long? AdGroupId { get; set; }
[Column(DbType = "bigint")]
public long? AdId { get; set; }
public string AdName { get; set; }
/// <summary>
/// 业务线(快车:2 京速推:134217728)
/// </summary>
[Column(DbType = "int")]
public int? BusinessType { get; set; }
/// <summary>
/// 计划Id
/// </summary>
[Column(DbType = "bigint")]
public long? CampaignId { get; set; }
/// <summary>
/// 推广花费
/// </summary>
[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; }
/// <summary>
/// 推广维度GOI
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? PopularizeLevelGOI { get; set; }
/// <summary>
/// 推广维度毛利
/// </summary>
[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; }
}
}

96
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
{
/// <summary>
/// 计划Id
/// </summary>
[Column(DbType = "bigint", IsPrimary = true)]
public long Id { get; set; }
/// <summary>
/// 业务线(快车:2 京速推:134217728)
/// </summary>
[Column(DbType = "int")]
public int? BusinessType { get; set; }
public string CampaignName { get; set; }
[Column(DbType = "datetime")]
public DateTime? CreateTime { get; set; }
/// <summary>
/// 近30天推广花费
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent30dCost { get; set; }
/// <summary>
/// 近30天推广维度GOI
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent30dPopularizeLevelGOI { get; set; }
/// <summary>
/// 近30天推广维度毛利
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent30dPopularizeLevelProfit { get; set; }
/// <summary>
/// 近7天推广花费
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent7dCost { get; set; }
/// <summary>
/// 近7天推广维度GOI
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent7dPopularizeLevelGOI { get; set; }
/// <summary>
/// 近7天推广维度毛利
/// </summary>
[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; }
/// <summary>
/// 昨天推广花费
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? YestodayCost { get; set; }
/// <summary>
/// 昨天推广维度GOI
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? YestodayPopularizeLevelGOI { get; set; }
/// <summary>
/// 昨天推广维度毛利
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? YestodayPopularizeLevelProfit { get; set; }
/// <summary>
/// 数据日期
/// </summary>
[Column(DbType = "datetime")]
public DateTime? Date { get; set; }
}
}

57
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; }
/// <summary>
/// 业务线(快车:2 京速推:134217728)
/// </summary>
[Column(DbType = "int")]
public int? BusinessType { get; set; }
/// <summary>
/// 计划Id
/// </summary>
[Column(DbType = "bigint")]
public long? CampaignId { get; set; }
public string CampaignName { get; set; }
/// <summary>
/// 推广花费
/// </summary>
[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; }
/// <summary>
/// 推广维度GOI
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? PopularizeLevelGOI { get; set; }
/// <summary>
/// 推广维度毛利
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? PopularizeLevelProfit { get; set; }
[Column(DbType = "bigint")]
public long? ShopId { get; set; }
}
}

125
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
{
/// <summary>
/// sku
/// </summary>
[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; }
/// <summary>
/// 近30天推广花费
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent30dCost { get; set; }
/// <summary>
/// 近30天推广维度GOI
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent30dPopularizeLevelGOI { get; set; }
/// <summary>
/// 近30天推广维度毛利
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent30dPopularizeLevelProfit { get; set; }
/// <summary>
/// 近30天产品维度GOI
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent30dProductLevelGOI { get; set; }
/// <summary>
/// 近30天产品维度毛利
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent30dProductLevelProfit { get; set; }
/// <summary>
/// 近7天推广花费
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent7dCost { get; set; }
/// <summary>
/// 近7天推广维度GOI
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent7dPopularizeLevelGOI { get; set; }
/// <summary>
/// 近7天推广维度毛利
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent7dPopularizeLevelProfit { get; set; }
/// <summary>
/// 近7天产品维度GOI
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent7dProductLevelGOI { get; set; }
/// <summary>
/// 近7天产品维度毛利
/// </summary>
[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; }
/// <summary>
/// 昨天推广花费
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? YestodayCost { get; set; }
/// <summary>
/// 昨天推广维度GOI
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? YestodayPopularizeLevelGOI { get; set; }
/// <summary>
/// 昨天推广维度毛利
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? YestodayPopularizeLevelProfit { get; set; }
/// <summary>
/// 昨天产品维度GOI
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? YestodayProductLevelGOI { get; set; }
/// <summary>
/// 昨天产品维度毛利
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? YestodayProductLevelProfit { get; set; }
/// <summary>
/// 数据日期
/// </summary>
[Column(DbType = "datetime")]
public DateTime? Date { get; set; }
}
}

66
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; }
/// <summary>
/// 推广花费
/// </summary>
[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; }
/// <summary>
/// 推广维度GOI
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? PopularizeLevelGOI { get; set; }
/// <summary>
/// 推广维度毛利
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? PopularizeLevelProfit { get; set; }
/// <summary>
/// spu
/// </summary>
[Column(StringLength = 50)]
public string ProductId { get; set; }
/// <summary>
/// 产品维度GOI
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? ProductLevelGOI { get; set; }
/// <summary>
/// 产品维度毛利
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? ProductLevelProfit { get; set; }
[Column(DbType = "bigint")]
public long? ShopId { get; set; }
/// <summary>
/// sku
/// </summary>
[Column(StringLength = 50)]
public string SkuId { get; set; }
}
}

122
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
{
/// <summary>
/// spu
/// </summary>
[Column(StringLength = 50, IsPrimary = true, IsNullable = false)]
public string Id { get; set; }
[Column(DbType = "datetime")]
public DateTime? CreateTime { get; set; }
/// <summary>
/// 近30天推广花费
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent30dCost { get; set; }
/// <summary>
/// 近30天推广维度GOI
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent30dPopularizeLevelGOI { get; set; }
/// <summary>
/// 近30天推广维度毛利
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent30dPopularizeLevelProfit { get; set; }
/// <summary>
/// 近30天产品维度GOI
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent30dProductLevelGOI { get; set; }
/// <summary>
/// 近30天产品维度毛利
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent30dProductLevelProfit { get; set; }
/// <summary>
/// 近7天推广花费
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent7dCost { get; set; }
/// <summary>
/// 近7天推广维度GOI
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent7dPopularizeLevelGOI { get; set; }
/// <summary>
/// 近7天推广维度毛利
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent7dPopularizeLevelProfit { get; set; }
/// <summary>
/// 近7天产品维度GOI
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Recent7dProductLevelGOI { get; set; }
/// <summary>
/// 近7天产品维度毛利
/// </summary>
[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; }
/// <summary>
/// 昨天推广花费
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? YestodayCost { get; set; }
/// <summary>
/// 昨天推广维度GOI
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? YestodayPopularizeLevelGOI { get; set; }
/// <summary>
/// 昨天推广维度毛利
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? YestodayPopularizeLevelProfit { get; set; }
/// <summary>
/// 昨天产品维度GOI
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? YestodayProductLevelGOI { get; set; }
/// <summary>
/// 昨天产品维度毛利
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? YestodayProductLevelProfit { get; set; }
/// <summary>
/// 数据日期
/// </summary>
[Column(DbType = "datetime")]
public DateTime? Date { get; set; }
}
}

65
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; }
/// <summary>
/// 推广花费
/// </summary>
[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; }
/// <summary>
/// 推广维度GOI
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? PopularizeLevelGOI { get; set; }
/// <summary>
/// 推广维度毛利
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? PopularizeLevelProfit { get; set; }
/// <summary>
/// spu
/// </summary>
[Column(StringLength = 50)]
public string ProductId { get; set; }
/// <summary>
/// 产品维度GOI
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? ProductLevelGOI { get; set; }
/// <summary>
/// 产品维度毛利
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? ProductLevelProfit { get; set; }
[Column(DbType = "bigint")]
public long? ShopId { get; set; }
/// <summary>
/// 商品营业额(SKU实收之和)
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? ActualAmount { get; set; } = 0.00M;
}
}

75
SBF.Model/Db/JD/JDOrderPopularizeRelation.cs

@ -0,0 +1,75 @@
using FreeSql.DataAnnotations;
using System;
namespace SBF.Model.Db
{
/// <summary>
/// 京东订单推广归属关系表
/// </summary>
[Table(Name = "jdorderpopularizerelation", DisableSyncStructure = true)]
public partial class JDOrderPopularizeRelation {
[ Column(IsPrimary = true)]
public long Id { get; set; }
/// <summary>
/// 单元Id
/// </summary>
public long? AdGroupId { get; set; }
/// <summary>
/// 创意Id
/// </summary>
public long? AdId { get; set; }
/// <summary>
/// 业务线(快车:2 京速推:134217728)
/// </summary>
public int? BusinessType { get; set; }
/// <summary>
/// 计划Id
/// </summary>
public long? CampaignId { get; set; }
[Column(DbType = "datetime")]
public DateTime? CreateTime { get; set; }
[Column(DbType = "datetime")]
public DateTime? OrderTime { get; set; }
/// <summary>
/// 点击时间
/// </summary>
[Column(DbType = "datetime")]
public DateTime? CookieTime { get; set; }
/// <summary>
/// 订单Id
/// </summary>
[Column(StringLength = 50)]
public string OrderId { get; set; }
/// <summary>
/// 下单Sku
/// </summary>
[Column(StringLength = 50)]
public string PlaceOrderSku { get; set; }
/// <summary>
/// 推广Sku
/// </summary>
[Column(StringLength = 50)]
public string PopularizeSku { get; set; }
public long? ShopId { get; set; }
}
}

88
SBF.Model/Db/JD/JDPopularizeAdGroup.cs

@ -0,0 +1,88 @@
using FreeSql.DataAnnotations;
using System;
namespace SBF.Model.Db
{
/// <summary>
/// 京东推广单元表
/// </summary>
[Table(Name = "jdpopularizeadgroup", DisableSyncStructure = true)]
public partial class JDPopularizeAdGroup
{
[Column(IsPrimary = true)]
public long Id { get; set; }
/// <summary>
/// 单元Id
/// </summary>
public long? AdGroupId { get; set; }
[Column(StringLength = 100)]
public string AdGroupName { get; set; }
/// <summary>
/// 业务线(快车:2 京速推:134217728)
/// </summary>
public int? BusinessType { get; set; }
/// <summary>
/// 计划Id
/// </summary>
public long? CampaignId { get; set; }
/// <summary>
/// 点击数
/// </summary>
[Column(Name = "clicks")]
public int? Clicks { get; set; }
/// <summary>
/// 总花费
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Cost { get; set; }
[Column(DbType = "datetime")]
public DateTime? CreateTime { get; set; }
/// <summary>
/// 计费日期
/// </summary>
[Column(DbType = "datetime")]
public DateTime? Date { get; set; }
/// <summary>
/// 展现次数
/// </summary>
[Column(Name = "impressions")]
public int? Impressions { get; set; }
/// <summary>
/// 账号归属
/// </summary>
[Column(Name = "pin")]
public string Pin { get; set; }
public long? ShopId { get; set; }
/// <summary>
/// 总加购人数
/// </summary>
[Column(Name = "totalCartCnt")]
public int? TotalCartCnt { get; set; }
/// <summary>
/// 总订单数
/// </summary>
[Column(Name = "totalOrderCnt")]
public int? TotalOrderCnt { get; set; }
}
}

111
SBF.Model/Db/JD/JDPopularizeAdSku.cs

@ -0,0 +1,111 @@
using FreeSql.DataAnnotations;
using System;
namespace SBF.Model.Db
{
/// <summary>
/// 京东推广SKU创意表
/// </summary>
[Table(Name = "jdpopularizeadsku", DisableSyncStructure = true)]
public partial class JDPopularizeAdSku
{
[Column(IsPrimary = true)]
public long Id { get; set; }
/// <summary>
/// 单元Id
/// </summary>
public long? AdGroupId { get; set; }
/// <summary>
/// 创意Id
/// </summary>
public long? AdId { get; set; }
[Column(StringLength = 100)]
public string AdName { get; set; }
/// <summary>
/// 业务线(快车:2 京速推:134217728)
/// </summary>
public int? BusinessType { get; set; }
/// <summary>
/// 计划Id
/// </summary>
public long? CampaignId { get; set; }
/// <summary>
/// 点击数
/// </summary>
[Column(Name = "clicks")]
public int? Clicks { get; set; }
/// <summary>
/// 总花费
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Cost { get; set; }
[Column(DbType = "datetime")]
public DateTime? CreateTime { get; set; }
/// <summary>
/// 计费日期
/// </summary>
[Column(DbType = "datetime")]
public DateTime? Date { get; set; }
/// <summary>
/// 展现次数
/// </summary>
[Column(Name = "impressions")]
public int? Impressions { get; set; }
/// <summary>
/// 账号归属
/// </summary>
[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; }
/// <summary>
/// 总加购人数
/// </summary>
[Column(Name = "totalCartCnt")]
public int? TotalCartCnt { get; set; }
/// <summary>
/// 总订单数
/// </summary>
[Column(Name = "totalOrderCnt")]
public int? TotalOrderCnt { get; set; }
/// <summary>
/// 总订单金额
/// </summary>
[Column(Name = "totalOrderSum")]
public decimal TotalOrderSum { get; set; }
/// <summary>
/// 访客数
/// </summary>
[Column(Name = "visitorCnt")]
public int VisitorCnt { get; set; }
}
}

81
SBF.Model/Db/JD/JDPopularizeCampaign.cs

@ -0,0 +1,81 @@
using FreeSql.DataAnnotations;
using System;
namespace SBF.Model.Db
{
/// <summary>
/// 京东推广计划表
/// </summary>
[Table(Name = "jdpopularizecampaign", DisableSyncStructure = true)]
public partial class JDPopularizeCampaign
{
[Column(IsPrimary = true)]
public long Id { get; set; }
/// <summary>
/// 业务线(快车:2 京速推:134217728)
/// </summary>
public int? BusinessType { get; set; }
/// <summary>
/// 计划Id
/// </summary>
public long? CampaignId { get; set; }
[Column(StringLength = 100)]
public string CampaignName { get; set; }
/// <summary>
/// 点击数
/// </summary>
[Column(Name = "clicks")]
public int? Clicks { get; set; }
/// <summary>
/// 总花费
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Cost { get; set; }
[Column(DbType = "datetime")]
public DateTime? CreateTime { get; set; }
/// <summary>
/// 计费日期
/// </summary>
[Column(DbType = "datetime")]
public DateTime? Date { get; set; }
/// <summary>
/// 展现次数
/// </summary>
[Column(Name = "impressions")]
public int? Impressions { get; set; }
/// <summary>
/// 账号归属
/// </summary>
[Column(Name = "pin")]
public string Pin { get; set; }
public long? ShopId { get; set; }
/// <summary>
/// 总加购人数
/// </summary>
[Column(Name = "totalCartCnt")]
public int? TotalCartCnt { get; set; }
/// <summary>
/// 总订单数
/// </summary>
[Column(Name = "totalOrderCnt")]
public int? TotalOrderCnt { get; set; }
}
}

21
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; }
}
}

57
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
{
/// <summary>
/// SPU
/// </summary>
[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; }
/// <summary>
/// 货号
/// </summary>
[Column(StringLength = 100)]
public string ProductItemNum { get; set; }
public long? ShopId { get; set; }
/// <summary>
/// 标题
/// </summary>
public string Title { get; set; }
/// <summary>
/// 主SkuId
/// </summary>
[Column(StringLength = 50)]
public string MainSkuId { get; set; }
/// <summary>
/// 京东商品状态【-1:删除 1:从未上架 2:自主下架 4:系统下架 8:上架 513:从未上架待审 514:自主下架待审 516:系统下架待审 520:上架待审核 1028:系统下架审核失败】
/// </summary>
public int? State { get; set; }
/// <summary>
/// 商品阶段 新品款=0 成长款=1 日销款=2 TOP款=3 清仓款=4
/// </summary>
[Column(DbType = "int", MapType = typeof(int?))]
public Enums.Stage? Stage { get; set; } = 0;
}
}

56
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
{
/// <summary>
/// SKU
/// </summary>
[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; }
/// <summary>
/// 售价
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Price { get; set; }
/// <summary>
/// SPU
/// </summary>
[Column(StringLength = 50)]
public string ProductId { get; set; }
public long? ShopId { get; set; }
public string Title { get; set; }
/// <summary>
/// 京东Sku状态【1:上架 2:下架 4:删除】
/// </summary>
public int? State { get; set; }
/// <summary>
/// 三级类目Id
/// </summary>
public int? CategoryId { get; set; }
public string CategoryName { get; set; }
}
}

9
SBF.Model/Dto/Request/CreateTrusteeshipRequest.cs

@ -0,0 +1,9 @@
namespace SBF.Model.Dto
{
public class CreateTrusteeshipRequest
{
public long ShopId { get; set; }
public List<string> SkuList { get; set; }
}
}

19
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; }
}
}

19
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; }
/// <summary>
/// 有sku条件会忽略spu
/// </summary>
public string Sku { get; set; }
/// <summary>
/// 有skuList条件会忽略spu和sku
/// </summary>
public List<string> SkuList { get; set; }
}
}

9
SBF.Model/Dto/Response/ListResponse.cs

@ -0,0 +1,9 @@
namespace SBF.Model.Dto
{
public class ListResponse<T> where T : class
{
public List<T> ItemList { get; set; }
public long Count { get; set; }
}
}

9
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; }
}
}

45
SBF.Model/Dto/Response/SkuJoinPopularizeChannelResponse.cs

@ -0,0 +1,45 @@
namespace SBF.Model.Dto
{
public class SkuJoinPopularizeChannelResponse
{
/// <summary>
/// 业务类型/渠道 快车=2,智能投放=134217728
/// </summary>
public int BusinessType { get; set; }
/// <summary>
/// 计划Id
/// </summary>
public long? CampaignId { get; set; }
/// <summary>
/// 计划名称
/// </summary>
public string CampaignName { get; set; }
/// <summary>
/// 单元Id
/// </summary>
public long? AdGroupId { get; set; }
/// <summary>
/// 单元名称
/// </summary>
public string AdGroupName { get; set; }
/// <summary>
/// 创意Id
/// </summary>
public long? AdId { get; set; }
/// <summary>
/// 创意名称
/// </summary>
public string AdName { get; set; }
/// <summary>
/// 所属Sku
/// </summary>
public string Sku { get; set; }
}
}

111
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; }
/// <summary>
/// 评价数
/// </summary>
public int PevaluationCount { get; set; }
#endregion
/// <summary>
/// 计划Id
/// </summary>
public long? CampaignId { get; set; }
/// <summary>
/// 计划名称
/// </summary>
public string CampaginName { get; set; }
public long? AdGroupId { get; set; }
/// <summary>
/// 单元名称
/// </summary>
public string AdGroupName { get; set; }
/// <summary>
/// 创意Id
/// </summary>
public long? AdId { get; set; }
/// <summary>
/// 创意名称
/// </summary>
public string AdName { get; set; }
/// <summary>
/// 预算
/// </summary>
public decimal? Budget { get; set; }
/// <summary>
/// 出价
/// </summary>
public decimal? BidPrice { get; set; }
/// <summary>
/// 托管期间实收金额(营业额)
/// </summary>
public decimal? ActualAmountInTrusteeship { get; set; }
/// <summary>
/// 托管期间总花费
/// </summary>
public decimal? CostInTrusteeship { get; set; }
/// <summary>
/// 按日期分组的推广花费
/// </summary>
public IList<NumberByDate> CostByDateList { get; set; }
/// <summary>
/// 按日期分组的商品营业额
/// </summary>
public IList<NumberByDate> ActualAmountByDateList { get; set; }
/// <summary>
/// 按日期分组的免费访客量
/// </summary>
public IList<NumberByDate> FreeCountByDateList { get; set; }
/// <summary>
/// 托管开始时间
/// </summary>
public DateTime CreateTime { get; set; }
/// <summary>
/// 托管结束时间
/// </summary>
public DateTime? EndTime { get; set; }
/// <summary>
/// 是否结束
/// </summary>
public bool IsEnd { get; set; }
/// <summary>
/// 开始计算托管日期
/// </summary>
public DateTime StartTrusteeshipDate { get; set; }
/// <summary>
/// 业务类型/渠道 快车=2,智能投放=134217728
/// </summary>
public int BusinessType { get; set; }
}
}

400
SBF.Model/Enums.cs

@ -0,0 +1,400 @@
namespace SBF.Model
{
public class Enums
{
/// <summary>
/// 电商平台 淘宝 = 0,京东 = 1,阿里巴巴 = 2, 拼多多 = 3,微信 = 4,拳探 = 10
/// </summary>
public enum Platform
{
= 0,
= 1,
= 2,
= 3,
= 4,
= 10
}
/// <summary>
/// 采购方式 线上采购 = 0,线下采购 = 1
/// </summary>
public enum PurchaseMethod
{
线 = 0,
线 = 1
}
/// <summary>
/// 采购单模式 批发 = 0,代发 = 1
/// </summary>
public enum PurchaseOrderMode
{
= 0,
= 1
}
/// <summary>
/// 采购商品API模式 Spider = 0,OneBound = 1
/// </summary>
public enum PurchaseProductAPIMode
{
Spider = 0,
OneBound = 1
}
/// <summary>
/// 仓储类型
/// </summary>
public enum StorageType
{
= 0,
= 1,
= 2,
= 3,
SD = 4
}
/// <summary>
/// 订单类型
/// </summary>
public enum OrderType
{
#region JD订单类型
SOP = 22,
LOC = 75,
FBP = 21
#endregion
}
/// <summary>
/// 支付方式
/// </summary>
public enum PayType
{
= 1,
= 2,
= 3,
线 = 4,
= 5,
= 6
}
/// <summary>
/// 订单状态
/// </summary>
public enum OrderState
{
= 0,
= 1,
= 2,
= 3,
= 4,
= 5,
= 6,
= 7,
退 = 8
}
/// <summary>
/// 刷单类型
/// </summary>
public enum SDType
{
= 0,
= 1,
= 2,
= 3
}
/// <summary>
/// 订单同步任务状态
/// </summary>
public enum OrderSyncState
{
Running = 0,
End = 1
}
public enum PayChannelType
{
= 0,
= 1,
= 2
}
/// <summary>
/// 服务单处理结果
/// </summary>
public enum ServiceResult
{
退 = 0,
= 1,
= 2,
线 = 3,
= 4,
= 5,
退 = 6,
SD退货 = 7
}
/// <summary>
/// 商品处理方式
/// </summary>
public enum ProductResult
{
_退 = 0,
退 = 1,
退 = 2,
退 = 3,
退 = 4
}
/// <summary>
/// 商品情况
/// </summary>
public enum ProductHealth
{
= 0,
_ = 1,
退退 = 2,
退 = 3,
= 4
}
/// <summary>
/// 排序时间类型
/// </summary>
public enum SortTimeType
{
ModifyTime = 0, StartTime = 1
}
/// <summary>
/// 支付账单类型
/// </summary>
public enum PayBillType
{
= 0,
= 1,
= 2
}
/// <summary>
/// 资金类型
/// </summary>
public enum AuditCapitalType
{
= 0,
退 = 1,
= 2,
退 = 3,
= 4,
= 5,
= 6,
= 7,
= 8,
= 9,
e赊还款 = 10,
= 11,
= 12,
= 13,
= 14,
= 15,
= 16,
= 17,
= 18,
= 19
}
/// <summary>
/// 仓库类型(如业务不需要则为null) 商家仓 = 1, 京仓 = 2, 云仓 = 3, 聚水潭齐越仓 = 4, 聚水潭惠安仓 = 5
/// </summary>
public enum StockType
{
= 1, = 2, = 3, = 4, = 5
}
/// <summary>
/// 仓库状态 0暂停,1使用
/// </summary>
public enum StockStatus
{
= 0, 使 = 1
}
/// <summary>
/// SKU库存周期 暂无周期=0,增长期=1,稳定期=2,衰退期=3
/// </summary>
public enum SkuStockNumCycleType
{
= 0,
= 1,
= 2,
退 = 3
}
/// <summary>
/// 司南周期 暂无周期 = -1,成长加速期 = 0,成熟利润期 = 1,稳定日销期 = 2,策马奔腾期 = 3
/// </summary>
public enum SBFCycleType
{
= -1,
= 0,
= 1,
= 2,
= 3
}
/// <summary>
/// 促销任务状态 等待 = 0,进行中 = 1,已完成 = 2, 已停止 = 3
/// </summary>
public enum PromitionTaskStatus
{
= 0,
= 1,
= 2,
= 3
}
/// <summary>
/// AppKey类型 全类型 = 0, 订单管理 = 1, 商品管理 = 2
/// </summary>
public enum AppKeyType
{
= 0, = 1, = 2
}
/// <summary>
/// 服务单状态
/// </summary>
public enum ServiceOrderState
{
= 10005,
= 10011,
= 10010
}
public enum ReturnDirection
{
退 = 0, = 1, = 2, 退 = 3
}
/// <summary>
/// 运输状态 0=待质检,1=已入库,2=派送中,3=运输中
/// </summary>
public enum TransportState
{
= 0, = 1, = 2, = 3
}
/// <summary>
/// 商品健康状态 残次品=0 良品=1
/// </summary>
public enum NewProductHealth
{
= 0, = 1
}
/// <summary>
/// 产品功能 坏=0 好=1
/// </summary>
public enum ProductFunction
{
= 0, = 1
}
/// <summary>
/// 产品外观 (严重损=0 轻微损=1 新=2)
/// </summary>
public enum ProductAppearance
{
= 0, = 1, = 2
}
/// <summary>
/// 产品包装(无=0 非新=1 新=2)
/// </summary>
public enum ProductPackage
{
= 0, = 1, = 2
}
/// <summary>
/// 订单状态
/// <para>待付款 = 0</para>
/// <para>等待采购 = 1, 部分采购 = 110</para>
/// <para>待发货 = 2, 部分发货 = 120</para>
/// <para>待收货 = 3, 部分收货 = 130</para>
/// <para>已完成 = 4</para>
/// <para>已取消 = 6</para>
/// <para>待验收 = 140</para>
/// <para>待核算 = 150</para>
/// </summary>
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
}
/// <summary>
/// 入仓类型 (发回齐越 = 0, 厂商代发入仓 = 1, 其他仓不包装=2)
/// </summary>
public enum IntoStoreType
{
= 0, = 1, = 2
}
/// <summary>
/// 库存类型状态(可销售=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)
/// </summary>
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
}
/// <summary>
/// 商品阶段 新品款 = 0 成长款 = 1 日销款 = 2 TOP款 = 3 清仓款 = 4
/// </summary>
public enum Stage
{
= 0, = 1, = 2, TOP款 = 3, = 4
}
}
}

33
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<Sbf_TrusteeshipTask, TrusteeshipTaskResponse>().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));
}
}
}

15
SBF.Model/SBF.Model.csproj

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="12.0.1" />
<PackageReference Include="FreeSql" Version="3.2.805" />
</ItemGroup>
</Project>

164
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; }
/// <summary>
/// 托管期间SPU营业额
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? ActualAmountInTrusteeship { get; set; }
/// <summary>
/// 单元Id
/// </summary>
[Column(DbType = "bigint")]
public long? AdGroupId { get; set; }
/// <summary>
/// 单元名称
/// </summary>
[Column(StringLength = 100)]
public string AdGroupName { get; set; }
/// <summary>
/// 创意Id
/// </summary>
[Column(DbType = "bigint")]
public long? AdId { get; set; }
/// <summary>
/// 创意名称
/// </summary>
[Column(StringLength = 100)]
public string AdName { get; set; }
/// <summary>
/// 出价
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? BidPrice { get; set; } = 0.00M;
/// <summary>
/// 预算
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? Budget { get; set; } = 0.00M;
/// <summary>
/// 业务类型/渠道 快车=2,智能投放=134217728
/// </summary>
[Column(DbType = "int")]
public int? BusinessType { get; set; }
/// <summary>
/// 计划名称
/// </summary>
[Column(StringLength = 100)]
public string CampaginName { get; set; }
/// <summary>
/// 计划Id
/// </summary>
[Column(DbType = "bigint")]
public long? CampaignId { get; set; }
/// <summary>
/// 托管期间总花费
/// </summary>
[Column(DbType = "decimal(18,2)")]
public decimal? CostInTrusteeship { get; set; }
/// <summary>
/// 托管任务创建时间
/// </summary>
[Column(DbType = "datetime")]
public DateTime? CreateTime { get; set; }
/// <summary>
/// 托管结束时间
/// </summary>
[Column(DbType = "datetime")]
public DateTime? EndTime { get; set; }
/// <summary>
/// 是否结束托管
/// </summary>
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; }
/// <summary>
/// 开始纳入托管时间
/// </summary>
[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; }
/// <summary>
/// 售价
/// </summary>
[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
}
}

2
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"

43
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
Loading…
Cancel
Save