diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0db2396 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +[*.cs] + +# CS8618: 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。 +dotnet_diagnostic.CS8618.severity = none + +# CS1591: 缺少对公共可见类型或成员的 XML 注释 +dotnet_diagnostic.CS1591.severity = none diff --git a/Binance.TradeRobot.API.sln b/Binance.TradeRobot.API.sln index e492144..ae57b65 100644 --- a/Binance.TradeRobot.API.sln +++ b/Binance.TradeRobot.API.sln @@ -5,9 +5,16 @@ VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Binance.TradeRobot.API", "Binance.TradeRobot.API\Binance.TradeRobot.API.csproj", "{D568610C-F70C-406F-AB41-C6E2B89B327F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Binance.TradeRobot.Model", "Binance.TradeRobot.Model\Binance.TradeRobot.Model.csproj", "{2E56BEBE-2330-41D8-AAB7-B6B5CC707BBB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Binance.TradeRobot.Model", "Binance.TradeRobot.Model\Binance.TradeRobot.Model.csproj", "{2E56BEBE-2330-41D8-AAB7-B6B5CC707BBB}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Binance.TradeRobot.Business", "Binance.TradeRobot.Business\Binance.TradeRobot.Business.csproj", "{74CD58F4-1AA3-4EE9-A68B-386C76CF2125}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Binance.TradeRobot.Business", "Binance.TradeRobot.Business\Binance.TradeRobot.Business.csproj", "{74CD58F4-1AA3-4EE9-A68B-386C76CF2125}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Binance.TradeRobot.Common", "Binance.TradeRobot.Common\Binance.TradeRobot.Common.csproj", "{C840DCF2-0E1E-4F9F-9EAA-5A4F80B51BD2}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B55091A9-5FBF-486C-98D6-B7E4AF95D345}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -27,6 +34,10 @@ Global {74CD58F4-1AA3-4EE9-A68B-386C76CF2125}.Debug|Any CPU.Build.0 = Debug|Any CPU {74CD58F4-1AA3-4EE9-A68B-386C76CF2125}.Release|Any CPU.ActiveCfg = Release|Any CPU {74CD58F4-1AA3-4EE9-A68B-386C76CF2125}.Release|Any CPU.Build.0 = Release|Any CPU + {C840DCF2-0E1E-4F9F-9EAA-5A4F80B51BD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C840DCF2-0E1E-4F9F-9EAA-5A4F80B51BD2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C840DCF2-0E1E-4F9F-9EAA-5A4F80B51BD2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C840DCF2-0E1E-4F9F-9EAA-5A4F80B51BD2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Binance.TradeRobot.API/Binance.TradeRobot.API.csproj b/Binance.TradeRobot.API/Binance.TradeRobot.API.csproj index a880a34..6025e8a 100644 --- a/Binance.TradeRobot.API/Binance.TradeRobot.API.csproj +++ b/Binance.TradeRobot.API/Binance.TradeRobot.API.csproj @@ -3,13 +3,19 @@ netcoreapp3.1 True + Binance.TradeRobot.API.xml + + + + + @@ -17,6 +23,7 @@ + diff --git a/Binance.TradeRobot.API/Binance.TradeRobot.API.xml b/Binance.TradeRobot.API/Binance.TradeRobot.API.xml new file mode 100644 index 0000000..cc60b1f --- /dev/null +++ b/Binance.TradeRobot.API/Binance.TradeRobot.API.xml @@ -0,0 +1,86 @@ + + + + Binance.TradeRobot.API + + + + + 用户登录 + + + + + + + 获取用户列表 + + + + + 获取用户资金变更记录 + + + + + 获取用户盈亏记录 + + + + + 追投 + + + + + 提现 + + + + + 转移资金 + + + + + 管道请求委托 + + + + + 需要处理的状态码字典 + + + + + + + + + + + + 处理方式:返回Json格式 + + + + + + + + + 注册自定义异常中间件 + + + + + + + 添加Swagger服务 + + + + + + + diff --git a/Binance.TradeRobot.API/Controllers/BaseApiController.cs b/Binance.TradeRobot.API/Controllers/BaseApiController.cs new file mode 100644 index 0000000..7cb4b3d --- /dev/null +++ b/Binance.TradeRobot.API/Controllers/BaseApiController.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Binance.TradeRobot.API.Controllers +{ + [Produces("application/json")] + [Route("Api/[Controller]/[Action]")] + [ApiController] + public class BaseApiController : ControllerBase + { + } +} diff --git a/Binance.TradeRobot.API/Controllers/UserController.cs b/Binance.TradeRobot.API/Controllers/UserController.cs new file mode 100644 index 0000000..9157db0 --- /dev/null +++ b/Binance.TradeRobot.API/Controllers/UserController.cs @@ -0,0 +1,79 @@ +using Binance.TradeRobot.Business; +using Binance.TradeRobot.Model.Dto; +using Microsoft.AspNetCore.Mvc; + +namespace Binance.TradeRobot.API.Controllers +{ + public class UserController : BaseApiController + { + private UserBusiness userBusiness; + public UserController(UserBusiness userBusiness) + { + this.userBusiness = userBusiness; + } + + /// + /// 用户登录 + /// + /// + /// + [HttpPost] + public LoginResponse Login([FromBody] LoginRequest loginRequest) + { + return userBusiness.Login(loginRequest); + } + + /// + /// 获取用户列表 + /// + [HttpGet] + public void GetUserList() + { + + } + + /// + /// 获取用户资金变更记录 + /// + [HttpGet] + public void GetUserAccountFundChangeRecordList() + { + + } + + /// + /// 获取用户盈亏记录 + /// + [HttpGet] + public void GetUserAccountProfitLossRecordList() + { + } + + /// + /// 追投 + /// + [HttpPost] + public void AddFunds() + { + + } + + /// + /// 提现 + /// + [HttpPost] + public void ReduceFunds() + { + + } + + /// + /// 转移资金 + /// + [HttpPost] + public void TransferFunds() + { + + } + } +} diff --git a/Binance.TradeRobot.API/Controllers/WeatherForecastController.cs b/Binance.TradeRobot.API/Controllers/WeatherForecastController.cs deleted file mode 100644 index 07ac644..0000000 --- a/Binance.TradeRobot.API/Controllers/WeatherForecastController.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Binance.TradeRobot.API.Controllers -{ - [ApiController] - [Route("[controller]")] - public class WeatherForecastController : ControllerBase - { - private static readonly string[] Summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - - private readonly ILogger _logger; - - public WeatherForecastController(ILogger logger) - { - _logger = logger; - } - - [HttpGet] - public IEnumerable Get() - { - var rng = new Random(); - return Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = DateTime.Now.AddDays(index), - TemperatureC = rng.Next(-20, 55), - Summary = Summaries[rng.Next(Summaries.Length)] - }) - .ToArray(); - } - } -} diff --git a/Binance.TradeRobot.API/Extensions/StartupExtenions.cs b/Binance.TradeRobot.API/Extensions/StartupExtenions.cs index 53d6a81..a923bdc 100644 --- a/Binance.TradeRobot.API/Extensions/StartupExtenions.cs +++ b/Binance.TradeRobot.API/Extensions/StartupExtenions.cs @@ -1,4 +1,5 @@ using Binance.TradeRobot.API.Middlewares; +using Binance.TradeRobot.Common.DI; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; @@ -13,41 +14,33 @@ namespace Binance.TradeRobot.API.Extensions { public static class StartupExtenions { - /// - /// 批量注册服务 - /// - /// DI服务 - /// 需要批量注册的程序集集合 - /// 基础类/接口 - /// 服务生命周期 - /// - public static IServiceCollection BatchRegisterService(this IServiceCollection services, Assembly[] assemblys, Type baseType, ServiceLifetime serviceLifetime = ServiceLifetime.Singleton) + public static IServiceCollection BatchRegisterService(this IServiceCollection services, Assembly[] assemblys) { - List typeList = new List(); //所有符合注册条件的类集合 + var registrationInterfaceTypes = new Dictionary(); //待注册集合 + var registrationSelfTypes = new List(); + foreach (var assembly in assemblys) { //筛选当前程序集下符合条件的类 - var types = assembly.GetTypes().Where(t => !t.IsInterface && !t.IsSealed && !t.IsAbstract && baseType.IsAssignableFrom(t)); - if (types != null && types.Count() > 0) - typeList.AddRange(types); - } - if (typeList.Count() == 0) - return services; - - var typeDic = new Dictionary(); //待注册集合 - foreach (var type in typeList) - { - var interfaces = type.GetInterfaces(); //获取接口 - typeDic.Add(type, interfaces); - } - if (typeDic.Keys.Count() > 0) - { - foreach (var instanceType in typeDic.Keys) + var types = assembly.GetTypes().Where(t => !t.IsInterface && !t.IsSealed && !t.IsAbstract && Attribute.IsDefined(t, typeof(BatchRegistrationAttribute))); + if (types == null && types.Count() == 0) + continue; + foreach (var type in types) { - foreach (var interfaceType in typeDic[instanceType]) + var batchRegistrationAttribute = type.GetCustomAttribute(true); + if (batchRegistrationAttribute == null) + continue; + + if (batchRegistrationAttribute.RegistrationType == RegistrationType.Self) + services.Add(new ServiceDescriptor(type, type, batchRegistrationAttribute.ServiceLifetime)); + else if (batchRegistrationAttribute.RegistrationType == RegistrationType.Interface) { - //根据指定的生命周期进行注册 - services.Add(new ServiceDescriptor(interfaceType, instanceType, serviceLifetime)); + var interfaces = type.GetInterfaces(); //获取接口 + foreach (var interfaceType in interfaces) + { + //根据指定的生命周期进行注册 + services.Add(new ServiceDescriptor(interfaceType, type, batchRegistrationAttribute.ServiceLifetime)); + } } } } diff --git a/Binance.TradeRobot.API/Properties/launchSettings.json b/Binance.TradeRobot.API/Properties/launchSettings.json index 19db158..f88824f 100644 --- a/Binance.TradeRobot.API/Properties/launchSettings.json +++ b/Binance.TradeRobot.API/Properties/launchSettings.json @@ -12,7 +12,7 @@ "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, - "launchUrl": "weatherforecast", + "launchUrl": "", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } @@ -20,7 +20,7 @@ "Binance.TradeRobot.API": { "commandName": "Project", "launchBrowser": true, - "launchUrl": "weatherforecast", + "launchUrl": "", "applicationUrl": "https://localhost:5001;http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/Binance.TradeRobot.API/Startup.cs b/Binance.TradeRobot.API/Startup.cs index c0ee4a2..1f8ddf0 100644 --- a/Binance.TradeRobot.API/Startup.cs +++ b/Binance.TradeRobot.API/Startup.cs @@ -1,10 +1,21 @@ +using Binance.TradeRobot.API.Extensions; using Binance.TradeRobot.API.Filters; +using Binance.TradeRobot.Business; +using Binance.TradeRobot.Common.Extensions; +using Binance.TradeRobot.Common.Http; +using Binance.TradeRobot.Model.Base; +using HY.TradingRobot.Model.Base; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.IdentityModel.Tokens; +using Newtonsoft.Json; using Newtonsoft.Json.Serialization; +using System.Reflection; +using System.Text; using Yitter.IdGenerator; namespace Binance.TradeRobot.API @@ -21,6 +32,9 @@ namespace Binance.TradeRobot.API // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { + services.AddSingleton(); + services.AddSingleton(); + var fsql = new FreeSql.FreeSqlBuilder().UseConnectionString(FreeSql.DataType.SqlServer, Configuration.GetConnectionString("DB")).Build(); services.AddSingleton(typeof(IFreeSql), fsql); @@ -47,26 +61,61 @@ namespace Binance.TradeRobot.API setupAction.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Include; setupAction.SerializerSettings.DefaultValueHandling = Newtonsoft.Json.DefaultValueHandling.Include; }); + services.AddSwagger("ҰԶAPI"); + services.BatchRegisterService(new Assembly[] { Assembly.Load("Binance.TradeRobot.Business") }); + services.AddMapper(new MappingProfiles()); + JsonConvert.DefaultSettings = () => new JsonSerializerSettings() + { + ContractResolver = new DefaultContractResolver(), + DateFormatString = "yyyy-MM-dd HH:mm:ss", + NullValueHandling = NullValueHandling.Include, + DefaultValueHandling = DefaultValueHandling.Include + }; + services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, x => + { + var jwtSecret = Configuration.GetSection("JwtConfig:Secret").Value; + var jwtIssuer = Configuration.GetSection("JwtConfig:Issuer").Value; + var jwtAudience = Configuration.GetSection("JwtConfig:Audience").Value; + + x.SaveToken = true; + x.RequireHttpsMetadata = false; + x.TokenValidationParameters = new TokenValidationParameters() + { + ValidateIssuerSigningKey = true, + ValidateIssuer = true, + ValidateAudience = true, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSecret)), + ValidIssuer = jwtIssuer, + ValidAudience = jwtAudience, + ValidateLifetime = true + }; + }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } + app.UseSwagger(c => c.SerializeAsV2 = true) + .UseSwaggerUI(c => + { + c.SwaggerEndpoint("/swagger/v1/swagger.json", "Binance.TradeRobot.API"); + c.RoutePrefix = string.Empty; + }); - app.UseHttpsRedirection(); + app.UseCustomException(); - app.UseRouting(); + //app.UseHttpsRedirection(); + app.UseRouting(); + app.UseCors(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + + //websocket } } } diff --git a/Binance.TradeRobot.API/WeatherForecast.cs b/Binance.TradeRobot.API/WeatherForecast.cs deleted file mode 100644 index 5bb17c6..0000000 --- a/Binance.TradeRobot.API/WeatherForecast.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Binance.TradeRobot.API -{ - public class WeatherForecast - { - public DateTime Date { get; set; } - - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - - public string Summary { get; set; } - } -} diff --git a/Binance.TradeRobot.API/appsettings.json b/Binance.TradeRobot.API/appsettings.json index c97a722..f52a8dd 100644 --- a/Binance.TradeRobot.API/appsettings.json +++ b/Binance.TradeRobot.API/appsettings.json @@ -9,6 +9,11 @@ "AllowedHosts": "*", "ConnectionStrings": { //"DB": "Data Source=192.168.201.44;Initial Catalog=HY.TradingRobot.DB;User ID=sa;Pwd=kaicn1132+-;" - "DB": "Data Source=.;Initial Catalog=Binance.TradeRobot.DB;User ID=sa;Pwd=pc911103;" + "DB": "Data Source=.;Initial Catalog=Binance.TradeRobot.DB;User ID=sa;Pwd=pc911103;Encrypt=True; TrustServerCertificate=True;" + }, + "JwtConfig": { + "Secret": "heyitraderobot11", + "Issuer": "heyi", + "Audience": "heyi" } } diff --git a/Binance.TradeRobot.Business/BaseBusiness.cs b/Binance.TradeRobot.Business/BaseBusiness.cs new file mode 100644 index 0000000..c9dcd0d --- /dev/null +++ b/Binance.TradeRobot.Business/BaseBusiness.cs @@ -0,0 +1,13 @@ +namespace Binance.TradeRobot.Business +{ + public class BaseBusiness + { + protected IFreeSql fsql; + protected NLogManager logManager; + public BaseBusiness(IFreeSql fsql, NLogManager logManager) + { + this.fsql = fsql; + this.logManager = logManager; + } + } +} diff --git a/Binance.TradeRobot.Business/Binance.TradeRobot.Business.csproj b/Binance.TradeRobot.Business/Binance.TradeRobot.Business.csproj index 10ef69a..f46ef42 100644 --- a/Binance.TradeRobot.Business/Binance.TradeRobot.Business.csproj +++ b/Binance.TradeRobot.Business/Binance.TradeRobot.Business.csproj @@ -3,6 +3,8 @@ netstandard2.1 enable + True + Binance.TradeRobot.Business.xml @@ -13,6 +15,7 @@ + diff --git a/Binance.TradeRobot.Business/Binance.TradeRobot.Business.xml b/Binance.TradeRobot.Business/Binance.TradeRobot.Business.xml new file mode 100644 index 0000000..4de0ee0 --- /dev/null +++ b/Binance.TradeRobot.Business/Binance.TradeRobot.Business.xml @@ -0,0 +1,8 @@ + + + + Binance.TradeRobot.Business + + + + diff --git a/Binance.TradeRobot.Business/UserBusiness.cs b/Binance.TradeRobot.Business/UserBusiness.cs new file mode 100644 index 0000000..1395819 --- /dev/null +++ b/Binance.TradeRobot.Business/UserBusiness.cs @@ -0,0 +1,57 @@ +using Binance.TradeRobot.Common.DI; +using Binance.TradeRobot.Common.Extensions; +using Binance.TradeRobot.Model.Base; +using Binance.TradeRobot.Model.Db; +using Binance.TradeRobot.Model.Dto; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; + +namespace Binance.TradeRobot.Business +{ + [BatchRegistration(ServiceLifetime.Singleton, RegistrationType.Self)] + public class UserBusiness : BaseBusiness + { + private IConfiguration configuration; + + public UserBusiness(IFreeSql fsql, NLogManager logManager, IConfiguration configuration) : base(fsql, logManager) + { + this.configuration = configuration; + } + + public LoginResponse Login(LoginRequest loginRequest) + { + var pwd = loginRequest.Pwd.ToMD5(); + var user = fsql.Select().Where(u => u.UserName == loginRequest.UserName && u.Pwd == pwd).ToOne(); + if (user == null) + throw new BusinessException("用户名或密码错误"); + + #region 签发token + var claims = new List() + { + new Claim("Id",user.Id.ToString()), + new Claim("UserName",user.UserName), + }; + + var jwtSecret = configuration.GetSection("JwtConfig:Secret").Value; + var jwtIssuer = configuration.GetSection("JwtConfig:Issuer").Value; + var jwtAudience = configuration.GetSection("JwtConfig:Audience").Value; + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSecret)); + var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); + var jwtToken = new JwtSecurityToken(jwtIssuer, jwtAudience, claims, expires: DateTime.Now.AddDays(30), signingCredentials: credentials); + var token = new JwtSecurityTokenHandler().WriteToken(jwtToken); + #endregion + return new LoginResponse() + { + Id = user.Id, + UserName = user.UserName, + Token = token + }; + } + } +} diff --git a/Binance.TradeRobot.Common/Binance.TradeRobot.Common.csproj b/Binance.TradeRobot.Common/Binance.TradeRobot.Common.csproj new file mode 100644 index 0000000..4734353 --- /dev/null +++ b/Binance.TradeRobot.Common/Binance.TradeRobot.Common.csproj @@ -0,0 +1,15 @@ + + + + netstandard2.1 + enable + + + + + + + + + + diff --git a/Binance.TradeRobot.Common/DI/BatchRegistrationAttribute.cs b/Binance.TradeRobot.Common/DI/BatchRegistrationAttribute.cs new file mode 100644 index 0000000..adbf8d8 --- /dev/null +++ b/Binance.TradeRobot.Common/DI/BatchRegistrationAttribute.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.DependencyInjection; +using System; + +namespace Binance.TradeRobot.Common.DI +{ + [AttributeUsage(AttributeTargets.Class)] + public class BatchRegistrationAttribute : Attribute + { + public ServiceLifetime ServiceLifetime; + public RegistrationType RegistrationType; + + /// + /// 批量注册特性 + /// + /// 生命周期 + /// 注册类型 + public BatchRegistrationAttribute(ServiceLifetime serviceLifetime, RegistrationType registrationType) + { + this.ServiceLifetime = serviceLifetime; + this.RegistrationType = registrationType; + } + } + + /// + /// 注册类型 + /// + public enum RegistrationType + { + Interface, Self + } +} diff --git a/Binance.TradeRobot.Common/Extensions/CryptographyExtension.cs b/Binance.TradeRobot.Common/Extensions/CryptographyExtension.cs new file mode 100644 index 0000000..98dffb8 --- /dev/null +++ b/Binance.TradeRobot.Common/Extensions/CryptographyExtension.cs @@ -0,0 +1,48 @@ +using System; +using System.Security.Cryptography; +using System.Text; + +namespace Binance.TradeRobot.Common.Extensions +{ + public static class CryptographyExtension + { + public static string ToHmacSha256(this string str, string secret, bool toBase64 = true) + { + secret = secret ?? ""; + byte[] keyByte = Encoding.UTF8.GetBytes(secret); + byte[] strBytes = Encoding.UTF8.GetBytes(str); + using (var hmacsha256 = new HMACSHA256(keyByte)) + { + byte[] hashStr = hmacsha256.ComputeHash(strBytes); + if (toBase64) + return Convert.ToBase64String(hashStr); + else + { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < hashStr.Length; i++) + { + builder.Append(hashStr[i].ToString("x2")); + } + return builder.ToString(); + } + } + } + + public static string ToMD5(this string str, bool isUpper = false) + { + var format = isUpper ? "X2" : "x2"; + //32位大写 + using (var md5 = MD5.Create()) + { + var data = md5.ComputeHash(Encoding.UTF8.GetBytes(str)); + StringBuilder builder = new StringBuilder(); + // 循环遍历哈希数据的每一个字节并格式化为十六进制字符串 + for (int i = 0; i < data.Length; i++) + { + builder.Append(data[i].ToString(format)); + } + return builder.ToString(); + } + } + } +} diff --git a/Binance.TradeRobot.Common/Extensions/DateTimeExtension.cs b/Binance.TradeRobot.Common/Extensions/DateTimeExtension.cs new file mode 100644 index 0000000..4458aac --- /dev/null +++ b/Binance.TradeRobot.Common/Extensions/DateTimeExtension.cs @@ -0,0 +1,101 @@ +using System; +using System.Runtime.InteropServices; + +namespace Binance.TradeRobot.Common.Extensions +{ + public static class DateTimeExtension + { + private static readonly DateTime beginTime = new DateTime(1970, 1, 1, 0, 0, 0, 0); + + /// + /// 时间戳转时间 + /// + /// 时间 + /// true:13, false:10 + /// + [Obsolete] + public static DateTime StampToDateTime(this long timeStamp) + { + DateTime dt = TimeZone.CurrentTimeZone.ToLocalTime(beginTime); + return timeStamp.ToString().Length == 13 ? dt.AddMilliseconds(timeStamp) : dt.AddSeconds(timeStamp); + } + + /// + /// 时间转时间戳 + /// + /// 时间 + /// true:13, false:10 + /// + [Obsolete] + public static long DateTimeToStamp(this DateTime time, bool len13 = true) + { + TimeSpan ts = time.ToUniversalTime() - beginTime; + if (len13) + return Convert.ToInt64(ts.TotalMilliseconds); + else + return Convert.ToInt64(ts.TotalSeconds); + } + + /// + /// 将秒数转换为时分秒形式 + /// + /// + /// + public static string FormatToHHmmss(long second) + { + if (second < 60) + return $"00:00:{(second >= 10 ? $"{second}" : $"0{second}")}"; + if (second < 3600) + { + var minute = second / 60; + var s = second % 60; + return $"00:{(minute >= 10 ? $"{minute}" : $"0{minute}")}:{(s >= 10 ? $"{s}" : $"0{s}")}"; + } + else + { + var hour = second / 3600; + var minute = (second - (hour * 3600)) / 60; + var s = (second - ((hour * 3600) + minute * 60)) % 60; + return $"{(hour >= 10 ? $"{hour}" : $"0{hour}")}:{(minute >= 10 ? $"{minute}" : $"0{minute}")}:{(s >= 10 ? $"{s}" : $"0{s}")}"; + } + } + + #region SetLocalTime + [DllImport("Kernel32.dll")] + private static extern bool SetLocalTime(ref SYSTEMTIME lpSystemTime); + + [StructLayout(LayoutKind.Sequential)] + private struct SYSTEMTIME + { + public ushort wYear; + public ushort wMonth; + public ushort wDayOfWeek; + public ushort wDay; + public ushort wHour; + public ushort wMinute; + public ushort wSecond; + public ushort wMilliseconds; + } + + /// + /// 修改系统时间(需要管理员权限) + /// + /// + public static void SetSystemTime(DateTime date) + { + SYSTEMTIME lpTime = new SYSTEMTIME(); + lpTime.wYear = Convert.ToUInt16(date.Year); + lpTime.wMonth = Convert.ToUInt16(date.Month); + lpTime.wDayOfWeek = Convert.ToUInt16(date.DayOfWeek); + lpTime.wDay = Convert.ToUInt16(date.Day); + DateTime time = date; + lpTime.wHour = Convert.ToUInt16(time.Hour); + lpTime.wMinute = Convert.ToUInt16(time.Minute); + lpTime.wSecond = Convert.ToUInt16(time.Second); + lpTime.wMilliseconds = Convert.ToUInt16(time.Millisecond); + var r = SetLocalTime(ref lpTime); + Console.WriteLine($"修改系统时间 {r}"); + } + #endregion + } +} diff --git a/Binance.TradeRobot.Common/Extensions/EnumExtension.cs b/Binance.TradeRobot.Common/Extensions/EnumExtension.cs new file mode 100644 index 0000000..58a0df1 --- /dev/null +++ b/Binance.TradeRobot.Common/Extensions/EnumExtension.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; + +namespace Binance.TradeRobot.Common.Extensions +{ + public static class EnumExtension + { + /// + /// 从枚举中获取Description + /// + /// 需要获取枚举描述的枚举 + /// 描述内容 + public static string GetDescription(this System.Enum enumName) + { + var fieldInfo = enumName.GetType().GetField(enumName.ToString()); + var attributes = GetDescriptAttr(fieldInfo); + string description; + if (attributes != null && attributes.Length > 0) + { + description = attributes[0].Description; + } + else + { + description = enumName.ToString(); + } + return description; + } + + /// + /// 根据 value 值获取Description + /// + /// + /// + /// + public static string GetDescription(this Type enumType, int value) + { + var Key = GetNameAndValue(enumType).FirstOrDefault(p => p.Value.Equals(value)).Key; + if (Key == null) + return null; + + return Key.ToString(); + } + + /// + /// 获取字段Description + /// + /// FieldInfo + /// DescriptionAttribute[] + private static DescriptionAttribute[] GetDescriptAttr(FieldInfo fieldInfo) + { + if (fieldInfo != null) + { + return (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false); + } + return null; + } + + /// + /// 获取枚举所有名称 + /// + /// 枚举类型typeof(T) + /// 枚举名称列表 + public static List GetEnumNamesList(this Type enumType) + { + return Enum.GetNames(enumType).ToList(); + } + + /// + /// 获取所有枚举对应的值 + /// + /// 枚举类型typeof(T) + /// 枚举值列表 + public static List GetEnumValuesList(this Type enumType) + { + var list = new List(); + foreach (var value in System.Enum.GetValues(enumType)) + { + list.Add(Convert.ToInt32(value)); + } + return list; + } + + /// + /// 获取枚举名以及对应的Description + /// + /// 枚举类型typeof(T) + /// 返回Dictionary ,Key为枚举名, Value为枚举对应的Description + public static Dictionary GetNameAndDescriptions(this Type type) + { + if (type.IsEnum) + { + var dic = new Dictionary(); + var enumValues = System.Enum.GetValues(type); + foreach (System.Enum value in enumValues) + { + dic.Add(value, GetDescription(value)); + } + return dic; + } + return null; + } + + /// + /// 获取枚举名以及对应的Value + /// + /// 枚举类型typeof(T) + /// 返回Dictionary ,Key为描述名, Value为枚举对应的值 + public static Dictionary GetNameAndValue(this Type type) + { + if (type.IsEnum) + { + var dic = new Dictionary(); + var enumValues = System.Enum.GetValues(type); + foreach (System.Enum value in enumValues) + { + dic.Add(GetDescription(value), value.GetHashCode()); + } + return dic; + } + return null; + } + + public static int GetValue(this System.Enum enumName) + { + return Convert.ToInt32(enumName.GetType().GetField(enumName.ToString()).GetValue(enumName)); + } + } +} diff --git a/Binance.TradeRobot.Common/Extensions/MapperExtension.cs b/Binance.TradeRobot.Common/Extensions/MapperExtension.cs new file mode 100644 index 0000000..2e5ac6b --- /dev/null +++ b/Binance.TradeRobot.Common/Extensions/MapperExtension.cs @@ -0,0 +1,59 @@ +using AutoMapper; +using Microsoft.Extensions.DependencyInjection; + +namespace Binance.TradeRobot.Common.Extensions +{ + public static class MapperExtension + { + private static IMapper _mapper; + + /// + /// 注册对象映射器 + /// + /// + /// + /// + public static IServiceCollection AddMapper(this IServiceCollection services, Profile profile) + { + var config = new MapperConfiguration(cfg => + { + cfg.AddProfile(profile); + }); + _mapper = config.CreateMapper(); + return services; + } + + /// + /// 设置对象映射执行者 + /// + /// 映射执行者 + public static void SetMapper(IMapper mapper) + { + _mapper = mapper; + } + + /// + /// 将对象映射为指定类型 + /// + /// 要映射的目标类型 + /// 源对象 + /// 目标类型的对象 + public static TTarget Map(this object source) + { + return _mapper.Map(source); + } + + /// + /// 使用源类型的对象更新目标类型的对象 + /// + /// 源类型 + /// 目标类型 + /// 源对象 + /// 待更新的目标对象 + /// 更新后的目标类型对象 + public static TTarget Map(this TSource source, TTarget target) + { + return _mapper.Map(source, target); + } + } +} diff --git a/Binance.TradeRobot.Common/Extensions/MathExtension.cs b/Binance.TradeRobot.Common/Extensions/MathExtension.cs new file mode 100644 index 0000000..8a65c62 --- /dev/null +++ b/Binance.TradeRobot.Common/Extensions/MathExtension.cs @@ -0,0 +1,51 @@ +using System; + +namespace Binance.TradeRobot.Common.Extensions +{ + public static class MathExtension + { + /// + /// 四舍五入保留小数 + /// + /// + /// 保留的位数 + /// + public static decimal Round(this decimal d, int length) + { + return Math.Round(d, length, MidpointRounding.AwayFromZero); + } + + /// + /// string转换decimal + /// + /// + /// + /// + public static decimal ToDecimal(this string s, int? length = null) + { + if (!decimal.TryParse(s, out decimal d)) + return 0M; + if (length != null) + return d.Round(length.Value); + return d; + } + + /// + /// 截取小数位数,不进行四舍五入 + /// + /// + /// 截取的位数 + /// + public static decimal CutDecimal(this decimal d, int length) + { + var dStr = d.ToString(); + var dotIndex = dStr.IndexOf("."); + var cutLength = dotIndex + 1 + length; + if (dotIndex == -1 || dStr.Length <= cutLength) + dStr = string.Format($"{{0:F{length}}}", d); + else + dStr = dStr.Substring(0, cutLength); + return decimal.Parse(dStr); + } + } +} diff --git a/Binance.TradeRobot.Common/Helpers/ClearMemoryHelper.cs b/Binance.TradeRobot.Common/Helpers/ClearMemoryHelper.cs new file mode 100644 index 0000000..b1e8e8d --- /dev/null +++ b/Binance.TradeRobot.Common/Helpers/ClearMemoryHelper.cs @@ -0,0 +1,27 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Binance.TradeRobot.Common.Helpers +{ + /// + /// 清理内存 + /// + public class ClearMemoryHelper + { + [DllImport("psapi.dll")] + static extern int EmptyWorkingSet(IntPtr hwProc); + + public static void ClearMemory(string processName) + { + Process[] processes = string.IsNullOrEmpty(processName) ? + new Process[] { Process.GetCurrentProcess() } : + Process.GetProcessesByName(processName); + try + { + Array.ForEach(processes, p => EmptyWorkingSet(p.Handle)); + } + catch { throw; } + } + } +} diff --git a/Binance.TradeRobot.Common/Helpers/ShellExecuteHelper.cs b/Binance.TradeRobot.Common/Helpers/ShellExecuteHelper.cs new file mode 100644 index 0000000..134e260 --- /dev/null +++ b/Binance.TradeRobot.Common/Helpers/ShellExecuteHelper.cs @@ -0,0 +1,49 @@ +using System; +using System.Runtime.InteropServices; + +namespace Binance.TradeRobot.Common.Helpers +{ + public class ShellExecuteHelper + { + public enum ShowCommands : int + { + SW_HIDE = 0, + SW_SHOWNORMAL = 1, + SW_NORMAL = 1, + SW_SHOWMINIMIZED = 2, + SW_SHOWMAXIMIZED = 3, + SW_MAXIMIZE = 3, + SW_SHOWNOACTIVATE = 4, + SW_SHOW = 5, + SW_MINIMIZE = 6, + SW_SHOWMINNOACTIVE = 7, + SW_SHOWNA = 8, + SW_RESTORE = 9, + SW_SHOWDEFAULT = 10, + SW_FORCEMINIMIZE = 11, + SW_MAX = 11 + } + + /// + /// 内核调用 + /// + /// ShellExecuteHelper.ShellExecute(IntPtr.Zero, "open", , string.Empty, string.Empty, ShellExecuteHelper.ShowCommands.SW_SHOWNORMAL); + /// + /// + /// + /// + /// + /// + /// + /// + /// + [DllImport("shell32.dll")] + public static extern IntPtr ShellExecute( + IntPtr hwnd, + string lpOperation, + string lpFile, + string lpParameters, + string lpDirectory, + ShowCommands nShowCmd); + } +} diff --git a/Binance.TradeRobot.Common/Http/HttpDownloader.cs b/Binance.TradeRobot.Common/Http/HttpDownloader.cs new file mode 100644 index 0000000..3791766 --- /dev/null +++ b/Binance.TradeRobot.Common/Http/HttpDownloader.cs @@ -0,0 +1,456 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Threading; + +namespace Binance.TradeRobot.Common.Http +{ + /// + /// Http下载器 + /// + public class HttpDownloader + { + + private IHttpClientFactory httpClientFactory; + + public HttpDownloader(IHttpClientFactory httpClientFactory, DownloadSetting dwSetting = null) + { + this.httpClientFactory = httpClientFactory; + complateArgs = new DownloadCompletedEventArgs(); + changeArgs = new DownloadProgressChangedEventArgs(); + if (dwSetting == null) + dwSetting = new DownloadSetting(); + downloadSetting = dwSetting; + buffer = new byte[downloadSetting.BufferSize]; + } + + ~HttpDownloader() + { + this.buffer = null; + this.downloadSetting = null; + this.complateArgs.Error = null; + this.complateArgs = null; + this.changeArgs = null; + } + + #region Property + + /// + /// 下载进度变化参数 + /// + private DownloadProgressChangedEventArgs changeArgs; + + /// + /// 下载完成参数 + /// + private DownloadCompletedEventArgs complateArgs; + + /// + /// 下载参数配置类 + /// + private DownloadSetting downloadSetting; + + /// + /// 下载缓冲区 + /// + private byte[] buffer; + + #endregion + + #region Delegate + public delegate void DownloadProgressChangedEventHandler(object sender, DownloadProgressChangedEventArgs e); + + public delegate void DownloadCompletedEventHandler(object sender, DownloadCompletedEventArgs e); + + public delegate void ReDownloadEventHandler(object sender, DownloadCompletedEventArgs e); + #endregion + + #region Event + /// + /// 下载进度变化事件 + /// + public event DownloadProgressChangedEventHandler OnDownloadProgressChanged; + + /// + /// 下载完成事件 + /// + public event DownloadCompletedEventHandler OnDownloadComplated; + + /// + /// 自动重下事件 + /// + public event ReDownloadEventHandler OnReDownload; + #endregion + + #region Method + /// + /// 设置下载参数 + /// + /// + public void SetDownloadSetting(DownloadSetting dwSetting) + { + if (dwSetting != null) + downloadSetting = dwSetting; + } + + private void DoProcessChangedEvent(DownloadProgressChangedEventArgs args) + { + OnDownloadProgressChanged?.Invoke(this, args); + } + + private void DoCompletedEvent(DownloadCompletedEventArgs args) + { + OnDownloadComplated?.Invoke(this, args); + } + + private void DoReDownloadEvent(DownloadCompletedEventArgs args) + { + OnReDownload?.Invoke(this, args); + } + + private void ArgsInit() + { + changeArgs.Cancel = false; + complateArgs.Cancelled = false; + complateArgs.Error = null; + } + + /// + /// 获取文件总大小 + /// + /// + /// + public long GetTotalLength(string url) + { + long length = 0; + try + { + using (var httpClient = httpClientFactory.CreateClient()) + { + var requestMessage = new HttpRequestMessage(HttpMethod.Head, url); + var httpResponse = httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead).Result; + if (httpResponse.IsSuccessStatusCode) + length = httpResponse.Content.Headers.ContentLength ?? 0; + } + + //var req = (HttpWebRequest)WebRequest.Create(new Uri(url)); + //req.Method = "HEAD"; + //req.Timeout = 5000; + //var res = (HttpWebResponse)req.GetResponse(); + //if (res.StatusCode == HttpStatusCode.OK) + //{ + // length = res.ContentLength; + //} + //res.Close(); + return length; + } + catch (WebException wex) + { + throw wex; + } + } + + + private bool DownloadFile(string url, string savePath, long startPosition, long totalSize) + { + long currentReadSize = 0; //当前已经读取的字节数 + if (startPosition != 0) + currentReadSize = startPosition; + using (var httpClient = httpClientFactory.CreateClient()) + { + try + { + httpClient.Timeout = new TimeSpan(0, 0, 20); + if (currentReadSize != 0 && currentReadSize == totalSize) + { + changeArgs.CurrentSize = changeArgs.TotalSize = totalSize; + return true; + } + if (currentReadSize != 0) + { + try + { + httpClient.DefaultRequestHeaders.Range = new System.Net.Http.Headers.RangeHeaderValue(currentReadSize, totalSize); + } + catch (Exception ex) + { + //http 416 + currentReadSize = 0; + } + } + + using (var response = httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead).Result) + { + using (var responseStream = response.Content.ReadAsStreamAsync().Result) + { + changeArgs.TotalSize = totalSize; + using (var fs = new FileStream(savePath, currentReadSize == 0 ? FileMode.Create : FileMode.Append, FileAccess.Write)) + { + //判断服务器是否支持断点续下 + if (currentReadSize != 0 && + response.StatusCode == HttpStatusCode.PartialContent && + response.Content.Headers.ContentLength != totalSize) + fs.Seek(startPosition, SeekOrigin.Begin); //支持,从本地文件流末尾进行写入 + else + currentReadSize = 0; //不支持,从本地文件流开始进行写入 + + if (buffer.Length != downloadSetting.BufferSize) + buffer = new byte[downloadSetting.BufferSize]; + int size = responseStream.Read(buffer, 0, buffer.Length); + while (size != 0) + { + fs.Write(buffer, 0, size); + if (buffer.Length != downloadSetting.BufferSize) + buffer = new byte[downloadSetting.BufferSize]; + size = responseStream.Read(buffer, 0, buffer.Length); + currentReadSize += size; //累计下载大小 + //执行下载进度变化事件 + changeArgs.CurrentSize = currentReadSize; + DoProcessChangedEvent(changeArgs); + //判断挂起状态 + if (changeArgs.Cancel) + return true; + } + + //执行下载进度变化事件 + changeArgs.CurrentSize = changeArgs.TotalSize; + DoProcessChangedEvent(changeArgs); + fs.Flush(true); + } + + //验证远程服务器文件字节数和本地文件字节数是否一致 + long localFileSize = 0; + try + { + localFileSize = new FileInfo(savePath).Length; + } + catch (Exception ex) + { + localFileSize = changeArgs.CurrentSize; + } + + if (totalSize != localFileSize) + { + complateArgs.Error = new Exception(string.Format("远程文件字节:[{0}]与本地文件字节:[{1}]不一致", totalSize, localFileSize)); + } + } + } + } + catch (IOException ex) + { + complateArgs.Error = ex; + } + catch (WebException ex) + { + complateArgs.Error = ex; + } + catch (Exception ex) + { + complateArgs.Error = ex; + } + } + return complateArgs.Error == null; + } + + + public bool DownloadFile(string url, string saveFolderPath, string saveFileName, long? totalSize) + { + ArgsInit(); + var result = false; + //验证文件夹 + try + { + if (!Directory.Exists(saveFolderPath)) + Directory.CreateDirectory(saveFolderPath); + } + catch (Exception ex) + { + complateArgs.Error = ex; + DoCompletedEvent(complateArgs); + return result; + } + + if (totalSize == null || totalSize.Value == 0) + { + try + { + totalSize = GetTotalLength(url); + } + catch (Exception ex) + { + Console.WriteLine("获取远程服务器字节数失败 {0}", ex.Message); + complateArgs.Error = ex; + DoCompletedEvent(complateArgs); + return result; + } + } + + string saveFilePath = Path.Combine(saveFolderPath, saveFileName); + long startPosition = 0; + for (var i = 1; i <= downloadSetting.TryCount; i++) + { + if (File.Exists(saveFilePath)) + { + try + { + startPosition = new FileInfo(saveFilePath).Length; + } + catch + { + startPosition = 0; + } + } + + result = DownloadFile(url, saveFilePath, startPosition, totalSize.Value); + if (result) + { + break; + } + else + { + if (i != downloadSetting.TryCount) + { + complateArgs.TryCount = i + 1; + DoReDownloadEvent(complateArgs); + Thread.Sleep(downloadSetting.SleepTime); + if (complateArgs.Cancelled) + break; + ArgsInit(); + } + } + if (complateArgs.Cancelled) + break; + } + DoCompletedEvent(complateArgs); + return result; + } + + public byte[] DwonloadBytes(string url) + { + ArgsInit(); + using (var httpClient = httpClientFactory.CreateClient()) + { + return httpClient.GetByteArrayAsync(url).Result; + } + + } + + /// + /// 取消/暂停下载 + /// + public void CancelDownload() + { + this.changeArgs.Cancel = true; + this.complateArgs.Cancelled = true; + } + #endregion + } + + + /// + /// 下载进度变化参数 + /// + public class DownloadProgressChangedEventArgs + { + public DownloadProgressChangedEventArgs() + { + ProgressPercentage = 0.0; + Cancel = false; + } + /// + /// 下载进度百分比 + /// + public double ProgressPercentage; + + + private long currentSize; + + /// + /// 当前下载总大小 + /// + public long CurrentSize + { + get { return currentSize; } + set + { + currentSize = value; + this.ProgressPercentage = Math.Round((CurrentSize * 1.0 / TotalSize) * 100, 2); + } + } + + /// + /// 文件总大小 + /// + public long TotalSize; + + /// + /// 取消状态 + /// + public bool Cancel; + } + + /// + /// 下载完成参数 + /// + public class DownloadCompletedEventArgs + { + public DownloadCompletedEventArgs() + { + Cancelled = false; + Error = null; + } + /// + /// 下载异常 + /// + public Exception Error; + + /// + /// 重试次数 + /// + public int TryCount; + + /// + /// 下载操作是否被取消 【取消则为true;默认false】 + /// + public bool Cancelled; + } + + public class DownloadSetting + { + public DownloadSetting() + { + this.BufferSize = 1024 * 1024; + this.TryCount = 5; + this.SleepTime = 5000; + } + + /// + /// + /// + /// + /// + /// 毫秒 + public DownloadSetting(int bufferSize, int tryCount, int sleepTime) + { + this.BufferSize = bufferSize; + this.TryCount = tryCount; + this.SleepTime = sleepTime; + } + + /// + /// 下载缓冲区大小 + /// + public int BufferSize; + + /// + /// 重试次数 + /// + public int TryCount; + + /// + /// 自动重下休眠时间, 毫秒 + /// + public int SleepTime; + } +} diff --git a/Binance.TradeRobot.Common/Http/RestAPIService.cs b/Binance.TradeRobot.Common/Http/RestAPIService.cs new file mode 100644 index 0000000..9184d8a --- /dev/null +++ b/Binance.TradeRobot.Common/Http/RestAPIService.cs @@ -0,0 +1,93 @@ +using Binance.TradeRobot.Common.Extensions; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Text; + +namespace Binance.TradeRobot.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, 20); + + private IHttpClientFactory httpClientFactory; + + public RestApiService(IHttpClientFactory httpClientFactory) + { + this.httpClientFactory = httpClientFactory; + } + + /// + /// 发送请求 + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public string SendRequest(string apiHost, + string apiPath, + object param, + IDictionary headers, + HttpMethod httpMethod, + string contentType = ContentType_Json, + ParamPosition paramPosition = ParamPosition.Body, + bool enableRandomTimeStamp = false) + { + //Get和Delete强制使用QueryString形式传参 + if (httpMethod == HttpMethod.Get || httpMethod == HttpMethod.Delete) + paramPosition = ParamPosition.Query; + + //拼接Url + var url = $"{apiHost}{(apiHost.EndsWith("/") ? 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 = httpClientFactory.CreateClient()) + { + httpClient.Timeout = TimeOut; + using (var request = new HttpRequestMessage(httpMethod, url)) + { + if (headers != null && headers.Count > 0) + foreach (var key in headers.Keys) + request.Headers.Add(key, headers[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).Result) + { + if (!response.IsSuccessStatusCode) + throw new Exception($"HttpCode {response.StatusCode}"); + return response.Content.ReadAsStringAsync().Result; + } + } + } + } + } + + /// + /// 参数传递位置 + /// + public enum ParamPosition + { + Query, + Body + } +} diff --git a/Binance.TradeRobot.Common/Tasks/DelayTrigger.cs b/Binance.TradeRobot.Common/Tasks/DelayTrigger.cs new file mode 100644 index 0000000..1a54f9c --- /dev/null +++ b/Binance.TradeRobot.Common/Tasks/DelayTrigger.cs @@ -0,0 +1,68 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Binance.TradeRobot.Common +{ + /// + /// 延迟触发器 + /// + public class DelayTrigger + { + public DelayTrigger(int delayTime = 500) + { + if (delayTime < 500) + delayTime = 500; + this.delayTime = delayTime; + } + /// + /// 延迟执行时间(ms) + /// + private int delayTime; + + /// + /// 关键字 + /// + private string currentKey; + + /// + /// 是否可以执行 + /// + private volatile bool canExecute; + + /// + /// 是否正在延迟响应中 + /// + private volatile bool isDelaying; + + + public Action OnExecute; + + + public void SetKey(string key) + { + currentKey = key; + if (isDelaying) + { + canExecute = false; + return; + } + Task.Factory.StartNew(delegate + { + isDelaying = true; + while (true) + { + canExecute = true; + Thread.Sleep(delayTime); + if (canExecute) + { + Console.WriteLine($"{currentKey} Execute at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:ffff")}"); + OnExecute?.Invoke(currentKey); + isDelaying = false; + break; + } + } + }); + } + } +} diff --git a/Binance.TradeRobot.Common/Tasks/LimitedConcurrencyLevelTaskScheduler.cs b/Binance.TradeRobot.Common/Tasks/LimitedConcurrencyLevelTaskScheduler.cs new file mode 100644 index 0000000..96814b0 --- /dev/null +++ b/Binance.TradeRobot.Common/Tasks/LimitedConcurrencyLevelTaskScheduler.cs @@ -0,0 +1,156 @@ +//-------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// File: LimitedConcurrencyTaskScheduler.cs +// +//-------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Binance.TradeRobot.Common.Tasks +{ + /// + /// Provides a task scheduler that ensures a maximum concurrency level while + /// running on top of the ThreadPool. + /// + public class LimitedConcurrencyLevelTaskScheduler : TaskScheduler + { + /// Whether the current thread is processing work items. + [ThreadStatic] + private static bool _currentThreadIsProcessingItems; + /// The list of tasks to be executed. + private readonly LinkedList _tasks = new LinkedList(); // protected by lock(_tasks) + /// The maximum concurrency level allowed by this scheduler. + private readonly int _maxDegreeOfParallelism; + /// Whether the scheduler is currently processing work items. + private int _delegatesQueuedOrRunning = 0; // protected by lock(_tasks) + + /// + /// 最大并发数 + /// + private readonly int maxConcurrencyCountOfSystem = Environment.ProcessorCount * 2; + + /// + /// Initializes an instance of the LimitedConcurrencyLevelTaskScheduler class with the + /// specified degree of parallelism. + /// + /// The maximum degree of parallelism provided by this scheduler. + public LimitedConcurrencyLevelTaskScheduler(int maxDegreeOfParallelism) + { + if (maxDegreeOfParallelism < 1) throw new ArgumentOutOfRangeException("maxDegreeOfParallelism"); + if (maxDegreeOfParallelism > maxConcurrencyCountOfSystem) + maxDegreeOfParallelism = maxConcurrencyCountOfSystem; + _maxDegreeOfParallelism = maxDegreeOfParallelism; + } + + /// Queues a task to the scheduler. + /// The task to be queued. + protected sealed override void QueueTask(Task task) + { + // Add the task to the list of tasks to be processed. If there aren't enough + // delegates currently queued or running to process tasks, schedule another. + lock (_tasks) + { + _tasks.AddLast(task); + if (_delegatesQueuedOrRunning < _maxDegreeOfParallelism) + { + ++_delegatesQueuedOrRunning; + NotifyThreadPoolOfPendingWork(); + } + } + } + + /// + /// Informs the ThreadPool that there's work to be executed for this scheduler. + /// + private void NotifyThreadPoolOfPendingWork() + { + ThreadPool.UnsafeQueueUserWorkItem(_ => + { + // Note that the current thread is now processing work items. + // This is necessary to enable inlining of tasks into this thread. + //new Thread(() => + //{ + _currentThreadIsProcessingItems = true; + try + { + // Process all available items in the queue. + while (true) + { + Task item; + lock (_tasks) + { + // When there are no more items to be processed, + // note that we're done processing, and get out. + if (_tasks.Count == 0) + { + --_delegatesQueuedOrRunning; + break; + } + + // Get the next item from the queue + item = _tasks.First.Value; + _tasks.RemoveFirst(); + } + + // Execute the task we pulled out of the queue + base.TryExecuteTask(item); + } + } + // We're done processing items on the current thread + finally { _currentThreadIsProcessingItems = false; } + //}) + //{ IsBackground = true }.Start(); + }, null); + } + + /// Attempts to execute the specified task on the current thread. + /// The task to be executed. + /// + /// Whether the task could be executed on the current thread. + protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) + { + // If this thread isn't already processing a task, we don't support inlining + if (!_currentThreadIsProcessingItems) return false; + + // If the task was previously queued, remove it from the queue + if (taskWasPreviouslyQueued) TryDequeue(task); + + // Try to run the task. + return base.TryExecuteTask(task); + } + + /// Attempts to remove a previously scheduled task from the scheduler. + /// The task to be removed. + /// Whether the task could be found and removed. + protected sealed override bool TryDequeue(Task task) + { + lock (_tasks) return _tasks.Remove(task); + } + + /// Gets the maximum concurrency level supported by this scheduler. + public sealed override int MaximumConcurrencyLevel { get { return _maxDegreeOfParallelism; } } + + /// Gets an enumerable of the tasks currently scheduled on this scheduler. + /// An enumerable of the tasks currently scheduled. + protected sealed override IEnumerable GetScheduledTasks() + { + bool lockTaken = false; + try + { + Monitor.TryEnter(_tasks, ref lockTaken); + if (lockTaken) return _tasks.ToArray(); + else throw new NotSupportedException(); + } + finally + { + if (lockTaken) Monitor.Exit(_tasks); + } + } + } +} \ No newline at end of file diff --git a/Binance.TradeRobot.Model/Base/MappingProfiles.cs b/Binance.TradeRobot.Model/Base/MappingProfiles.cs new file mode 100644 index 0000000..b63616d --- /dev/null +++ b/Binance.TradeRobot.Model/Base/MappingProfiles.cs @@ -0,0 +1,8 @@ +using AutoMapper; + +namespace Binance.TradeRobot.Model.Base +{ + public class MappingProfiles : Profile + { + } +} diff --git a/Binance.TradeRobot.Model/Binance.TradeRobot.Model.csproj b/Binance.TradeRobot.Model/Binance.TradeRobot.Model.csproj index d3054bb..78b48d1 100644 --- a/Binance.TradeRobot.Model/Binance.TradeRobot.Model.csproj +++ b/Binance.TradeRobot.Model/Binance.TradeRobot.Model.csproj @@ -3,13 +3,12 @@ netstandard2.1 enable + True + Binance.TradeRobot.Model.xml - - - - + diff --git a/Binance.TradeRobot.Model/Binance.TradeRobot.Model.xml b/Binance.TradeRobot.Model/Binance.TradeRobot.Model.xml new file mode 100644 index 0000000..079612d --- /dev/null +++ b/Binance.TradeRobot.Model/Binance.TradeRobot.Model.xml @@ -0,0 +1,123 @@ + + + + Binance.TradeRobot.Model + + + + + 业务异常 + + + + + 错误代码 + + + + + 现货 + + + + + 合约 + + + + + 资金变更类型 追投=0,提现=1,转移=2 + + + + + 增加资金 + + + + + 减少资金 + + + + + 转移资金 + + + + + 资金方向 转入=0,转出=1 + + + + + 机器人状态 Stop=0,Runing=1 + + + + + 投资本金 + + + + + 收益 + + + + + 提前金额 + + + + + 变更金额 + + + + + 操作者Id + + + + + 用户Id + + + + + 对端用户Id + + + + + 业务类型 + + + + + 变更金额 + + + + + 分红比例 + + + + + 订单利润 + + + + + 用户投资收益 + + + + + 密码需要Md5小写加密 + + + + diff --git a/Binance.TradeRobot.Model/Dto/Request/User/LoginRequest.cs b/Binance.TradeRobot.Model/Dto/Request/User/LoginRequest.cs new file mode 100644 index 0000000..fe1fc30 --- /dev/null +++ b/Binance.TradeRobot.Model/Dto/Request/User/LoginRequest.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Binance.TradeRobot.Model.Dto +{ + public class LoginRequest + { + public string UserName { get; set; } + + /// + /// 密码需要Md5小写加密 + /// + public string Pwd { get; set; } + } +} diff --git a/Binance.TradeRobot.Model/Dto/Response/User/LoginResponse.cs b/Binance.TradeRobot.Model/Dto/Response/User/LoginResponse.cs new file mode 100644 index 0000000..96fcd1d --- /dev/null +++ b/Binance.TradeRobot.Model/Dto/Response/User/LoginResponse.cs @@ -0,0 +1,11 @@ +namespace Binance.TradeRobot.Model.Dto +{ + public class LoginResponse + { + public long Id { get; set; } + + public string UserName { get; set; } + + public string Token { get; set; } + } +} diff --git a/Binance.TradeRobot.Model/Dto/Response/User/UserResponse.cs b/Binance.TradeRobot.Model/Dto/Response/User/UserResponse.cs new file mode 100644 index 0000000..6057e9f --- /dev/null +++ b/Binance.TradeRobot.Model/Dto/Response/User/UserResponse.cs @@ -0,0 +1,6 @@ +namespace Binance.TradeRobot.Model.Dto +{ + public class UserResponse + { + } +}