From fa03add754fd7422792d488c23fea15e3af6f423 Mon Sep 17 00:00:00 2001 From: chfei Date: Tue, 22 Sep 2026 17:52:42 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=90=8C=E6=AD=A5=E7=89=A9?= =?UTF-8?q?=E6=B5=81=E6=A8=A1=E5=9D=97=20=E4=BF=AE=E6=94=B9=E7=8E=B0?= =?UTF-8?q?=E6=9C=89=E6=9E=B6=E6=9E=84=EF=BC=8C=E6=94=B9=E4=B8=BA=E5=88=86?= =?UTF-8?q?=E6=A8=A1=E5=9D=97(=20=E7=AE=80=E6=98=93=E7=89=88)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- KJD_WebService.sln | 18 + .../Requests/OemWithdrawalAuditRequest.cs | 2 +- .../Requests/OemWithdrawalQueryRequest.cs | 2 +- .../BLL/WXXD/Requests/OemWithdrawalRequest.cs | 2 +- KJD_WebService/BLL/WXXD/WXOemManage.cs | 3 +- KJD_WebService/ContainerConfig.cs | 162 +-- .../Controllers/AsmxApiController.cs | 1 + .../Controllers/ImageCacheController.cs | 4 +- .../Controllers/WX/OEMController.cs | 5 +- .../Controllers/WX/WXUserController.cs | 4 +- .../Controllers/WX/WXUser_N_Controller.cs | 4 +- KJD_WebService/Core/Logger/ILoggerFacade.cs | 69 - KJD_WebService/Core/Logger/LogLevel.cs | 16 - .../DependencyInjection/IScopedDependency.cs | 9 - .../ISingletonDependency.cs | 9 - .../ITransientDependency.cs | 9 - KJD_WebService/Global.asax.cs | 21 +- KJD_WebService/KJD_WebService.csproj | 49 +- KJD_WebService/Mappers/WeOrderMapper.cs | 13 + KJD_WebService/NotFoundMessageFilter.cs | 4 +- KJD_WebService/Requests/RequestBase.cs | 14 +- .../Requests/Users/CUUserInfoRequest.cs | 3 +- .../Requests/Users/ShopRenewRequest.cs | 3 +- .../Services/Users/UserService.FeeRecord.cs | 164 +++ KJD_WebService/SignValidateFilterAttribute.cs | 6 +- KJD_WebService/Startup.cs | 30 + KJD_WebService/Web.config | 7 + KJD_WebService/packages.config | 1 - MAX.Core/Caches/ICache.cs | 99 ++ MAX.Core/Caches/MemoryCache.cs | 168 +++ MAX.Core/Commons/CommonValue.cs | 37 + MAX.Core/Commons/Result.cs | 55 + MAX.Core/Cryptography/Base64Helper.cs | 69 + MAX.Core/Cryptography/DESHelper.cs | 215 +++ MAX.Core/Cryptography/MD5Helper.cs | 62 + MAX.Core/Cryptography/UrlHelper.cs | 51 + .../IDependencyRegistrar.cs | 15 + .../DependencyInjection/IScopedDependency.cs | 8 + .../DependencyInjection/IServiceRegistrar.cs | 81 ++ .../ISingletonDependency.cs | 8 + .../ITransientDependency.cs | 8 + .../LazyServiceProvider.cs | 37 + MAX.Core/Extensions/Convert.cs | 218 ++++ MAX.Core/Extensions/DictionaryExtension.cs | 33 + MAX.Core/Helpers/Common.cs | 55 + MAX.Core/Helpers/CommonItem.cs | 78 ++ MAX.Core/Helpers/Convert.cs | 385 ++++++ MAX.Core/Helpers/CopyHelper.cs | 1151 +++++++++++++++++ MAX.Core/Helpers/DateHelper.cs | 110 ++ MAX.Core/Helpers/EnumHelper.cs | 341 +++++ MAX.Core/Helpers/EnvironmentHelper.cs | 65 + MAX.Core/Helpers/FileHelper.cs | 188 +++ MAX.Core/Helpers/HttpHelper.cs | 1047 +++++++++++++++ MAX.Core/Helpers/IdHelper.cs | 45 + MAX.Core/Helpers/ParametersBase.cs | 369 ++++++ MAX.Core/Helpers/ReflectionHelper.cs | 614 +++++++++ MAX.Core/Helpers/UriHelper.cs | 149 +++ MAX.Core/Interfaces/IPriority.cs | 33 + MAX.Core/Interfaces/IStartup.cs | 18 + MAX.Core/Interfaces/SingleInstance.cs | 42 + MAX.Core/Logger/ConsoleLogger.cs | 124 ++ MAX.Core/Logger/ICustomLogger.cs | 11 + MAX.Core/Logger/ILoggerFacade.cs | 68 + MAX.Core/Logger/LogLevel.cs | 15 + MAX.Core/MAX.Core.csproj | 14 + MAX.Core/Reflections/AppTypeFinder.cs | 197 +++ MAX.Core/Reflections/Finder.cs | 129 ++ MAX.Core/Startup.cs | 45 + Store.Contracts/Class1.cs | 6 + .../Order/Entitys/OrderPddBuyerOrderEntity.cs | 128 ++ .../Order/Entitys/WeiOrderEntity.cs | 416 ++++++ .../Order/Entitys/WeiOrderItemEntity.cs | 236 ++++ .../Order/Interfaces/IOrderPddBuyerService.cs | 20 + Store.Contracts/Store.Contracts.csproj | 16 + Store.Framework/Base/ApiControllerBase.cs | 59 + Store.Framework/Class1.cs | 6 - Store.Framework/Commons/StoreAppTypeFinder.cs | 25 + .../DependencyInjection/ContainerConfig.cs | 99 ++ .../ServiceRegistrarProxy.cs | 135 ++ Store.Framework/Dtos/SignRequestBase.cs | 19 + .../Logger/NLogger.cs | 0 .../Models}/ApiResult.cs | 3 +- Store.Framework/Startup.cs | 25 + Store.Framework/Store.Framework.csproj | 11 +- .../Utils/ApiRequestLogger.cs | 0 .../Utils/EncrypUtil.cs | 0 .../Utils/Extension/Extension.Convert.cs | 0 .../Utils/Extension/Extension.Enum.cs | 0 .../Utils/Extension/Extension.Exception.cs | 0 .../Utils/Extension/Extension.Linq.cs | 0 .../Utils/Extension/Extension.Validate.cs | 0 .../Utils/Helper/HtmlHelper.cs | 0 .../Utils/Helper/JsonHelper.cs | 0 .../Utils/Helper/ReflectionHelper.cs | 0 .../Utils/Helper/RegexHelper.cs | 0 .../Utils/HtmlUtils.cs | 3 +- .../Utils/HttpUtils.cs | 3 +- .../Utils/JsonUtils.cs | 0 .../Utils/MySqlUtils.cs | 1 - .../Utils/RandomUtils.cs | 0 .../Utils/RegexUtils.cs | 0 .../Utils/TimeUtils.cs | 0 .../Apis/VirtualLogisticsApi.cs | 253 ++++ .../Controllers/LogisticsController.cs | 146 +++ .../Entitys/YhSyncLogistics.cs | 41 + Store.SyncLogistics/Models/BuyModels.cs | 303 +++++ Store.SyncLogistics/Models/CangItem.cs | 36 + Store.SyncLogistics/Models/ExpressItem.cs | 71 + Store.SyncLogistics/Models/GoodsItem.cs | 36 + .../Models/OrderStatusQuery.cs | 45 + Store.SyncLogistics/Models/PickupModels.cs | 39 + Store.SyncLogistics/Models/UserInfo.cs | 14 + .../Services/RandomNameHelper.cs | 46 + .../Services/VirtualLogisticsService.cs | 164 +++ .../Store.SyncLogistics.csproj | 17 + test/WebServiceTest/App.config | 4 +- test/WebServiceTest/GiftLogisticsTest.cs | 153 +++ test/WebServiceTest/UnitTest1.cs | 36 +- test/WebServiceTest/UserTest.cs | 30 +- test/WebServiceTest/WebServiceTest.csproj | 1 + 120 files changed, 9438 insertions(+), 299 deletions(-) delete mode 100644 KJD_WebService/Core/Logger/ILoggerFacade.cs delete mode 100644 KJD_WebService/Core/Logger/LogLevel.cs delete mode 100644 KJD_WebService/DependencyInjection/IScopedDependency.cs delete mode 100644 KJD_WebService/DependencyInjection/ISingletonDependency.cs delete mode 100644 KJD_WebService/DependencyInjection/ITransientDependency.cs create mode 100644 KJD_WebService/Mappers/WeOrderMapper.cs create mode 100644 KJD_WebService/Startup.cs create mode 100644 MAX.Core/Caches/ICache.cs create mode 100644 MAX.Core/Caches/MemoryCache.cs create mode 100644 MAX.Core/Commons/CommonValue.cs create mode 100644 MAX.Core/Commons/Result.cs create mode 100644 MAX.Core/Cryptography/Base64Helper.cs create mode 100644 MAX.Core/Cryptography/DESHelper.cs create mode 100644 MAX.Core/Cryptography/MD5Helper.cs create mode 100644 MAX.Core/Cryptography/UrlHelper.cs create mode 100644 MAX.Core/DependencyInjection/IDependencyRegistrar.cs create mode 100644 MAX.Core/DependencyInjection/IScopedDependency.cs create mode 100644 MAX.Core/DependencyInjection/IServiceRegistrar.cs create mode 100644 MAX.Core/DependencyInjection/ISingletonDependency.cs create mode 100644 MAX.Core/DependencyInjection/ITransientDependency.cs create mode 100644 MAX.Core/DependencyInjection/LazyServiceProvider.cs create mode 100644 MAX.Core/Extensions/Convert.cs create mode 100644 MAX.Core/Extensions/DictionaryExtension.cs create mode 100644 MAX.Core/Helpers/Common.cs create mode 100644 MAX.Core/Helpers/CommonItem.cs create mode 100644 MAX.Core/Helpers/Convert.cs create mode 100644 MAX.Core/Helpers/CopyHelper.cs create mode 100644 MAX.Core/Helpers/DateHelper.cs create mode 100644 MAX.Core/Helpers/EnumHelper.cs create mode 100644 MAX.Core/Helpers/EnvironmentHelper.cs create mode 100644 MAX.Core/Helpers/FileHelper.cs create mode 100644 MAX.Core/Helpers/HttpHelper.cs create mode 100644 MAX.Core/Helpers/IdHelper.cs create mode 100644 MAX.Core/Helpers/ParametersBase.cs create mode 100644 MAX.Core/Helpers/ReflectionHelper.cs create mode 100644 MAX.Core/Helpers/UriHelper.cs create mode 100644 MAX.Core/Interfaces/IPriority.cs create mode 100644 MAX.Core/Interfaces/IStartup.cs create mode 100644 MAX.Core/Interfaces/SingleInstance.cs create mode 100644 MAX.Core/Logger/ConsoleLogger.cs create mode 100644 MAX.Core/Logger/ICustomLogger.cs create mode 100644 MAX.Core/Logger/ILoggerFacade.cs create mode 100644 MAX.Core/Logger/LogLevel.cs create mode 100644 MAX.Core/MAX.Core.csproj create mode 100644 MAX.Core/Reflections/AppTypeFinder.cs create mode 100644 MAX.Core/Reflections/Finder.cs create mode 100644 MAX.Core/Startup.cs create mode 100644 Store.Contracts/Class1.cs create mode 100644 Store.Contracts/Order/Entitys/OrderPddBuyerOrderEntity.cs create mode 100644 Store.Contracts/Order/Entitys/WeiOrderEntity.cs create mode 100644 Store.Contracts/Order/Entitys/WeiOrderItemEntity.cs create mode 100644 Store.Contracts/Order/Interfaces/IOrderPddBuyerService.cs create mode 100644 Store.Contracts/Store.Contracts.csproj create mode 100644 Store.Framework/Base/ApiControllerBase.cs delete mode 100644 Store.Framework/Class1.cs create mode 100644 Store.Framework/Commons/StoreAppTypeFinder.cs create mode 100644 Store.Framework/DependencyInjection/ContainerConfig.cs create mode 100644 Store.Framework/DependencyInjection/ServiceRegistrarProxy.cs create mode 100644 Store.Framework/Dtos/SignRequestBase.cs rename {KJD_WebService/Core => Store.Framework}/Logger/NLogger.cs (100%) rename {KJD_WebService/Model => Store.Framework/Models}/ApiResult.cs (98%) create mode 100644 Store.Framework/Startup.cs rename {KJD_WebService => Store.Framework}/Utils/ApiRequestLogger.cs (100%) rename {KJD_WebService => Store.Framework}/Utils/EncrypUtil.cs (100%) rename {KJD_WebService => Store.Framework}/Utils/Extension/Extension.Convert.cs (100%) rename {KJD_WebService => Store.Framework}/Utils/Extension/Extension.Enum.cs (100%) rename {KJD_WebService => Store.Framework}/Utils/Extension/Extension.Exception.cs (100%) rename {KJD_WebService => Store.Framework}/Utils/Extension/Extension.Linq.cs (100%) rename {KJD_WebService => Store.Framework}/Utils/Extension/Extension.Validate.cs (100%) rename {KJD_WebService => Store.Framework}/Utils/Helper/HtmlHelper.cs (100%) rename {KJD_WebService => Store.Framework}/Utils/Helper/JsonHelper.cs (100%) rename {KJD_WebService => Store.Framework}/Utils/Helper/ReflectionHelper.cs (100%) rename {KJD_WebService => Store.Framework}/Utils/Helper/RegexHelper.cs (100%) rename {KJD_WebService => Store.Framework}/Utils/HtmlUtils.cs (99%) rename {KJD_WebService => Store.Framework}/Utils/HttpUtils.cs (99%) rename {KJD_WebService => Store.Framework}/Utils/JsonUtils.cs (100%) rename {KJD_WebService => Store.Framework}/Utils/MySqlUtils.cs (99%) rename {KJD_WebService => Store.Framework}/Utils/RandomUtils.cs (100%) rename {KJD_WebService => Store.Framework}/Utils/RegexUtils.cs (100%) rename {KJD_WebService => Store.Framework}/Utils/TimeUtils.cs (100%) create mode 100644 Store.SyncLogistics/Apis/VirtualLogisticsApi.cs create mode 100644 Store.SyncLogistics/Controllers/LogisticsController.cs create mode 100644 Store.SyncLogistics/Entitys/YhSyncLogistics.cs create mode 100644 Store.SyncLogistics/Models/BuyModels.cs create mode 100644 Store.SyncLogistics/Models/CangItem.cs create mode 100644 Store.SyncLogistics/Models/ExpressItem.cs create mode 100644 Store.SyncLogistics/Models/GoodsItem.cs create mode 100644 Store.SyncLogistics/Models/OrderStatusQuery.cs create mode 100644 Store.SyncLogistics/Models/PickupModels.cs create mode 100644 Store.SyncLogistics/Models/UserInfo.cs create mode 100644 Store.SyncLogistics/Services/RandomNameHelper.cs create mode 100644 Store.SyncLogistics/Services/VirtualLogisticsService.cs create mode 100644 Store.SyncLogistics/Store.SyncLogistics.csproj create mode 100644 test/WebServiceTest/GiftLogisticsTest.cs diff --git a/KJD_WebService.sln b/KJD_WebService.sln index a8a48da..f017682 100644 --- a/KJD_WebService.sln +++ b/KJD_WebService.sln @@ -20,6 +20,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Store.User", "Store.User\St EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Store.Framework", "Store.Framework\Store.Framework.csproj", "{CD6FB773-0431-4FAE-9E30-65953118FEBF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MAX.Core", "MAX.Core\MAX.Core.csproj", "{281F6609-E5BC-8A5C-8382-CD7C4670ACBF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Store.Contracts", "Store.Contracts\Store.Contracts.csproj", "{3694EDE2-3192-44E6-B45C-9CDC99916876}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Store.SyncLogistics", "Store.SyncLogistics\Store.SyncLogistics.csproj", "{11AF9AF8-8DA0-E764-D5FF-8EF35E1FF7C8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -42,6 +48,18 @@ Global {CD6FB773-0431-4FAE-9E30-65953118FEBF}.Debug|Any CPU.Build.0 = Debug|Any CPU {CD6FB773-0431-4FAE-9E30-65953118FEBF}.Release|Any CPU.ActiveCfg = Release|Any CPU {CD6FB773-0431-4FAE-9E30-65953118FEBF}.Release|Any CPU.Build.0 = Release|Any CPU + {281F6609-E5BC-8A5C-8382-CD7C4670ACBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {281F6609-E5BC-8A5C-8382-CD7C4670ACBF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {281F6609-E5BC-8A5C-8382-CD7C4670ACBF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {281F6609-E5BC-8A5C-8382-CD7C4670ACBF}.Release|Any CPU.Build.0 = Release|Any CPU + {3694EDE2-3192-44E6-B45C-9CDC99916876}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3694EDE2-3192-44E6-B45C-9CDC99916876}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3694EDE2-3192-44E6-B45C-9CDC99916876}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3694EDE2-3192-44E6-B45C-9CDC99916876}.Release|Any CPU.Build.0 = Release|Any CPU + {11AF9AF8-8DA0-E764-D5FF-8EF35E1FF7C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {11AF9AF8-8DA0-E764-D5FF-8EF35E1FF7C8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {11AF9AF8-8DA0-E764-D5FF-8EF35E1FF7C8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {11AF9AF8-8DA0-E764-D5FF-8EF35E1FF7C8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/KJD_WebService/BLL/WXXD/Requests/OemWithdrawalAuditRequest.cs b/KJD_WebService/BLL/WXXD/Requests/OemWithdrawalAuditRequest.cs index 5ef8100..0224b1b 100644 --- a/KJD_WebService/BLL/WXXD/Requests/OemWithdrawalAuditRequest.cs +++ b/KJD_WebService/BLL/WXXD/Requests/OemWithdrawalAuditRequest.cs @@ -1,4 +1,4 @@ -using KJD_Web.Requests; +using Store.Framework.Dtos; using System; using System.Collections.Generic; using System.Linq; diff --git a/KJD_WebService/BLL/WXXD/Requests/OemWithdrawalQueryRequest.cs b/KJD_WebService/BLL/WXXD/Requests/OemWithdrawalQueryRequest.cs index 7243ef8..c7f075b 100644 --- a/KJD_WebService/BLL/WXXD/Requests/OemWithdrawalQueryRequest.cs +++ b/KJD_WebService/BLL/WXXD/Requests/OemWithdrawalQueryRequest.cs @@ -1,4 +1,4 @@ -using KJD_Web.Requests; +using Store.Framework.Dtos; using System; using System.Collections.Generic; using System.Linq; diff --git a/KJD_WebService/BLL/WXXD/Requests/OemWithdrawalRequest.cs b/KJD_WebService/BLL/WXXD/Requests/OemWithdrawalRequest.cs index 440a711..19898d9 100644 --- a/KJD_WebService/BLL/WXXD/Requests/OemWithdrawalRequest.cs +++ b/KJD_WebService/BLL/WXXD/Requests/OemWithdrawalRequest.cs @@ -1,4 +1,4 @@ -using KJD_Web.Requests; +using Store.Framework.Dtos; using System; using System.Collections.Generic; using System.Linq; diff --git a/KJD_WebService/BLL/WXXD/WXOemManage.cs b/KJD_WebService/BLL/WXXD/WXOemManage.cs index 95b140d..fc79be6 100644 --- a/KJD_WebService/BLL/WXXD/WXOemManage.cs +++ b/KJD_WebService/BLL/WXXD/WXOemManage.cs @@ -2,12 +2,13 @@ using KJD_Web.BLL.WXXD.Dtos; using KJD_Web.BLL.WXXD.Requests; using KJD_Web.Model; using KJD_Web.Model.WxModel; -using KJD_Web.Requests; using KJD_Web.Utils; using NaiBa.Util.Extension; using NaiBa.Util.Helper; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; +using Store.Framework.Dtos; +using Store.Framework.Models; using System; using System.Collections.Generic; using System.Data; diff --git a/KJD_WebService/ContainerConfig.cs b/KJD_WebService/ContainerConfig.cs index ffb007b..55e58db 100644 --- a/KJD_WebService/ContainerConfig.cs +++ b/KJD_WebService/ContainerConfig.cs @@ -1,92 +1,102 @@ -using Autofac; -using Autofac.Integration.WebApi; -using FreeSql; -using KJD_Web.Repository; -using MAX.Core.DependencyInjection; -using MAX.Core.Logger; -using MAX.Framework.Logger; -using System; -using System.Collections.Generic; -using System.Configuration; -using System.Linq; -using System.Reflection; -using System.Web; +//using Autofac; +//using Autofac.Core; +//using Autofac.Integration.WebApi; +//using FreeSql; +//using KJD_Web.Repository; +//using MAX.Core; +//using MAX.Core.DependencyInjection; +//using MAX.Core.Helpers; +//using MAX.Core.Interfaces; +//using MAX.Core.Logger; +//using MAX.Core.Reflections; +//using MAX.Framework.Logger; +//using Store.Framework.Commons; +//using Store.VirtualLogistics.Services; +//using System; +//using System.Collections.Generic; +//using System.Configuration; +//using System.Diagnostics; +//using System.Linq; +//using System.Reflection; +//using System.Web; +//using System.Web.Services.Description; -namespace KJD_Web; +//namespace KJD_Web; -public class ContainerConfig -{ +//public class ContainerConfig +//{ - public static IContainer BuildContainer() - { - var builder = new ContainerBuilder(); - var appAssembly = Assembly.GetExecutingAssembly(); +// public static IContainer BuildContainer() +// { +// var builder = new ContainerBuilder(); +// var appAssembly = StoreAppTypeFinder.Current.GetAssemblies().ToArray(); - var connection = ConfigurationManager.ConnectionStrings["ConnKJD"]; - if (connection == null || string.IsNullOrWhiteSpace(connection.ConnectionString)) - { - throw new ConfigurationErrorsException("未配置连接字符串:ConnKJD"); - } +// var connection = ConfigurationManager.ConnectionStrings["ConnKJD"]; +// if (connection == null || string.IsNullOrWhiteSpace(connection.ConnectionString)) +// { +// throw new ConfigurationErrorsException("未配置连接字符串:ConnKJD"); +// } +// var ss = StoreAppTypeFinder.Current.Find(); +// // ImageCacheService 使用独立的 imagecache 数据库。 +// builder.Register(_ => +// { +// return new FreeSqlBuilder() +// .UseConnectionString( +// DataType.MySql, +// connection.ConnectionString) +// .Build(); +// }) +// .As() +// .SingleInstance(); - // ImageCacheService 使用独立的 imagecache 数据库。 - builder.Register(_ => - { - return new FreeSqlBuilder() - .UseConnectionString( - DataType.MySql, - connection.ConnectionString) - .Build(); - }) - .As() - .SingleInstance(); - - builder.RegisterGeneric(typeof(AutofacRepository<>)) - .As(typeof(IBaseRepository<>)) - .UsingConstructor(typeof(IFreeSql)) - .InstancePerRequest(); +// builder.RegisterGeneric(typeof(AutofacRepository<>)) +// .As(typeof(IBaseRepository<>)) +// .UsingConstructor(typeof(IFreeSql)) +// .InstancePerRequest(); - // 注册 Web API 所有 Controller - builder.RegisterApiControllers(appAssembly); - builder.RegisterInstance(NLogger.Instance).As().SingleInstance(); +// // 注册 Web API 所有 Controller +// builder.RegisterApiControllers(appAssembly); +// builder.RegisterInstance(NLogger.Instance).As().SingleInstance(); - #region 注册所有IOC接口 +// #region 注册所有IOC接口 - Type typeSingleton = typeof(ISingletonDependency); - builder.RegisterAssemblyTypes(appAssembly) - .Where(a => - typeSingleton.IsAssignableFrom(a) - && a.IsClass - && !a.IsAbstract - ) - .AsImplementedInterfaces() - .AsSelf() - .SingleInstance(); +// Type typeSingleton = typeof(ISingletonDependency); +// builder.RegisterAssemblyTypes(appAssembly) +// .Where(a => +// typeSingleton.IsAssignableFrom(a) +// && a.IsClass +// && !a.IsAbstract +// ) +// .AsImplementedInterfaces() +// .AsSelf() +// .SingleInstance(); - Type typeTransient = typeof(ITransientDependency); - builder.RegisterAssemblyTypes(appAssembly) - .Where(a => - typeTransient.IsAssignableFrom(a) - && a.IsClass - && !a.IsAbstract - ) - .AsImplementedInterfaces() - .AsSelf() - .InstancePerDependency(); +// Type typeTransient = typeof(ITransientDependency); +// builder.RegisterAssemblyTypes(appAssembly) +// .Where(a => +// typeTransient.IsAssignableFrom(a) +// && a.IsClass +// && !a.IsAbstract +// ) +// .AsImplementedInterfaces() +// .AsSelf() +// .InstancePerDependency(); - Type typeScoped = typeof(IScopedDependency); - builder.RegisterAssemblyTypes(appAssembly) - .Where(a => typeScoped.IsAssignableFrom(a) && a.IsClass && !a.IsAbstract) - .AsImplementedInterfaces() - .AsSelf() - .InstancePerRequest(); +// Type typeScoped = typeof(IScopedDependency); +// builder.RegisterAssemblyTypes(appAssembly) +// .Where(a => typeScoped.IsAssignableFrom(a) && a.IsClass && !a.IsAbstract) +// .AsImplementedInterfaces() +// .AsSelf() +// .InstancePerRequest(); - #endregion +// #endregion + +// var app = builder.Build(); +// return app; +// } - return builder.Build(); - } - -} \ No newline at end of file +//} \ No newline at end of file diff --git a/KJD_WebService/Controllers/AsmxApiController.cs b/KJD_WebService/Controllers/AsmxApiController.cs index 62faf5f..d821fb0 100644 --- a/KJD_WebService/Controllers/AsmxApiController.cs +++ b/KJD_WebService/Controllers/AsmxApiController.cs @@ -1,6 +1,7 @@ using KJD_Web.BLL.WXXD; using KJD_Web.Requests; using KJD_Web.Utils; +using Store.Framework.Base; using System; using System.Collections.Generic; using System.Collections.Specialized; diff --git a/KJD_WebService/Controllers/ImageCacheController.cs b/KJD_WebService/Controllers/ImageCacheController.cs index 7871ae8..ba65919 100644 --- a/KJD_WebService/Controllers/ImageCacheController.cs +++ b/KJD_WebService/Controllers/ImageCacheController.cs @@ -1,7 +1,7 @@ -using KJD_Web.Model; -using KJD_Web.Services; +using KJD_Web.Services; using MAX.Core.Logger; using MySqlX.XDevAPI.Common; +using Store.Framework.Models; using System; using System.Collections.Generic; using System.Web.Http; diff --git a/KJD_WebService/Controllers/WX/OEMController.cs b/KJD_WebService/Controllers/WX/OEMController.cs index 65a4cc5..96366b9 100644 --- a/KJD_WebService/Controllers/WX/OEMController.cs +++ b/KJD_WebService/Controllers/WX/OEMController.cs @@ -1,7 +1,8 @@ -using KJD_Web.Model; -using KJD_Web.Requests.Users; +using KJD_Web.Requests.Users; using KJD_Web.Responses.Users; using KJD_Web.Services.Users; +using Store.Framework.Base; +using Store.Framework.Models; using System; using System.Collections.Generic; using System.Linq; diff --git a/KJD_WebService/Controllers/WX/WXUserController.cs b/KJD_WebService/Controllers/WX/WXUserController.cs index fe14e1d..37211e1 100644 --- a/KJD_WebService/Controllers/WX/WXUserController.cs +++ b/KJD_WebService/Controllers/WX/WXUserController.cs @@ -1,9 +1,9 @@ using KJD_Web.BLL.WXXD; -using KJD_Web.Model; -using KJD_Web.Requests; using KJD_Web.Requests.Users; using KJD_Web.Utils; using NaiBa.Util.Extension; +using Store.Framework.Dtos; +using Store.Framework.Models; using System; using System.Collections.Generic; using System.IO; diff --git a/KJD_WebService/Controllers/WX/WXUser_N_Controller.cs b/KJD_WebService/Controllers/WX/WXUser_N_Controller.cs index bb5d64d..7df3f24 100644 --- a/KJD_WebService/Controllers/WX/WXUser_N_Controller.cs +++ b/KJD_WebService/Controllers/WX/WXUser_N_Controller.cs @@ -1,11 +1,11 @@ using KJD_Web.BLL.WXXD; -using KJD_Web.Model; -using KJD_Web.Requests; using KJD_Web.Requests.Users; using KJD_Web.Services.Users; using MAX.Core.Logger; using NaiBa.Util.Extension; using Org.BouncyCastle.Ocsp; +using Store.Framework.Dtos; +using Store.Framework.Models; using System; using System.Web.Http; diff --git a/KJD_WebService/Core/Logger/ILoggerFacade.cs b/KJD_WebService/Core/Logger/ILoggerFacade.cs deleted file mode 100644 index 6c1d035..0000000 --- a/KJD_WebService/Core/Logger/ILoggerFacade.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; - -namespace MAX.Core.Logger -{ - /// - /// 日志操作 - /// - public interface ILoggerFacade - { - /// - /// 调试级别是否启用 - /// - bool IsDebugEnabled { get; } - - /// - /// 调试 - /// - void Debug(Exception ex, string message); - /// - /// 调试 - /// - /// 日志消息 - void Debug(string message); - /// - /// 信息 - /// - void Info(Exception ex, string message); - /// - /// 信息 - /// - /// 日志消息 - void Info(string message); - /// - /// 警告 - /// - void Warn(Exception ex, string message); - /// - /// 警告 - /// - /// 日志消息 - void Warn(string message); - /// - /// 错误 - /// - void Error(Exception ex, string message); - /// - /// 错误 - /// - /// 日志消息 - void Error(string message); - /// - /// 致命错误 - /// - void Fatal(Exception ex, string message); - /// - /// 致命错误 - /// - /// 日志消息 - void Fatal(string message); - void Error(Exception ex); - - /// - /// 写入日志 - /// - /// - /// - void Log(string message, LogLevel level); - } -} diff --git a/KJD_WebService/Core/Logger/LogLevel.cs b/KJD_WebService/Core/Logger/LogLevel.cs deleted file mode 100644 index ead21f0..0000000 --- a/KJD_WebService/Core/Logger/LogLevel.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace MAX.Core.Logger -{ - /// - /// ÈÕ־µȼ¶ - /// - public enum LogLevel - { - Trace = 0, - Debug = 1, - Info = 2, - Warn = 3, - Error = 4, - Fatal = 5, - None = 6 - } -} \ No newline at end of file diff --git a/KJD_WebService/DependencyInjection/IScopedDependency.cs b/KJD_WebService/DependencyInjection/IScopedDependency.cs deleted file mode 100644 index c3cee41..0000000 --- a/KJD_WebService/DependencyInjection/IScopedDependency.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MAX.Core.DependencyInjection -{ - /// - /// 作用域(每个 Scope/请求一个实例) - /// - public interface IScopedDependency - { - } -} diff --git a/KJD_WebService/DependencyInjection/ISingletonDependency.cs b/KJD_WebService/DependencyInjection/ISingletonDependency.cs deleted file mode 100644 index 1a841ad..0000000 --- a/KJD_WebService/DependencyInjection/ISingletonDependency.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MAX.Core.DependencyInjection -{ - /// - /// 单例 - /// - public interface ISingletonDependency - { - } -} diff --git a/KJD_WebService/DependencyInjection/ITransientDependency.cs b/KJD_WebService/DependencyInjection/ITransientDependency.cs deleted file mode 100644 index 73006f9..0000000 --- a/KJD_WebService/DependencyInjection/ITransientDependency.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MAX.Core.DependencyInjection -{ - /// - /// 每次实例化 - /// - public interface ITransientDependency - { - } -} diff --git a/KJD_WebService/Global.asax.cs b/KJD_WebService/Global.asax.cs index 0b766d2..916ad52 100644 --- a/KJD_WebService/Global.asax.cs +++ b/KJD_WebService/Global.asax.cs @@ -1,5 +1,6 @@ using Autofac; using Autofac.Integration.WebApi; +using AutoMapper; using FreeSql; using KJD_Web.Repository; using KJD_Web.Utils; @@ -8,6 +9,7 @@ using MAX.Core.Logger; using MAX.Framework.Logger; using MySql.Data.MySqlClient; using NLog; +using Store.Framework.DependencyInjection; using System; using System.Collections.Generic; using System.Configuration; @@ -27,30 +29,15 @@ namespace KJD_Web NLogger.Instance.Info("应用程序启动"); var container = ContainerConfig.BuildContainer(); + // 让 Web API 用 Autofac 解析 Controller GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container); GlobalConfiguration.Configure(WebApiConfig.Register); + } - private static IFreeSql CreateImageCacheFreeSql() - { - var connection = ConfigurationManager.ConnectionStrings["ConnKJD"]; - if (connection == null || string.IsNullOrWhiteSpace(connection.ConnectionString)) - { - throw new ConfigurationErrorsException("未配置连接字符串:ConnKJD"); - } - //var connectionBuilder = new MySqlConnectionStringBuilder(connection.ConnectionString) - //{ - // Database = "imagecache" - //}; - - return new FreeSqlBuilder() - .UseConnectionString(DataType.MySql, connection.ConnectionString) - .UseAutoSyncStructure(false) - .Build(); - } protected void Session_Start(object sender, EventArgs e) { diff --git a/KJD_WebService/KJD_WebService.csproj b/KJD_WebService/KJD_WebService.csproj index 4a99835..58e3844 100644 --- a/KJD_WebService/KJD_WebService.csproj +++ b/KJD_WebService/KJD_WebService.csproj @@ -190,7 +190,6 @@ - @@ -214,12 +213,7 @@ - - - - - - + @@ -233,7 +227,6 @@ - @@ -278,8 +271,8 @@ - + @@ -297,8 +290,6 @@ WXProd.aspx - - Global.asax @@ -316,21 +307,6 @@ - - - - - - - - - - - - - - - @@ -349,6 +325,27 @@ 3.5.311 + + + {281F6609-E5BC-8A5C-8382-CD7C4670ACBF} + MAX.Core + + + {3694ede2-3192-44e6-b45c-9cdc99916876} + Store.Contracts + + + {cd6fb773-0431-4fae-9e30-65953118febf} + Store.Framework + + + {11af9af8-8da0-e764-d5ff-8ef35e1ff7c8} + Store.SyncLogistics + + + + + 10.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) diff --git a/KJD_WebService/Mappers/WeOrderMapper.cs b/KJD_WebService/Mappers/WeOrderMapper.cs new file mode 100644 index 0000000..791435d --- /dev/null +++ b/KJD_WebService/Mappers/WeOrderMapper.cs @@ -0,0 +1,13 @@ +using AutoMapper; +using KJD_Web.Entitys; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace KJD_Web.Mapper; + +public class WeOrderProfile : Profile +{ + +} \ No newline at end of file diff --git a/KJD_WebService/NotFoundMessageFilter.cs b/KJD_WebService/NotFoundMessageFilter.cs index 89ede5c..0e2c93c 100644 --- a/KJD_WebService/NotFoundMessageFilter.cs +++ b/KJD_WebService/NotFoundMessageFilter.cs @@ -1,6 +1,6 @@ -using KJD_Web.Model; -using Newtonsoft.Json; +using Newtonsoft.Json; using Newtonsoft.Json.Serialization; +using Store.Framework.Models; using System; using System.Collections.Generic; using System.Linq; diff --git a/KJD_WebService/Requests/RequestBase.cs b/KJD_WebService/Requests/RequestBase.cs index baa57ad..584a614 100644 --- a/KJD_WebService/Requests/RequestBase.cs +++ b/KJD_WebService/Requests/RequestBase.cs @@ -1,21 +1,9 @@ using Newtonsoft.Json; +using Store.Framework.Dtos; using System; namespace KJD_Web.Requests; -/// -/// Sign签名 -/// -public class SignRequestBase -{ - public string time { get; set; } - public string code { get; set; } - public string code_version { get; set; } - public string version { get; set; } - [JsonProperty("user_name")] - public string UserName { get; set; } - public string path { get; set; } -} diff --git a/KJD_WebService/Requests/Users/CUUserInfoRequest.cs b/KJD_WebService/Requests/Users/CUUserInfoRequest.cs index 83e057a..bdb65be 100644 --- a/KJD_WebService/Requests/Users/CUUserInfoRequest.cs +++ b/KJD_WebService/Requests/Users/CUUserInfoRequest.cs @@ -1,4 +1,5 @@ -using System; +using Store.Framework.Dtos; +using System; using System.Collections.Generic; using System.Linq; using System.Web; diff --git a/KJD_WebService/Requests/Users/ShopRenewRequest.cs b/KJD_WebService/Requests/Users/ShopRenewRequest.cs index 0a2b49a..96103aa 100644 --- a/KJD_WebService/Requests/Users/ShopRenewRequest.cs +++ b/KJD_WebService/Requests/Users/ShopRenewRequest.cs @@ -1,4 +1,5 @@ -using System; +using Store.Framework.Dtos; +using System; using System.Collections.Generic; using System.Linq; using System.Web; diff --git a/KJD_WebService/Services/Users/UserService.FeeRecord.cs b/KJD_WebService/Services/Users/UserService.FeeRecord.cs index e4a374b..25811f6 100644 --- a/KJD_WebService/Services/Users/UserService.FeeRecord.cs +++ b/KJD_WebService/Services/Users/UserService.FeeRecord.cs @@ -1,10 +1,12 @@ using KJD_Web.Entitys; using KJD_Web.Model.WxModel; using MySqlX.XDevAPI; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Data.Common; using System.Diagnostics; +using System.IO; using System.Linq; using System.Text; using System.Web; @@ -267,8 +269,170 @@ namespace KJD_Web.Services.Users summary.ConsumeTotal = (long)Math.Round(sum * 100m); return summary; } + + + } + /// + /// 填充历史数据 + /// + // public void FillHistoryOemAmount() + // { + // List data = new List(); + // string path = @"d:\ddddd.json"; + // if (File.Exists(path)) + // { + // data = JsonConvert.DeserializeObject>(File.ReadAllText(path)); + // } + // else + // { + // data = _freeSql.Select() + // .Where(a => a.type == 20 || a.type == 26) + // .Where(a => a.create_time > DateTime.Parse("2026-09-01 00:00:00")) + // .OrderBy(a => a.id) + // .ToList(); + // } + // Debug.WriteLine($"共;{data.Count}"); + // int max = 500; + // int count = 0; + // List list = new List(); + // foreach (var item in data) + // { + // //if (item.IsFill) continue; + // try + // { + // if (!string.IsNullOrEmpty(item.remark)) + // { + // item.OrderId = item.remark.Split(':')[1]; + // list.Add(item); + // if (list.Count == max) + // { + // var orderNames = list.Select(a => a.OrderId); + // List orders = _freeSql.Select() + // .Where(a => orderNames.Contains(a.order_id)) + // .ToList(); + // fillOemAmount(list, orders); + // var uCount = _freeSql.Update() + // .SetSource(list) + // .UpdateColumns(a => new { a.oem_amount }) + // .ExecuteAffrows(); + // if (uCount != list.Count) + // { + // Debug.WriteLine("数量不对"); + // } + // list.ForEach(a => a.IsFill = true); + // File.WriteAllText(path, JsonConvert.SerializeObject(data)); + // count += list.Count; + // list.Clear(); + // Debug.WriteLine($"成功修改;{list.Count}"); + // Debug.WriteLine($"共修改;{count}"); + + // } + // } + // } + // finally + // { + + // } + // } + // } + + // private void fillOemAmount(List list, List orders) + // { + // foreach (var item in list) + // { + // var order = orders.FirstOrDefault(a => a.order_id == item.OrderId); + // if (order != null) + // { + // item.oem_amount = getoemamount(order.province_name, item.amount.Value, item.type); + // } + // } + // } + // private Dictionary DicWarehousePostage = new Dictionary + // { + // { "湖北", 2.2m }, + //{ "湖南", 2.2m }, + //{ "江西", 2.2m }, + //{ "河南", 2.2m }, + //{ "上海", 3m }, + //{ "浙江", 2.2m }, + //{ "安徽", 2.2m }, + //{ "江苏", 2.2m }, + //{ "河北", 2.2m }, + //{ "山东", 2.2m }, + //{ "福建", 2.2m }, + //{ "广东", 2.2m }, + //{ "深圳", 2.8m }, + //{ "天津", 2.2m }, + //{ "北京", 3.2m }, + //{ "山西", 2.2m }, + //{ "陕西", 2.2m }, + //{ "广西", 2.2m }, + //{ "云南", 2.2m }, + //{ "贵州", 2.2m }, + //{ "四川", 2.2m }, + //{ "重庆", 2.2m }, + //{ "黑龙江", 2.7m }, + //{ "吉林", 2.7m }, + //{ "辽宁", 2.7m }, + //{ "青海", 2.7m }, + //{ "宁夏", 2.7m }, + //{ "甘肃", 2.7m }, + //{ "内蒙", 4.5m }, + //{ "海南", 4.5m }, + //{ "新疆", 12.5m }, + //{ "西藏", 12.5m }, + // }; + // private decimal getoemamount(string province, decimal amount, int type) + // { + // foreach (var item in DicWarehousePostage) + // { + // if (province.StartsWith(item.Key)) + // { + // if (type == 20) + // return amount - item.Value; + // if (type == 26) + // return amount - item.Value - 0.3m; + + // } + // } + // return amount; + // } + //} + + + //[FreeSql.DataAnnotations.Table(Name = "wxxd.user_payment_record")] + //public class UserPaymentRecordSmall() + //{ + // public long id { get; set; } + // public int type { get; set; } + // /// + // /// 金额(元) + // /// + // public decimal? amount { get; set; } + // /// + // /// 归属oem用户的金额 + // /// + // public decimal? oem_amount { get; set; } + // public string remark { get; set; } + + // public DateTime create_time { get; set; } + + // [FreeSql.DataAnnotations.Column(IsIgnore = true)] + // public bool IsFill { get; set; } + + // [FreeSql.DataAnnotations.Column(IsIgnore = true)] + // public string OrderId { get; set; } + //} + //[FreeSql.DataAnnotations.Table(Name = "erp.oms_wei_order")] + //public class weiOrder + //{ + // public string order_id { get; set; } + // public string province_name { get; set; } + //} + + public class UserAmountSummary { /// diff --git a/KJD_WebService/SignValidateFilterAttribute.cs b/KJD_WebService/SignValidateFilterAttribute.cs index 8af3efc..6947f57 100644 --- a/KJD_WebService/SignValidateFilterAttribute.cs +++ b/KJD_WebService/SignValidateFilterAttribute.cs @@ -1,7 +1,7 @@ -using KJD_Web.Model; -using KJD_Web.Requests; -using KJD_Web.Utils; +using KJD_Web.Utils; using Newtonsoft.Json; +using Store.Framework.Dtos; +using Store.Framework.Models; using System; using System.Collections.Generic; using System.Collections.Specialized; diff --git a/KJD_WebService/Startup.cs b/KJD_WebService/Startup.cs new file mode 100644 index 0000000..096d69c --- /dev/null +++ b/KJD_WebService/Startup.cs @@ -0,0 +1,30 @@ +using Autofac; +using FreeSql; +using KJD_Web.Repository; +using MAX.Core.DependencyInjection; +using MAX.Core.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace KJD_Web; + +public class Startup : IStartup +{ + public int Priority => IntPriority.Normal; + + public void Initialize() + { + + } + + public void Register(IServiceRegistrar registrar) + { + (registrar.Services as ContainerBuilder).RegisterGeneric(typeof(AutofacRepository<>)) + .As(typeof(IBaseRepository<>)) + .UsingConstructor(typeof(IFreeSql)) + .InstancePerRequest(); + + } +} \ No newline at end of file diff --git a/KJD_WebService/Web.config b/KJD_WebService/Web.config index 5a5d82d..cc05afc 100644 --- a/KJD_WebService/Web.config +++ b/KJD_WebService/Web.config @@ -82,6 +82,13 @@ + + + + + + + diff --git a/KJD_WebService/packages.config b/KJD_WebService/packages.config index d741829..070d315 100644 --- a/KJD_WebService/packages.config +++ b/KJD_WebService/packages.config @@ -22,7 +22,6 @@ - diff --git a/MAX.Core/Caches/ICache.cs b/MAX.Core/Caches/ICache.cs new file mode 100644 index 0000000..9407565 --- /dev/null +++ b/MAX.Core/Caches/ICache.cs @@ -0,0 +1,99 @@ +using System; + +namespace MAX.Core.Caches; + +/// +/// 缓存 +/// +public interface ICache +{ + /// + /// 是否存在指定键的缓存 + /// + /// 缓存键 + bool Exists(string key); + /// + /// 从缓存中获取数据 + /// + /// + /// + T Get(string key); + /// + /// 从缓存中获取数据,如果不存在,则执行获取数据操作并添加到缓存中 + /// + /// 缓存数据类型 + /// 缓存键 + /// 获取数据操作 + T Get(string key, Func func); + /// + /// 当缓存数据不存在则添加,已存在不会添加,添加成功返回true + /// + /// 缓存数据类型 + /// 缓存键 + /// 值 + bool TryAdd(string key, T value); + /// + /// 覆盖添加 + /// + /// + /// + /// + /// + bool OverlayAdd(string key, T value); + /// + /// 移除缓存 + /// + /// 缓存键 + void Remove(string key); + /// + /// 清空缓存 + /// + void Clear(); +} + +public interface ICache +{ + /// + /// 是否存在指定键的缓存 + /// + /// 缓存键 + bool Exists(string key); + + /// + /// 从缓存中获取数据,如果不存在,则执行获取数据操作并添加到缓存中 + /// + /// 缓存数据类型 + /// 缓存键 + T Get(string key); + /// + /// 从缓存中获取数据,如果不存在,则执行获取数据操作并添加到缓存中 + /// + /// 缓存数据类型 + /// 缓存键 + /// 获取数据操作 + T Get(string key, Func func); + /// + /// 当缓存数据不存在则添加,已存在不会添加,添加成功返回true + /// + /// 缓存数据类型 + /// 缓存键 + /// 值 + bool TryAdd(string key, T value); + /// + /// 覆盖添加 + /// + /// + /// + /// + /// + bool OverlayAdd(string key, T value); + /// + /// 移除缓存 + /// + /// 缓存键 + void Remove(string key); + /// + /// 清空缓存 + /// + void Clear(); +} diff --git a/MAX.Core/Caches/MemoryCache.cs b/MAX.Core/Caches/MemoryCache.cs new file mode 100644 index 0000000..4631bc3 --- /dev/null +++ b/MAX.Core/Caches/MemoryCache.cs @@ -0,0 +1,168 @@ + +using MAX.Core.DependencyInjection; +using MAX.Core.Interfaces; +using System; +using System.Collections.Concurrent; +using System.Linq; + +namespace MAX.Core.Caches; + +/// +/// 内存缓存 +/// +public class MemoryCache : ICache, ISingletonDependency +{ + //private static readonly Object _locker = new object(); + private readonly ConcurrentDictionary _cache = new(); + + public virtual void Clear() + { + _cache.Clear(); + } + + public virtual bool Exists(string key) + { + return _cache.ContainsKey(key); + } + public virtual T Get(string key) + { + if (Exists(key)) + { + return (T)_cache[key]; + } + return default; + } + public virtual TResult Get(string key, Func func) + { + if (Exists(key)) + { + return (TResult)_cache[key]; + } + else + { + if (func != null) + { + TResult t = func(); + TryAdd(key, t); + return t; + } + } + return default; + } + + public virtual bool OverlayAdd(string key, T value) + { + if (Exists(key)) + { + _cache[key] = value; + } + else + { + _cache.TryAdd(key, value); + } + return true; + } + + public virtual void Remove(string key) + { + _cache.TryRemove(key, out _); + } + + public virtual bool TryAdd(string key, T value) + { + _cache.AddOrUpdate(key, value, (oldkey, oldvalue) => value); + return true; + } + /// + /// 获取所有Key + /// + /// + public string[] AllKeys() + { + return _cache.Keys.ToArray(); + } +} +/// +/// 单例缓存抽象类 +/// +/// 单例对象 +/// 缓存类型 +public abstract class SingleInstanceLittleCache : SingleInstance, ICache where TModel : class +{ + private readonly ConcurrentDictionary _cache = new(); + /// + /// 自定义前缀 + /// + //public abstract string KeyPrefix { get; } + //private string FormatKey(string key) + //{ + // return KeyPrefix + key; + //} + + public virtual void Clear() + { + _cache.Clear(); + } + + public virtual bool Exists(string key) + { + return _cache.ContainsKey(key); + } + public virtual TValue Get(string key) + { + if (Exists(key)) + { + return _cache[key]; + } + return default; + } + public virtual TValue Get(string key, Func func) + { + if (Exists(key)) + { + return _cache[key]; + } + else + { + if (func != null) + { + TValue t = func(); + TryAdd(key, t); + return t; + } + } + return default; + } + + public virtual bool OverlayAdd(string key, TValue value) + { + if (Exists(key)) + { + _cache[key] = value; + } + else + { + _cache.TryAdd(key, value); + } + return true; + } + + public virtual void Remove(string key) + { + _cache.TryRemove(key, out _); + } + + public virtual bool TryAdd(string key, TValue value) + { + _cache.AddOrUpdate(key, value, (oldkey, oldvalue) => value); + return true; + } + /// + /// 获取所有Key + /// + /// + public string[] AllKeys() + { + return _cache.Keys.ToArray(); + } +} diff --git a/MAX.Core/Commons/CommonValue.cs b/MAX.Core/Commons/CommonValue.cs new file mode 100644 index 0000000..ddcd731 --- /dev/null +++ b/MAX.Core/Commons/CommonValue.cs @@ -0,0 +1,37 @@ +namespace MAX.Core.Commons; + +/// +/// 公共Value +/// +/// +public class CommonValue +{ + public CommonValue() { } + public CommonValue(TValue value) + { + Value = value; + } + public TValue Value { get; set; } +} +/// +/// 公共TextValue +/// +/// +public class CommonTextValue : CommonValue +{ + public string Text { get; set; } +} +/// +/// 公共TextValue +/// +public class CommonTextValue : CommonTextValue +{ + +} +/// +/// 默认公共键值对 Key Value +/// +public class CommonKeyValue : CommonValue +{ + public string Key { get; set; } +} diff --git a/MAX.Core/Commons/Result.cs b/MAX.Core/Commons/Result.cs new file mode 100644 index 0000000..5d71941 --- /dev/null +++ b/MAX.Core/Commons/Result.cs @@ -0,0 +1,55 @@ +using System; + +namespace MAX.Core.Commons; + +/// +/// 通用返回 +/// +public class Result +{ + /// + /// 返回结果是否成功 + /// + public bool Success { get; set; } = false; + /// + /// 返回消息 + /// + public string Msg { get; set; } = ""; + /// + /// 返回内容 + /// + public object Data { get; set; } = null; + + /// + /// 返回状态码 + /// + public int Code { get; set; } + +} +/// +/// 通用返回 指定Data类型 +/// +/// 数据类型 +public class Result : Result +{ + /// + /// 返回内容 + /// + public new TData Data { get; set; } +} +/// +/// 通用返回 +/// +/// 状态类型枚举 +/// 数据类型 +public class Result : Result where TStatus : Enum +{ + /// + /// 状态 + /// + public TStatus Status { get; set; } + /// + /// 返回内容 + /// + public new TData Data { get; set; } +} diff --git a/MAX.Core/Cryptography/Base64Helper.cs b/MAX.Core/Cryptography/Base64Helper.cs new file mode 100644 index 0000000..768c732 --- /dev/null +++ b/MAX.Core/Cryptography/Base64Helper.cs @@ -0,0 +1,69 @@ +using System; +using System.Text; + +namespace MAX.Core.Cryptography; + +public class Base64Helper +{ + /// + /// Base64加密 + /// + /// 加密采用的编码方式 + /// 待加密的明文 + /// + public static string Encode(string source, Encoding encode) + { + string encodeValue = ""; + byte[] bytes = encode.GetBytes(source); + try + { + encodeValue = Convert.ToBase64String(bytes); + } + catch + { + encodeValue = source; + } + return encodeValue; + } + + /// + /// Base64加密,采用utf8编码方式加密 + /// + /// 待加密的明文 + /// 加密后的字符串 + public static string Encode(string source) + { + return Encode(source, Encoding.UTF8); + } + + /// + /// Base64解密 + /// + /// 解密采用的编码方式,注意和加密时采用的方式一致 + /// 待解密的密文 + /// 解密后的字符串 + public static string Decode(string result, Encoding encode) + { + string decode = ""; + byte[] bytes = Convert.FromBase64String(result); + try + { + decode = encode.GetString(bytes); + } + catch + { + decode = result; + } + return decode; + } + + /// + /// Base64解密,采用utf8编码方式解密 + /// + /// 待解密的密文 + /// 解密后的字符串 + public static string Decode(string result) + { + return Decode(result, Encoding.UTF8); + } +} diff --git a/MAX.Core/Cryptography/DESHelper.cs b/MAX.Core/Cryptography/DESHelper.cs new file mode 100644 index 0000000..67e37f0 --- /dev/null +++ b/MAX.Core/Cryptography/DESHelper.cs @@ -0,0 +1,215 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace MAX.Core.Cryptography; + +public sealed class DESHelper +{ + + //默认密钥向量 + private static readonly byte[] IVKey = { 36, 109, 97, 120, 111, 110, 120, 36 }; + /// + /// DES加密字符串 + /// + /// 待加密的字符串 + /// 加密密钥,要求为8位 + /// 加密成功返回加密后的字符串,失败返回源串 + public static string Encrypt(string encryptString, string encryptKey) + { + try + { + encryptKey = GetSubString(encryptKey, 8, ""); + encryptKey = encryptKey.PadRight(8, ' '); + byte[] rgbKey = Encoding.UTF8.GetBytes(encryptKey.Substring(0, 8)); + byte[] rgbIV = IVKey; + byte[] inputByteArray = Encoding.UTF8.GetBytes(encryptString); + DES dCSP = DES.Create(); + MemoryStream mStream = new(); + CryptoStream cStream = new(mStream, dCSP.CreateEncryptor(rgbKey, rgbIV), CryptoStreamMode.Write); + cStream.Write(inputByteArray, 0, inputByteArray.Length); + cStream.FlushFinalBlock(); + return Convert.ToBase64String(mStream.ToArray()); + } + catch + { + return encryptString; + } + } + + /// + /// DES解密字符串 + /// + /// 待解密的字符串 + /// 解密密钥,要求为8位,和加密密钥相同 + /// 解密成功返回解密后的字符串,失败返源串 + public static string Decrypt(string decryptString, string decryptKey) + { + try + { + decryptKey = GetSubString(decryptKey, 0, 8, ""); + decryptKey = decryptKey.PadRight(8, ' '); + byte[] rgbKey = Encoding.UTF8.GetBytes(decryptKey); + byte[] rgbIV = IVKey; + byte[] inputByteArray = Convert.FromBase64String(decryptString); + DES DCSP = DES.Create(); + + MemoryStream mStream = new(); + CryptoStream cStream = new(mStream, DCSP.CreateDecryptor(rgbKey, rgbIV), CryptoStreamMode.Write); + cStream.Write(inputByteArray, 0, inputByteArray.Length); + cStream.FlushFinalBlock(); + return Encoding.UTF8.GetString(mStream.ToArray()); + } + catch + { + return string.Empty; + } + + } + + #region 截取字符串函数 + + /// + /// 截取字符串函数 + /// + /// 所要截取的字符串 + /// 截取字符串的长度 + /// + public static string GetSubString(string Str, int Num) + { + if (Str is null or "") + { + return ""; + } + string outstr = string.Empty; + int n = 0; + foreach (char ch in Str) + { + n += Encoding.Default.GetByteCount(ch.ToString()); + + if (n > Num) + { + break; + } + else + { + outstr += ch; + } + } + + return outstr; + } + /// + /// 截取字符串函数,如果超过指定长度则将超出的部分用指定字符串代替 + /// + /// 所要截取的字符串 + /// 截取字符串的长度 + /// 截取字符串后省略部分的字符串 + /// + public static string GetSubString(string Str, int Num, string LastStr) + { + return GetSubString(Str, 0, Num, LastStr); + } + + #endregion + + #region 截取字符串,超过部分用指定字符串替换 + + /* 宗波于 2010-6-12 日添加, 来源于DiscuzNt */ + /// + /// 取指定长度的字符串 + /// + /// 要检查的字符串 + /// 起始位置 + /// 指定长度 + /// 用于替换的字符串 + /// 截取后的字符串 + public static string GetSubString(string p_SrcString, int p_StartIndex, int p_Length, string p_TailString) + { + string myResult = p_SrcString; + + byte[] bComments = Encoding.UTF8.GetBytes(p_SrcString); + foreach (char c in Encoding.UTF8.GetChars(bComments)) + { //当是日文或韩文时(注:中文的范围:\u4e00 - \u9fa5, 日文在\u0800 - \u4e00, 韩文为\xAC00-\xD7A3) + if (c is (> '\u0800' and < '\u4e00') or (> '\xAC00' and < '\xD7A3')) + { + //if (System.Text.RegularExpressions.Regex.IsMatch(p_SrcString, "[\u0800-\u4e00]+") || System.Text.RegularExpressions.Regex.IsMatch(p_SrcString, "[\xAC00-\xD7A3]+")) + //当截取的起始位置超出字段串长度时 + if (p_StartIndex >= p_SrcString.Length) + { + return ""; + } + else + { + return p_SrcString.Substring(p_StartIndex, + p_Length + p_StartIndex > p_SrcString.Length ? p_SrcString.Length - p_StartIndex : p_Length); + } + } + } + + if (p_Length >= 0) + { + byte[] bsSrcString = Encoding.Default.GetBytes(p_SrcString); + + //当字符串长度大于起始位置 + if (bsSrcString.Length > p_StartIndex) + { + int p_EndIndex = bsSrcString.Length; + + //当要截取的长度在字符串的有效长度范围内 + if (bsSrcString.Length > p_StartIndex + p_Length) + { + p_EndIndex = p_Length + p_StartIndex; + } + else + { //当不在有效范围内时,只取到字符串的结尾 + + p_Length = bsSrcString.Length - p_StartIndex; + p_TailString = ""; + } + + int nRealLength = p_Length; + int[] anResultFlag = new int[p_Length]; + byte[] bsResult = null; + + int nFlag = 0; + for (int i = p_StartIndex; i < p_EndIndex; i++) + { + if (bsSrcString[i] > 127) + { + nFlag++; + if (nFlag == 3) + { + nFlag = 1; + } + } + else + { + nFlag = 0; + } + + anResultFlag[i] = nFlag; + } + + if (bsSrcString[p_EndIndex - 1] > 127 && anResultFlag[p_Length - 1] == 1) + { + nRealLength = p_Length + 1; + } + + bsResult = new byte[nRealLength]; + + Array.Copy(bsSrcString, p_StartIndex, bsResult, 0, nRealLength); + + myResult = Encoding.Default.GetString(bsResult); + myResult += p_TailString; + } + } + + return myResult; + } + + + #endregion + +} diff --git a/MAX.Core/Cryptography/MD5Helper.cs b/MAX.Core/Cryptography/MD5Helper.cs new file mode 100644 index 0000000..8397396 --- /dev/null +++ b/MAX.Core/Cryptography/MD5Helper.cs @@ -0,0 +1,62 @@ +using System; +using System.Security.Cryptography; +using System.Text; + +namespace MAX.Core.Cryptography; + +public class MD5Helper +{ + /// + /// MD5加密 + /// + /// + /// + public static string Md5_32(string instring) + { + return _Md532(instring); + } + public static string Md5_Lower32(string instring) + { + return _Md532(instring, true); + } + public static string _Md532(string instring, bool IsLower = false) + { + byte[] result = Encoding.UTF8.GetBytes(string.IsNullOrEmpty(instring) ? "" : instring.Trim()); + MD5 md5 = MD5.Create(); + byte[] output = md5.ComputeHash(result); + string value = BitConverter.ToString(output).Replace("-", ""); + if (IsLower) + { + return value.ToLower(); + } + return value; + } + + + /// + /// MD5加密 + /// + /// + public static long GetLong(byte[] result) + { + return + Convert.ToInt64( + BitConverter.ToString(MD5.Create().ComputeHash(result), 4, + 8).Replace("-", ""), 16); + } + + /// + /// MD5 16位加密转long + /// + /// + /// + public static long GetLong(string url) + { + if (string.IsNullOrEmpty(url)) + { + return 0; + } + + return Convert.ToInt64(BitConverter.ToString(MD5.Create().ComputeHash(Encoding.Default.GetBytes(url)), 4, 8).Replace("-", ""), 16); + } +} diff --git a/MAX.Core/Cryptography/UrlHelper.cs b/MAX.Core/Cryptography/UrlHelper.cs new file mode 100644 index 0000000..35f2521 --- /dev/null +++ b/MAX.Core/Cryptography/UrlHelper.cs @@ -0,0 +1,51 @@ +using System.Text; + +namespace MAX.Core.Cryptography; + +/// +/// Url 加密 解密 +/// +public class UrlHelper +{ + /// + /// Url加密 + /// + /// 加密采用的编码方式 + /// 待加密的明文 + /// + public static string Encode(string source, Encoding encode) + { + return System.Web.HttpUtility.UrlEncode(source, encode); + } + + /// + /// Url加密,采用utf8编码方式加密 + /// + /// 待加密的明文 + /// 加密后的字符串 + public static string Encode(string source) + { + return Encode(source, Encoding.UTF8); + } + + /// + /// Url解密 + /// + /// 解密采用的编码方式,注意和加密时采用的方式一致 + /// 待解密的密文 + /// 解密后的字符串 + public static string Decode(string result, Encoding encode) + { + return System.Web.HttpUtility.UrlDecode(result, Encoding.UTF8); + } + + /// + /// Url解密,采用utf8编码方式解密 + /// + /// 待解密的密文 + /// 解密后的字符串 + public static string Decode(string result) + { + return Decode(result, Encoding.UTF8); + } +} diff --git a/MAX.Core/DependencyInjection/IDependencyRegistrar.cs b/MAX.Core/DependencyInjection/IDependencyRegistrar.cs new file mode 100644 index 0000000..4abc09e --- /dev/null +++ b/MAX.Core/DependencyInjection/IDependencyRegistrar.cs @@ -0,0 +1,15 @@ +using MAX.Core.Interfaces; + +namespace MAX.Core.DependencyInjection; + +/// +/// 依赖注册器:把当前模块的服务注册到 。 +/// 底层使用哪个容器由宿主项目决定,MAX.Core 不引用任何容器。 +/// +public interface IDependencyRegistrar: IPriority +{ + /// + /// 注册服务 + /// + void Register(IServiceRegistrar registrar); +} diff --git a/MAX.Core/DependencyInjection/IScopedDependency.cs b/MAX.Core/DependencyInjection/IScopedDependency.cs new file mode 100644 index 0000000..eb004ed --- /dev/null +++ b/MAX.Core/DependencyInjection/IScopedDependency.cs @@ -0,0 +1,8 @@ +namespace MAX.Core.DependencyInjection; + +/// +/// 作用域(每个 Scope/请求一个实例) +/// +public interface IScopedDependency +{ +} diff --git a/MAX.Core/DependencyInjection/IServiceRegistrar.cs b/MAX.Core/DependencyInjection/IServiceRegistrar.cs new file mode 100644 index 0000000..37a3414 --- /dev/null +++ b/MAX.Core/DependencyInjection/IServiceRegistrar.cs @@ -0,0 +1,81 @@ +using System; + +namespace MAX.Core.DependencyInjection; + +/// +/// 依赖注册抽象(由 MAX.Core 自己定义,不依赖任何第三方 DI 容器)。 +/// 具体实现由宿主项目提供,内部适配外部容器(Autofac、Microsoft.Extensions.DependencyInjection 等)。 +/// +public interface IServiceRegistrar +{ + object Services { get; } + /// + /// 注册单例: 实现 + /// + void RegisterSingleton() where TService : class where TImplementation : class, TService; + + /// + /// 注册单例实例 + /// + void RegisterSingleton(TService instance) where TService : class; + + /// + /// 注册单例(匿名工厂) + /// + void RegisterSingleton(Func factory) where TService : class; + + /// + /// 注册单例(key): 实现 + /// + void RegisterSingleton(object? serviceKey) where TService : class where TImplementation : class, TService; + + /// + /// 注册单例实例(key) + /// + void RegisterSingleton(object? serviceKey, TService instance) where TService : class; + + /// + /// 注册单例(key + 匿名工厂) + /// + void RegisterSingleton(object? serviceKey, Func factory) where TService : class; + + /// + /// 注册作用域生命周期 + /// + void RegisterScoped() where TService : class where TImplementation : class, TService; + + /// + /// 注册作用域生命周期(匿名工厂) + /// + void RegisterScoped(Func factory) where TService : class; + + /// + /// 注册作用域生命周期(key): 实现 + /// + void RegisterScoped(object? serviceKey) where TService : class where TImplementation : class, TService; + + /// + /// 注册作用域生命周期(key + 匿名工厂) + /// + void RegisterScoped(object? serviceKey, Func factory) where TService : class; + + /// + /// 注册瞬时生命周期 + /// + void RegisterTransient() where TService : class where TImplementation : class, TService; + + /// + /// 注册瞬时生命周期(匿名工厂) + /// + void RegisterTransient(Func factory) where TService : class; + + /// + /// 注册瞬时生命周期(key): 实现 + /// + void RegisterTransient(object? serviceKey) where TService : class where TImplementation : class, TService; + + /// + /// 注册瞬时生命周期(key + 匿名工厂) + /// + void RegisterTransient(object? serviceKey, Func factory) where TService : class; +} diff --git a/MAX.Core/DependencyInjection/ISingletonDependency.cs b/MAX.Core/DependencyInjection/ISingletonDependency.cs new file mode 100644 index 0000000..bcacfcb --- /dev/null +++ b/MAX.Core/DependencyInjection/ISingletonDependency.cs @@ -0,0 +1,8 @@ +namespace MAX.Core.DependencyInjection; + +/// +/// 单例 +/// +public interface ISingletonDependency +{ +} diff --git a/MAX.Core/DependencyInjection/ITransientDependency.cs b/MAX.Core/DependencyInjection/ITransientDependency.cs new file mode 100644 index 0000000..3da15a0 --- /dev/null +++ b/MAX.Core/DependencyInjection/ITransientDependency.cs @@ -0,0 +1,8 @@ +namespace MAX.Core.DependencyInjection; + +/// +/// 每次实例化 +/// +public interface ITransientDependency +{ +} diff --git a/MAX.Core/DependencyInjection/LazyServiceProvider.cs b/MAX.Core/DependencyInjection/LazyServiceProvider.cs new file mode 100644 index 0000000..ccc84dd --- /dev/null +++ b/MAX.Core/DependencyInjection/LazyServiceProvider.cs @@ -0,0 +1,37 @@ +//using MAX.Core.Autofac; +//using MAX.Core.Infrastructure; +//using System; +//using System.Collections.Concurrent; + +//namespace MAX.Core.DependencyInjection; + +//public interface ILazyServiceProvider +//{ +// object LazyGetService(Type serviceType); +// T LazyGetService(); +//} + +//public class LazyServiceProvider : SingleInstance, ILazyServiceProvider +//{ +// protected ConcurrentDictionary> CachedServices { get; } +// private LazyServiceProvider() +// { +// CachedServices = new ConcurrentDictionary>(); +// } +// public virtual T LazyGetService() +// { +// return (T)LazyGetService(typeof(T)); +// } + +// public virtual object LazyGetService(Type serviceType) +// { +// return GetService(serviceType); +// } +// public virtual object GetService(Type serviceType) +// { +// return CachedServices.GetOrAdd( +// serviceType, +// _ => new Lazy(() => IocManager.Resolve(serviceType)) +// ).Value; +// } +//} diff --git a/MAX.Core/Extensions/Convert.cs b/MAX.Core/Extensions/Convert.cs new file mode 100644 index 0000000..382d8b2 --- /dev/null +++ b/MAX.Core/Extensions/Convert.cs @@ -0,0 +1,218 @@ +using MAX.Core.Helpers; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MAX.Core.Extensions; + +/// +/// 系统扩展 - 类型转换 +/// +public static partial class Extensions +{ + /// + /// IsNullOrEmpty + /// + /// + /// + public static bool IsNullOrEmpty(this string input) + { + return string.IsNullOrEmpty(input); + } + /// + /// 安全转换为字符串,去除两端空格,当值为null时返回"" + /// + /// 输入值 + public static string SafeString(this object input) + { + return input?.ToString().Trim() ?? string.Empty; + } + + /// + /// 转换为bool + /// + /// 数据 + public static bool ToBool(this string obj, bool defvalue = false) + { + return Helpers.Convert.ToBool(obj, defvalue); + } + + /// + /// 转换为可空bool + /// + /// 数据 + public static bool? ToBoolOrNull(this string obj) + { + return Helpers.Convert.ToBoolOrNull(obj); + } + + /// + /// 转换为int + /// + /// 数据 + public static int ToInt(this string obj, int defvalue = 0) + { + return Helpers.Convert.ToInt(obj, defvalue); + } + + /// + /// 转换为可空int + /// + /// 数据 + public static int? ToIntOrNull(this string obj) + { + return Helpers.Convert.ToIntOrNull(obj); + } + + /// + /// 转换为long + /// + /// 数据 + public static long ToLong(this string obj, long defvalue = 0) + { + return Helpers.Convert.ToLong(obj, defvalue); + } + + /// + /// 转换为可空long + /// + /// 数据 + public static long? ToLongOrNull(this string obj) + { + return Helpers.Convert.ToLongOrNull(obj); + } + + /// + /// 转换为double + /// + /// 数据 + public static double ToDouble(this string obj, double defvalue = 0) + { + return Helpers.Convert.ToDouble(obj, defvalue: defvalue); + } + + /// + /// 转换为可空double + /// + /// 数据 + public static double? ToDoubleOrNull(this string obj) + { + return Helpers.Convert.ToDoubleOrNull(obj); + } + + /// + /// 转换为decimal + /// + /// 数据 + public static decimal ToDecimal(this string obj, decimal defvalue = 0) + { + return Helpers.Convert.ToDecimal(obj, defvalue: defvalue); + } + + /// + /// 转换为可空decimal + /// + /// 数据 + public static decimal? ToDecimalOrNull(this string obj) + { + return Helpers.Convert.ToDecimalOrNull(obj); + } + + /// + /// 转换为日期 + /// + /// 数据 + public static DateTime ToDate(this string obj) + { + return Helpers.Convert.ToDate(obj); + } + + /// + /// 转换为可空日期 + /// + /// 数据 + public static DateTime? ToDateOrNull(this string obj) + { + return Helpers.Convert.ToDateOrNull(obj); + } + + /// + /// 转换为Guid + /// + /// 数据 + public static Guid ToGuid(this string obj) + { + return Helpers.Convert.ToGuid(obj); + } + + /// + /// 转换为可空Guid + /// + /// 数据 + public static Guid? ToGuidOrNull(this string obj) + { + return Helpers.Convert.ToGuidOrNull(obj); + } + + /// + /// 转换为Guid集合 + /// + /// 数据,范例: "83B0233C-A24F-49FD-8083-1337209EBC9A,EAB523C6-2FE7-47BE-89D5-C6D440C3033A" + public static List ToGuidList(this string obj) + { + return Helpers.Convert.ToGuidList(obj); + } + + /// + /// 转换为Guid集合 + /// + /// 字符串集合 + public static List ToGuidList(this IList obj) + { + if (obj == null) + { + return []; + } + + return obj.Select(t => t.ToGuid()).ToList(); + } + /// + /// 转换枚举描述 + /// + /// + /// + public static string ToDescription(this Enum obj) + { + return EnumHelper.GetDescription(obj.GetType(), obj); + } + + /// + /// 枚举值 → 单个 EnumItem + /// + /// + /// + /// + public static EnumItem ToItem(this TEnum value) where TEnum : struct, Enum + { + return new EnumItem(value); + } + /// + /// 是否单一标记 + /// + /// + /// + public static bool IsSingleFlag(Enum obj) + { + return EnumHelper.IsSingleFlag(obj); + } + /// + /// 获取第一个标记 + /// + /// + /// + /// + public static Enum GetFirstFlag(this Enum obj) + { + return EnumHelper.GetFirstFlag(obj); + } +} diff --git a/MAX.Core/Extensions/DictionaryExtension.cs b/MAX.Core/Extensions/DictionaryExtension.cs new file mode 100644 index 0000000..d71917e --- /dev/null +++ b/MAX.Core/Extensions/DictionaryExtension.cs @@ -0,0 +1,33 @@ +using MAX.Core.Cryptography; +using System.Collections.Generic; +using System.Text; + +namespace MAX.Core.Extensions; + +public static class DictionaryExtension +{ + /// + /// 字典转url Get 参数 + /// + /// + /// + public static string ToUrlParams(this IDictionary parameters, bool IsUrlEncode = true) + { + IDictionary sortedParams = new SortedDictionary(parameters); + IEnumerator> dem = sortedParams.GetEnumerator(); + StringBuilder sb = new(""); + while (dem.MoveNext()) + { + string key = dem.Current.Key; + string value = dem.Current.Value; + if (!string.IsNullOrEmpty(key)) + { + sb.Append(key).Append("=").Append(IsUrlEncode ? UrlHelper.Encode(value) : value).Append("&"); + + } + } + string content = sb.ToString().Substring(0, sb.Length - 1); + + return content; + } +} diff --git a/MAX.Core/Helpers/Common.cs b/MAX.Core/Helpers/Common.cs new file mode 100644 index 0000000..172d10d --- /dev/null +++ b/MAX.Core/Helpers/Common.cs @@ -0,0 +1,55 @@ +using System; + +namespace MAX.Core.Helpers; + +/// +/// 常用公共操作 +/// +public static partial class Common +{ + /// + /// 获取类型 + /// + /// 类型 + public static Type GetType() + { + return GetType(typeof(T)); + } + + /// + /// 获取类型 + /// + /// 类型 + public static Type GetType(Type type) + { + return Nullable.GetUnderlyingType(type) ?? type; + } + + /// + /// 换行符 + /// + public static string Line => Environment.NewLine; + + +#if NET5_0 + /// + /// 是否Linux操作系统 + /// + public static bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + + /// + /// 是否Windows操作系统 + /// + public static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + + /// + /// 是否苹果操作系统 + /// + public static bool IsOsx => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + + /// + /// 当前操作系统 + /// + public static string System => IsWindows ? "Windows" : IsLinux ? "Linux" : IsOsx ? "OSX" : string.Empty; +#endif +} diff --git a/MAX.Core/Helpers/CommonItem.cs b/MAX.Core/Helpers/CommonItem.cs new file mode 100644 index 0000000..7d83ee4 --- /dev/null +++ b/MAX.Core/Helpers/CommonItem.cs @@ -0,0 +1,78 @@ +using System; + +namespace MAX.Core.Helpers; + +public class CommonItem : IComparable +{ + /// + /// 初始化 + /// + /// 文本 + /// 值 + /// 排序号 + /// 组 + /// 禁用 + public CommonItem(string text, object value, string des = "", int? sortId = null, string group = null, bool? disabled = null) + { + Text = text; + Value = value; + Description = des; + SortId = sortId; + Group = group; + Disabled = disabled; + } + + /// + /// 文本 + /// + public string Text { get; } + + /// + /// 值 + /// + public object Value { get; protected set; } + /// + /// 描述 + /// + public string Description { get; set; } + /// + /// 排序号 + /// + public int? SortId { get; set; } + + /// + /// 组 + /// + public string Group { get; } + /// + /// 是否选中 + /// + public bool IsSelected { get; set; } + /// + /// 禁用 + /// + public bool? Disabled { get; } + + /// + /// 比较 + /// + /// 其它列表项 + public int CompareTo(CommonItem other) + { + return string.Compare(Text, other.Text, StringComparison.CurrentCulture); + } +} + +public class CommonItem : CommonItem +{ + public new T Value + { + get { return (T)base.Value; } + set { base.Value = value; } + } + public CommonItem(string text, T value, string des = "", int? sortId = null, string group = null, bool? disabled = null) : base(text, value, des, sortId, group, disabled) + { + } + + +} diff --git a/MAX.Core/Helpers/Convert.cs b/MAX.Core/Helpers/Convert.cs new file mode 100644 index 0000000..03dc271 --- /dev/null +++ b/MAX.Core/Helpers/Convert.cs @@ -0,0 +1,385 @@ +using MAX.Core.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MAX.Core.Helpers; + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field)] +public class ConvertAttribute : Attribute +{ + public Type ConverterType { get; set; } + public ConvertAttribute(Type type) + { + ConverterType = type; + } +} +public interface IConverter +{ + object Converter(object input); +} +/// +/// 类型转换 +/// +public static partial class Convert +{ + /// + /// 转换为32位整型 + /// + /// 输入值 + public static int ToInt(object input, int defvalue = 0) + { + return ToIntOrNull(input) ?? defvalue; + } + + /// + /// 转换为32位可空整型 + /// + /// 输入值 + public static int? ToIntOrNull(object input) + { + bool success = int.TryParse(input.SafeString(), out int result); + if (success) + { + return result; + } + + try + { + double? temp = ToDoubleOrNull(input, 0); + if (temp == null) + { + return null; + } + + return System.Convert.ToInt32(temp); + } + catch + { + return null; + } + } + + /// + /// 转换为64位整型 + /// + /// 输入值 + public static long ToLong(object input, long defvalue = 0) + { + return ToLongOrNull(input) ?? defvalue; + } + + /// + /// 转换为64位可空整型 + /// + /// 输入值 + public static long? ToLongOrNull(object input) + { + bool success = long.TryParse(input.SafeString(), out long result); + if (success) + { + return result; + } + + try + { + decimal? temp = ToDecimalOrNull(input, 0); + if (temp == null) + { + return null; + } + + return System.Convert.ToInt64(temp); + } + catch + { + return null; + } + } + + /// + /// 转换为32位浮点型,并按指定小数位舍入 + /// + /// 输入值 + /// 小数位数 + public static float ToFloat(object input, int? digits = null) + { + return ToFloatOrNull(input, digits) ?? 0; + } + + /// + /// 转换为32位可空浮点型,并按指定小数位舍入 + /// + /// 输入值 + /// 小数位数 + public static float? ToFloatOrNull(object input, int? digits = null) + { + bool success = float.TryParse(input.SafeString(), out float result); + if (!success) + { + return null; + } + + if (digits == null) + { + return result; + } + + return (float)Math.Round(result, digits.Value); + } + + /// + /// 转换为64位浮点型,并按指定小数位舍入 + /// + /// 输入值 + /// 小数位数 + public static double ToDouble(object input, int? digits = null, double defvalue = 0) + { + return ToDoubleOrNull(input, digits) ?? defvalue; + } + + /// + /// 转换为64位可空浮点型,并按指定小数位舍入 + /// + /// 输入值 + /// 小数位数 + public static double? ToDoubleOrNull(object input, int? digits = null) + { + if (input is bool b) + { + return b ? 1 : 0; + } + bool success = double.TryParse(input.SafeString(), out double result); + if (!success) + { + return null; + } + + if (digits == null) + { + return result; + } + + return Math.Round(result, digits.Value); + } + + /// + /// 转换为128位浮点型,并按指定小数位舍入 + /// + /// 输入值 + /// 小数位数 + public static decimal ToDecimal(object input, int? digits = null, decimal defvalue = 0) + { + return ToDecimalOrNull(input, digits) ?? defvalue; + } + + /// + /// 转换为128位可空浮点型,并按指定小数位舍入 + /// + /// 输入值 + /// 小数位数 + public static decimal? ToDecimalOrNull(object input, int? digits = null) + { + bool success = decimal.TryParse(input.SafeString(), out decimal result); + if (!success) + { + return null; + } + + if (digits == null) + { + return result; + } + + return Math.Round(result, digits.Value); + } + + /// + /// 转换为布尔值 + /// + /// 输入值 + public static bool ToBool(object input, bool defvalue = false) + { + return ToBoolOrNull(input) ?? defvalue; + } + + /// + /// 转换为可空布尔值 + /// + /// 输入值 + public static bool? ToBoolOrNull(object input) + { + bool? value = GetBool(input); + if (value != null) + { + return value.Value; + } + + return bool.TryParse(input.SafeString(), out bool result) ? result : null; + } + + /// + /// 获取布尔值 + /// + private static bool? GetBool(object input) + { + return input.SafeString().ToLower() switch + { + "0" => false, + "否" => false, + "不" => false, + "no" => false, + "fail" => false, + "1" => true, + "是" => true, + "ok" => true, + "yes" => true, + _ => null, + }; + } + + /// + /// 转换为日期 + /// + /// 输入值 + public static DateTime ToDate(object input) + { + return ToDateOrNull(input) ?? DateTime.MinValue; + } + + /// + /// 转换为可空日期 + /// + /// 输入值 + public static DateTime? ToDateOrNull(object input) + { + return DateTime.TryParse(input.SafeString(), out DateTime result) ? result : null; + } + + /// + /// 转换为Guid + /// + /// 输入值 + public static Guid ToGuid(object input) + { + return ToGuidOrNull(input) ?? Guid.Empty; + } + + /// + /// 转换为可空Guid + /// + /// 输入值 + public static Guid? ToGuidOrNull(object input) + { + return Guid.TryParse(input.SafeString(), out Guid result) ? result : null; + } + + /// + /// 转换为Guid集合 + /// + /// 以逗号分隔的Guid集合字符串,范例:83B0233C-A24F-49FD-8083-1337209EBC9A,EAB523C6-2FE7-47BE-89D5-C6D440C3033A + public static List ToGuidList(string input) + { + return ToList(input); + } + + /// + /// 泛型集合转换 + /// + /// 目标元素类型 + /// 以逗号分隔的元素集合字符串,范例:83B0233C-A24F-49FD-8083-1337209EBC9A,EAB523C6-2FE7-47BE-89D5-C6D440C3033A + public static List ToList(string input) + { + List result = []; + if (string.IsNullOrWhiteSpace(input)) + { + return result; + } + + string[] array = input.Split(','); + result.AddRange(from each in array where !string.IsNullOrWhiteSpace(each) select To(each)); + return result; + } + + /// + /// 通用泛型转换 + /// + /// 目标类型 + /// 输入值 + public static T To(object input) + { + Type type = Common.GetType(); + return (T)To(type, input); + } + public static object To(Type type, object input) + { + object defValue = null; + if (type != null) + { + defValue = type.IsValueType ? Activator.CreateInstance(type) : null; + } + if (input == null) + { + return defValue; + } + + if (input is string && string.IsNullOrWhiteSpace(input.ToString())) + { + return defValue; + } + + string typeName = type.Name.ToLower(); + try + { + if (typeName == "string") + { + return input.ToString(); + } + + if (typeName == "guid") + { + return new Guid(input.ToString()); + } + + if (type.IsEnum) + { + return EnumHelper.Parse(type, input); + } + + if (typeof(IConverter).IsAssignableFrom(type)) + { + object instance = Activator.CreateInstance(type); + return ((IConverter)instance).Converter(input); + } + if (input is IConvertible) + { + return System.Convert.ChangeType(input, type); + } + + return input; + } + catch + { + return defValue; + } + } + /// + /// 转换为字节数组 + /// + /// 输入值 + public static byte[] ToBytes(string input) + { + return ToBytes(input, Encoding.UTF8); + } + + /// + /// 转换为字节数组 + /// + /// 输入值 + /// 字符编码 + public static byte[] ToBytes(string input, Encoding encoding) + { + return string.IsNullOrWhiteSpace(input) ? new byte[] { } : encoding.GetBytes(input); + } +} diff --git a/MAX.Core/Helpers/CopyHelper.cs b/MAX.Core/Helpers/CopyHelper.cs new file mode 100644 index 0000000..dfb4e5f --- /dev/null +++ b/MAX.Core/Helpers/CopyHelper.cs @@ -0,0 +1,1151 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; + +namespace MAX.Core.Helpers; + +public class CopyIgnoreAttribute : Attribute { } + +/// +/// 拷贝帮助 +/// +public class CopyHelper +{ + /// + /// 反射深拷贝 + /// + /// + /// + /// + public static T DeepCopy(T obj) + { + return (T)DeepCopy((Object)obj, null); + } + + public static T DeepCopy(T obj, Func filter = null) + { + return (T)DeepCopy((Object)obj, filter); + } + + public static Object DeepCopy(Object originalObject, Func filter = null) + { + return InternalCopy( + originalObject, + new Dictionary(new ReferenceEqualityComparer()), + filter + ); + } + + private static readonly MethodInfo CloneMethod = typeof(Object).GetMethod( + "MemberwiseClone", + BindingFlags.NonPublic | BindingFlags.Instance + ); + + public static bool IsPrimitive(Type type) + { + if (type == typeof(String)) + { + return true; + } + + return type.IsValueType & type.IsPrimitive; + } + + private static Object InternalCopy( + Object originalObject, + IDictionary visited, + Func filter = null + ) + { + if (originalObject == null) + { + return null; + } + + Type typeToReflect = originalObject.GetType(); + if (IsPrimitive(typeToReflect)) + { + return originalObject; + } + + if (visited.ContainsKey(originalObject)) + { + return visited[originalObject]; + } + + if (typeof(Delegate).IsAssignableFrom(typeToReflect)) + { + return null; + } + + object cloneObject = CloneMethod.Invoke(originalObject, null); + if (typeToReflect.IsArray) + { + Type arrayType = typeToReflect.GetElementType(); + if (IsPrimitive(arrayType) == false) + { + Array clonedArray = (Array)cloneObject; + clonedArray.ForEach( + (array, indices) => + array.SetValue( + InternalCopy(clonedArray.GetValue(indices), visited, filter), + indices + ) + ); + } + } + visited.Add(originalObject, cloneObject); + CopyFields(originalObject, visited, cloneObject, typeToReflect, filter: filter); + RecursiveCopyBaseTypePrivateFields( + originalObject, + visited, + cloneObject, + typeToReflect, + filter + ); + return cloneObject; + } + + private static void RecursiveCopyBaseTypePrivateFields( + object originalObject, + IDictionary visited, + object cloneObject, + Type typeToReflect, + Func filter = null + ) + { + if (typeToReflect.BaseType != null) + { + RecursiveCopyBaseTypePrivateFields( + originalObject, + visited, + cloneObject, + typeToReflect.BaseType + ); + CopyFields( + originalObject, + visited, + cloneObject, + typeToReflect.BaseType, + BindingFlags.Instance | BindingFlags.NonPublic, + info => info.IsPrivate + ); + } + } + + private static void CopyFields( + object originalObject, + IDictionary visited, + object cloneObject, + Type typeToReflect, + BindingFlags bindingFlags = + BindingFlags.Instance + | BindingFlags.NonPublic + | BindingFlags.Public + | BindingFlags.FlattenHierarchy, + Func filter = null + ) + { + foreach (FieldInfo fieldInfo in typeToReflect.GetFields(bindingFlags)) + { + if (filter != null && filter(fieldInfo) == false) + { + continue; + } + + if (IsPrimitive(fieldInfo.FieldType)) + { + continue; + } + + object originalFieldValue = fieldInfo.GetValue(originalObject); + object clonedFieldValue = InternalCopy(originalFieldValue, visited, filter: filter); + fieldInfo.SetValue(cloneObject, clonedFieldValue); + } + } +} + +/// +/// Superfast deep copier class, which uses Expression trees. +/// +public static class DeepCopyByExpressionTrees +{ + private static readonly object IsStructTypeToDeepCopyDictionaryLocker = new(); + private static Dictionary IsStructTypeToDeepCopyDictionary = + []; + + private static readonly object CompiledCopyFunctionsDictionaryLocker = new(); + private static Dictionary< + Type, + Func, object> + > CompiledCopyFunctionsDictionary = + []; + + private static readonly Type ObjectType = typeof(Object); + private static readonly Type ObjectDictionaryType = typeof(Dictionary); + + /// + /// Creates a deep copy of an object. + /// + /// Object type. + /// Object to copy. + /// Dictionary of already copied objects (Keys: original objects, Values: their copies). + /// + public static T DeepCopyByExpressionTree( + this T original, + Dictionary copiedReferencesDict = null + ) + { + return (T)DeepCopyByExpressionTreeObj( + original, + false, + copiedReferencesDict + ?? new Dictionary(new ReferenceEqualityComparer()) + ); + } + + private static object DeepCopyByExpressionTreeObj( + object original, + bool forceDeepCopy, + Dictionary copiedReferencesDict + ) + { + if (original == null) + { + return null; + } + + Type type = original.GetType(); + + if (IsDelegate(type)) + { + return null; + } + + if (!forceDeepCopy && !IsTypeToDeepCopy(type)) + { + return original; + } + + + if (copiedReferencesDict.TryGetValue(original, out object alreadyCopiedObject)) + { + return alreadyCopiedObject; + } + + if (type == ObjectType) + { + return new object(); + } + + Func, object> compiledCopyFunction = GetOrCreateCompiledLambdaCopyFunction(type); + + object copy = compiledCopyFunction(original, copiedReferencesDict); + + return copy; + } + + private static Func< + object, + Dictionary, + object + > GetOrCreateCompiledLambdaCopyFunction(Type type) + { + // The following structure ensures that multiple threads can use the dictionary + // even while dictionary is locked and being updated by other thread. + // That is why we do not modify the old dictionary instance but + // we replace it with a new instance everytime. + + + if (!CompiledCopyFunctionsDictionary.TryGetValue(type, out Func, object> compiledCopyFunction)) + { + lock (CompiledCopyFunctionsDictionaryLocker) + { + if ( + !CompiledCopyFunctionsDictionary.TryGetValue(type, out compiledCopyFunction) + ) + { + Expression, object>> uncompiledCopyFunction = CreateCompiledLambdaCopyFunctionForType(type); + + compiledCopyFunction = uncompiledCopyFunction.Compile(); + + Dictionary, object>> dictionaryCopy = CompiledCopyFunctionsDictionary.ToDictionary( + pair => pair.Key, + pair => pair.Value + ); + + dictionaryCopy.Add(type, compiledCopyFunction); + + CompiledCopyFunctionsDictionary = dictionaryCopy; + } + } + } + + return compiledCopyFunction; + } + + private static Expression< + Func, object> + > CreateCompiledLambdaCopyFunctionForType(Type type) + { + + ///// INITIALIZATION OF EXPRESSIONS AND VARIABLES + + InitializeExpressions( + type, + out ParameterExpression inputParameter, + out ParameterExpression inputDictionary, + out ParameterExpression outputVariable, + out ParameterExpression boxingVariable, + out LabelTarget endLabel, + out List variables, + out List expressions + ); + + ///// RETURN NULL IF ORIGINAL IS NULL + + IfNullThenReturnNullExpression(inputParameter, endLabel, expressions); + + ///// MEMBERWISE CLONE ORIGINAL OBJECT + + MemberwiseCloneInputToOutputExpression( + type, + inputParameter, + outputVariable, + expressions + ); + + ///// STORE COPIED OBJECT TO REFERENCES DICTIONARY + + if (IsClassOtherThanString(type)) + { + StoreReferencesIntoDictionaryExpression( + inputParameter, + inputDictionary, + outputVariable, + expressions + ); + } + + ///// COPY ALL NONVALUE OR NONPRIMITIVE FIELDS + + FieldsCopyExpressions( + type, + inputParameter, + inputDictionary, + outputVariable, + boxingVariable, + expressions + ); + + ///// COPY ELEMENTS OF ARRAY + + if (IsArray(type) && IsTypeToDeepCopy(type.GetElementType())) + { + CreateArrayCopyLoopExpression( + type, + inputParameter, + inputDictionary, + outputVariable, + variables, + expressions + ); + } + + ///// COMBINE ALL EXPRESSIONS INTO LAMBDA FUNCTION + + Expression, object>> lambda = CombineAllIntoLambdaFunctionExpression( + inputParameter, + inputDictionary, + outputVariable, + endLabel, + variables, + expressions + ); + + return lambda; + } + + private static void InitializeExpressions( + Type type, + out ParameterExpression inputParameter, + out ParameterExpression inputDictionary, + out ParameterExpression outputVariable, + out ParameterExpression boxingVariable, + out LabelTarget endLabel, + out List variables, + out List expressions + ) + { + inputParameter = Expression.Parameter(ObjectType); + + inputDictionary = Expression.Parameter(ObjectDictionaryType); + + outputVariable = Expression.Variable(type); + + boxingVariable = Expression.Variable(ObjectType); + + endLabel = Expression.Label(); + + variables = []; + + expressions = []; + + variables.Add(outputVariable); + variables.Add(boxingVariable); + } + + private static void IfNullThenReturnNullExpression( + ParameterExpression inputParameter, + LabelTarget endLabel, + List expressions + ) + { + ///// Intended code: + ///// + ///// if (input == null) + ///// { + ///// return null; + ///// } + + ConditionalExpression ifNullThenReturnNullExpression = Expression.IfThen( + Expression.Equal(inputParameter, Expression.Constant(null, ObjectType)), + Expression.Return(endLabel) + ); + + expressions.Add(ifNullThenReturnNullExpression); + } + + private static void MemberwiseCloneInputToOutputExpression( + Type type, + ParameterExpression inputParameter, + ParameterExpression outputVariable, + List expressions + ) + { + ///// Intended code: + ///// + ///// var output = ()input.MemberwiseClone(); + + MethodInfo memberwiseCloneMethod = ObjectType.GetMethod( + "MemberwiseClone", + BindingFlags.NonPublic | BindingFlags.Instance + ); + + BinaryExpression memberwiseCloneInputExpression = Expression.Assign( + outputVariable, + Expression.Convert(Expression.Call(inputParameter, memberwiseCloneMethod), type) + ); + + expressions.Add(memberwiseCloneInputExpression); + } + + private static void StoreReferencesIntoDictionaryExpression( + ParameterExpression inputParameter, + ParameterExpression inputDictionary, + ParameterExpression outputVariable, + List expressions + ) + { + ///// Intended code: + ///// + ///// inputDictionary[(Object)input] = (Object)output; + + BinaryExpression storeReferencesExpression = Expression.Assign( + Expression.Property( + inputDictionary, + ObjectDictionaryType.GetProperty("Item"), + inputParameter + ), + Expression.Convert(outputVariable, ObjectType) + ); + + expressions.Add(storeReferencesExpression); + } + + private static Expression< + Func, object> + > CombineAllIntoLambdaFunctionExpression( + ParameterExpression inputParameter, + ParameterExpression inputDictionary, + ParameterExpression outputVariable, + LabelTarget endLabel, + List variables, + List expressions + ) + { + expressions.Add(Expression.Label(endLabel)); + + expressions.Add(Expression.Convert(outputVariable, ObjectType)); + + BlockExpression finalBody = Expression.Block(variables, expressions); + + Expression, object>> lambda = Expression.Lambda, object>>( + finalBody, + inputParameter, + inputDictionary + ); + + return lambda; + } + + private static void CreateArrayCopyLoopExpression( + Type type, + ParameterExpression inputParameter, + ParameterExpression inputDictionary, + ParameterExpression outputVariable, + List variables, + List expressions + ) + { + ///// Intended code: + ///// + ///// int i1, i2, ..., in; + ///// + ///// int length1 = inputarray.GetLength(0); + ///// i1 = 0; + ///// while (true) + ///// { + ///// if (i1 >= length1) + ///// { + ///// goto ENDLABELFORLOOP1; + ///// } + ///// int length2 = inputarray.GetLength(1); + ///// i2 = 0; + ///// while (true) + ///// { + ///// if (i2 >= length2) + ///// { + ///// goto ENDLABELFORLOOP2; + ///// } + ///// ... + ///// ... + ///// ... + ///// int lengthn = inputarray.GetLength(n); + ///// in = 0; + ///// while (true) + ///// { + ///// if (in >= lengthn) + ///// { + ///// goto ENDLABELFORLOOPn; + ///// } + ///// outputarray[i1, i2, ..., in] + ///// = ()DeepCopyByExpressionTreeObj( + ///// (Object)inputarray[i1, i2, ..., in]) + ///// in++; + ///// } + ///// ENDLABELFORLOOPn: + ///// ... + ///// ... + ///// ... + ///// i2++; + ///// } + ///// ENDLABELFORLOOP2: + ///// i1++; + ///// } + ///// ENDLABELFORLOOP1: + + int rank = type.GetArrayRank(); + + List indices = GenerateIndices(rank); + + variables.AddRange(indices); + + Type elementType = type.GetElementType(); + + BinaryExpression assignExpression = ArrayFieldToArrayFieldAssignExpression( + inputParameter, + inputDictionary, + outputVariable, + elementType, + type, + indices + ); + + Expression forExpression = assignExpression; + + for (int dimension = 0; dimension < rank; dimension++) + { + ParameterExpression indexVariable = indices[dimension]; + + forExpression = LoopIntoLoopExpression( + inputParameter, + indexVariable, + forExpression, + dimension + ); + } + + expressions.Add(forExpression); + } + + private static List GenerateIndices(int arrayRank) + { + ///// Intended code: + ///// + ///// int i1, i2, ..., in; + + List indices = []; + + for (int i = 0; i < arrayRank; i++) + { + ParameterExpression indexVariable = Expression.Variable(typeof(Int32)); + + indices.Add(indexVariable); + } + + return indices; + } + + private static BinaryExpression ArrayFieldToArrayFieldAssignExpression( + ParameterExpression inputParameter, + ParameterExpression inputDictionary, + ParameterExpression outputVariable, + Type elementType, + Type arrayType, + List indices + ) + { + ///// Intended code: + ///// + ///// outputarray[i1, i2, ..., in] + ///// = ()DeepCopyByExpressionTreeObj( + ///// (Object)inputarray[i1, i2, ..., in]); + + IndexExpression indexTo = Expression.ArrayAccess(outputVariable, indices); + + MethodCallExpression indexFrom = Expression.ArrayIndex( + Expression.Convert(inputParameter, arrayType), + indices + ); + + bool forceDeepCopy = elementType != ObjectType; + + UnaryExpression rightSide = Expression.Convert( + Expression.Call( + DeepCopyByExpressionTreeObjMethod, + Expression.Convert(indexFrom, ObjectType), + Expression.Constant(forceDeepCopy, typeof(Boolean)), + inputDictionary + ), + elementType + ); + + BinaryExpression assignExpression = Expression.Assign(indexTo, rightSide); + + return assignExpression; + } + + private static BlockExpression LoopIntoLoopExpression( + ParameterExpression inputParameter, + ParameterExpression indexVariable, + Expression loopToEncapsulate, + int dimension + ) + { + ///// Intended code: + ///// + ///// int length = inputarray.GetLength(dimension); + ///// i = 0; + ///// while (true) + ///// { + ///// if (i >= length) + ///// { + ///// goto ENDLABELFORLOOP; + ///// } + ///// loopToEncapsulate; + ///// i++; + ///// } + ///// ENDLABELFORLOOP: + + ParameterExpression lengthVariable = Expression.Variable(typeof(Int32)); + + LabelTarget endLabelForThisLoop = Expression.Label(); + + LoopExpression newLoop = Expression.Loop( + Expression.Block( + new ParameterExpression[0], + Expression.IfThen( + Expression.GreaterThanOrEqual(indexVariable, lengthVariable), + Expression.Break(endLabelForThisLoop) + ), + loopToEncapsulate, + Expression.PostIncrementAssign(indexVariable) + ), + endLabelForThisLoop + ); + + BinaryExpression lengthAssignment = GetLengthForDimensionExpression( + lengthVariable, + inputParameter, + dimension + ); + + BinaryExpression indexAssignment = Expression.Assign(indexVariable, Expression.Constant(0)); + + return Expression.Block( + new[] { lengthVariable }, + lengthAssignment, + indexAssignment, + newLoop + ); + } + + private static BinaryExpression GetLengthForDimensionExpression( + ParameterExpression lengthVariable, + ParameterExpression inputParameter, + int i + ) + { + ///// Intended code: + ///// + ///// length = ((Array)input).GetLength(i); + + MethodInfo getLengthMethod = typeof(Array).GetMethod( + "GetLength", + BindingFlags.Public | BindingFlags.Instance + ); + + ConstantExpression dimensionConstant = Expression.Constant(i); + + return Expression.Assign( + lengthVariable, + Expression.Call( + Expression.Convert(inputParameter, typeof(Array)), + getLengthMethod, + new[] { dimensionConstant } + ) + ); + } + + private static void FieldsCopyExpressions( + Type type, + ParameterExpression inputParameter, + ParameterExpression inputDictionary, + ParameterExpression outputVariable, + ParameterExpression boxingVariable, + List expressions + ) + { + FieldInfo[] fields = GetAllRelevantFields(type); + + List readonlyFields = fields.Where(f => f.IsInitOnly).ToList(); + List writableFields = fields.Where(f => !f.IsInitOnly).ToList(); + + ///// READONLY FIELDS COPY (with boxing) + + bool shouldUseBoxing = readonlyFields.Any(); + + if (shouldUseBoxing) + { + BinaryExpression boxingExpression = Expression.Assign( + boxingVariable, + Expression.Convert(outputVariable, ObjectType) + ); + + expressions.Add(boxingExpression); + } + + foreach (FieldInfo field in readonlyFields) + { + if (IsDelegate(field.FieldType)) + { + ReadonlyFieldToNullExpression(field, boxingVariable, expressions); + } + else + { + ReadonlyFieldCopyExpression( + type, + field, + inputParameter, + inputDictionary, + boxingVariable, + expressions + ); + } + } + + if (shouldUseBoxing) + { + BinaryExpression unboxingExpression = Expression.Assign( + outputVariable, + Expression.Convert(boxingVariable, type) + ); + + expressions.Add(unboxingExpression); + } + + ///// NOT-READONLY FIELDS COPY + + foreach (FieldInfo field in writableFields) + { + if (IsDelegate(field.FieldType)) + { + WritableFieldToNullExpression(field, outputVariable, expressions); + } + else + { + WritableFieldCopyExpression( + type, + field, + inputParameter, + inputDictionary, + outputVariable, + expressions + ); + } + } + } + + private static FieldInfo[] GetAllRelevantFields(Type type, bool forceAllFields = false) + { + List fieldsList = []; + + Type typeCache = type; + + while (typeCache != null) + { + fieldsList.AddRange( + typeCache + .GetFields( + BindingFlags.Instance + | BindingFlags.Public + | BindingFlags.NonPublic + | BindingFlags.FlattenHierarchy + ) + .Where(field => forceAllFields || IsTypeToDeepCopy(field.FieldType)) + ); + + typeCache = typeCache.BaseType; + } + + return fieldsList.ToArray(); + } + + private static FieldInfo[] GetAllFields(Type type) + { + return GetAllRelevantFields(type, forceAllFields: true); + } + + private static readonly Type FieldInfoType = typeof(FieldInfo); + private static readonly MethodInfo SetValueMethod = FieldInfoType.GetMethod( + "SetValue", + new[] { ObjectType, ObjectType } + ); + + private static void ReadonlyFieldToNullExpression( + FieldInfo field, + ParameterExpression boxingVariable, + List expressions + ) + { + // This option must be implemented by Reflection because of the following: + // https://visualstudio.uservoice.com/forums/121579-visual-studio-2015/suggestions/2727812-allow-expression-assign-to-set-readonly-struct-f + + ///// Intended code: + ///// + ///// fieldInfo.SetValue(boxing, null); + + MethodCallExpression fieldToNullExpression = Expression.Call( + Expression.Constant(field), + SetValueMethod, + boxingVariable, + Expression.Constant(null, field.FieldType) + ); + + expressions.Add(fieldToNullExpression); + } + + private static readonly Type ThisType = typeof(DeepCopyByExpressionTrees); + private static readonly MethodInfo DeepCopyByExpressionTreeObjMethod = ThisType.GetMethod( + "DeepCopyByExpressionTreeObj", + BindingFlags.NonPublic | BindingFlags.Static + ); + + private static void ReadonlyFieldCopyExpression( + Type type, + FieldInfo field, + ParameterExpression inputParameter, + ParameterExpression inputDictionary, + ParameterExpression boxingVariable, + List expressions + ) + { + // This option must be implemented by Reflection (SetValueMethod) because of the following: + // https://visualstudio.uservoice.com/forums/121579-visual-studio-2015/suggestions/2727812-allow-expression-assign-to-set-readonly-struct-f + + ///// Intended code: + ///// + ///// fieldInfo.SetValue(boxing, DeepCopyByExpressionTreeObj((Object)(()input).)) + + MemberExpression fieldFrom = Expression.Field(Expression.Convert(inputParameter, type), field); + + bool forceDeepCopy = field.FieldType != ObjectType; + + MethodCallExpression fieldDeepCopyExpression = Expression.Call( + Expression.Constant(field, FieldInfoType), + SetValueMethod, + boxingVariable, + Expression.Call( + DeepCopyByExpressionTreeObjMethod, + Expression.Convert(fieldFrom, ObjectType), + Expression.Constant(forceDeepCopy, typeof(Boolean)), + inputDictionary + ) + ); + + expressions.Add(fieldDeepCopyExpression); + } + + private static void WritableFieldToNullExpression( + FieldInfo field, + ParameterExpression outputVariable, + List expressions + ) + { + ///// Intended code: + ///// + ///// output. = ()null; + + MemberExpression fieldTo = Expression.Field(outputVariable, field); + + BinaryExpression fieldToNullExpression = Expression.Assign( + fieldTo, + Expression.Constant(null, field.FieldType) + ); + + expressions.Add(fieldToNullExpression); + } + + private static void WritableFieldCopyExpression( + Type type, + FieldInfo field, + ParameterExpression inputParameter, + ParameterExpression inputDictionary, + ParameterExpression outputVariable, + List expressions + ) + { + ///// Intended code: + ///// + ///// output. = ()DeepCopyByExpressionTreeObj((Object)(()input).); + + MemberExpression fieldFrom = Expression.Field(Expression.Convert(inputParameter, type), field); + + Type fieldType = field.FieldType; + + MemberExpression fieldTo = Expression.Field(outputVariable, field); + + bool forceDeepCopy = field.FieldType != ObjectType; + + BinaryExpression fieldDeepCopyExpression = Expression.Assign( + fieldTo, + Expression.Convert( + Expression.Call( + DeepCopyByExpressionTreeObjMethod, + Expression.Convert(fieldFrom, ObjectType), + Expression.Constant(forceDeepCopy, typeof(Boolean)), + inputDictionary + ), + fieldType + ) + ); + + expressions.Add(fieldDeepCopyExpression); + } + + private static bool IsArray(Type type) + { + return type.IsArray; + } + + private static bool IsDelegate(Type type) + { + return typeof(Delegate).IsAssignableFrom(type); + } + + private static bool IsTypeToDeepCopy(Type type) + { + return IsClassOtherThanString(type) || IsStructWhichNeedsDeepCopy(type); + } + + private static bool IsClassOtherThanString(Type type) + { + return !type.IsValueType && type != typeof(String); + } + + private static bool IsStructWhichNeedsDeepCopy(Type type) + { + // The following structure ensures that multiple threads can use the dictionary + // even while dictionary is locked and being updated by other thread. + // That is why we do not modify the old dictionary instance but + // we replace it with a new instance everytime. + + + if (!IsStructTypeToDeepCopyDictionary.TryGetValue(type, out bool isStructTypeToDeepCopy)) + { + lock (IsStructTypeToDeepCopyDictionaryLocker) + { + if ( + !IsStructTypeToDeepCopyDictionary.TryGetValue( + type, + out isStructTypeToDeepCopy + ) + ) + { + isStructTypeToDeepCopy = IsStructWhichNeedsDeepCopy_NoDictionaryUsed(type); + + Dictionary newDictionary = IsStructTypeToDeepCopyDictionary.ToDictionary( + pair => pair.Key, + pair => pair.Value + ); + + newDictionary[type] = isStructTypeToDeepCopy; + + IsStructTypeToDeepCopyDictionary = newDictionary; + } + } + } + + return isStructTypeToDeepCopy; + } + + private static bool IsStructWhichNeedsDeepCopy_NoDictionaryUsed(Type type) + { + return IsStructOtherThanBasicValueTypes(type) + && HasInItsHierarchyFieldsWithClasses(type); + } + + private static bool IsStructOtherThanBasicValueTypes(Type type) + { + return type.IsValueType && !type.IsPrimitive && !type.IsEnum && type != typeof(Decimal); + } + + private static bool HasInItsHierarchyFieldsWithClasses( + Type type, + HashSet alreadyCheckedTypes = null + ) + { + alreadyCheckedTypes ??= []; + + alreadyCheckedTypes.Add(type); + + FieldInfo[] allFields = GetAllFields(type); + + List allFieldTypes = allFields.Select(f => f.FieldType).Distinct().ToList(); + + bool hasFieldsWithClasses = allFieldTypes.Any(IsClassOtherThanString); + + if (hasFieldsWithClasses) + { + return true; + } + + List notBasicStructsTypes = allFieldTypes + .Where(IsStructOtherThanBasicValueTypes) + .ToList(); + + List typesToCheck = notBasicStructsTypes + .Where(t => !alreadyCheckedTypes.Contains(t)) + .ToList(); + + foreach (Type typeToCheck in typesToCheck) + { + if (HasInItsHierarchyFieldsWithClasses(typeToCheck, alreadyCheckedTypes)) + { + return true; + } + } + + return false; + } + + public class ReferenceEqualityComparer : EqualityComparer + { + public override bool Equals(object x, object y) + { + return ReferenceEquals(x, y); + } + + public override int GetHashCode(object obj) + { + if (obj == null) + { + return 0; + } + + return obj.GetHashCode(); + } + } +} + +public class ReferenceEqualityComparer : EqualityComparer +{ + public override bool Equals(object x, object y) + { + return ReferenceEquals(x, y); + } + + public override int GetHashCode(object obj) + { + if (obj == null) + { + return 0; + } + + return obj.GetHashCode(); + } +} + +public static class ArrayExtensions +{ + public static void ForEach(this Array array, Action action) + { + if (array.LongLength == 0) + { + return; + } + + ArrayTraverse walker = new(array); + do + { + action(array, walker.Position); + } + while (walker.Step()); + } +} + +internal class ArrayTraverse +{ + public int[] Position; + private readonly int[] maxLengths; + + public ArrayTraverse(Array array) + { + maxLengths = new int[array.Rank]; + for (int i = 0; i < array.Rank; ++i) + { + maxLengths[i] = array.GetLength(i) - 1; + } + Position = new int[array.Rank]; + } + + public bool Step() + { + for (int i = 0; i < Position.Length; ++i) + { + if (Position[i] < maxLengths[i]) + { + Position[i]++; + for (int j = 0; j < i; j++) + { + Position[j] = 0; + } + return true; + } + } + return false; + } +} diff --git a/MAX.Core/Helpers/DateHelper.cs b/MAX.Core/Helpers/DateHelper.cs new file mode 100644 index 0000000..d9eb110 --- /dev/null +++ b/MAX.Core/Helpers/DateHelper.cs @@ -0,0 +1,110 @@ +using System; + +namespace MAX.Core.Helpers; + +public class DateHelper +{ + /// + /// Unix纪元时间 + /// + private static readonly DateTime UnixEpoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + /// + /// 将时间转换为秒级时间戳 + /// + /// + /// + public static long GetUnixTimestampInSeconds(DateTime dateTime) + { + TimeSpan timeSpan = dateTime.ToUniversalTime() - UnixEpoch; + return (long)timeSpan.TotalSeconds; + } + + /// + /// 将时间转换为毫秒级时间戳 + /// + /// + /// + public static long GetUnixTimestampInMilliseconds(DateTime dateTime) + { + TimeSpan timeSpan = dateTime.ToUniversalTime() - UnixEpoch; + return (long)timeSpan.TotalMilliseconds; + } + + /// + /// 将时间转换为微秒时间戳 + /// + /// + /// + public static long GetUnixTimestampInMicroseconds(DateTime dateTime) + { + long us = dateTime.ToUniversalTime().Ticks - UnixEpoch.Ticks; + return us; + } + /// + /// 将时间Ticks转换为毫微秒时间戳 + /// + /// + /// + public static long GetUnixTimestampInMicroseconds(long ticks) + { + long us = ticks - UnixEpoch.Ticks; + return us; + } + + /// + /// 将秒级时间戳转换为时间 + /// + /// + /// + public static DateTime ConvertFromUnixTimestampInSeconds(long timestamp) + { + return UnixEpoch.AddSeconds(timestamp).ToLocalTime(); + } + + /// + /// 将毫秒级时间戳转换为时间 + /// + /// + /// + public static DateTime ConvertFromUnixTimestampInMilliseconds(long timestamp) + { + return UnixEpoch.AddMilliseconds(timestamp).ToLocalTime(); + } + /// + /// 将微秒级时间戳转换为时间 + /// + /// + /// + public static DateTime ConvertFromUnixTimestampInMicroseconds(long timestamp) + { + long us = timestamp + UnixEpoch.Ticks; + return new DateTime(us); + } + /// + /// 获取当前时间的秒级时间戳 + /// + /// + public static long GetCurrentUnixTimestampInSeconds() + { + return GetUnixTimestampInSeconds(DateTime.UtcNow); + } + + /// + /// 获取当前时间的毫秒级时间戳 + /// + /// + public static long GetCurrentUnixTimestampInMilliseconds() + { + return GetUnixTimestampInMilliseconds(DateTime.UtcNow); + } + + /// + /// 获取当前时间的微秒级时间戳 + /// + /// + public static long GetCurrentUnixTimestampInMicroseconds() + { + return GetUnixTimestampInMicroseconds(DateTime.UtcNow); + } +} diff --git a/MAX.Core/Helpers/EnumHelper.cs b/MAX.Core/Helpers/EnumHelper.cs new file mode 100644 index 0000000..439042d --- /dev/null +++ b/MAX.Core/Helpers/EnumHelper.cs @@ -0,0 +1,341 @@ +using MAX.Core.Extensions; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; + +namespace MAX.Core.Helpers; + +/// +/// 枚举操作 +/// +public static partial class EnumHelper +{ + /// + /// 获取实例 + /// + /// 枚举类型 + /// 成员名或值,范例:Enum1枚举有成员A=0,则传入"A"或"0"获取 Enum1.A + public static TEnum Parse(object member) + { + string value = member.SafeString(); + if (string.IsNullOrWhiteSpace(value)) + { + if (typeof(TEnum).IsGenericType) + { + return default; + } + + throw new ArgumentNullException(nameof(member)); + } + return (TEnum)Enum.Parse(Common.GetType(), value, true); + } + public static object Parse(Type enumObj, object member) + { + string value = member.SafeString(); + if (string.IsNullOrWhiteSpace(value)) + { + if (enumObj.IsGenericType) + { + return null; + } + + throw new ArgumentNullException(nameof(member)); + } + return Enum.Parse(enumObj, value, true); + } + public static TEnum ParseByDescription(string description) + { + return (TEnum)ParseByDescription(Common.GetType(), description); + } + public static object ParseByDescription(Type enumObj, string description) + { + List listitem = GetItems(enumObj); + foreach (EnumItem item in listitem) + { + if (item.Description == description) + { + return Enum.Parse(enumObj, item.Numeric.ToString(), true); + } + } + return null; + } + /// + /// 获取成员名 + /// + /// 枚举类型 + /// 成员名、值、实例均可,范例:Enum1枚举有成员A=0,则传入Enum1.A或0,获取成员名"A" + public static string GetName(object member) + { + return GetName(Common.GetType(), member); + } + + /// + /// 获取成员名 + /// + /// 枚举类型 + /// 成员名、值、实例均可 + public static string GetName(Type type, object member) + { + if (type == null) + { + return string.Empty; + } + + if (member == null) + { + return string.Empty; + } + + if (member is string) + { + return member.ToString(); + } + + if (type.GetTypeInfo().IsEnum == false) + { + return string.Empty; + } + + return Enum.GetName(type, member); + } + + /// + /// 获取成员值 + /// + /// 枚举类型 + /// 成员名、值、实例均可,范例:Enum1枚举有成员A=0,可传入"A"、0、Enum1.A,获取值0 + public static int GetValue(object member) + { + return GetValue(Common.GetType(), member); + } + + /// + /// 获取成员值 + /// + /// 枚举类型 + /// 成员名、值、实例均可 + public static int GetValue(Type type, object member) + { + string value = member.SafeString(); + if (string.IsNullOrWhiteSpace(value)) + { + throw new ArgumentNullException(nameof(member)); + } + + return (int)Enum.Parse(type, member.ToString(), true); + } + /// + /// 获取描述,使用System.ComponentModel.Description特性设置描述 + /// + /// 枚举类型 + /// 成员名、值、实例均可 + public static string GetDescription(object member) + { + return ReflectionHelper.GetDescription(GetName(member)); + } + + /// + /// 获取描述,使用System.ComponentModel.Description特性设置描述 + /// + /// 枚举类型 + /// 成员名、值、实例均可 + public static string GetDescription(Type type, object member) + { + return ReflectionHelper.GetDescription(type, GetName(type, member)); + } + + + /// + /// 获取项集合,文本设置为Description,值为Value + /// + /// 枚举类型 + public static List GetItems() + { + return GetItems(typeof(TEnum)); + } + + /// + /// 获取项集合,文本设置为Description,值为Value + /// + /// 枚举类型 + public static List GetItems(Type type) + { + type = Common.GetType(type); + if (type.IsEnum == false) + { + throw new InvalidOperationException($"类型 {type} 不是枚举"); + } + + List result = []; + foreach (FieldInfo field in type.GetFields()) + { + AddItem(type, result, field); + } + + return result.OrderBy(t => t.Numeric).ToList(); + } + + public static CommonItem GetItem(object obj) + { + CommonItem item = null; + if (obj is Enum em) + { + //item.Text = em.get + string text = em.ToString(); + int value = GetValue(obj.GetType(), obj); + string description = em.ToDescription(); + item = new CommonItem(text, value, des: description); + } + return item; + } + + /// + /// 添加描述项 + /// + private static void AddItem(Type type, ICollection result, FieldInfo field) + { + if (!field.FieldType.IsEnum) + { + return; + } + + string text = field.Name; + int value = GetValue(type, field.Name); + string description = ReflectionHelper.GetDescription(field); + result.Add(new EnumItem(text, value, description)); + } + + /// + /// 获取名称集合 + /// + /// 枚举类型 + public static List GetNames() + { + return GetNames(typeof(TEnum)); + } + + /// + /// 获取名称集合 + /// + /// 枚举类型 + public static List GetNames(Type type) + { + type = Common.GetType(type); + if (type.IsEnum == false) + { + throw new InvalidOperationException($"类型 {type} 不是枚举"); + } + + List result = []; + foreach (FieldInfo field in type.GetFields()) + { + if (!field.FieldType.IsEnum) + { + continue; + } + + result.Add(field.Name); + } + return result; + } + + + /// + /// 获取名称集合 + /// + /// 枚举类型 + public static List GetDescriptions() + { + return GetDescriptions(typeof(TEnum)); + } + + /// + /// 获取名称集合 + /// + /// 枚举类型 + public static List GetDescriptions(Type type) + { + type = Common.GetType(type); + if (type.IsEnum == false) + { + throw new InvalidOperationException($"类型 {type} 不是枚举"); + } + + List result = []; + foreach (FieldInfo field in type.GetFields()) + { + if (!field.FieldType.IsEnum) + { + continue; + } + + string description = ReflectionHelper.GetDescription(field); + result.Add(description); + } + return result; + } + /// + /// 是否单一标记 + /// + /// + /// + public static bool IsSingleFlag(Enum obj) + { + int intValue = GetValue(obj.GetType(), obj); + return (intValue > 0) && ((intValue & (intValue - 1)) == 0); + } + /// + /// 获取第一个标记 + /// + /// + /// + /// + public static T GetFirstFlag(T value) where T : Enum + { + long v = System.Convert.ToInt64(value); + long first = v & -v; + return Enum.GetValues(typeof(T)).Cast().First(flag => System.Convert.ToInt64(flag) == first); + } +} +/// +/// Enum展示类型 +/// +public enum EnumDisplayType +{ + Text, + Value, + Description, + CommonItem +} +public class EnumItem +{ + public string Name { get; init; } + public int Numeric { get; init; } + public string Description { get; init; } + + public EnumItem(string name, int numeric, string description) + { + Name = name; + Numeric = numeric; + Description = description; + } +} +public class EnumItem : EnumItem where TEnum : struct, Enum +{ + public TEnum Value { get; } + + public EnumItem(TEnum value) + : base(value.ToString(), System.Convert.ToInt32(value), GetDescription(value)) + { + Value = value; + } + private static string GetDescription(TEnum value) + { + var field = typeof(TEnum).GetField(value.ToString())!; + var attr = field.GetCustomAttribute(); + return attr?.Description ?? string.Empty; + } + +} \ No newline at end of file diff --git a/MAX.Core/Helpers/EnvironmentHelper.cs b/MAX.Core/Helpers/EnvironmentHelper.cs new file mode 100644 index 0000000..640d962 --- /dev/null +++ b/MAX.Core/Helpers/EnvironmentHelper.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MAX.Core.Helpers; +public class EnvironmentHelper +{ + private static string? _currentEnvironment; + + // 获取当前环境 + public static string CurrentEnvironment + { + get + { + if (_currentEnvironment == null) + { + _currentEnvironment = DetermineEnvironment(); + } + return _currentEnvironment; + } + } + public static bool IsTest => CurrentEnvironment.Equals("Test", StringComparison.OrdinalIgnoreCase); + public static bool IsDevelopment => CurrentEnvironment.Equals("Development", StringComparison.OrdinalIgnoreCase); + + public static bool IsProduction => CurrentEnvironment.Equals("Production", StringComparison.OrdinalIgnoreCase); + + private static string DetermineEnvironment() + { + // 1. 检查环境变量 + var envVar = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? + Environment.GetEnvironmentVariable("ENVIRONMENT"); + + if (!string.IsNullOrEmpty(envVar)) + { + return envVar; + } + + + // 检查当前机器名 + var machineName = Environment.MachineName; + if (machineName.ToUpper().Contains("DEV") || + machineName.ToUpper().Contains("TEST")) + { + return machineName.Contains("TEST") ? "Test" : "Development"; + } + + // 4. 默认返回 Production + return "Production"; + } + + // 获取环境描述 + public static string EnvironmentDescription + { + get + { + if (IsTest) return "测试环境"; + if (IsDevelopment) return "开发环境"; + if (IsProduction) return "生产环境"; + return $"未知环境 ({CurrentEnvironment})"; + } + } +} diff --git a/MAX.Core/Helpers/FileHelper.cs b/MAX.Core/Helpers/FileHelper.cs new file mode 100644 index 0000000..e03a4d6 --- /dev/null +++ b/MAX.Core/Helpers/FileHelper.cs @@ -0,0 +1,188 @@ +using System.IO; +using System.Text; +using System.Threading.Tasks; + +namespace MAX.Core.Helpers; + +/// +/// 文件和流操作 +/// +public static partial class FileHelper +{ + /// + /// 流转换为字节流 + /// + /// 流 + public static async Task ToBytesAsync(Stream stream) + { + stream.Seek(0, SeekOrigin.Begin); + byte[] buffer = new byte[stream.Length]; + await stream.ReadAsync(buffer, 0, buffer.Length); + return buffer; + } + + /// + /// 流转换为字节流 + /// + /// 流 + public static byte[] ToBytes(Stream stream) + { + stream.Seek(0, SeekOrigin.Begin); + byte[] buffer = new byte[stream.Length]; + stream.Read(buffer, 0, buffer.Length); + return buffer; + } + + /// + /// 字符串转换成字节数组 + /// + /// 数据,默认字符编码utf-8 + public static byte[] ToBytes(string data) + { + return ToBytes(data, Encoding.UTF8); + } + + /// + /// 字符串转换成字节数组 + /// + /// 数据 + /// 字符编码 + public static byte[] ToBytes(string data, Encoding encoding) + { + if (string.IsNullOrWhiteSpace(data)) + { + return new byte[] { }; + } + + return encoding.GetBytes(data); + } + + /// + /// 将文件读取到字节流中 + /// + /// 文件的绝对路径 + public static byte[] Read(string filePath) + { + if (!File.Exists(filePath)) + { + return null; + } + + FileInfo fileInfo = new(filePath); + using BinaryReader reader = new(fileInfo.Open(FileMode.Open)); + return reader.ReadBytes((int)fileInfo.Length); + } + + /// + /// 流转换成字符串 + /// + /// 流 + /// 字符编码 + /// 缓冲区大小 + /// 读取完成是否释放流,默认为true + public static string ToString(Stream stream, Encoding encoding = null, int bufferSize = 1024 * 2, bool isCloseStream = true) + { + if (stream == null) + { + return string.Empty; + } + + encoding ??= Encoding.UTF8; + + if (stream.CanRead == false) + { + return string.Empty; + } + + using StreamReader reader = new(stream, encoding, true, bufferSize, !isCloseStream); + if (stream.CanSeek) + { + stream.Seek(0, SeekOrigin.Begin); + } + + string result = reader.ReadToEnd(); + if (stream.CanSeek) + { + stream.Seek(0, SeekOrigin.Begin); + } + + return result; + } + + /// + /// 流转换成字符串 + /// + /// 流 + /// 字符编码 + /// 缓冲区大小 + /// 读取完成是否释放流,默认为true + public static async Task ToStringAsync(Stream stream, Encoding encoding = null, int bufferSize = 1024 * 2, bool isCloseStream = true) + { + if (stream == null) + { + return string.Empty; + } + + encoding ??= Encoding.UTF8; + + if (stream.CanRead == false) + { + return string.Empty; + } + + using StreamReader reader = new(stream, encoding, true, bufferSize, !isCloseStream); + if (stream.CanSeek) + { + stream.Seek(0, SeekOrigin.Begin); + } + + string result = await reader.ReadToEndAsync(); + if (stream.CanSeek) + { + stream.Seek(0, SeekOrigin.Begin); + } + + return result; + } + + /// + /// 复制流并转换成字符串 + /// + /// 流 + /// 字符编码 + public static async Task CopyToStringAsync(Stream stream, Encoding encoding = null) + { + if (stream == null) + { + return string.Empty; + } + + encoding ??= Encoding.UTF8; + + if (stream.CanRead == false) + { + return string.Empty; + } + + using MemoryStream memoryStream = new(); + using StreamReader reader = new(memoryStream, encoding); + if (stream.CanSeek) + { + stream.Seek(0, SeekOrigin.Begin); + } + + stream.CopyTo(memoryStream); + if (memoryStream.CanSeek) + { + memoryStream.Seek(0, SeekOrigin.Begin); + } + + string result = await reader.ReadToEndAsync(); + if (stream.CanSeek) + { + stream.Seek(0, SeekOrigin.Begin); + } + + return result; + } +} diff --git a/MAX.Core/Helpers/HttpHelper.cs b/MAX.Core/Helpers/HttpHelper.cs new file mode 100644 index 0000000..14e1e42 --- /dev/null +++ b/MAX.Core/Helpers/HttpHelper.cs @@ -0,0 +1,1047 @@ +using System; +using System.IO; +using System.IO.Compression; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Text.RegularExpressions; + +namespace MAX.Core.Helpers; + + +/// +/// Http连接操作帮助类 +/// +public class HttpHelper +{ + public static HttpResult Get(string Url) + { + return new HttpHelper().GetHtml(new HttpItem + { + URL = Url, + Method = MethodType.Get, + }); + } + + public static HttpResult Post(string Url, string PostData) + { + return new HttpHelper().GetHtml(new HttpItem + { + URL = Url, + Method = MethodType.Get, + Postdata = PostData, + PostDataType = PostDataType.String + }); + } + public static HttpResult Request(HttpItem item) + { + return new HttpHelper().GetHtml(item); + } + /// + /// 下载文件 + /// + /// + /// + public static byte[] DownloadFile(string address) + { + HttpItem item = new() + { + URL = address, + Timeout = 3000, + ResultType = ResultType.Byte + }; + HttpHelper http = new(); + HttpResult result = http.GetHtml(item); + if (result.StatusCode == HttpStatusCode.OK) + { + return result.ResultByte; + } + else + { + throw new HttpListenerException((int)result.StatusCode, result.StatusDescription); + } + } + + /// + /// 下载文件 + /// + /// + /// + public static bool DownloadFile(string address, string fileName) + { + HttpItem item = new() + { + URL = address, + Timeout = 3000, + ResultType = ResultType.Byte + }; + HttpHelper http = new(); + HttpResult result = http.GetHtml(item); + if (result.StatusCode == HttpStatusCode.OK) + { + FileStream fs = new(fileName, FileMode.CreateNew); + BinaryWriter bw = new(fs); + bw.Write(result.ResultByte); + fs.Close(); + bw.Close(); + } + else + { + throw new HttpListenerException((int)result.StatusCode, result.StatusDescription); + } + + return true; + } + + #region 预定义方法或者变更 + //默认的编码 + private Encoding encoding = Encoding.Default; + //Post数据编码 + private Encoding postencoding = Encoding.Default; + //HttpWebRequest对象用来发起请求 + private HttpWebRequest request = null; + //获取影响流的数据对象 + private HttpWebResponse response = null; + /// + /// 根据相传入的数据,得到相应页面数据 + /// + /// 参数类对象 + /// 返回HttpResult类型 + public HttpResult GetHtml(HttpItem item) + { + //返回参数 + HttpResult result = new() + { + item = item + }; + try + { + //准备参数 + SetRequest(item); + } + catch (Exception ex) + { + result = new HttpResult + { + Cookie = string.Empty, + Header = null, + Html = ex.Message, + StatusDescription = "配置参数时出错:" + ex.Message + }; + return result; + } + try + { + //请求数据 + using (response = (HttpWebResponse)request.GetResponse()) + { + GetData(item, result); + } + //#region 得到请求的response + //using (response = (HttpWebResponse)request.GetResponse()) + //{ + // result.StatusCode = response.StatusCode; + // result.StatusDescription = response.StatusDescription; + // result.Header = response.Headers; + // if (response.Cookies != null) result.CookieCollection = response.Cookies; + // if (response.Headers["set-cookie"] != null) result.Cookie = response.Headers["set-cookie"]; + // MemoryStream _stream = new MemoryStream(); + // //GZIIP处理 + // if (response.ContentEncoding != null && response.ContentEncoding.Equals("gzip", StringComparison.InvariantCultureIgnoreCase)) + // { + // //开始读取流并设置编码方式 + // //new GZipStream(response.GetResponseStream(), CompressionMode.Decompress).CopyTo(_stream, 10240); + // //.net4.0以下写法 + // _stream = GetMemoryStream(new GZipStream(response.GetResponseStream(), CompressionMode.Decompress)); + + // } + // else + // { + // //开始读取流并设置编码方式 + // //response.GetResponseStream().CopyTo(_stream, 10240); + // //.net4.0以下写法 + // _stream = GetMemoryStream(response.GetResponseStream()); + // } + // //获取Byte + // byte[] ResponseByte = _stream.ToArray(); + // _stream.Close(); + // if (ResponseByte != null & ResponseByte.Length > 0) + // { + // //是否返回Byte类型数据 + // if (item.ResultType == ResultType.Byte) result.ResultByte = ResponseByte; + // //从这里开始我们要无视编码了 + // if (encoding == null) + // { + // Match meta = Regex.Match(Encoding.Default.GetString(ResponseByte), " 1) ? meta.Groups[2].Value.ToLower().Trim() : string.Empty; + // if (c.Length > 2) + // { + // try + // { + // if (c.IndexOf(" ") > 0) c = c.Substring(0, c.IndexOf(" ")); + // encoding = Encoding.GetEncoding(c.Replace("\"", "").Replace("'", "").Replace(";", "").Replace("iso-8859-1", "gbk").Trim()); + // } + // catch + // { + // if (string.IsNullOrEmpty(response.CharacterSet)) encoding = Encoding.UTF8; + // else encoding = Encoding.GetEncoding(response.CharacterSet); + // } + // } + // else + // { + // if (string.IsNullOrEmpty(response.CharacterSet)) encoding = Encoding.UTF8; + // else encoding = Encoding.GetEncoding(response.CharacterSet); + // } + // } + // //得到返回的HTML + // result.Html = encoding.GetString(ResponseByte); + // } + // else + // { + // //得到返回的HTML + // result.Html = "本次请求并未返回任何数据"; + // } + //} + //#endregion + } + catch (WebException ex) + { + if (ex.Response != null) + { + using (response = (HttpWebResponse)ex.Response) + { + GetData(item, result); + } + } + else + { + result.Html = ex.Message; + } + + } + catch (Exception ex) + { + result.Html = ex.Message; + } + if (item.IsToLower) + { + result.Html = result.Html.ToLower(); + } + + return result; + } + + #region GetData + /// + /// 获取数据的并解析的方法 + /// + /// + /// + private void GetData(HttpItem item, HttpResult result) + { + if (response == null) + { + return; + } + #region base + //获取StatusCode + result.StatusCode = response.StatusCode; + //获取最后访问的URl + //获取StatusDescription + result.StatusDescription = response.StatusDescription; + //获取Headers + result.Header = response.Headers; + //获取CookieCollection + if (response.Cookies != null) + { + result.CookieCollection = response.Cookies; + } + //获取set-cookie + if (response.Headers["set-cookie"] != null) + { + result.Cookie = response.Headers["set-cookie"]; + } + + #endregion + #region 用户设置用编码 + //处理网页Byte + byte[] ResponseByte = GetByte(item); + + if (ResponseByte != null && ResponseByte.Length > 0) + { + //设置编码 + SetEncoding(item, result, ResponseByte); + + //设置返回的Byte + SetResultByte(item, result, ResponseByte); + } + else + { + //没有返回任何Html代码 + result.Html = string.Empty; + } + + #endregion + } + /// + /// 设置返回的Byte + /// + /// HttpItem + /// result + /// byte + private void SetResultByte(HttpItem item, HttpResult result, byte[] enByte) + { + //是否返回Byte类型数据 + if (item.ResultType == ResultType.Byte) + { + //Byte数据 + result.ResultByte = enByte; + } + else if (item.ResultType == ResultType.String) + { + //得到返回的HTML + result.Html = encoding.GetString(enByte); + } + + } + /// + /// 设置编码 + /// + /// HttpItem + /// HttpResult + /// byte[] + private void SetEncoding(HttpItem item, HttpResult result, byte[] ResponseByte) + { + //从这里开始我们要无视编码了 + if (encoding == null) + { + Match meta = Regex.Match(Encoding.Default.GetString(ResponseByte), " 0) + { + c = meta.Groups[1].Value.ToLower().Trim(); + } + string cs = string.Empty; + if (!string.IsNullOrWhiteSpace(response.CharacterSet)) + { + cs = response.CharacterSet.Trim().Replace("\"", "").Replace("\'", ""); + } + + if (c.Length > 2) + { + try + { + encoding = Encoding.GetEncoding(c.Replace("\"", string.Empty).Replace("'", "").Replace(";", "").Replace("iso-8859-1", "gbk").Trim()); + } + catch + { + if (string.IsNullOrEmpty(cs)) + { + encoding = Encoding.UTF8; + } + else + { + encoding = Encoding.GetEncoding(cs); + } + } + } + else + { + if (string.IsNullOrEmpty(cs)) + { + encoding = Encoding.UTF8; + } + else + { + encoding = Encoding.GetEncoding(cs); + } + } + } + } + /// + /// 提取网页Byte + /// + /// + private byte[] GetByte(HttpItem item) + { + byte[] ResponseByte = null; + using (MemoryStream _stream = new()) + { + + //GZIIP处理 + if (response.ContentEncoding != null && response.ContentEncoding.Equals("gzip", StringComparison.InvariantCultureIgnoreCase)) + { + //开始读取流并设置编码方式 + new GZipStream(response.GetResponseStream(), CompressionMode.Decompress).CopyTo(_stream, 10240); + } + else + { + //开始读取流并设置编码方式 + response.GetResponseStream().CopyTo(_stream, 10240); + } + + //获取Byte + ResponseByte = _stream.ToArray(); + } + return ResponseByte; + } + #endregion + + + /// + /// 4.0以下.net版本取数据使用 + /// + /// 流 + private MemoryStream GetMemoryStream(Stream streamResponse) + { + MemoryStream _stream = new(); + int Length = 256; + Byte[] buffer = new Byte[Length]; + int bytesRead = streamResponse.Read(buffer, 0, Length); + while (bytesRead > 0) + { + _stream.Write(buffer, 0, bytesRead); + bytesRead = streamResponse.Read(buffer, 0, Length); + } + return _stream; + } + /// + /// 为请求准备参数 + /// + ///参数列表 + private void SetRequest(HttpItem item) + + { + // 验证证书 + SetCer(item); + //设置Header参数 + if (item.Header != null && item.Header.Count > 0) + { + foreach (string key in item.Header.AllKeys) + { + request.Headers.Add(key, item.Header[key]); + } + } + // 设置代理 + SetProxy(item); + if (item.ProtocolVersion != null) + { + request.ProtocolVersion = item.ProtocolVersion; + } + + request.ServicePoint.Expect100Continue = item.Expect100Continue; + //请求方式Get或者Post + request.Method = item.Method.ToString(); + request.Timeout = item.Timeout; + request.KeepAlive = item.KeepAlive; + request.ReadWriteTimeout = item.ReadWriteTimeout; + //Accept + request.Accept = item.Accept; + //ContentType返回类型 + request.ContentType = item.ContentType; + //UserAgent客户端的访问类型,包括浏览器版本和操作系统信息 + request.UserAgent = item.UserAgent; + // 编码 + encoding = item.Encoding; + //设置安全凭证 + request.Credentials = item.ICredentials; + //设置Cookie + SetCookie(item); + //来源地址 + request.Referer = item.Referer; + //是否执行跳转功能 + request.AllowAutoRedirect = item.Allowautoredirect; + //设置Post数据 + SetPostData(item); + //设置最大连接 + if (item.Connectionlimit > 0) + { + request.ServicePoint.ConnectionLimit = item.Connectionlimit; + } + } + /// + /// 设置证书 + /// + /// +#pragma warning disable SYSLIB0014 // Type or member is obsolete + private void SetCer(HttpItem item) + { + if (!string.IsNullOrEmpty(item.CerPath)) + { + //这一句一定要写在创建连接的前面。使用回调的方法进行证书验证。 + ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckValidationResult); + //初始化对像,并设置请求的URL地址 + request = (HttpWebRequest)WebRequest.Create(item.URL); + SetCerList(item); + //将证书添加到请求里 + request.ClientCertificates.Add(new X509Certificate(item.CerPath)); + } + else + { + //初始化对像,并设置请求的URL地址 + request = (HttpWebRequest)WebRequest.Create(item.URL); + SetCerList(item); + } + } +#pragma warning restore SYSLIB0014 // Type or member is obsolete + /// + /// 设置多个证书 + /// + /// + private void SetCerList(HttpItem item) + { + if (item.ClentCertificates != null && item.ClentCertificates.Count > 0) + { + foreach (X509Certificate c in item.ClentCertificates) + { + request.ClientCertificates.Add(c); + } + } + } + /// + /// 设置Cookie + /// + /// Http参数 + private void SetCookie(HttpItem item) + { + if (!string.IsNullOrEmpty(item.Cookie)) + { + request.Headers[HttpRequestHeader.Cookie] = item.Cookie; + } + //设置CookieCollection + if (item.ResultCookieType == ResultCookieType.CookieCollection) + { + request.CookieContainer = new CookieContainer(); + if (item.CookieCollection != null && item.CookieCollection.Count > 0) + { + request.CookieContainer.Add(item.CookieCollection); + } + } + } + /// + /// 设置Post数据 + /// + /// Http参数 + private void SetPostData(HttpItem item) + { + //验证在得到结果时是否有传入数据 + if (request.Method.Trim().ToLower().Contains("post")) + { + if (item.PostEncoding != null) + { + postencoding = item.PostEncoding; + } + byte[] buffer = null; + //写入Byte类型 + if (item.PostDataType == PostDataType.Byte && item.PostdataByte != null && item.PostdataByte.Length > 0) + { + //验证在得到结果时是否有传入数据 + buffer = item.PostdataByte; + }//写入文件 + else if (item.PostDataType == PostDataType.FilePath && !string.IsNullOrEmpty(item.Postdata)) + { + StreamReader r = new(item.Postdata, postencoding); + buffer = postencoding.GetBytes(r.ReadToEnd()); + r.Close(); + } //写入字符串 + else if (!string.IsNullOrEmpty(item.Postdata)) + { + buffer = postencoding.GetBytes(item.Postdata); + } + if (buffer != null) + { + request.ContentLength = buffer.Length; + request.GetRequestStream().Write(buffer, 0, buffer.Length); + } + } + } + /// + /// 设置代理 + /// + /// 参数对象 + private void SetProxy(HttpItem item) + { + bool isIeProxy = item.ProxyIp.ToLower().Contains("ieproxy"); + if (!string.IsNullOrEmpty(item.ProxyIp) && !isIeProxy) + { + //设置代理服务器 + if (item.ProxyIp.Contains(":")) + { + string[] plist = item.ProxyIp.Split(':'); + WebProxy myProxy = new(plist[0].Trim(), Convert.ToInt(plist[1].Trim())) + { + //建议连接 + Credentials = new NetworkCredential(item.ProxyUserName, item.ProxyPwd) + }; + //给当前请求对象 + request.Proxy = myProxy; + } + else + { + WebProxy myProxy = new(item.ProxyIp, false) + { + //建议连接 + Credentials = new NetworkCredential(item.ProxyUserName, item.ProxyPwd) + }; + //给当前请求对象 + request.Proxy = myProxy; + } + } + else if (isIeProxy) + { + //设置为IE代理 + } + else + { + request.Proxy = item.WebProxy; + } + } + /// + /// 回调验证证书问题 + /// + /// 流对象 + /// 证书 + /// X509Chain + /// SslPolicyErrors + /// bool + public bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) { return true; } + #endregion +} +/// +/// Http请求参考类 +/// +public class HttpItem +{ + private string _URL = string.Empty; + /// + /// 请求URL必须填写 + /// + public string URL + { + get { return _URL; } + set { _URL = value; } + } + + private MethodType _Method = MethodType.Get; + /// + /// 请求方式默认为GET方式,当为POST方式时必须设置Postdata的值 + /// + public MethodType Method + { + get { return _Method; } + set + { + _Method = value; + if (_Method == MethodType.Post) + { + _ContentType = "application/x-www-form-urlencoded"; + } + } + } + + private int _Timeout = 100000; + /// + /// 默认请求超时时间 + /// + public int Timeout + { + get { return _Timeout; } + set { _Timeout = value; } + } + + private int _ReadWriteTimeout = 30000; + /// + /// 默认写入Post数据超时间 + /// + public int ReadWriteTimeout + { + get { return _ReadWriteTimeout; } + set { _ReadWriteTimeout = value; } + } + + private Boolean _KeepAlive = true; + /// + /// 获取或设置一个值,该值指示是否与 Internet 资源建立持久性连接默认为true。 + /// + public Boolean KeepAlive + { + get { return _KeepAlive; } + set { _KeepAlive = value; } + } + + private string _Accept = "text/html, application/xhtml+xml, */*"; + /// + /// 请求标头值 默认为text/html, application/xhtml+xml, */* + /// + public string Accept + { + get { return _Accept; } + set { _Accept = value; } + } + + private string _ContentType = "text/html"; + /// + /// 请求返回类型默认 text/html + /// + public string ContentType + { + get { return _ContentType; } + set { _ContentType = value; } + } + + private string _UserAgent = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"; + /// + /// 客户端访问信息默认Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0) + /// + public string UserAgent + { + get { return _UserAgent; } + set { _UserAgent = value; } + } + + private Encoding _Encoding = null; + /// + /// 返回数据编码默认为NUll,可以自动识别,一般为utf-8,gbk,gb2312 + /// + public Encoding Encoding + { + get { return _Encoding; } + set { _Encoding = value; } + } + private PostDataType _PostDataType = PostDataType.String; + /// + /// Post的数据类型 + /// + public PostDataType PostDataType + { + get { return _PostDataType; } + set { _PostDataType = value; } + } + + private string _Postdata = string.Empty; + /// + /// Post请求时要发送的字符串Post数据 + /// + public string Postdata + { + get { return _Postdata; } + set { _Postdata = value; } + } + private byte[] _PostdataByte = null; + /// + /// Post请求时要发送的Byte类型的Post数据 + /// + public byte[] PostdataByte + { + get { return _PostdataByte; } + set { _PostdataByte = value; } + } + private WebProxy _WebProxy; + /// + /// 设置代理对象,不想使用IE默认配置就设置为Null,而且不要设置ProxyIp + /// + public WebProxy WebProxy + { + get { return _WebProxy; } + set { _WebProxy = value; } + } + + private CookieCollection cookiecollection = null; + /// + /// Cookie对象集合 + /// + public CookieCollection CookieCollection + { + get { return cookiecollection; } + set { cookiecollection = value; } + } + + private string _Cookie = string.Empty; + /// + /// 请求时的Cookie + /// + public string Cookie + { + get { return _Cookie; } + set { _Cookie = value; } + } + + private string _Referer = string.Empty; + /// + /// 来源地址,上次访问地址 + /// + public string Referer + { + get { return _Referer; } + set { _Referer = value; } + } + + private string _CerPath = string.Empty; + /// + /// 证书绝对路径 + /// + public string CerPath + { + get { return _CerPath; } + set { _CerPath = value; } + } + private Boolean isToLower = false; + /// + /// 是否设置为全文小写,默认为不转化 + /// + public Boolean IsToLower + { + get { return isToLower; } + set { isToLower = value; } + } + private Boolean allowautoredirect = true; + /// + /// 支持跳转页面,查询结果将是跳转后的页面 + /// + public Boolean Allowautoredirect + { + get { return allowautoredirect; } + set { allowautoredirect = value; } + } + private int connectionlimit = 1024; + /// + /// 最大连接数 + /// + public int Connectionlimit + { + get { return connectionlimit; } + set { connectionlimit = value; } + } + private string proxyusername = string.Empty; + /// + /// 代理Proxy 服务器用户名 + /// + public string ProxyUserName + { + get { return proxyusername; } + set { proxyusername = value; } + } + private string proxypwd = string.Empty; + /// + /// 代理 服务器密码 + /// + public string ProxyPwd + { + get { return proxypwd; } + set { proxypwd = value; } + } + private string proxyip = string.Empty; + /// + /// 代理 服务IP ,如果要使用IE代理就设置为ieproxy + /// + public string ProxyIp + { + get { return proxyip; } + set { proxyip = value; } + } + private ResultType resulttype = ResultType.String; + /// + /// 设置返回类型String和Byte + /// + public ResultType ResultType + { + get { return resulttype; } + set { resulttype = value; } + } + private WebHeaderCollection header = []; + /// + /// header对象 + /// + public WebHeaderCollection Header + { + get { return header; } + set { header = value; } + } + + private Version _ProtocolVersion; + + /// + /// 获取或设置用于请求的 HTTP 版本。返回结果:用于请求的 HTTP 版本。默认为 System.Net.HttpVersion.Version11。 + /// + public Version ProtocolVersion + { + get { return _ProtocolVersion; } + set { _ProtocolVersion = value; } + } + private Boolean _expect100continue = true; + /// + /// 获取或设置一个 System.Boolean 值,该值确定是否使用 100-Continue 行为。如果 POST 请求需要 100-Continue 响应,则为 true;否则为 false。默认值为 true。 + /// + public Boolean Expect100Continue + { + get { return _expect100continue; } + set { _expect100continue = value; } + } + private X509CertificateCollection _ClentCertificates; + /// + /// 设置509证书集合 + /// + public X509CertificateCollection ClentCertificates + { + get { return _ClentCertificates; } + set { _ClentCertificates = value; } + } + private Encoding _PostEncoding = Encoding.UTF8; + /// + /// 设置或获取Post参数编码,默认的为Default编码 + /// + public Encoding PostEncoding + { + get { return _PostEncoding; } + set { _PostEncoding = value; } + } + private ResultCookieType _ResultCookieType = ResultCookieType.String; + /// + /// Cookie返回类型,默认的是只返回字符串类型 + /// + public ResultCookieType ResultCookieType + { + get { return _ResultCookieType; } + set { _ResultCookieType = value; } + } + + private ICredentials _ICredentials = CredentialCache.DefaultCredentials; + /// + /// 获取或设置请求的身份验证信息。 + /// + public ICredentials ICredentials + { + get { return _ICredentials; } + set { _ICredentials = value; } + } + +} +/// +/// Http返回参数类 +/// +public class HttpResult +{ + private string _Cookie; + /// + /// Http请求返回的Cookie + /// + public string Cookie + { + get { return _Cookie; } + set { _Cookie = value; } + } + + private CookieCollection _CookieCollection; + /// + /// Cookie对象集合 + /// + public CookieCollection CookieCollection + { + get { return _CookieCollection; } + set { _CookieCollection = value; } + } + private string _Html; + /// + /// 返回的String类型数据 只有ResultType.String时才返回数据,其它情况为空 + /// + public string Html + { + get { return _Html; } + set { _Html = value; } + } + private byte[] _ResultByte; + /// + /// 返回的Byte数组 只有ResultType.Byte时才返回数据,其它情况为空 + /// + public byte[] ResultByte + { + get { return _ResultByte; } + set { _ResultByte = value; } + } + + private WebHeaderCollection _Header; + /// + /// header对象 + /// + public WebHeaderCollection Header + { + get { return _Header; } + set { _Header = value; } + } + + private string _StatusDescription; + /// + /// 返回状态说明 + /// + public string StatusDescription + { + get { return _StatusDescription; } + set { _StatusDescription = value; } + } + private HttpStatusCode _StatusCode; + /// + /// 返回状态码,默认为OK + /// + public HttpStatusCode StatusCode + { + get { return _StatusCode; } + set { _StatusCode = value; } + } + /// + /// HttpItem类,用于存储实际执行所用到的HttpItem对象 + /// + public HttpItem item { get; set; } +} +/// +/// 返回类型 +/// +public enum ResultType +{ + /// + /// 表示只返回字符串 只有Html有数据 + /// + String, + /// + /// 表示返回字符串和字节流 ResultByte和Html都有数据返回 + /// + Byte +} +/// +/// Post的数据格式默认为string +/// +public enum PostDataType +{ + /// + /// 字符串类型,这时编码Encoding可不设置 + /// + String, + /// + /// Byte类型,需要设置PostdataByte参数的值编码Encoding可设置为空 + /// + Byte, + /// + /// 传文件,Postdata必须设置为文件的绝对路径,必须设置Encoding的值 + /// + FilePath +} +/// +/// Cookie返回类型 +/// +public enum ResultCookieType +{ + /// + /// 只返回字符串类型的Cookie + /// + String, + /// + /// CookieCollection格式的Cookie集合同时也返回String类型的cookie + /// + CookieCollection +} +public enum MethodType +{ + Get, + Post +} + diff --git a/MAX.Core/Helpers/IdHelper.cs b/MAX.Core/Helpers/IdHelper.cs new file mode 100644 index 0000000..224a70c --- /dev/null +++ b/MAX.Core/Helpers/IdHelper.cs @@ -0,0 +1,45 @@ +namespace MAX.Core.Helpers; + +/// +/// 标识生成器 +/// +public static partial class IdHelper +{ + /// + /// 标识 + /// + private static string _id; + + /// + /// 设置标识 + /// + /// Id + public static void SetId(string id) + { + _id = id; + } + + /// + /// 重置标识 + /// + public static void Reset() + { + _id = null; + } + + /// + /// 用Guid创建标识,去掉分隔符 + /// + public static string Guid() + { + return string.IsNullOrWhiteSpace(_id) ? System.Guid.NewGuid().ToString("N") : _id; + } + + ///// + ///// 获取Guid + ///// + //public static Guid GetGuid() + //{ + // return string.IsNullOrWhiteSpace(_id) ? System.Guid.NewGuid() : _id.ToGuid(); + //} +} diff --git a/MAX.Core/Helpers/ParametersBase.cs b/MAX.Core/Helpers/ParametersBase.cs new file mode 100644 index 0000000..9cf8d97 --- /dev/null +++ b/MAX.Core/Helpers/ParametersBase.cs @@ -0,0 +1,369 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; + + +namespace MAX.Core.Helpers; + +/// +/// This is a generic parameters base class used for Dialog Parameters and Navigation Parameters. +/// +public abstract class ParametersBase : IEnumerable> +{ + private readonly List> _entries = []; + + /// + /// Default constructor. + /// + protected ParametersBase() + { + } + + /// + /// Constructs a list of parameters. + /// + /// Query string to be parsed. + protected ParametersBase(string query) + { + if (!string.IsNullOrWhiteSpace(query)) + { + int num = query.Length; + for (int i = ((query.Length > 0) && (query[0] == '?')) ? 1 : 0; i < num; i++) + { + int startIndex = i; + int num4 = -1; + while (i < num) + { + char ch = query[i]; + if (ch == '=') + { + if (num4 < 0) + { + num4 = i; + } + } + else if (ch == '&') + { + break; + } + i++; + } + string key = null; + string value; + if (num4 >= 0) + { + key = query.Substring(startIndex, num4 - startIndex); + value = query.Substring(num4 + 1, i - num4 - 1); + } + else + { + value = query.Substring(startIndex, i - startIndex); + } + + if (key != null) + { + Add(Uri.UnescapeDataString(key), Uri.UnescapeDataString(value)); + } + } + } + } + + /// + /// Searches Parameter collection and returns value if Collection contains key. + /// Otherwise returns null. + /// + /// The key for the value to be returned. + /// The value of the parameter referenced by the key; otherwise null. + public object this[string key] + { + get + { + foreach (KeyValuePair entry in _entries) + { + if (string.Compare(entry.Key, key, StringComparison.Ordinal) == 0) + { + return entry.Value; + } + } + + return null; + } + } + + /// + /// The count, or number, of parameters in collection. + /// + public int Count => _entries.Count; + + /// + /// Returns an IEnumerable of the Keys in the collection. + /// + public IEnumerable Keys => + _entries.Select(x => x.Key); + + /// + /// Adds the key and value to the parameters collection. + /// + /// The key to reference this value in the parameters collection. + /// The value of the parameter to store. + public void Add(string key, object value) => + _entries.Add(new KeyValuePair(key, value)); + + + public void Remove(string key) + { + _entries.RemoveAll(x => x.Key == key); + } + + public void Update(string key, object value) + { + _entries.RemoveAll(x => x.Key == key); + Add(key, value); + } + + /// + /// Checks collection for presence of key. + /// + /// The key to check in the collection. + /// true if key exists; else returns false. + public bool ContainsKey(string key) + { + return _entries.Any(x => string.Compare(x.Key, key, StringComparison.Ordinal) == 0); + } + + + /// + /// Gets an enumerator for the KeyValuePairs in parameter collection. + /// + /// Enumerator. + public IEnumerator> GetEnumerator() => + _entries.GetEnumerator(); + + /// + /// Returns the value of the member referenced by key. + /// + /// The type of object to be returned. + /// The key for the value to be returned. + /// Returns a matching parameter of if one exists in the Collection. + public T GetValue(string key) => + _entries.GetValue(key); + + /// + /// Returns an IEnumerable of all parameters. + /// + /// The type for the values to be returned. + /// The key for the values to be returned. + ///Returns a IEnumerable of all the instances of type . + public IEnumerable GetValues(string key) => + _entries.GetValues(key); + + /// + /// Checks to see if the parameter collection contains the value. + /// + /// The type for the values to be returned. + /// The key for the value to be returned. + /// Value of the returned parameter if it exists. + public bool TryGetValue(string key, out T value) => + _entries.TryGetValue(key, out value); + + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); + + /// + /// Converts parameter collection to a parameter string. + /// + /// A string representation of the parameters. + public override string ToString() + { + StringBuilder queryBuilder = new(); + + if (_entries.Count > 0) + { + queryBuilder.Append('?'); + bool first = true; + + foreach (KeyValuePair kvp in _entries) + { + if (!first) + { + queryBuilder.Append('&'); + } + else + { + first = false; + } + + queryBuilder.Append(Uri.EscapeDataString(kvp.Key)); + queryBuilder.Append('='); + queryBuilder.Append(Uri.EscapeDataString(kvp.Value != null ? kvp.Value.ToString() : "")); + } + } + + return queryBuilder.ToString(); + } + + /// + /// Adds a collection of parameters to the local parameter list. + /// + /// An IEnumerable of KeyValuePairs to add to the current parameter list. + public void FromParameters(IEnumerable> parameters) => + _entries.AddRange(parameters); +} + + +public static class ParametersExtensions +{ + /// + /// Searches for + /// + /// The type of the parameter to return + /// A collection of parameters to search + /// The key of the parameter to find + /// A matching value of if it exists + [EditorBrowsable(EditorBrowsableState.Never)] + public static T GetValue(this IEnumerable> parameters, string key) => + (T)GetValue(parameters, key, typeof(T)); + + /// + /// Searches for value referenced by + /// + /// A collection of parameters to search + /// The key of the parameter to find + /// The type of the parameter to return + /// A matching value of if it exists + /// Unable to convert the value of Type + [EditorBrowsable(EditorBrowsableState.Never)] + public static object GetValue(this IEnumerable> parameters, string key, Type type) + { + foreach (KeyValuePair kvp in parameters) + { + if (string.Compare(kvp.Key, key, StringComparison.Ordinal) == 0) + { + if (TryGetValueInternal(kvp, type, out object value)) + { + return value; + } + + throw new InvalidCastException($"Unable to convert the value of Type '{kvp.Value.GetType().FullName}' to '{type.FullName}' for the key '{key}' "); + } + } + + return GetDefault(type); + } + + /// + /// Searches for value referenced by + /// + /// The type of the parameter to return + /// A collection of parameters to search + /// The key of the parameter to find + /// The value of parameter to return + /// Success if value is found; otherwise returns false + [EditorBrowsable(EditorBrowsableState.Never)] + public static bool TryGetValue(this IEnumerable> parameters, string key, out T value) + { + Type type = typeof(T); + + foreach (KeyValuePair kvp in parameters) + { + if (string.Compare(kvp.Key, key, StringComparison.Ordinal) == 0) + { + bool success = TryGetValueInternal(kvp, typeof(T), out object valueAsObject); + value = (T)valueAsObject; + return success; + } + } + + value = default; + return false; + } + + /// + /// Searches for value referenced by + /// + /// The type of the parameter to return + /// A collection of parameters to search + /// The key of the parameter to find + /// An IEnumerable{T} of all the values referenced by key + [EditorBrowsable(EditorBrowsableState.Never)] + public static IEnumerable GetValues(this IEnumerable> parameters, string key) + { + List values = []; + Type type = typeof(T); + + foreach (KeyValuePair kvp in parameters) + { + if (string.Compare(kvp.Key, key, StringComparison.Ordinal) == 0) + { + TryGetValueInternal(kvp, type, out object value); + values.Add((T)value); + } + } + + return values.AsEnumerable(); + } + + private static bool TryGetValueInternal(KeyValuePair kvp, Type type, out object value) + { + value = GetDefault(type); + bool success = false; + if (kvp.Value == null) + { + success = true; + } + else if (kvp.Value.GetType() == type) + { + success = true; + value = kvp.Value; + } + else if (type.IsAssignableFrom(kvp.Value.GetType())) + { + success = true; + value = kvp.Value; + } + else if (type.IsEnum) + { + string valueAsString = kvp.Value.ToString(); + if (Enum.IsDefined(type, valueAsString)) + { + success = true; + value = Enum.Parse(type, valueAsString); + } + else if (int.TryParse(valueAsString, out int numericValue)) + { + success = true; + value = Enum.ToObject(type, numericValue); + } + } + + if (!success && type.GetInterface("System.IConvertible") != null) + { + success = true; + value = System.Convert.ChangeType(kvp.Value, type); + } + + return success; + } + + /// + /// Checks to see if key exists in parameter collection + /// + /// IEnumerable to search + /// The key to search the for existence + /// true if key exists; false otherwise + [EditorBrowsable(EditorBrowsableState.Never)] + public static bool ContainsKey(this IEnumerable> parameters, string key) => + parameters.Any(x => string.Compare(x.Key, key, StringComparison.Ordinal) == 0); + + private static object GetDefault(Type type) + { + if (type.IsValueType) + { + return Activator.CreateInstance(type); + } + return null; + } +} diff --git a/MAX.Core/Helpers/ReflectionHelper.cs b/MAX.Core/Helpers/ReflectionHelper.cs new file mode 100644 index 0000000..8056697 --- /dev/null +++ b/MAX.Core/Helpers/ReflectionHelper.cs @@ -0,0 +1,614 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; + +namespace MAX.Core.Helpers; + +/// +/// 反射操作 +/// +public static class ReflectionHelper +{ + /// + /// 获取类型描述,使用DescriptionAttribute设置描述 + /// + /// 类型 + public static string GetDescription() + { + return GetDescription(Common.GetType()); + } + + /// + /// 获取类型成员描述,使用DescriptionAttribute设置描述 + /// + /// 类型 + /// 成员名称 + public static string GetDescription(string memberName) + { + return GetDescription(Common.GetType(), memberName); + } + + /// + /// 获取类型成员描述,使用DescriptionAttribute设置描述 + /// + /// 类型 + /// 成员名称 + public static string GetDescription(Type type, string memberName) + { + if (type == null) + { + return string.Empty; + } + + if (string.IsNullOrWhiteSpace(memberName)) + { + return string.Empty; + } + + return GetDescription(type.GetTypeInfo().GetMember(memberName).FirstOrDefault()); + } + + /// + /// 获取类型成员描述,使用DescriptionAttribute设置描述 + /// + /// 成员 + public static string GetDescription(MemberInfo member) + { + if (member == null) + { + return string.Empty; + } + + return member.GetCustomAttribute() is DescriptionAttribute attribute ? attribute.Description : member.Name; + } + + /// + /// 获取显示名称,使用DisplayNameAttribute设置显示名称 + /// + /// 类型 + public static string GetDisplayName() + { + return GetDisplayName(Common.GetType()); + } + + /// + /// 获取显示名称,使用DisplayAttribute或DisplayNameAttribute设置显示名称 + /// + public static string GetDisplayName(MemberInfo member) + { + if (member == null) + { + return string.Empty; + } + + + + if (member.GetCustomAttribute() is DisplayNameAttribute displayNameAttribute) + { + return displayNameAttribute.DisplayName; + } + + return string.Empty; + } + + /// + /// 获取显示名称或描述,使用DisplayNameAttribute设置显示名称,使用DescriptionAttribute设置描述 + /// + /// 类型 + public static string GetDisplayNameOrDescription() + { + return GetDisplayNameOrDescription(Common.GetType()); + } + + /// + /// 获取属性显示名称或描述,使用DisplayAttribute或DisplayNameAttribute设置显示名称,使用DescriptionAttribute设置描述 + /// + public static string GetDisplayNameOrDescription(MemberInfo member) + { + string result = GetDisplayName(member); + return string.IsNullOrWhiteSpace(result) ? GetDescription(member) : result; + } + + /// + /// 查找类型列表 + /// + /// 查找类型 + /// 待查找的程序集列表 + public static List FindTypes(params Assembly[] assemblies) + { + Type findType = typeof(TFind); + return FindTypes(findType, assemblies); + } + + /// + /// 查找类型列表 + /// + /// 查找类型 + /// 待查找的程序集列表 + public static List FindTypes(Type findType, params Assembly[] assemblies) + { + List result = []; + foreach (Assembly assembly in assemblies) + { + result.AddRange(GetTypes(findType, assembly)); + } + + return result.Distinct().ToList(); + } + + /// + /// 获取类型列表 + /// + private static List GetTypes(Type findType, Assembly assembly) + { + List result = []; + if (assembly == null) + { + return result; + } + + Type[] types; + try + { + types = assembly.GetTypes(); + } + catch (ReflectionTypeLoadException ex) + { + Debug.WriteLine(ex); + return result; + } + foreach (Type type in types) + { + AddType(result, findType, type); + } + + return result; + } + + /// + /// 添加类型 + /// + private static void AddType(List result, Type findType, Type type) + { + if (type.IsInterface || type.IsAbstract) + { + return; + } + + if (findType.IsAssignableFrom(type) == false && MatchGeneric(findType, type) == false) + { + return; + } + + result.Add(type); + } + + /// + /// 泛型匹配 + /// + private static bool MatchGeneric(Type findType, Type type) + { + if (findType.IsGenericTypeDefinition == false) + { + return false; + } + + Type definition = findType.GetGenericTypeDefinition(); + foreach (Type implementedInterface in type.FindInterfaces((filter, criteria) => true, null)) + { + if (implementedInterface.IsGenericType == false) + { + continue; + } + + return definition.IsAssignableFrom(implementedInterface.GetGenericTypeDefinition()); + } + return false; + } + + /// + /// 获取实现了接口的所有实例 + /// + /// 接口类型 + /// 待查找的程序集列表 + public static List GetInstancesByInterface(params Assembly[] assemblies) + { + return FindTypes(assemblies) + .Select(t => CreateInstance(t)).ToList(); + } + + /// + /// 动态创建实例 + /// + /// 目标类型 + /// 类型 + /// 传递给构造函数的参数 + public static T CreateInstance(Type type, params object[] parameters) + { + return Convert.To(Activator.CreateInstance(type, parameters)); + } + + public static Type GetType(string typeName) + { + + Type type = null; + + Assembly[] assemblyArray = AppDomain.CurrentDomain.GetAssemblies(); + + int assemblyArrayLength = assemblyArray.Length; + + for (int i = 0; i < assemblyArrayLength; ++i) + { + + type = assemblyArray[i].GetType(typeName); + + if (type != null) + { + return type; + } + } + + for (int i = 0; i < assemblyArrayLength; ++i) + { + Type[] typeArray = assemblyArray[i].GetTypes(); + int typeArrayLength = typeArray.Length; + for (int j = 0; j < typeArrayLength; ++j) + { + if (typeArray[j].Name.Equals(typeName)) + { + return typeArray[j]; + } + } + } + + return type; + + } + + /// + /// 获取程序集 + /// + /// 程序集名称 + public static Assembly GetAssembly(string assemblyName) + { + return Assembly.Load(new AssemblyName(assemblyName)); + } + + /// + /// 是否布尔类型 + /// + /// 成员 + public static bool IsBool(MemberInfo member) + { + if (member == null) + { + return false; + } + + return member.MemberType switch + { + MemberTypes.TypeInfo => member.ToString() == "System.Boolean", + MemberTypes.Property => IsBool((PropertyInfo)member), + _ => false, + }; + } + + /// + /// 是否布尔类型 + /// + private static bool IsBool(PropertyInfo property) + { + return property.PropertyType == typeof(bool) || property.PropertyType == typeof(bool?); + } + + /// + /// 是否枚举类型 + /// + /// 成员 + public static bool IsEnum(MemberInfo member) + { + if (member == null) + { + return false; + } + + return member.MemberType switch + { + MemberTypes.TypeInfo => ((TypeInfo)member).IsEnum, + MemberTypes.Property => IsEnum((PropertyInfo)member), + _ => false, + }; + } + + /// + /// 是否枚举类型 + /// + private static bool IsEnum(PropertyInfo property) + { + if (property.PropertyType.GetTypeInfo().IsEnum) + { + return true; + } + + Type value = Nullable.GetUnderlyingType(property.PropertyType); + if (value == null) + { + return false; + } + + return value.GetTypeInfo().IsEnum; + } + + /// + /// 是否日期类型 + /// + /// 成员 + public static bool IsDate(MemberInfo member) + { + if (member == null) + { + return false; + } + + return member.MemberType switch + { + MemberTypes.TypeInfo => member.ToString() == "System.DateTime", + MemberTypes.Property => IsDate((PropertyInfo)member), + _ => false, + }; + } + + /// + /// 是否日期类型 + /// + private static bool IsDate(PropertyInfo property) + { + if (property.PropertyType == typeof(DateTime)) + { + return true; + } + + if (property.PropertyType == typeof(DateTime?)) + { + return true; + } + + return false; + } + + /// + /// 是否整型 + /// + /// 成员 + public static bool IsInt(MemberInfo member) + { + if (member == null) + { + return false; + } + + return member.MemberType switch + { + MemberTypes.TypeInfo => member.ToString() is "System.Int32" or "System.Int16" or "System.Int64", + MemberTypes.Property => IsInt((PropertyInfo)member), + _ => false, + }; + } + + /// + /// 是否整型 + /// + private static bool IsInt(PropertyInfo property) + { + if (property.PropertyType == typeof(int)) + { + return true; + } + + if (property.PropertyType == typeof(int?)) + { + return true; + } + + if (property.PropertyType == typeof(short)) + { + return true; + } + + if (property.PropertyType == typeof(short?)) + { + return true; + } + + if (property.PropertyType == typeof(long)) + { + return true; + } + + if (property.PropertyType == typeof(long?)) + { + return true; + } + + return false; + } + + /// + /// 是否数值类型 + /// + /// 成员 + public static bool IsNumber(MemberInfo member) + { + if (member == null) + { + return false; + } + + if (IsInt(member)) + { + return true; + } + + return member.MemberType switch + { + MemberTypes.TypeInfo => member.ToString() is "System.Double" or "System.Decimal" or "System.Single", + MemberTypes.Property => IsNumber((PropertyInfo)member), + _ => false, + }; + } + + /// + /// 是否数值类型 + /// + private static bool IsNumber(PropertyInfo property) + { + if (property.PropertyType == typeof(double)) + { + return true; + } + + if (property.PropertyType == typeof(double?)) + { + return true; + } + + if (property.PropertyType == typeof(decimal)) + { + return true; + } + + if (property.PropertyType == typeof(decimal?)) + { + return true; + } + + if (property.PropertyType == typeof(float)) + { + return true; + } + + if (property.PropertyType == typeof(float?)) + { + return true; + } + + return false; + } + + /// + /// 是否集合 + /// + /// 类型 + public static bool IsCollection(Type type) + { + if (type.IsArray) + { + return true; + } + + return IsGenericCollection(type); + } + + /// + /// 是否泛型集合 + /// + /// 类型 + public static bool IsGenericCollection(Type type) + { + if (!type.IsGenericType) + { + return false; + } + + Type typeDefinition = type.GetGenericTypeDefinition(); + return typeDefinition == typeof(IEnumerable<>) + || typeDefinition == typeof(IReadOnlyCollection<>) + || typeDefinition == typeof(IReadOnlyList<>) + || typeDefinition == typeof(ICollection<>) + || typeDefinition == typeof(IList<>) + || typeDefinition == typeof(List<>); + } + + /// + /// 从目录中获取所有程序集 + /// + /// 目录绝对路径 + public static List GetAssemblies(string directoryPath) + { + return Directory.GetFiles(directoryPath, "*.*", SearchOption.AllDirectories).ToList() + .Where(t => t.EndsWith(".exe") || t.EndsWith(".dll")) + .Select(path => Assembly.Load(new AssemblyName(path))).ToList(); + } + + /// + /// 获取公共属性列表 + /// + /// 实例 + public static List GetPublicProperties(object instance) + { + PropertyInfo[] properties = instance.GetType().GetProperties(); + return properties.ToList().Select(t => new CommonItem(t.Name, t.GetValue(instance))).ToList(); + } + + /// + /// 获取顶级基类 + /// + /// 类型 + public static Type GetTopBaseType() + { + return GetTopBaseType(typeof(T)); + } + + /// + /// 获取顶级基类 + /// + /// 类型 + public static Type GetTopBaseType(Type type) + { + if (type == null) + { + return null; + } + + if (type.IsInterface) + { + return type; + } + + if (type.BaseType == typeof(object)) + { + return type; + } + + return GetTopBaseType(type.BaseType); + } + + /// + /// 获取元素类型,如果是集合,返回集合的元素类型 + /// + /// 类型 + public static Type GetElementType(Type type) + { + if (IsCollection(type) == false) + { + return type; + } + + if (type.IsArray) + { + return type.GetElementType(); + } + + Type[] genericArgumentsTypes = type.GetTypeInfo().GetGenericArguments(); + if (genericArgumentsTypes == null || genericArgumentsTypes.Length == 0) + { + throw new ArgumentException("泛型类型参数不能为空"); + } + + return genericArgumentsTypes[0]; + } +} diff --git a/MAX.Core/Helpers/UriHelper.cs b/MAX.Core/Helpers/UriHelper.cs new file mode 100644 index 0000000..bfa9f33 --- /dev/null +++ b/MAX.Core/Helpers/UriHelper.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MAX.Core.Helpers; + +public class UriHelper +{ + #region 对外公开方法 + /// + /// 带有前缀的域名 + /// + /// + /// + public static string GetSchemeDomain(string url) + { + Uri uri = new(url); + return $"{uri.Scheme}://{uri.Authority}"; + } + /// + /// 获取域名 + /// + /// + /// + public static string GetDomain(string url) + { + Uri uri = new(url); + return uri.Host; + } + /// + /// 获取 URL + /// + /// URL 地址 + /// + public static string GetUrl(string url) + { + Uri uri = new(url); + return uri.AbsolutePath; + } + /// + /// URL 解码 + /// + /// URL 地址 + /// + public static string UrlDecode(string url) + { + return System.Web.HttpUtility.UrlDecode(url); + } + /// + /// URL 解码 + /// + /// URL 地址 + /// 编码格式 + /// + public static string UrlDecode(string url, Encoding encoding) + { + return System.Web.HttpUtility.UrlDecode(url, encoding); + } + /// + /// URL 编码 + /// + /// URL 地址 + /// + public static string UrlEncode(string url) + { + return System.Web.HttpUtility.UrlEncode(url); + } + /// + /// URL 编码 + /// + /// URL 地址 + /// 编码格式 + /// + public static string UrlEncode(string url, Encoding encoding) + { + return System.Web.HttpUtility.UrlEncode(url, encoding); + } + /// + /// 获取参数列表 + /// + /// Url 地址 + /// + public static List GetQueryList(string url) + { + return GetQueryList(url, Encoding.UTF8); + } + /// + /// 获取参数列表 + /// + /// Url 地址 + /// 编码格式 + /// + public static List GetQueryList(string url, Encoding encoding) + { + Uri uri = new(url); + string query = uri.PathAndQuery; + if (query.IndexOf(".") > 0) + { + query = query.Substring(0, query.IndexOf(".")); + } + + List dataList = []; + string[] queryDataList = query.Split(new string[] { "/" }, StringSplitOptions.RemoveEmptyEntries); + if (queryDataList != null && queryDataList.Length > 0) + { + foreach (string queryData in queryDataList) + { + dataList.Add(System.Web.HttpUtility.UrlDecode(queryData, encoding)); + } + } + return dataList; + } + /// + /// 获取参数字典 + /// + /// Url 地址 + /// + public static Dictionary GetQueryDict(string url) + { + return GetQueryDict(url, Encoding.UTF8); + } + /// + /// 获取参数字典 + /// + /// Url 地址 + /// 编码格式 + /// + public static Dictionary GetQueryDict(string url, Encoding encoding) + { + Uri uri = new(url); + string query = uri.Query.TrimStart(new char[] { '?' }); + + Dictionary dataDict = []; + string[] queryDataList = query.Split(new string[] { "&" }, StringSplitOptions.RemoveEmptyEntries); + if (queryDataList != null && queryDataList.Length > 0) + { + foreach (string queryData in queryDataList) + { + string[] keyValue = queryData.Split(new string[] { "=" }, StringSplitOptions.RemoveEmptyEntries); + if (!dataDict.ContainsKey(keyValue[0])) + { + dataDict.Add(keyValue[0].ToLower(), System.Web.HttpUtility.UrlDecode(keyValue[1], encoding)); + } + } + } + return dataDict; + } + #endregion +} diff --git a/MAX.Core/Interfaces/IPriority.cs b/MAX.Core/Interfaces/IPriority.cs new file mode 100644 index 0000000..3b61afb --- /dev/null +++ b/MAX.Core/Interfaces/IPriority.cs @@ -0,0 +1,33 @@ +namespace MAX.Core.Interfaces; + +public interface IPriority +{ + /// 优先级(优先级越高越先执行) + int Priority { get; } +} + +public static class IntPriority +{ + public const int Highest = 5000; + + public const int VeryHigh = 4000; + + public const int Higher = 3000; + + public const int High = 2000; + + public const int AboveNormal = 1000; + + public const int Normal = 0; + + public const int BelowNormal = -1000; + + public const int Low = -2000; + + public const int Lower = -3000; + + public const int VeryLow = -4000; + + public const int Lowest = -5000; + +} diff --git a/MAX.Core/Interfaces/IStartup.cs b/MAX.Core/Interfaces/IStartup.cs new file mode 100644 index 0000000..0767a3f --- /dev/null +++ b/MAX.Core/Interfaces/IStartup.cs @@ -0,0 +1,18 @@ +using MAX.Core.DependencyInjection; + +namespace MAX.Core.Interfaces; + +/// +/// 启动器:实现 ,由宿主项目统一发现并按 排序执行。 +/// +public interface IStartup : IDependencyRegistrar +{ + /// + /// 优先级(优先级越高越先执行) + /// + int Priority { get; } + /// + /// 初始化 + /// + void Initialize(); +} diff --git a/MAX.Core/Interfaces/SingleInstance.cs b/MAX.Core/Interfaces/SingleInstance.cs new file mode 100644 index 0000000..7a2df56 --- /dev/null +++ b/MAX.Core/Interfaces/SingleInstance.cs @@ -0,0 +1,42 @@ +using System; +using System.Reflection; + +namespace MAX.Core.Interfaces; + +/// +/// 单例,需要在当前类中定义 +/// +/// +public class SingleInstance where T : class +{ + private static readonly object lockObj = new(); + private static T mySelf;//= default(T); + public static T Instance + { + get + { + lock (lockObj) + { + mySelf ??= InstanceCreator.CreateInstance(); + } + + return mySelf; + } + } +} + +public static class InstanceCreator +{ + public static T CreateInstance() + { + Type type = typeof(T); + try + { + return (T)type.Assembly.CreateInstance(type.FullName, true, BindingFlags.NonPublic | BindingFlags.Instance, null, null, null, null); + } + catch (MissingMethodException ex) + { + throw new Exception($"{ex.Message}\n(单例模式下,构造函数必须为 private)"); + } + } +} diff --git a/MAX.Core/Logger/ConsoleLogger.cs b/MAX.Core/Logger/ConsoleLogger.cs new file mode 100644 index 0000000..2c330dd --- /dev/null +++ b/MAX.Core/Logger/ConsoleLogger.cs @@ -0,0 +1,124 @@ +using System; +using System.Diagnostics; +using System.Globalization; +using System.IO; + +namespace MAX.Core.Logger; + +/// +/// Implementation of that logs into a . +/// +public class ConsoleLogger : ILoggerFacade +{ + private readonly bool _debug; + private readonly bool _console; + + public bool IsDebugEnabled => true; + + /// + /// Initializes a new instance of that writes to + /// the console output. + /// + public ConsoleLogger(bool debug = true, bool console = true) + { + _debug = debug; + _console = console; + } + + + + + + + ///// + ///// Disposes the associated . + ///// + ///// When , disposes the associated . + //protected virtual void Dispose(bool disposing) + //{ + // if (disposing) + // { + // writer?.Dispose(); + // } + //} + + /// + ///Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + /// Calls . + ///2 + public void Dispose() + { + GC.SuppressFinalize(this); + } + + public void Debug(Exception ex, string message) + { + Log(message, LogLevel.Debug); + } + + public void Debug(string message) + { + Log(message, LogLevel.Debug); + } + + public void Info(Exception ex, string message) + { + Log(message, LogLevel.Info); + } + + public void Info(string message) + { + Log(message, LogLevel.Info); + } + + public void Warn(Exception ex, string message) + { + Log(message, LogLevel.Warn); + } + + public void Warn(string message) + { + Log(message, LogLevel.Warn); + } + + public void Error(Exception ex, string message) + { + Log(message, LogLevel.Error); + } + + public void Error(string message) + { + Log(message, LogLevel.Error); + } + + public void Fatal(Exception ex, string message) + { + Log(message, LogLevel.Fatal); + } + + public void Fatal(string message) + { + Log(message, LogLevel.Fatal); + } + + public void Error(Exception ex) + { + Log(ex.Message, LogLevel.Error); + } + + public void Log(string message, LogLevel level) + { + string messageToLog = string.Format(CultureInfo.InvariantCulture, "{1}: {2}. Timestamp:{0:u}.", DateTime.Now, + level.ToString().ToUpper(CultureInfo.InvariantCulture), message); + //Trace.WriteLine(messageToLog); + if (_debug) + { + System.Diagnostics.Debug.WriteLine(messageToLog); + } + if (_console) + { + Console.WriteLine(messageToLog); + } + } +} \ No newline at end of file diff --git a/MAX.Core/Logger/ICustomLogger.cs b/MAX.Core/Logger/ICustomLogger.cs new file mode 100644 index 0000000..2014a9c --- /dev/null +++ b/MAX.Core/Logger/ICustomLogger.cs @@ -0,0 +1,11 @@ + +namespace MAX.Core.Logger; + +/// 自定义日志记录器接口 +public interface ICustomLogger +{ + /// 记录日志信息 + /// 日志分组 + /// 日志内容 + void Log(string group, string message); +} diff --git a/MAX.Core/Logger/ILoggerFacade.cs b/MAX.Core/Logger/ILoggerFacade.cs new file mode 100644 index 0000000..02bd991 --- /dev/null +++ b/MAX.Core/Logger/ILoggerFacade.cs @@ -0,0 +1,68 @@ +using System; + +namespace MAX.Core.Logger; + +/// +/// 日志操作 +/// +public interface ILoggerFacade +{ + /// + /// 调试级别是否启用 + /// + bool IsDebugEnabled { get; } + + /// + /// 调试 + /// + void Debug(Exception ex, string message); + /// + /// 调试 + /// + /// 日志消息 + void Debug(string message); + /// + /// 信息 + /// + void Info(Exception ex, string message); + /// + /// 信息 + /// + /// 日志消息 + void Info(string message); + /// + /// 警告 + /// + void Warn(Exception ex, string message); + /// + /// 警告 + /// + /// 日志消息 + void Warn(string message); + /// + /// 错误 + /// + void Error(Exception ex, string message); + /// + /// 错误 + /// + /// 日志消息 + void Error(string message); + /// + /// 致命错误 + /// + void Fatal(Exception ex, string message); + /// + /// 致命错误 + /// + /// 日志消息 + void Fatal(string message); + void Error(Exception ex); + + /// + /// 写入日志 + /// + /// + /// + void Log(string message, LogLevel level); +} diff --git a/MAX.Core/Logger/LogLevel.cs b/MAX.Core/Logger/LogLevel.cs new file mode 100644 index 0000000..941b991 --- /dev/null +++ b/MAX.Core/Logger/LogLevel.cs @@ -0,0 +1,15 @@ +namespace MAX.Core.Logger; + +/// +/// ÈÕ־µȼ¶ +/// +public enum LogLevel +{ + Trace = 0, + Debug = 1, + Info = 2, + Warn = 3, + Error = 4, + Fatal = 5, + None = 6 +} \ No newline at end of file diff --git a/MAX.Core/MAX.Core.csproj b/MAX.Core/MAX.Core.csproj new file mode 100644 index 0000000..bd0ec4f --- /dev/null +++ b/MAX.Core/MAX.Core.csproj @@ -0,0 +1,14 @@ + + + + NU1608;CS1574;CS1573;CS1572;CS1571;CS1711;CS1570;CS1591 + True + + + + + + + + + diff --git a/MAX.Core/Reflections/AppTypeFinder.cs b/MAX.Core/Reflections/AppTypeFinder.cs new file mode 100644 index 0000000..d712adb --- /dev/null +++ b/MAX.Core/Reflections/AppTypeFinder.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; + +namespace MAX.Core.Reflections; + +public partial class AppTypeFinder : Finder +{ + public static AppTypeFinder Instance { get; } = new AppTypeFinder(); + + /// + /// 所有程序集 + /// + public static List Assemblies => Instance.GetAssemblies(); + + public static Exception LoadDLL(string dllPath, out Assembly assembly) + { + return Instance.LoadAssemblyToAppDomain(dllPath, out assembly); + } + + /// + /// 程序集是否匹配 + /// + protected override bool Match(string assemblyName) + { + if (base.Match(assemblyName)) + { + if (AvailablePrefix().Exists(assemblyName.StartsWith)) + { + return true; + } + return Regex.IsMatch(assemblyName, "^(MAX\\.)", RegexOptions.IgnoreCase | RegexOptions.Compiled); + } + return false; + } + + + protected virtual List AvailablePrefix() + { + return ["MAX."]; + } + + public static IEnumerable GetAllTypes() + { + List types = []; + foreach (Assembly item in Assemblies) + { + try + { + types.AddRange(item.GetTypes()); + } + catch + { + + } + } + return types; + } + + /// + /// 查找当前程序集中所有继承或实现了指定类型的非抽象类。 + /// + /// + /// 要匹配的目标类型,可以是: + /// + /// 普通接口类型(例如 IMyService) + /// 开放泛型接口类型(例如 IRepository<>) + /// 普通基类类型(例如 BaseHandler) + /// 开放泛型基类类型(例如 BaseHandler<>) + /// + /// + /// + /// 所有满足条件的具体类(非抽象类)的 数组。 + /// + /// + /// 当 为 null 时抛出。 + /// + /// + /// + /// 此方法会遍历 GetAllTypes() 返回的所有类型,并筛选出: + /// + /// 实现了指定接口的类(包括开放泛型接口的封闭实现) + /// 继承了指定基类的类(包括开放泛型基类的封闭实现) + /// + /// + /// 示例: + /// + /// // 查找所有实现了 IEnumDisplayHandler<T> 的类 + /// var handlers = FindAllDerivedTypes(typeof(IEnumDisplayHandler<>)); + /// + /// // 查找所有实现了 IMyService 的类 + /// var services = FindAllDerivedTypes(typeof(IMyService)); + /// + /// // 查找所有继承 BaseHandler 的类 + /// var subclasses = FindAllDerivedTypes(typeof(BaseHandler)); + /// + /// // 查找所有继承开放泛型基类 BaseHandler<T> 的类 + /// var genericSubclasses = FindAllDerivedTypes(typeof(BaseHandler<>)); + /// + /// + public static Type[] FindAllDerivedTypes(Type targetType) + { + if (targetType == null) + { + throw new ArgumentNullException(nameof(targetType)); + } + + return GetAllTypes().Where(t => + { + try + { + if (!t.IsClass || t.IsAbstract) + { + return false; + } + + // 开放泛型接口 + if (targetType.IsInterface && targetType.IsGenericTypeDefinition) + { + return t.GetInterfaces().Any(i => + i.IsGenericType && i.GetGenericTypeDefinition() == targetType); + } + + // 普通接口 + if (targetType.IsInterface) + { + return targetType.IsAssignableFrom(t); + } + + // 开放泛型基类 + if (targetType.IsGenericTypeDefinition) + { + return IsSubclassOfRawGeneric(t, targetType); + } + + // 普通基类 + return targetType.IsAssignableFrom(t) && t != targetType; + } + catch + { + return false; + } + }) + .ToArray(); + } + + /// + /// 检查指定类型是否继承自某个开放泛型基类。 + /// + /// 要检查的类型。 + /// 开放泛型基类类型,例如 BaseHandler<>。 + /// + /// 如果 的继承链中存在指定的开放泛型基类,则返回 true;否则返回 false。 + /// + /// + /// 此方法会沿着 的继承链向上遍历, + /// 并在每一级检查是否为指定的开放泛型基类定义。 + /// + private static bool IsSubclassOfRawGeneric(Type type, Type generic) + { + while (type != null && type != typeof(object)) + { + Type cur = type.IsGenericType ? type.GetGenericTypeDefinition() : type; + if (generic == cur) + { + return true; + } + + type = type.BaseType; + } + return false; + } + + /// + /// 泛型包装方法,查找所有继承或实现了指定类型 的非抽象类。 + /// + /// + /// 目标类型,可以是接口、开放泛型接口、基类或开放泛型基类。 + /// + /// + /// 所有满足条件的具体类(非抽象类)的 数组。 + /// + /// + /// 调用示例: + /// + /// var handlers = FindAllDerivedTypes<IEnumDisplayHandler<>>(); + /// var services = FindAllDerivedTypes<IMyService>(); + /// var subclasses = FindAllDerivedTypes<BaseHandler>(); + /// var genericSubclasses = FindAllDerivedTypes<BaseHandler<>>(); + /// + /// + public static Type[] FindAllDerivedTypes() => FindAllDerivedTypes(typeof(T)); + + +} diff --git a/MAX.Core/Reflections/Finder.cs b/MAX.Core/Reflections/Finder.cs new file mode 100644 index 0000000..18eb0b7 --- /dev/null +++ b/MAX.Core/Reflections/Finder.cs @@ -0,0 +1,129 @@ +using MAX.Core.Helpers; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; + +namespace MAX.Core.Reflections; + +public class Finder +{ + /// + /// 跳过的程序集 + /// + private const string SkipAssemblies = "^System|^Microsoft|^mscorlib|^netstandard|^Autofac|^AutoMapper|^EntityFramework|^Newtonsoft|^Castle|^NLog|^Pomelo|^AspectCore|^Xunit|^Nito|^Npgsql|^Exceptionless|^MySqlConnector|^Anonymously|^libuv|^api-ms|^clrcompression|^clretwrc|^clrjit|^coreclr|^dbgshim|^e_sqlite3|^hostfxr|^hostpolicy|^MessagePack|^mscordaccore|^mscordbi|^mscorrc|^sni|^sos|^SOS\\.NETCore|^sos_amd64|^SQLitePCLRaw|^StackExchange|^Swashbuckle|^WindowsBase|^ucrtbase|^DotNetCore\\.CAP|^MongoDB|^Confluent\\.Kafka|^librdkafka|^EasyCaching|^RabbitMQ|^Consul|^Dapper|^EnyimMemcachedCore|^Pipelines|^DnsClient|^IdentityModel|^zlib|^Runtime\\.|^Google|^Grpc|^Internal\\.|^Azure|^DevExpress\\.|^FreeSql\\.|^PresentationFramework|^PresentationCore|^Prism|^DirectWriteForwarder|^MAX\\.Common|^ReflectionHelperTypesPublicWrappers|^UIAutomation|^WindowsFormsIntegration"; + + /// + /// 获取程序集列表 + /// + public virtual List GetAssemblies() + { + LoadAssemblies(AppDomain.CurrentDomain.BaseDirectory); + return GetAssembliesFromCurrentAppDomain(); + } + + /// + /// 加载程序集到当前应用程序域 + /// + /// 目录绝对路径 + protected void LoadAssemblies(string path) + { + foreach (string file in Directory.GetFiles(path, "*.dll")) + { + if (Match(Path.GetFileName(file)) == false) + { + continue; + } + + LoadAssemblyToAppDomain(file, out _); + } + } + + /// + /// 程序集是否匹配 + /// + protected virtual bool Match(string assemblyName) + { + return Regex.IsMatch(assemblyName, SkipAssemblies, RegexOptions.IgnoreCase | RegexOptions.Compiled) == false; + } + + /// + /// 将程序集添加当前应用程序域 + /// + protected Exception LoadAssemblyToAppDomain(string file, out Assembly assembly) + { + try + { + //Debug.WriteLine($"Load Assembly : {Path.GetFileName(file)}"); + AssemblyName assemblyName = AssemblyName.GetAssemblyName(file); + assembly = AppDomain.CurrentDomain.Load(assemblyName); + return null; + } + catch (FileNotFoundException) + { + // 如果找不到文件,回退到 LoadFrom 方式 + try + { + assembly = Assembly.LoadFrom(file); + return null; + } + catch (Exception ex) + { + assembly = null; + return ex; + } + } + catch (Exception ex) + { + assembly = null; + return ex; + } + } + + /// + /// 从当前应用程序域获取程序集列表 + /// + protected virtual List GetAssembliesFromCurrentAppDomain() + { + List result = []; + foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + if (Match(assembly)) + { + result.Add(assembly); + } + } + return result.Distinct().ToList(); + } + + /// + /// 程序集是否匹配 + /// + private bool Match(Assembly assembly) + { + return !Regex.IsMatch(assembly.FullName, SkipAssemblies, RegexOptions.IgnoreCase | RegexOptions.Compiled); + } + + /// + /// 查找类型列表 + /// + /// 查找类型 + /// 在指定的程序集列表中查找 + public List Find(List assemblies = null) + { + return Find(typeof(T), assemblies); + } + + /// + /// 查找类型列表 + /// + /// 查找类型 + /// 在指定的程序集列表中查找 + public List Find(Type findType, List assemblies = null) + { + assemblies ??= GetAssemblies(); + return ReflectionHelper.FindTypes(findType, assemblies.ToArray()); + } +} diff --git a/MAX.Core/Startup.cs b/MAX.Core/Startup.cs new file mode 100644 index 0000000..2f5cb1d --- /dev/null +++ b/MAX.Core/Startup.cs @@ -0,0 +1,45 @@ +/*** + * _ooOoo_ + * o8888888o + * 88" . "88 + * (| -_- |) + * O\ = /O + * ____/`---'\____ + * . ' \\| |// `. + * / \\||| : |||// \ + * / _||||| -:- |||||- \ + * | | \\\ - /// | | + * | \_| ''\---/'' | | + * \ .-\__ `-` ___/-. / + * ___`. .' /--.--\ `. . __ + * ."" '< `.___\_<|>_/___.' >'"". + * | | : `- \`.;`\ _ /`;.`/ - ` : | | + * \ \ `-. \_ __\ /__ _/ .-` / / + * ======`-.____`-.___\_____/___.-`____.-'====== + * `=---=' + * + * ............................................. + * 佛祖保佑 永无BUG + * 快加工资 不改需求 + */ + +using MAX.Core.DependencyInjection; +using MAX.Core.Interfaces; + +namespace MAX.Core; + +public class Startup : IStartup +{ + public int Priority => int.MaxValue; + + public void Initialize() + { + + } + + public void Register(IServiceRegistrar registrar) + { + // 示例:恢复 LazyServiceProvider 后,可在这里注册为单例 + // registrar.RegisterSingleton(); + } +} diff --git a/Store.Contracts/Class1.cs b/Store.Contracts/Class1.cs new file mode 100644 index 0000000..c3a5a3b --- /dev/null +++ b/Store.Contracts/Class1.cs @@ -0,0 +1,6 @@ +namespace Store.Contracts; + +public class Class1 +{ + +} diff --git a/Store.Contracts/Order/Entitys/OrderPddBuyerOrderEntity.cs b/Store.Contracts/Order/Entitys/OrderPddBuyerOrderEntity.cs new file mode 100644 index 0000000..784fcaf --- /dev/null +++ b/Store.Contracts/Order/Entitys/OrderPddBuyerOrderEntity.cs @@ -0,0 +1,128 @@ +using FreeSql.DataAnnotations; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace Store.Contracts.Order.Entitys; + +/// +/// 拼多多买手订单表 +/// +[Table(Name = "wxxd.order_pdd_buyer_order")] +public class OrderPddBuyerOrderEntity +{ + /// + /// id + /// + [Column(Name = "id", IsPrimary = true, IsIdentity = true)] + public long Id { get; set; } + + /// + /// 微信订单 + /// + [Column(Name = "wx_order_id")] + public string WxOrderId { get; set; } + + /// + /// 微信订单sku + /// + [Column(Name = "wx_sku_id")] + public string WxSkuId { get; set; } + + /// + /// 微信订单发货类型 0 中转仓,1店铺后台直发 + /// + [Column(Name = "wx_send_type")] + public byte WxSendType { get; set; } + + /// + /// 中转单创建时间 + /// + [Column(Name = "TransitCreateTime")] + public DateTime? TransitCreateTime { get; set; } + + /// + /// 中转仓取号状态 + /// + [Column(Name = "pre_allocated_order_no")] + public int? PreAllocatedOrderNo { get; set; } + + /// + /// 买手编号 + /// + [Column(Name = "buyerId")] + public string BuyerId { get; set; } + + /// + /// 买手名称 + /// + [Column(Name = "buyerName")] + public string BuyerName { get; set; } + + /// + /// 买手单号 + /// + [Column(Name = "order_sn")] + public string OrderSn { get; set; } + + /// + /// 拍单订单金额 + /// + [Column(Name = "order_amount")] + public int? OrderAmount { get; set; } + + /// + /// 拍单时间 + /// + [Column(Name = "order_time")] + public long? OrderTime { get; set; } + + /// + /// 物流公司名称缩写 + /// + [Column(Name = "outExpressCompanyCode")] + public string OutExpressCompanyCode { get; set; } + + /// + /// 拍单物流单号 + /// + [Column(Name = "tracking_number")] + public string TrackingNumber { get; set; } + + /// + /// 订单状态 + /// + [Column(Name = "order_status_prompt")] + public string OrderStatusPrompt { get; set; } + + /// + /// 买手售后状态 2, 3, 4, 5, 7, 8, 10, 31 // 为售后,不支持修改物流 0:无售后 2:买家申请退款,待商家处理 3:退货退款,待商家处理 4:商家同意退款,退款中 5:平台同意退款,退款中 6:驳回退款,待买家处理 7:已同意退货退款,待用户发货 8:平台处理中 9:平台拒绝退款,退款关闭 10:退款成功 11:买家撤销 12:买家逾期未处理,退款失败 13:买家逾期,超过有效期 14:换货补寄待商家处理 15:换货补寄待用户处理 16:换货补寄成功 17:换货补寄失败 18:换货补寄待用户确认完成 21:待商家同意维修 22:待用户确认发货 24:维修关闭 25:维修成功 27:待用户确认收货 31:已同意拒收退款,待用户拒收 32:补寄待商家发货 + /// + [Column(Name = "after_sales_status")] + public int? AfterSalesStatus { get; set; } + + /// + /// 买手订单详情 + /// + [Column(Name = "order_detail")] + public string OrderDetail { get; set; } + + /// + /// add_time + /// + [Column(Name = "add_time")] + public DateTime AddTime { get; set; } + + /// + /// mdy_time + /// + [Column(Name = "mdy_time")] + public DateTime MdyTime { get; set; } + + /// + /// 取件码 + /// + [Column(Name = "pickup_code")] + public string PickupCode { get; set; } +} diff --git a/Store.Contracts/Order/Entitys/WeiOrderEntity.cs b/Store.Contracts/Order/Entitys/WeiOrderEntity.cs new file mode 100644 index 0000000..13a732f --- /dev/null +++ b/Store.Contracts/Order/Entitys/WeiOrderEntity.cs @@ -0,0 +1,416 @@ +using FreeSql.DataAnnotations; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace Store.Contracts.Order.Entitys; + +/// +/// 微信订单表 +/// +[Table(Name = "erp.oms_wei_order")] +public class WeiOrderEntity +{ + /// + /// id + /// + [Column(Name = "id", IsPrimary = true, IsIdentity = true)] + public long Id { get; set; } + + /// + /// 店铺id + /// + [Column(Name = "shop_id")] + public long ShopId { get; set; } + + /// + /// 店铺名称 + /// + [Column(Name = "shop_name")] + public string ShopName { get; set; } + + /// + /// 商户id + /// + [Column(Name = "merchant_id")] + public long MerchantId { get; set; } + + /// + /// 订单号 + /// + [Column(Name = "order_id")] + public string OrderId { get; set; } + + /// + /// 买家身份标识 + /// + [Column(Name = "openid")] + public string Openid { get; set; } + + /// + /// 秒级时间戳 + /// + [Column(Name = "create_time")] + public int? CreateTime { get; set; } + + /// + /// 秒级时间戳 + /// + [Column(Name = "update_time")] + public int? UpdateTime { get; set; } + + /// + /// unionid + /// + [Column(Name = "unionid")] + public string Unionid { get; set; } + + /// + /// 状态10 待付款;20 待发货;21 部分发货;30 待收货;100 完成;200 全部商品售后之后,订单取消;250 未付款用户主动取消或超时未付款订单自动取消; + /// + [Column(Name = "status")] + public int? Status { get; set; } + + /// + /// 售后信息json + /// + [Column(Name = "aftersale_detail")] + public string AftersaleDetail { get; set; } + + /// + /// 支付信息json + /// + [Column(Name = "pay_info")] + public string PayInfo { get; set; } + + /// + /// 额外信息 + /// + [Column(Name = "ext_info")] + public string ExtInfo { get; set; } + + /// + /// 商品总价,单位为分 + /// + [Column(Name = "product_price")] + public int? ProductPrice { get; set; } + + /// + /// 订单金额,单位为分,order_price=original_order_price-discounted_price-deduction_price-change_down_price + /// + [Column(Name = "order_price")] + public int? OrderPrice { get; set; } + + /// + /// 采购价,单位为分 + /// + [Column(Name = "purchase_price")] + public int? PurchasePrice { get; set; } + + /// + /// 采购时间 + /// + [Column(Name = "purchase_time")] + public int? PurchaseTime { get; set; } + + /// + /// 运费,单位为分 + /// + [Column(Name = "freight")] + public int? Freight { get; set; } + + /// + /// 平台服务费(微信小店:预计技术服务费),单位为分 + /// + [Column(Name = "predict_commission_fee")] + public int? PredictCommissionFee { get; set; } + + /// + /// 达人佣金(微信小店:分账金额),单位为分 + /// + [Column(Name = "commission_amount")] + public int CommissionAmount { get; set; } + + /// + /// 优惠券优惠金额,单位为分 + /// + [Column(Name = "discounted_price")] + public int? DiscountedPrice { get; set; } + + /// + /// 利润(实付金额-采购价-平台服务费-达人佣金,达人佣金、采购价为空值则为0) + /// + [Column(Name = "profit")] + public int? Profit { get; set; } + + /// + /// 采购状态,默认值为:0未采购,推送仓库时修改为1采购 + /// + [Column(Name = "purchase_status")] + public int PurchaseStatus { get; set; } + + /// + /// 延迟发货时间(微信小店:商品发货时效,超时此时间未发货即为发货超时) + /// + [Column(Name = "delivery_deadline")] + public int? DeliveryDeadline { get; set; } + + /// + /// 发货时间 + /// + [Column(Name = "delivery_time")] + public int? DeliveryTime { get; set; } + + /// + /// 收货人姓名 + /// + [Column(Name = "user_name")] + public string UserName { get; set; } + + /// + /// 邮编 + /// + [Column(Name = "postal_code")] + public string PostalCode { get; set; } + + /// + /// 省份 + /// + [Column(Name = "province_name")] + public string ProvinceName { get; set; } + + /// + /// 城市 + /// + [Column(Name = "city_name")] + public string CityName { get; set; } + + /// + /// 区 + /// + [Column(Name = "county_name")] + public string CountyName { get; set; } + + /// + /// 详细地址 + /// + [Column(Name = "detail_info")] + public string DetailInfo { get; set; } + + /// + /// 联系方式 + /// + [Column(Name = "tel_number")] + public string TelNumber { get; set; } + + /// + /// 门牌号码 + /// + [Column(Name = "house_number")] + public string HouseNumber { get; set; } + + /// + /// 虚拟发货订单联系方式(deliver_method=1时返回) + /// + [Column(Name = "virtual_order_tel_number")] + public string VirtualOrderTelNumber { get; set; } + + /// + /// 额外的联系方式信息(虚拟号码相关) + /// + [Column(Name = "tel_number_ext_info")] + public string TelNumberExtInfo { get; set; } + + /// + /// 0:不使用虚拟号码,1:使用虚拟号码 + /// + [Column(Name = "use_tel_number")] + public int? UseTelNumber { get; set; } + + /// + /// 标识当前店铺下一个唯一的用户收货地址 + /// + [Column(Name = "hash_code")] + public string HashCode { get; set; } + + /// + /// 发货物流信息JSON + /// + [Column(Name = "delivery_product_info")] + public string DeliveryProductInfo { get; set; } + + /// + /// 发货完成时间,秒级时间戳 + /// + [Column(Name = "ship_done_time")] + public int? ShipDoneTime { get; set; } + + /// + /// 电子面单代发时的订单密文 + /// + [Column(Name = "ewaybill_order_code")] + public string EwaybillOrderCode { get; set; } + + /// + /// 结算信息json + /// + [Column(Name = "settle_info")] + public string SettleInfo { get; set; } + + /// + /// 订单审核状态(0待审核1已审核) + /// + [Column(Name = "audit_status")] + public int? AuditStatus { get; set; } + + /// + /// 订单审核时间 + /// + [Column(Name = "audit_time")] + public DateTime? AuditTime { get; set; } + + /// + /// erp发货快递公司 + /// + [Column(Name = "erp_send_company")] + public string ErpSendCompany { get; set; } + + /// + /// erp发货快递单号 + /// + [Column(Name = "erp_send_code")] + public string ErpSendCode { get; set; } + + /// + /// erp发货状态 + /// + [Column(Name = "erp_send_status")] + public int? ErpSendStatus { get; set; } + + /// + /// erp发货时间 + /// + [Column(Name = "erp_send_time")] + public DateTime? ErpSendTime { get; set; } + + /// + /// 旗帜颜色备注,1:灰旗,2:红旗,3:橙旗,4:绿旗,5:天蓝旗,6:紫旗 + /// + [Column(Name = "colour_flag")] + public int? ColourFlag { get; set; } + + /// + /// 旗帜备注时间 + /// + [Column(Name = "colour_flag_update_time")] + public DateTime? ColourFlagUpdateTime { get; set; } + + /// + /// 供应商备注异常单 + /// + [Column(Name = "supplier_remark_exception")] + public string SupplierRemarkException { get; set; } + + /// + /// 礼物单状态,默认值0:不存在礼物单,1:礼物单已发货,2:礼物单信息已更正,3:礼物单发货失败 + /// + [Column(Name = "gift_order_status")] + public byte GiftOrderStatus { get; set; } + + /// + /// 店铺备注:取微信ext_info.merchant_notes + /// + [Column(Name = "shop_remark")] + public string ShopRemark { get; set; } + + /// + /// 商家的系统备注 + /// + [Column(Name = "soft_remark")] + public string SoftRemark { get; set; } + + /// + /// 外采金额(单位:分,整数存储) + /// + [Column(Name = "out_amount")] + public int OutAmount { get; set; } + + /// + /// 外采账户 + /// + [Column(Name = "out_account")] + public string OutAccount { get; set; } + + /// + /// 外采订单号 + /// + [Column(Name = "out_order_no")] + public string OutOrderNo { get; set; } + + /// + /// 外采快递公司 + /// + [Column(Name = "out_express_company")] + public string OutExpressCompany { get; set; } + + /// + /// 外采快递单号 + /// + [Column(Name = "out_express_no")] + public string OutExpressNo { get; set; } + + /// + /// 外采是否发货(0 未发货,1 已发货) + /// + [Column(Name = "out_shipped")] + public byte OutShipped { get; set; } + + /// + /// 判断是否优化过发货0 未优化, 1已优化 + /// + [Column(Name = "is_shipment_optimized")] + public int IsShipmentOptimized { get; set; } + + /// + /// 判断是否优随机使用快递单号0 未使用, 1已使用 + /// + [Column(Name = "is_express_no_used")] + public int IsExpressNoUsed { get; set; } + + /// + /// 外采订单售后状态 + /// + [Column(Name = "out_after_sale_status")] + public string OutAfterSaleStatus { get; set; } + + /// + /// 是否支持运费险:0-不支持,1-支持 + /// + [Column(Name = "has_freight_insurance")] + public byte HasFreightInsurance { get; set; } + + /// + /// 运费险金额(单位:元) + /// + [Column(Name = "freight_insurance_amount")] + public decimal FreightInsuranceAmount { get; set; } + + /// + /// 是否外采礼物单:0-否,1-是(外采订单) + /// + [Column(Name = "external_gift")] + public byte ExternalGift { get; set; } + + /// + /// 中转仓的状态 0未使用,1已创建,2创建失败(购买失败),3已创建未有上游快递单号,4已创建有上游快递单号 + /// + [Column(Name = "transfer_warehouse_status")] + public byte? TransferWarehouseStatus { get; set; } + + /// + /// 商品运费(由店铺买家支付的) + /// + [Column(Name = "postage_price")] + public int? PostagePrice { get; set; } +} \ No newline at end of file diff --git a/Store.Contracts/Order/Entitys/WeiOrderItemEntity.cs b/Store.Contracts/Order/Entitys/WeiOrderItemEntity.cs new file mode 100644 index 0000000..fabb6ff --- /dev/null +++ b/Store.Contracts/Order/Entitys/WeiOrderItemEntity.cs @@ -0,0 +1,236 @@ +using FreeSql.DataAnnotations; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace Store.Contracts.Order.Entitys; + +/// +/// 微信订单商品明细表 +/// +[Table(Name = "erp.oms_wei_order_item")] +public class WeiOrderItemEntity +{ + /// + /// id + /// + [Column(Name = "id", IsPrimary = true, IsIdentity = true)] + public long Id { get; set; } + + /// + /// 店铺id + /// + [Column(Name = "shop_id")] + public long ShopId { get; set; } + + /// + /// 商户id + /// + [Column(Name = "merchant_id")] + public long MerchantId { get; set; } + + /// + /// 订单id(平台订单id) + /// + [Column(Name = "order_id")] + public string OrderId { get; set; } + + /// + /// 商品spuid + /// + [Column(Name = "product_id")] + public string ProductId { get; set; } + + /// + /// 商品skuid + /// + [Column(Name = "sku_id")] + public string SkuId { get; set; } + + /// + /// sku小图 + /// + [Column(Name = "thumb_img")] + public string ThumbImg { get; set; } + + /// + /// sku数量 + /// + [Column(Name = "sku_cnt")] + public int SkuCnt { get; set; } + + /// + /// 售卖单价(单位:分) + /// + [Column(Name = "sale_price")] + public int? SalePrice { get; set; } + + /// + /// 商品标题 + /// + [Column(Name = "title")] + public string Title { get; set; } + + /// + /// 正在售后/退款流程中的 sku 数量 + /// + [Column(Name = "on_aftersale_sku_cnt")] + public int? OnAftersaleSkuCnt { get; set; } + + /// + /// 完成售后/退款的 sku 数量 + /// + [Column(Name = "finish_aftersale_sku_cnt")] + public int? FinishAftersaleSkuCnt { get; set; } + + /// + /// 商品编码 + /// + [Column(Name = "sku_code")] + public string SkuCode { get; set; } + + /// + /// 市场单价(单位:分) + /// + [Column(Name = "market_price")] + public int? MarketPrice { get; set; } + + /// + /// sku属性JSON + /// + [Column(Name = "sku_attrs")] + public string SkuAttrs { get; set; } + + /// + /// sku实付总价,取estimate_price和change_price中较小值 + /// + [Column(Name = "real_price")] + public int? RealPrice { get; set; } + + /// + /// 商品外部spuid + /// + [Column(Name = "out_product_id")] + public string OutProductId { get; set; } + + /// + /// 采购价 + /// + [Column(Name = "purchase_price")] + public int? PurchasePrice { get; set; } + + /// + /// 运费 + /// + [Column(Name = "freight")] + public int? Freight { get; set; } + + /// + /// 商品外部skuid + /// + [Column(Name = "out_sku_id")] + public string OutSkuId { get; set; } + + /// + /// 是否有优惠金额,非必填,默认为false + /// + [Column(Name = "is_discounted")] + public string IsDiscounted { get; set; } + + /// + /// 优惠后sku总价,非必填,is_discounted为true时有值 + /// + [Column(Name = "estimate_price")] + public int? EstimatePrice { get; set; } + + /// + /// 是否修改过价格,非必填,默认为false + /// + [Column(Name = "is_change_price")] + public string IsChangePrice { get; set; } + + /// + /// 改价后sku总价,非必填,is_change_price为true时有值 + /// + [Column(Name = "change_price")] + public int? ChangePrice { get; set; } + + /// + /// 订单内商品维度平台券优惠金额 (利润需要计算这个金额在内) + /// + [Column(Name = "cash_coupon_discounted_price")] + public int? CashCouponDiscountedPrice { get; set; } + + /// + /// 区域库存id + /// + [Column(Name = "out_warehouse_id")] + public string OutWarehouseId { get; set; } + + /// + /// 退货地址 + /// + [Column(Name = "return_address")] + public string ReturnAddress { get; set; } + + /// + /// 商品发货信息JSON + /// + [Column(Name = "sku_deliver_info")] + public string SkuDeliverInfo { get; set; } + + /// + /// 商品额外服务信息JSON + /// + [Column(Name = "extra_service")] + public string ExtraService { get; set; } + + /// + /// 是否使用了会员积分抵扣 + /// + [Column(Name = "use_deduction")] + public string UseDeduction { get; set; } + + /// + /// 会员积分抵扣金额,单位为分 + /// + [Column(Name = "deduction_price")] + public int? DeductionPrice { get; set; } + + /// + /// 平台服务费(微信小店:预计技术服务费),单位为分 + /// + [Column(Name = "predict_commission_fee")] + public int? PredictCommissionFee { get; set; } + + /// + /// 达人佣金(微信小店:分账金额),单位为分 + /// + [Column(Name = "commission_amount")] + public int? CommissionAmount { get; set; } + + /// + /// 商品优惠券信息,逐步替换 order.order_detail.coupon_info + /// + [Column(Name = "order_product_coupon_info_list")] + public string OrderProductCouponInfoList { get; set; } + + /// + /// 商品id(o_goods外键) + /// + [Column(Name = "erp_goods_id")] + public long ErpGoodsId { get; set; } + + /// + /// 商品skuid(o_goods_sku外键) + /// + [Column(Name = "erp_goods_sku_id")] + public long ErpGoodsSkuId { get; set; } + + /// + /// 售后状态 1:无售后或售后关闭,2:售后处理中,3:退款中,4: 退款成功 11 已取消 + /// + [Column(Name = "refund_status")] + public int RefundStatus { get; set; } +} \ No newline at end of file diff --git a/Store.Contracts/Order/Interfaces/IOrderPddBuyerService.cs b/Store.Contracts/Order/Interfaces/IOrderPddBuyerService.cs new file mode 100644 index 0000000..4e8a288 --- /dev/null +++ b/Store.Contracts/Order/Interfaces/IOrderPddBuyerService.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Contracts.Order.Interfaces; + +/// +/// 拼多多买手号订单服务 +/// +public interface IOrderPddBuyerService +{ + /// + /// 获取微信订单号,从品多多订单号 + /// + /// + /// + string GetWeChatOrderIdByPddOrderId(string orderId); +} diff --git a/Store.Contracts/Store.Contracts.csproj b/Store.Contracts/Store.Contracts.csproj new file mode 100644 index 0000000..b7f4f2f --- /dev/null +++ b/Store.Contracts/Store.Contracts.csproj @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/Store.Framework/Base/ApiControllerBase.cs b/Store.Framework/Base/ApiControllerBase.cs new file mode 100644 index 0000000..f8c0612 --- /dev/null +++ b/Store.Framework/Base/ApiControllerBase.cs @@ -0,0 +1,59 @@ +using KJD_Web.Utils; +using Newtonsoft.Json; +using Store.Framework.Dtos; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Web; +using System.Web.Http; +using System.Web.Http.Controllers; + +namespace Store.Framework.Base; + +public abstract class ApiControllerBase : ApiController +{ + + protected bool ValidateSign(SignRequestBase request, bool isUserLogin = false) + { + if (request == null || string.IsNullOrEmpty(request.time) || string.IsNullOrEmpty(request.code)) + { + return false; + } + + int time; + if (!int.TryParse(request.time, out time)) + { + return false; + } + if (request.code != EncrypUtil.Encryption(time)) + { + return false; + } + if (!isUserLogin && string.IsNullOrWhiteSpace(request.UserName)) + { + return false; + } + + return true; + } + private bool IsAllowAnonymous(HttpActionContext context) + { + // Action 上 + if (context.ActionDescriptor.GetCustomAttributes().Any()) + return true; + // Controller 上 + if (context.ControllerContext.ControllerDescriptor.ControllerType + .GetCustomAttributes(typeof(AllowAnonymousAttribute), true).Any()) + return true; + return false; + } + private async Task OkResponseAsync(object content) + { + IHttpActionResult result = Ok(content); + return await result.ExecuteAsync(CancellationToken.None); + } +} \ No newline at end of file diff --git a/Store.Framework/Class1.cs b/Store.Framework/Class1.cs deleted file mode 100644 index 769542d..0000000 --- a/Store.Framework/Class1.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Store.Framework; - -public class Class1 -{ - -} diff --git a/Store.Framework/Commons/StoreAppTypeFinder.cs b/Store.Framework/Commons/StoreAppTypeFinder.cs new file mode 100644 index 0000000..5ec713a --- /dev/null +++ b/Store.Framework/Commons/StoreAppTypeFinder.cs @@ -0,0 +1,25 @@ +using MAX.Core.Reflections; +using System.Reflection; + +namespace Store.Framework.Commons +{ + public sealed class StoreAppTypeFinder : AppTypeFinder + { + public static StoreAppTypeFinder Current { get; } = new(); + + + /// + /// 获取程序集列表 + /// + public override List GetAssemblies() + { + LoadAssemblies(Environment.CurrentDirectory); + return GetAssembliesFromCurrentAppDomain(); + } + + protected override List AvailablePrefix() + { + return ["Max", "Store"]; + } + } +} diff --git a/Store.Framework/DependencyInjection/ContainerConfig.cs b/Store.Framework/DependencyInjection/ContainerConfig.cs new file mode 100644 index 0000000..9843a6d --- /dev/null +++ b/Store.Framework/DependencyInjection/ContainerConfig.cs @@ -0,0 +1,99 @@ +using Autofac; +using Autofac.Core; +using Autofac.Integration.WebApi; +using FreeSql; +using MAX.Core.DependencyInjection; +using MAX.Core.Helpers; +using MAX.Core.Interfaces; +using MAX.Core.Logger; +using MAX.Framework.Logger; +using Store.Framework.Commons; +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Framework.DependencyInjection; + +public class ContainerConfig +{ + + public static IContainer BuildContainer() + { + var builder = new ContainerBuilder(); + var appAssembly = StoreAppTypeFinder.Current.GetAssemblies().ToArray(); + + var connection = ConfigurationManager.ConnectionStrings["ConnKJD"]; + if (connection == null || string.IsNullOrWhiteSpace(connection.ConnectionString)) + { + throw new ConfigurationErrorsException("未配置连接字符串:ConnKJD"); + } + // ImageCacheService 使用独立的 imagecache 数据库。 + builder.Register(_ => + { + return new FreeSqlBuilder() + .UseConnectionString( + DataType.MySql, + connection.ConnectionString) + .Build(); + }) + .As() + .SingleInstance(); + + + // 注册 Web API 所有 Controller + builder.RegisterApiControllers(appAssembly); + builder.RegisterInstance(NLogger.Instance).As().SingleInstance(); + + + + + #region 注册所有IOC接口 + + Type typeSingleton = typeof(ISingletonDependency); + builder.RegisterAssemblyTypes(appAssembly) + .Where(a => + typeSingleton.IsAssignableFrom(a) + && a.IsClass + && !a.IsAbstract + ) + .AsImplementedInterfaces() + .AsSelf() + .SingleInstance(); + + Type typeTransient = typeof(ITransientDependency); + builder.RegisterAssemblyTypes(appAssembly) + .Where(a => + typeTransient.IsAssignableFrom(a) + && a.IsClass + && !a.IsAbstract + ) + .AsImplementedInterfaces() + .AsSelf() + .InstancePerDependency(); + + Type typeScoped = typeof(IScopedDependency); + builder.RegisterAssemblyTypes(appAssembly) + .Where(a => typeScoped.IsAssignableFrom(a) && a.IsClass && !a.IsAbstract) + .AsImplementedInterfaces() + .AsSelf() + .InstancePerRequest(); + + #endregion + IServiceRegistrar registrar = new ServiceRegistrarProxy(builder); + IEnumerable startupList = ReflectionHelper.GetInstancesByInterface(appAssembly); + foreach (IStartup item in startupList.OrderByDescending(a => a.Priority)) + { + Debug.WriteLine($"Startup : {item.GetType()}"); + item.Initialize(); + item.Register(registrar); + } + + var app = builder.Build(); + return app; + } + +} \ No newline at end of file diff --git a/Store.Framework/DependencyInjection/ServiceRegistrarProxy.cs b/Store.Framework/DependencyInjection/ServiceRegistrarProxy.cs new file mode 100644 index 0000000..db74674 --- /dev/null +++ b/Store.Framework/DependencyInjection/ServiceRegistrarProxy.cs @@ -0,0 +1,135 @@ +using Autofac; +using Autofac.Core; +using MAX.Core.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Store.Framework.DependencyInjection; + +internal class ServiceRegistrarProxy : IServiceRegistrar +{ + private readonly ContainerBuilder _services; + + public object Services => _services; + + public ServiceRegistrarProxy(ContainerBuilder builder) + { + _services = builder ?? throw new ArgumentNullException(nameof(builder)); + } + + // ---------------- Scoped ---------------- + + public void RegisterScoped(Func factory) + where TService : class + { + _services.Register(c => factory(c.Resolve())) + .As() + .InstancePerLifetimeScope(); + } + + public void RegisterScoped(object? serviceKey, + Func factory) + where TService : class + { + _services.Register(c => factory(c.Resolve(), serviceKey)) + .Keyed(serviceKey ?? throw new ArgumentNullException(nameof(serviceKey))) + .InstancePerLifetimeScope(); + } + + void IServiceRegistrar.RegisterScoped() + { + _services.RegisterType() + .As() + .InstancePerLifetimeScope(); + } + + void IServiceRegistrar.RegisterScoped(object? serviceKey) + { + _services.RegisterType() + .Keyed(serviceKey ?? throw new ArgumentNullException(nameof(serviceKey))) + .InstancePerLifetimeScope(); + } + + // ---------------- Singleton ---------------- + + public void RegisterSingleton(TService instance) + where TService : class + { + _services.RegisterInstance(instance) + .As() + .SingleInstance(); + } + + public void RegisterSingleton(Func factory) + where TService : class + { + _services.Register(c => factory(c.Resolve())) + .As() + .SingleInstance(); + } + + public void RegisterSingleton(object? serviceKey, TService instance) + where TService : class + { + _services.RegisterInstance(instance) + .Keyed(serviceKey ?? throw new ArgumentNullException(nameof(serviceKey))) + .SingleInstance(); + } + + public void RegisterSingleton(object? serviceKey, + Func factory) + where TService : class + { + _services.Register(c => factory(c.Resolve(), serviceKey)) + .Keyed(serviceKey ?? throw new ArgumentNullException(nameof(serviceKey))) + .SingleInstance(); + } + + void IServiceRegistrar.RegisterSingleton() + { + _services.RegisterType() + .As() + .SingleInstance(); + } + + void IServiceRegistrar.RegisterSingleton(object? serviceKey) + { + _services.RegisterType() + .Keyed(serviceKey ?? throw new ArgumentNullException(nameof(serviceKey))) + .SingleInstance(); + } + + // ---------------- Transient ---------------- + + public void RegisterTransient(Func factory) + where TService : class + { + _services.Register(c => factory(c.Resolve())) + .As() + .InstancePerDependency(); + } + + public void RegisterTransient(object? serviceKey, + Func factory) + where TService : class + { + _services.Register(c => factory(c.Resolve(), serviceKey)) + .Keyed(serviceKey ?? throw new ArgumentNullException(nameof(serviceKey))) + .InstancePerDependency(); + } + + void IServiceRegistrar.RegisterTransient() + { + _services.RegisterType() + .As() + .InstancePerDependency(); + } + + void IServiceRegistrar.RegisterTransient(object? serviceKey) + { + _services.RegisterType() + .Keyed(serviceKey ?? throw new ArgumentNullException(nameof(serviceKey))) + .InstancePerDependency(); + } +} \ No newline at end of file diff --git a/Store.Framework/Dtos/SignRequestBase.cs b/Store.Framework/Dtos/SignRequestBase.cs new file mode 100644 index 0000000..7c6cc8b --- /dev/null +++ b/Store.Framework/Dtos/SignRequestBase.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; +using System; + +namespace Store.Framework.Dtos; + +/// +/// Sign签名 +/// +public class SignRequestBase +{ + public string time { get; set; } + public string code { get; set; } + public string code_version { get; set; } + public string version { get; set; } + [JsonProperty("user_name")] + public string UserName { get; set; } + public string path { get; set; } +} + diff --git a/KJD_WebService/Core/Logger/NLogger.cs b/Store.Framework/Logger/NLogger.cs similarity index 100% rename from KJD_WebService/Core/Logger/NLogger.cs rename to Store.Framework/Logger/NLogger.cs diff --git a/KJD_WebService/Model/ApiResult.cs b/Store.Framework/Models/ApiResult.cs similarity index 98% rename from KJD_WebService/Model/ApiResult.cs rename to Store.Framework/Models/ApiResult.cs index b8d40cb..294cdf6 100644 --- a/KJD_WebService/Model/ApiResult.cs +++ b/Store.Framework/Models/ApiResult.cs @@ -1,8 +1,7 @@ - using System; using System.Collections.Generic; -namespace KJD_Web.Model +namespace Store.Framework.Models { public static class ResultCode { diff --git a/Store.Framework/Startup.cs b/Store.Framework/Startup.cs new file mode 100644 index 0000000..9205df9 --- /dev/null +++ b/Store.Framework/Startup.cs @@ -0,0 +1,25 @@ +using MAX.Core.DependencyInjection; +using MAX.Core.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Framework; + +internal class Startup : IStartup +{ + public int Priority => IntPriority.Normal; + + public void Initialize() + { + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; + } + + public void Register(IServiceRegistrar registrar) + { + + } +} diff --git a/Store.Framework/Store.Framework.csproj b/Store.Framework/Store.Framework.csproj index 93e965e..80c6ac7 100644 --- a/Store.Framework/Store.Framework.csproj +++ b/Store.Framework/Store.Framework.csproj @@ -2,15 +2,24 @@ enable - enable + + + + + + + + + + diff --git a/KJD_WebService/Utils/ApiRequestLogger.cs b/Store.Framework/Utils/ApiRequestLogger.cs similarity index 100% rename from KJD_WebService/Utils/ApiRequestLogger.cs rename to Store.Framework/Utils/ApiRequestLogger.cs diff --git a/KJD_WebService/Utils/EncrypUtil.cs b/Store.Framework/Utils/EncrypUtil.cs similarity index 100% rename from KJD_WebService/Utils/EncrypUtil.cs rename to Store.Framework/Utils/EncrypUtil.cs diff --git a/KJD_WebService/Utils/Extension/Extension.Convert.cs b/Store.Framework/Utils/Extension/Extension.Convert.cs similarity index 100% rename from KJD_WebService/Utils/Extension/Extension.Convert.cs rename to Store.Framework/Utils/Extension/Extension.Convert.cs diff --git a/KJD_WebService/Utils/Extension/Extension.Enum.cs b/Store.Framework/Utils/Extension/Extension.Enum.cs similarity index 100% rename from KJD_WebService/Utils/Extension/Extension.Enum.cs rename to Store.Framework/Utils/Extension/Extension.Enum.cs diff --git a/KJD_WebService/Utils/Extension/Extension.Exception.cs b/Store.Framework/Utils/Extension/Extension.Exception.cs similarity index 100% rename from KJD_WebService/Utils/Extension/Extension.Exception.cs rename to Store.Framework/Utils/Extension/Extension.Exception.cs diff --git a/KJD_WebService/Utils/Extension/Extension.Linq.cs b/Store.Framework/Utils/Extension/Extension.Linq.cs similarity index 100% rename from KJD_WebService/Utils/Extension/Extension.Linq.cs rename to Store.Framework/Utils/Extension/Extension.Linq.cs diff --git a/KJD_WebService/Utils/Extension/Extension.Validate.cs b/Store.Framework/Utils/Extension/Extension.Validate.cs similarity index 100% rename from KJD_WebService/Utils/Extension/Extension.Validate.cs rename to Store.Framework/Utils/Extension/Extension.Validate.cs diff --git a/KJD_WebService/Utils/Helper/HtmlHelper.cs b/Store.Framework/Utils/Helper/HtmlHelper.cs similarity index 100% rename from KJD_WebService/Utils/Helper/HtmlHelper.cs rename to Store.Framework/Utils/Helper/HtmlHelper.cs diff --git a/KJD_WebService/Utils/Helper/JsonHelper.cs b/Store.Framework/Utils/Helper/JsonHelper.cs similarity index 100% rename from KJD_WebService/Utils/Helper/JsonHelper.cs rename to Store.Framework/Utils/Helper/JsonHelper.cs diff --git a/KJD_WebService/Utils/Helper/ReflectionHelper.cs b/Store.Framework/Utils/Helper/ReflectionHelper.cs similarity index 100% rename from KJD_WebService/Utils/Helper/ReflectionHelper.cs rename to Store.Framework/Utils/Helper/ReflectionHelper.cs diff --git a/KJD_WebService/Utils/Helper/RegexHelper.cs b/Store.Framework/Utils/Helper/RegexHelper.cs similarity index 100% rename from KJD_WebService/Utils/Helper/RegexHelper.cs rename to Store.Framework/Utils/Helper/RegexHelper.cs diff --git a/KJD_WebService/Utils/HtmlUtils.cs b/Store.Framework/Utils/HtmlUtils.cs similarity index 99% rename from KJD_WebService/Utils/HtmlUtils.cs rename to Store.Framework/Utils/HtmlUtils.cs index ccddaea..77d2208 100644 --- a/KJD_WebService/Utils/HtmlUtils.cs +++ b/Store.Framework/Utils/HtmlUtils.cs @@ -1,5 +1,4 @@ -using KJD_Web.Upload.Prod; -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; diff --git a/KJD_WebService/Utils/HttpUtils.cs b/Store.Framework/Utils/HttpUtils.cs similarity index 99% rename from KJD_WebService/Utils/HttpUtils.cs rename to Store.Framework/Utils/HttpUtils.cs index 1a1b678..ab164a7 100644 --- a/KJD_WebService/Utils/HttpUtils.cs +++ b/Store.Framework/Utils/HttpUtils.cs @@ -184,8 +184,9 @@ namespace KJD_Web.Utils } else request = (HttpWebRequest)WebRequest.Create(url); + - request.KeepAlive = true; + request.KeepAlive = true; request.Headers.Add("sec-ch-ua", @"""Not(A:Brand"";v=""8"", ""Chromium"";v=""144"", ""Google Chrome"";v=""144"""); request.Headers.Add("sec-ch-ua-mobile", @"?0"); request.Headers.Add("sec-ch-ua-platform", @"""Windows"""); diff --git a/KJD_WebService/Utils/JsonUtils.cs b/Store.Framework/Utils/JsonUtils.cs similarity index 100% rename from KJD_WebService/Utils/JsonUtils.cs rename to Store.Framework/Utils/JsonUtils.cs diff --git a/KJD_WebService/Utils/MySqlUtils.cs b/Store.Framework/Utils/MySqlUtils.cs similarity index 99% rename from KJD_WebService/Utils/MySqlUtils.cs rename to Store.Framework/Utils/MySqlUtils.cs index a21ab1a..a1372c2 100644 --- a/KJD_WebService/Utils/MySqlUtils.cs +++ b/Store.Framework/Utils/MySqlUtils.cs @@ -7,7 +7,6 @@ using System.IO; using System.Linq; using System.Threading; using System.Web; -using System.Web.UI.WebControls.WebParts; namespace KJD_Web.Utils { diff --git a/KJD_WebService/Utils/RandomUtils.cs b/Store.Framework/Utils/RandomUtils.cs similarity index 100% rename from KJD_WebService/Utils/RandomUtils.cs rename to Store.Framework/Utils/RandomUtils.cs diff --git a/KJD_WebService/Utils/RegexUtils.cs b/Store.Framework/Utils/RegexUtils.cs similarity index 100% rename from KJD_WebService/Utils/RegexUtils.cs rename to Store.Framework/Utils/RegexUtils.cs diff --git a/KJD_WebService/Utils/TimeUtils.cs b/Store.Framework/Utils/TimeUtils.cs similarity index 100% rename from KJD_WebService/Utils/TimeUtils.cs rename to Store.Framework/Utils/TimeUtils.cs diff --git a/Store.SyncLogistics/Apis/VirtualLogisticsApi.cs b/Store.SyncLogistics/Apis/VirtualLogisticsApi.cs new file mode 100644 index 0000000..65a3378 --- /dev/null +++ b/Store.SyncLogistics/Apis/VirtualLogisticsApi.cs @@ -0,0 +1,253 @@ +using MAX.Core.Commons; +using MAX.Core.Cryptography; +using MAX.Core.DependencyInjection; +using MAX.Core.Helpers; +using MAX.Core.Logger; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; +using Store.VirtualLogistics.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; + +namespace Store.VirtualLogistics.Apis; + +public class VirtualLogisticsApi : ISingletonDependency +{ + private const string Host = "https://yuncang.df8899.top"; + private readonly ILoggerFacade _logger; + protected string UserName = "18756025201"; + protected string Passworkd = "aj123456"; + public VirtualLogisticsApi(ILoggerFacade logger) + { + _logger = logger; + } + public string GetSign(long unix) + { + string pwd = MD5Helper.Md5_Lower32(MD5Helper.Md5_Lower32(Passworkd)); + string s = $"{UserName}{pwd}{unix}"; + string sign = MD5Helper.Md5_Lower32(s); + return sign; + } + + public List GetExpressList() + { + var ret = ServerRequest>(new HttpItem + { + URL = "/api/lipin/list", + Method = MethodType.Post + }); + return ret.Data; + } + public UserMoney GetUserMoney() + { + var ret = ServerRequest(new HttpItem + { + URL = "/api/user/index", + Method = MethodType.Post + }); + return ret.Data; + } + + /// + /// 获取仓库列表及价格 + /// + public List GetCangList() + { + var ret = ServerRequest>(new HttpItem + { + URL = "/api/lipin/cang", + Method = MethodType.Post + }); + return ret.Data; + } + + /// + /// 获取礼品列表及价格 + /// + /// 快递ID,可选 + public List GetGoodsList(string kuaidiId = null) + { + var ret = ServerRequest>(new HttpItem + { + URL = "/api/lipin/goods", + Method = MethodType.Post + }); + return ret.Data; + } + + /// + /// 购买快递 + /// + public Result> Buy(BuyRequest request) + { + var res = ServerRequest>(new HttpItem + { + URL = $"/api/lipin/buy", + Postdata = JsonConvert.SerializeObject(request), + Method = MethodType.Post, + ContentType = "application/json", + }); + var ret = new Result>() + { + Code = res.Code, + Success = res.Success, + Msg = res.Msg, + }; + if (res.Success) + { + ret.Data = res.Data.Values.ToList(); + } + return ret; + } + /// + /// 查询快递订单信息及状态 + /// + /// 查询参数 + /// + public Result> QueryOrderStatus(string[] orderIds) + { + var res = ServerRequest>(new HttpItem + { + URL = "/api/lipin/status", + Postdata = JsonConvert.SerializeObject(new + { + order_sn = orderIds + }), + Method = MethodType.Post + }); + var ret = new Result>() + { + Code = res.Code, + Success = res.Success, + Msg = res.Msg, + }; + if (res.Success) + { + ret.Data = res.Data.Values.ToList(); + } + return ret; + } + /// + /// 上传取件码 + /// + public Result> UploadPickup(List data) + { + string postdata = $"username={UserName}&data={WebUtility.UrlEncode(Serialize(data))}"; + return ServerRequest>(new HttpItem + { + URL = "/api/lipin/uploadPickup", + Postdata = postdata, + Method = MethodType.Post + }); + } + + /// + /// 使用蛇形命名序列化对象,忽略空值,用于拼接表单参数 + /// + private static string Serialize(object obj) + { + var settings = new JsonSerializerSettings + { + ContractResolver = new DefaultContractResolver + { + NamingStrategy = new SnakeCaseNamingStrategy() + }, + NullValueHandling = NullValueHandling.Ignore + }; + return JsonConvert.SerializeObject(obj, settings); + } + + + /// + /// 服务器请求 + /// + /// + /// + public Result ServerRequest(HttpItem item) + { + try + { + if (!item.URL.StartsWith("http")) + { + item.URL = Host + item.URL; + } + item.Encoding = Encoding.UTF8; + + item.ProxyIp = "127.0.0.1:8080"; + + long unix = DateHelper.GetCurrentUnixTimestampInSeconds(); + + string sign = GetSign(unix); + HttpHelper http = new HttpHelper(); + item.ContentType = "application/json"; + var auth = new + { + username = UserName, + sid = unix, + sign = sign, + }; + if (string.IsNullOrWhiteSpace(item.Postdata)) + { + item.Postdata = JsonConvert.SerializeObject(auth); + } + else + { + var obj = JObject.Parse(item.Postdata); + obj["username"] = auth.username; + obj["sid"] = auth.sid; + obj["sign"] = auth.sign; + item.Postdata = obj.ToString(); + } + if (item.ContentType != "application/json") + { + if (!string.IsNullOrEmpty(item.Postdata)) + { + if (item.Postdata.StartsWith("&") == false) item.Postdata = "&" + item.Postdata; + } + item.Postdata = $"username={UserName}&sid={unix}&sign={sign}" + item.Postdata; + } + item.PostDataType = PostDataType.String; + item.PostEncoding = Encoding.UTF8; + HttpResult result = http.GetHtml(item); + if (result.StatusCode == HttpStatusCode.OK) + { + var settings = new JsonSerializerSettings + { + ContractResolver = new DefaultContractResolver + { + NamingStrategy = new SnakeCaseNamingStrategy() + } + }; + var res = JsonConvert.DeserializeObject>(result.Html, settings); + if (res.Code == 0) + { + return new Result + { + Success = true, + Code = res.Code, + Msg = res.Msg, + Data = res.Data.ToObject(JsonSerializer.CreateDefault(settings)) + }; + } + else + { + throw new Exception($"{res.Msg}:{res.Data["error"]?.ToString() ?? ""}"); + } + } + throw new Exception(result.Html); + } + catch (Exception ex) + { + _logger.Error(ex, $"VirtualLogisticsApi:{item.URL}\n{item.Postdata}"); + return new Result + { + Code = -1, + Msg = ex.Message, + }; + } + } +} diff --git a/Store.SyncLogistics/Controllers/LogisticsController.cs b/Store.SyncLogistics/Controllers/LogisticsController.cs new file mode 100644 index 0000000..9e4ae48 --- /dev/null +++ b/Store.SyncLogistics/Controllers/LogisticsController.cs @@ -0,0 +1,146 @@ +using Store.Framework.Base; +using Store.Framework.Models; +using Store.VirtualLogistics.Apis; +using Store.VirtualLogistics.Entitys; +using Store.VirtualLogistics.Models; +using Store.VirtualLogistics.Services; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Web.Http; + +namespace Store.VirtualLogistics.Controllers; + +[RoutePrefix("wxxd_vlc")] +[Route("{action}")] +public class LogisticsController : ApiControllerBase +{ + private readonly VirtualLogisticsService _logisticsService; + + public LogisticsController(VirtualLogisticsService logisticsService) + { + _logisticsService = logisticsService; + } + + /// + /// 获取账户余额 + /// + [HttpPost] + [HttpGet] + public IHttpActionResult GetUserMoney() + { + ApiResultBase ret = ApiResult.Fail(); + try + { + var money = _logisticsService.GetUserMoney(); + ret = ApiResult.Success(money); + } + catch (Exception ex) + { + ret = ApiResult.Fail(ex.Message); + } + return Ok(ret); + } + + /// + /// 获取快递列表 + /// + [HttpPost] + public IHttpActionResult GetExpressList() + { + try + { + var list = _logisticsService.GetExpressList(); + return Ok(ApiResult.Success(list)); + } + catch (Exception ex) + { + return Ok(ApiResult.Fail(ex.Message)); + } + } + + /// + /// 获取仓库列表及价格 + /// + [HttpPost] + public IHttpActionResult GetCangList() + { + try + { + var list = _logisticsService.GetCangList(); + return Ok(ApiResult.Success(list)); + } + catch (Exception ex) + { + return Ok(ApiResult.Fail(ex.Message)); + } + } + + /// + /// 获取礼品列表及价格 + /// + [HttpPost] + public IHttpActionResult GetGoodsList(string kuaidiId = null) + { + try + { + var list = _logisticsService.GetGoodsList(kuaidiId); + return Ok(ApiResult.Success(list)); + } + catch (Exception ex) + { + return Ok(ApiResult.Fail(ex.Message)); + } + } + + /// + /// 购买快递 + /// + [HttpPost] + public IHttpActionResult Buy(string orderId) + { + try + { + var result = _logisticsService.Buy(orderId, ""); + return Ok(ApiResult.Success(result)); + } + catch (Exception ex) + { + return Ok(ApiResult.Fail(ex.Message)); + } + } + + /// + /// 上传取件码 + /// + [HttpPost] + public IHttpActionResult UploadPickup(List data) + { + try + { + var result = _logisticsService.UploadPickup(data); + return Ok(result.Success + ? ApiResult>.Success(result.Data, result.Msg) + : ApiResult>.Fail(result.Msg, ResultCode.BusinessError, result.Data)); + } + catch (Exception ex) + { + return Ok(ApiResult.Fail(ex.Message)); + } + } + [HttpPost] + public IHttpActionResult GetSyncLogistics(string orderId) + { + try + { + var result = _logisticsService.GetSyncLogistics(orderId); + return Ok(ApiResult.Success(result)); + } + catch (Exception ex) + { + return Ok(ApiResult.Fail(ex.Message)); + } + } +} diff --git a/Store.SyncLogistics/Entitys/YhSyncLogistics.cs b/Store.SyncLogistics/Entitys/YhSyncLogistics.cs new file mode 100644 index 0000000..517a365 --- /dev/null +++ b/Store.SyncLogistics/Entitys/YhSyncLogistics.cs @@ -0,0 +1,41 @@ +using FreeSql.DataAnnotations; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.VirtualLogistics.Entitys; + +/// +/// 同步物流记录表 +/// +[Table(Name = "wxxd.yh_sync_logistics")] +public class YhSyncLogisticsEntity +{ + /// + /// id + /// + [Column(Name = "id", IsPrimary = true, IsIdentity = true)] + [JsonIgnore] + public long Id { get; set; } + + /// + /// 订单id + /// + [Column(Name = "order_id")] + public string OrderId { get; set; } + + /// + /// 同步物流编号 + /// + [Column(Name = "tracking_number")] + public string TrackingNumber { get; set; } + + /// + /// 原始物流编号 + /// + [Column(Name = "source_tracking_number")] + public string SourceTrackingNumber { get; set; } +} \ No newline at end of file diff --git a/Store.SyncLogistics/Models/BuyModels.cs b/Store.SyncLogistics/Models/BuyModels.cs new file mode 100644 index 0000000..a7676d9 --- /dev/null +++ b/Store.SyncLogistics/Models/BuyModels.cs @@ -0,0 +1,303 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.VirtualLogistics.Models; + +public class BuyRequest +{ + /// + /// 接入账号:本站注册账号 + /// + [JsonProperty("username")] + public string Username { get; set; } + + /// + /// 当前时间戳:1574769204 + /// + [JsonProperty("sid")] + public string Sid { get; set; } + + /// + /// + [JsonProperty("sign")] + public string Sign { get; set; } + + /// + /// 快递ID + /// + [JsonProperty("kuaidi_id")] + public string KuaidiId { get; set; } + + /// + /// 商品ID + /// + [JsonProperty("goods_id")] + public string GoodsId { get; set; } + + /// + /// 仓库ID + /// + [JsonProperty("cang_id")] + public string CangId { get; set; } + + /// + /// 平台,1淘宝 2拼多多 3京东 4抖音 5快手 6视频号 7阿里 8小红书 9淘工厂 + /// + [JsonProperty("platform")] + public int Platform { get; set; } + + /// + /// 是否授权下单,1是0否 + /// + [JsonProperty("auth")] + public int Auth { get; set; } + + /// + /// 发货人 + /// + [JsonProperty("send_name")] + public string SendName { get; set; } + + /// + /// 发货人手机 + /// + [JsonProperty("send_mobile")] + public string SendMobile { get; set; } + + /// + /// 发货人省份 + /// + [JsonProperty("send_province")] + public string SendProvince { get; set; } + + /// + /// 发货人城市 + /// + [JsonProperty("send_city")] + public string SendCity { get; set; } + + /// + /// 发货人区/县/镇 + /// + [JsonProperty("send_district")] + public string SendDistrict { get; set; } + + /// + /// 发货人详细地址 + /// + [JsonProperty("send_address")] + public string SendAddress { get; set; } + + /// + /// 收货地址列表,每次可传1000条;圆通实时包,每次最多100个 + /// + [JsonProperty("rec_list")] + public List RecList { get; set; } = new List(); +} +public class RecItem +{ + /// + /// 唯一订单号,生成一个不重复的字符串,字符串长度为16~40 + /// + [JsonProperty("order_sn")] + public string OrderSn { get; set; } + + /// + /// 收货人 + /// + [JsonProperty("rec_name")] + public string RecName { get; set; } + + /// + /// 收货人手机 + /// + [JsonProperty("rec_mobile")] + public string RecMobile { get; set; } + + /// + /// 收货人省份 + /// + [JsonProperty("rec_province")] + public string RecProvince { get; set; } + + /// + /// 收货人城市 + /// + [JsonProperty("rec_city")] + public string RecCity { get; set; } + + /// + /// 收货人区/县/镇 + /// + [JsonProperty("rec_district")] + public string RecDistrict { get; set; } + + /// + /// 收货人详细地址 + /// + [JsonProperty("rec_address")] + public string RecAddress { get; set; } + + /// + /// 授权下单必填,淘宝或拼多多等第三方平台订单号 + /// + [JsonProperty("shop_order")] + public string ShopOrder { get; set; } + + /// + /// 无货源必填,补发单号 + /// + [JsonProperty("other_kuaidi_sn")] + public string OtherKuaidiSn { get; set; } + + /// + /// 店铺名称 + /// + [JsonProperty("shop_name")] + public string ShopName { get; set; } + + /// + /// 淘宝授权发货必填 + /// + [JsonProperty("oaid")] + public string Oaid { get; set; } + + /// + /// 收货详细地址密文,拼多多、抖音授权发货必填 + /// + [JsonProperty("rec_address_encrypt")] + public string RecAddressEncrypt { get; set; } + + /// + /// 收货人姓名密文,拼多多、抖音授权发货必填 + /// + [JsonProperty("rec_name_encrypt")] + public string RecNameEncrypt { get; set; } + + /// + /// 收货人手机密文,拼多多、抖音授权发货必填 + /// + [JsonProperty("rec_phone_encrypt")] + public string RecPhoneEncrypt { get; set; } + + /// + /// 收货人手机号密文,京东授权发货必填 + /// + [JsonProperty("desen_mobile")] + public string DesenMobile { get; set; } + + /// + /// 收货人手机号密文,京东授权发货必填 + /// + [JsonProperty("desen_telephone")] + public string DesenTelephone { get; set; } + + /// + /// 订单店铺ID,快手授权发货必填 + /// + [JsonProperty("seller_open_id")] + public string SellerOpenId { get; set; } + + /// + /// 订单店铺昵称,快手授权发货必填 + /// + [JsonProperty("seller_nick")] + public string SellerNick { get; set; } + + /// + /// 授权发货必填 + /// + [JsonProperty("encrypt_datas")] + public EncryptDatas EncryptDatas { get; set; } + + + + /// + /// 编码,供应商代发时必填 + /// + [JsonProperty("ewaybill_order_code")] + public string EwaybillOrderCode { get; set; } + + /// + /// 扩展参数,视频号供应商代发时必填 + /// + [JsonProperty("extend_params")] + public ExtendParams ExtendParams { get; set; } + + +} + +public class EncryptDatas +{ + /// + /// 供应商订单ID,视频号供应商代发时必填 + /// + [JsonProperty("ds_order_id")] + public string DsOrderId { get; set; } + + /// + /// 视频号必填,自研时传店铺 appId,供应商传相应该的值 + /// + [JsonProperty("from_shop_code")] + public string FromShopCode { get; set; } + + /// + /// 订单商品sku_id,视频号必填 + /// + [JsonProperty("sku_id")] + public string SkuId { get; set; } + + /// + /// 订单商品id,视频号必填 + /// + [JsonProperty("product_id")] + public string ProductId { get; set; } + + /// + /// 订单商品数量,视频号必填 + /// + [JsonProperty("sku_cnt")] + public string SkuCnt { get; set; } +} +public class ExtendParams +{ + /// + /// 供应商订单ID,视频号供应商代发时必填 + /// + [JsonProperty("token")] + public string Token { get; set; } + + /// + /// 供应商编码,取决上家快递使用供应商,视频号供应商代发时必填 + /// + [JsonProperty("api")] + public string Api { get; set; } + /// + /// 供应商订单ID,视频号供应商代发时必填 + /// + [JsonProperty("ds_order_id")] + public string DsOrderId { get; set; } +} +public class BuyResultItem +{ + /// + /// 原传入的唯一订单号 + /// + public string OrderSn { get; set; } + + /// + /// 快递单号 + /// + public string KuaidiSn { get; set; } + + /// + /// 快递状态:2待揽件 3已揽件 + /// + public string Status { get; set; } + + public string OtherKuaidiSn { get; set; } +} diff --git a/Store.SyncLogistics/Models/CangItem.cs b/Store.SyncLogistics/Models/CangItem.cs new file mode 100644 index 0000000..41eb762 --- /dev/null +++ b/Store.SyncLogistics/Models/CangItem.cs @@ -0,0 +1,36 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.VirtualLogistics.Models; + +public class CangItem +{ + /// + /// 仓库ID + /// + public string CangId { get; set; } + + /// + /// 省 + /// + public string SendProvince { get; set; } + + /// + /// 市 + /// + public string SendCity { get; set; } + + /// + /// 区 + /// + public string SendDistrict { get; set; } + + /// + /// 地址 + /// + public string SendAddress { get; set; } +} diff --git a/Store.SyncLogistics/Models/ExpressItem.cs b/Store.SyncLogistics/Models/ExpressItem.cs new file mode 100644 index 0000000..67ae336 --- /dev/null +++ b/Store.SyncLogistics/Models/ExpressItem.cs @@ -0,0 +1,71 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.VirtualLogistics.Models; + +public class ExpressItem +{ + /// + /// 快递名称 + /// + public string Name { get; set; } + + /// + /// 快递价格 + /// + public string Price { get; set; } + + /// + /// 快递说明 + /// + public string Tips { get; set; } + + /// + /// 上次交单时间,实时快递忽略;该字段一般用于快递同步发货判定 + /// + public string ExportTime { get; set; } + + /// + /// 是否允许取消 1是 0否 + /// + public string IsCancel { get; set; } + + /// + /// 是否淘宝专用 1是 0否 + /// + public string IsTb { get; set; } + + /// + /// 是否天猫专用 1是 0否 + /// + public string IsTm { get; set; } + + /// + /// 是否阿里专用 1是 0否 + /// + public string IsAl { get; set; } + + /// + /// 是否京东专用 1是 0否 + /// + public string IsJd { get; set; } + + /// + /// 是否拼多多专用 1是 0否 + /// + public string IsPdd { get; set; } + + /// + /// 是否菜鸟面单、无界面单、电子面单 1是 0否 + /// + public string IsDz { get; set; } + + /// + /// 是否需要上传单号 1是 0否 + /// + public string IsZy { get; set; } +} \ No newline at end of file diff --git a/Store.SyncLogistics/Models/GoodsItem.cs b/Store.SyncLogistics/Models/GoodsItem.cs new file mode 100644 index 0000000..983f372 --- /dev/null +++ b/Store.SyncLogistics/Models/GoodsItem.cs @@ -0,0 +1,36 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.VirtualLogistics.Models; + +public class GoodsItem +{ + /// + /// 礼品ID + /// + public string GoodsId { get; set; } + + /// + /// 礼品名称 + /// + public string GoodsName { get; set; } + + /// + /// 礼品重量 + /// + public string GoodsWeight { get; set; } + + /// + /// 礼品价格 + /// + public string Price { get; set; } + + /// + /// 礼品图片 + /// + public string GoodsPic { get; set; } +} diff --git a/Store.SyncLogistics/Models/OrderStatusQuery.cs b/Store.SyncLogistics/Models/OrderStatusQuery.cs new file mode 100644 index 0000000..2672367 --- /dev/null +++ b/Store.SyncLogistics/Models/OrderStatusQuery.cs @@ -0,0 +1,45 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.VirtualLogistics.Models; + +/// +/// 查询快递订单信息及状态 - 请求参数 +/// +public class OrderStatusQuery +{ + /// + /// 查询唯一订单号(支持多个) + /// + public List order_sn { get; set; } + + /// + /// 查询唯一订单号(支持多个) + /// + public List kuaidi_sn { get; set; } +} +/// +/// 查询快递订单信息及状态 - 响应结果项 +/// +public class OrderStatusResultItem +{ + /// + /// 原传入唯一订单号 + /// + public string OrderSn { get; set; } + + /// + /// 快递单号 + /// + public string KuaidiSn { get; set; } + + /// + /// 快递状态:2待揽件 3已揽件 + /// + public string Status { get; set; } + public decimal Price { get; set; } +} \ No newline at end of file diff --git a/Store.SyncLogistics/Models/PickupModels.cs b/Store.SyncLogistics/Models/PickupModels.cs new file mode 100644 index 0000000..469731a --- /dev/null +++ b/Store.SyncLogistics/Models/PickupModels.cs @@ -0,0 +1,39 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.VirtualLogistics.Models; + +public class PickupItem +{ + /// + /// 上家快递单号 + /// + public string BufaSn { get; set; } + + /// + /// 取件码 + /// + public string PickupCode { get; set; } + + /// + /// 驿站地址 + /// + public string Address { get; set; } +} + +public class PickupResultItem +{ + /// + /// 上家单号 + /// + public string BufaSn { get; set; } + + /// + /// 是否成功 + /// + public bool Success { get; set; } +} diff --git a/Store.SyncLogistics/Models/UserInfo.cs b/Store.SyncLogistics/Models/UserInfo.cs new file mode 100644 index 0000000..7e3b541 --- /dev/null +++ b/Store.SyncLogistics/Models/UserInfo.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.VirtualLogistics.Models; + +public class UserMoney +{ + public decimal Money { get; set; } + public decimal ExpendMoney { get; set; } +} diff --git a/Store.SyncLogistics/Services/RandomNameHelper.cs b/Store.SyncLogistics/Services/RandomNameHelper.cs new file mode 100644 index 0000000..c03c7c8 --- /dev/null +++ b/Store.SyncLogistics/Services/RandomNameHelper.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.VirtualLogistics.Services; + +internal static class RandomNameHelper +{ + private static readonly string[] Surnames = + { + "赵", "钱", "孙", "李", "周", "吴", "郑", "王", "冯", "陈", + "褚", "卫", "蒋", "沈", "韩", "杨", "朱", "秦", "尤", "许", + "何", "吕", "施", "张", "孔", "曹", "严", "华", "金", "魏", + "陶", "姜", "戚", "谢", "邹", "喻", "柏", "水", "窦", "章", + "苏", "潘", "葛", "奚", "范", "彭", "郎", "鲁", "韦", "昌" + }; + + private static readonly string[] Titles = { "先生", "女士" }; + + private static readonly Random _random = new Random(); + + /// + /// 随机获取 姓+先生/女士 + /// + public static string GetRandomName() + { + var surname = Surnames[_random.Next(Surnames.Length)]; + var title = Titles[_random.Next(Titles.Length)]; + return $"{surname}{title}"; + } + + /// + /// 批量随机获取 + /// + public static List GetRandomNames(int count) + { + var list = new List(count); + for (int i = 0; i < count; i++) + { + list.Add(GetRandomName()); + } + return list; + } +} \ No newline at end of file diff --git a/Store.SyncLogistics/Services/VirtualLogisticsService.cs b/Store.SyncLogistics/Services/VirtualLogisticsService.cs new file mode 100644 index 0000000..20eb178 --- /dev/null +++ b/Store.SyncLogistics/Services/VirtualLogisticsService.cs @@ -0,0 +1,164 @@ +using MAX.Core.Commons; +using MAX.Core.DependencyInjection; +using MAX.Core.Logger; +using Store.Contracts.Order.Entitys; +using Store.VirtualLogistics.Apis; +using Store.VirtualLogistics.Entitys; +using Store.VirtualLogistics.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.VirtualLogistics.Services; + + +public class VirtualLogisticsService : ITransientDependency +{ + private IFreeSql _freeSql; + private readonly ILoggerFacade _logger; + private readonly VirtualLogisticsApi _api; + + public VirtualLogisticsService(VirtualLogisticsApi api, IFreeSql freeSql, ILoggerFacade logger) + { + _freeSql = freeSql ?? throw new ArgumentNullException(nameof(freeSql)); + _logger = logger; + _api = api; + } + + /// + /// 获取账户余额 + /// + public decimal GetUserMoney() + { + var user = _api.GetUserMoney(); + return user.Money; + } + + /// + /// 获取快递列表 + /// + public List GetExpressList() + { + return _api.GetExpressList(); + } + + /// + /// 获取仓库列表及价格 + /// + public List GetCangList() + { + return _api.GetCangList(); + } + + /// + /// 获取礼品列表及价格 + /// + /// 快递ID,可选 + public List GetGoodsList(string kuaidiId = null) + { + return _api.GetGoodsList(kuaidiId); + } + + /// + /// 购买快递 + /// + public List Buy(string orderId, string ds_orderId) + { + var item = _freeSql.Select() + .Where(a => a.OrderSn == orderId) + .First(); + if (item == null) throw new Exception("错误的订单号"); + + var w_order = _freeSql.Select() + .Where(a => a.OrderId == item.WxOrderId) + .First(); + + if (w_order == null) throw new Exception("错误的订单号"); + var productItem = _freeSql.Select() + .Where(a => a.OrderId == w_order.OrderId) + .First(); + if (productItem == null) throw new Exception("错误的订单详情号"); + + BuyRequest request = new BuyRequest() + { + KuaidiId = "874", + GoodsId = "8473", + CangId = "914", + Platform = 6, + Auth = 1, + SendName = RandomNameHelper.GetRandomName(), + SendMobile = "13024622251", + SendProvince = "上海市", + SendCity = "上海市", + SendDistrict = "普陀区", + SendAddress = "真如镇街道真北路2653弄F区808号楼", + RecList = [ + new RecItem + { + OrderSn = orderId, + RecName = w_order.UserName, + RecMobile = w_order.TelNumber, + RecProvince = w_order.ProvinceName, + RecCity = w_order.CityName, + RecDistrict = w_order.CountyName, + RecAddress = w_order.DetailInfo, + EncryptDatas=new EncryptDatas + { + DsOrderId=ds_orderId, + SkuId = productItem.SkuId, + ProductId = productItem.ProductId, + SkuCnt = productItem.SkuCnt.ToString(), + }, + OtherKuaidiSn= "6738192431"//item.TrackingNumber, + + } + ] + }; + Result> ret = _api.Buy(request); + if (ret.Success == false) + { + _logger.Error($"购买同步单错误:{ret.Msg}"); + throw new Exception("购买同步单失败"); + } + + + List yhSyncs = new List(); + foreach (var sync in ret.Data) + { + yhSyncs.Add(new YhSyncLogisticsEntity + { + OrderId = sync.OrderSn, + TrackingNumber = sync.KuaidiSn, + SourceTrackingNumber = sync.OtherKuaidiSn + }); + } + + var count = _freeSql.Insert(yhSyncs).ExecuteAffrows(); + if (count != yhSyncs.Count) + { + _logger.Error("购买同步单提交到数据库错误"); + throw new Exception("记录同步单失败"); + } + return yhSyncs; + + } + + /// + /// 上传取件码 + /// + public Result> UploadPickup(List data) + { + return _api.UploadPickup(data); + } + + public YhSyncLogisticsEntity GetSyncLogistics(string orderId) + { + var item = _freeSql.Select() + .Where(a => a.OrderId == orderId).First(); + + return item; + } + +} diff --git a/Store.SyncLogistics/Store.SyncLogistics.csproj b/Store.SyncLogistics/Store.SyncLogistics.csproj new file mode 100644 index 0000000..e8cf4d1 --- /dev/null +++ b/Store.SyncLogistics/Store.SyncLogistics.csproj @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/test/WebServiceTest/App.config b/test/WebServiceTest/App.config index 937ebae..48fdf49 100644 --- a/test/WebServiceTest/App.config +++ b/test/WebServiceTest/App.config @@ -5,9 +5,9 @@ --> - + - + diff --git a/test/WebServiceTest/GiftLogisticsTest.cs b/test/WebServiceTest/GiftLogisticsTest.cs new file mode 100644 index 0000000..372d92a --- /dev/null +++ b/test/WebServiceTest/GiftLogisticsTest.cs @@ -0,0 +1,153 @@ +using Autofac; +using Autofac.Integration.WebApi; +using KJD_Web; +using KJD_Web.Services.Users; +using MAX.Core.Cryptography; +using MAX.Core.Helpers; +using Store.Framework.DependencyInjection; +using Store.VirtualLogistics.Apis; +using Store.VirtualLogistics.Models; +using Store.VirtualLogistics.Services; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using System.Web.Http; + +namespace WebServiceTest; + +internal class GiftLogisticsTest +{ + private static NUnitDebugListener? _listener; + private IContainer _container; + private VirtualLogisticsApi logisticsApi; + + [SetUp] + public void Setup() + { + _listener = new NUnitDebugListener(); + Debug.Listeners.Add(_listener); + var config = new HttpConfiguration(); + _container = ContainerConfig.BuildContainer(); + config.DependencyResolver = new AutofacWebApiDependencyResolver(_container); + WebApiConfig.Register(config); + logisticsApi = _container.Resolve(); + } + [TearDown] + public void TearDown() + { + _container?.Dispose(); + _listener.Dispose(); + } + [Test] + public void TestSign() + { + //new GiftLogisticsApi().GetSign(); + long sid = 1574769204; + string UserName = "liang"; + string Passworkd = "123456"; + string pwd = MD5Helper.Md5_Lower32(MD5Helper.Md5_Lower32(Passworkd)); + string s = $"{UserName}{pwd}{sid}"; + string sign = MD5Helper.Md5_Lower32(s); + Console.Write(sign); + + } + [Test] + public void GetUserMoneyTest() + { + var user = logisticsApi.GetUserMoney(); + Console.WriteLine(user.Money); + } + [Test] + public void GetExpressListTest() + { + var user = logisticsApi.GetExpressList(); + Console.WriteLine(string.Join(" ", user.Select(a => a.Name))); + } + [Test] + public void GetCangListTest() + { + var list = logisticsApi.GetCangList(); + Console.WriteLine(string.Join(" ", list.Select(a => $"{a.CangId}:{a.SendProvince}{a.SendCity}{a.SendDistrict}{a.SendAddress}"))); + } + [Test] + public void GetGoodsListTest() + { + var list = logisticsApi.GetGoodsList(); + Console.WriteLine(string.Join(" ", list.Select(a => $"{a.GoodsId}:{a.GoodsName}({a.Price})"))); + } + [Test] + public void QueryOrderStatus() + { + try + { + var ret = logisticsApi.QueryOrderStatus(["260705-168736857712823"]); + Console.WriteLine(string.Join(" ", ret.Data.Select(a => $"{a.OrderSn}:{a.KuaidiSn}({a.Status})"))); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + } + [Test] + public void BuyTest() + { + //var request = new BuyRequest + //{ + // KuaidiId = "874", + // GoodsId = "8473", + // CangId = "914", + // Platform = 6, + // Auth = 1, + // SendName = "张三", + // SendMobile = "18255185853", + // SendProvince = "辽宁省", + // SendCity = "大连市", + // SendDistrict = "甘井子区", + // SendAddress = "革镇堡幸福家园2栋", + // RecList = new List + // { + // new RecItem + // { + // OrderSn = "3739252814971847936", + // RecName = "李四", + // RecMobile = "18255185853", + // RecProvince = "湖南省", + // RecCity = "衡阳市", + // RecDistrict = "雁峰区", + // RecAddress = "和谐小区 1栋", + // OtherKuaidiSn="315097997688420" + // } + // } + //}; + //var ret = logisticsApi.Buy(request); + var userService = _container.Resolve(); + var ret = userService.Buy("260705-168736857712823", "4439341152678214144"); + if (ret != null) + { + Console.WriteLine(string.Join(" ", ret.Select(a => $"{a.OrderId}:{a.TrackingNumber}"))); + } + } + [Test] + public void UploadPickupTest() + { + var data = new List + { + new PickupItem + { + BufaSn = "SF1234567890", + PickupCode = "1-2-3", + Address = "菜鸟驿站" + } + }; + var ret = logisticsApi.UploadPickup(data); + Console.WriteLine($"{ret.Code} {ret.Msg}"); + if (ret.Data != null) + { + Console.WriteLine(string.Join(" ", ret.Data.Select(a => $"{a.BufaSn}:{a.Success}"))); + } + } +} diff --git a/test/WebServiceTest/UnitTest1.cs b/test/WebServiceTest/UnitTest1.cs index f30e788..19a3977 100644 --- a/test/WebServiceTest/UnitTest1.cs +++ b/test/WebServiceTest/UnitTest1.cs @@ -45,25 +45,25 @@ namespace WebServiceTest } [Test] - [TestCase("15266666666")] - [TestCase("15698175212")] - [TestCase("18238114231")] - [TestCase("15870095938")] - [TestCase("13388889999")] - [TestCase("13860701921")] - [TestCase("15907090308")] - [TestCase("17837688880")] - [TestCase("13682525364")] - [TestCase("13048056467")] - [TestCase("18317672222")] - [TestCase("18500143331")] + //[TestCase("15266666666")] + //[TestCase("15698175212")] + //[TestCase("18238114231")] + //[TestCase("15870095938")] + //[TestCase("13388889999")] + //[TestCase("13860701921")] + //[TestCase("15907090308")] + //[TestCase("17837688880")] + //[TestCase("13682525364")] + //[TestCase("13048056467")] + //[TestCase("18317672222")] + //[TestCase("18500143331")] [TestCase("17318585555")] - [TestCase("13459139999")] - [TestCase("15206200631")] - [TestCase("18737568536")] - [TestCase("18949822610")] - [TestCase("18165955361")] - [TestCase("18556577919")] + //[TestCase("13459139999")] + //[TestCase("15206200631")] + //[TestCase("18737568536")] + //[TestCase("18949822610")] + //[TestCase("18165955361")] + //[TestCase("18556577919")] public void ApplyOemWithdrawalTest(string name) { WXOemManage manage = new WXOemManage(); diff --git a/test/WebServiceTest/UserTest.cs b/test/WebServiceTest/UserTest.cs index 94aaa4f..4f773e6 100644 --- a/test/WebServiceTest/UserTest.cs +++ b/test/WebServiceTest/UserTest.cs @@ -3,6 +3,7 @@ using Autofac.Integration.WebApi; using KJD_Web; using KJD_Web.Services.Users; using Newtonsoft.Json; +using Store.Framework.DependencyInjection; using System; using System.Collections.Generic; using System.Diagnostics; @@ -48,10 +49,29 @@ internal class UserTest } [Test] - public void SettleOEM() + //[TestCase("15698175212")] + //[TestCase("18238114231")] + //[TestCase("15870095938")] + //[TestCase("13388889999")] + //[TestCase("15907090308")] + //[TestCase("17837688880")] + //[TestCase("13048056467")] + [TestCase("17318585555")] + //[TestCase("13459139999")] + //[TestCase("18737568536")] + //[TestCase("18949822610")] + //[TestCase("18165955361")] + //[TestCase("18556577919")] + //[TestCase("18317672222")] + //[TestCase("17153959999")] + //[TestCase("15056001263")] + //[TestCase("17737544938")] + //[TestCase("13306583931")] + //[TestCase("13938677077")] + public void SettleOEM(string userName) { var userService = _container.Resolve(); - var oem = userService.SettleOemFlows("13938677077"); + var oem = userService.SettleOemFlows(userName); Console.WriteLine(new { oem.AvailableBalance, @@ -77,4 +97,10 @@ internal class UserTest var data = userService.GetUserAmountSummary("15395107196"); Console.WriteLine(JsonConvert.SerializeObject(data)); } + //[Test] + //public void Fill() + //{ + // var userService = _container.Resolve(); + // userService.FillHistoryOemAmount(); + //} } diff --git a/test/WebServiceTest/WebServiceTest.csproj b/test/WebServiceTest/WebServiceTest.csproj index 48552a9..657808b 100644 --- a/test/WebServiceTest/WebServiceTest.csproj +++ b/test/WebServiceTest/WebServiceTest.csproj @@ -21,6 +21,7 @@ +