From 0a3e5af6a9da54158a978898076704289761a97d Mon Sep 17 00:00:00 2001 From: chfei Date: Fri, 11 Sep 2026 19:11:12 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=A1=B9=E7=9B=AE=EF=BC=8C?= =?UTF-8?q?=E5=88=86=E5=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Directory.Build.props | 24 + MAX.Core/Autofac/AutowiredAttribute.cs | 12 + MAX.Core/Autofac/CustomPropertySelector.cs | 23 + MAX.Core/Autofac/IDependencyRegistrar.cs | 9 + MAX.Core/Autofac/IocManager.cs | 107 ++ 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 + .../DependencyInjection/IScopedDependency.cs | 8 + .../DependencyInjection/IServiceRegistrar.cs | 81 ++ .../ISingletonDependency.cs | 8 + .../ITransientDependency.cs | 8 + .../LazyServiceProvider.cs | 36 + 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 | 71 + 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/RandomHelper.cs | 148 +++ MAX.Core/Helpers/ReflectionHelper.cs | 586 +++++++++ MAX.Core/Helpers/UriHelper.cs | 149 +++ MAX.Core/Infrastructure/Bootstrap.cs | 42 + MAX.Core/Infrastructure/IStartup.cs | 15 + MAX.Core/Infrastructure/SingleInstance.cs | 42 + MAX.Core/IntPriority.cs | 74 ++ MAX.Core/Logger/ConsoleLogger.cs | 124 ++ MAX.Core/Logger/ICustomLogger.cs | 12 + MAX.Core/Logger/ILoggerFacade.cs | 68 + MAX.Core/Logger/LogLevel.cs | 15 + MAX.Core/MAX.Core.csproj | 16 + MAX.Core/Reflections/AppTypeFinder.cs | 197 +++ MAX.Core/Reflections/Finder.cs | 129 ++ MAX.Core/Startup.cs | 74 ++ MAX.Framework/Commons/KeyValue.cs | 35 + MAX.Framework/Configs/ConfigBase.cs | 46 + MAX.Framework/Configs/GlobalConfig.cs | 28 + .../Controls/Attach/DialogWindowProperty.cs | 47 + MAX.Framework/Controls/ConfirmDialog.cs | 290 +++++ MAX.Framework/Extensions/StyleExtensions.cs | 93 ++ MAX.Framework/Helpers/ConfigHelper.cs | 74 ++ MAX.Framework/Helpers/FileHelper.cs | 181 +++ MAX.Framework/Helpers/ImageBuild.cs | 1071 +++++++++++++++ MAX.Framework/Helpers/ObjectHelper.cs | 77 ++ MAX.Framework/Helpers/StringTypeHelper.cs | 144 +++ MAX.Framework/Helpers/SystemHelper.cs | 39 + MAX.Framework/Helpers/WinCmdHelper.cs | 157 +++ MAX.Framework/Helpers/XmlHelper.cs | 292 +++++ MAX.Framework/Interfaces/IAppConfig.cs | 17 + MAX.Framework/Interfaces/IAppInitialize.cs | 38 + MAX.Framework/Interfaces/IFolderConfig.cs | 20 + MAX.Framework/Logger/NLogger.cs | 153 +++ MAX.Framework/MAX.Framework.csproj | 29 + .../Commands/AsyncDelegateCommand.cs | 243 ++++ .../Commands/AsyncDelegateCommand{T}.cs | 284 ++++ .../PrismExtension/Commands/IAsyncCommand.cs | 28 + .../AutofacContainerExtension.cs | 334 +++++ .../IAutofacContainerExtension.cs | 16 + .../Prism.Autofac/IPreRegisterTypes.cs | 9 + .../Prism.Autofac/PrismApplication.cs | 44 + .../Prism.Autofac/PrismBootstrapper.cs | 31 + .../Prism.Autofac/PrismIocExtensions.cs | 40 + .../PrismExtension/RegionManagerExtension.cs | 35 + MAX.Framework/Startup.cs | 59 + Store.Framework/Agent/AgentConfig.cs | 19 + Store.Framework/Apis/ApiResponse.cs | 17 + Store.Framework/Apis/CommonApi.cs | 241 ++++ Store.Framework/Apis/OrderApi.cs | 128 ++ Store.Framework/Apis/PriceApi.cs | 80 ++ Store.Framework/Apis/ProductApi.cs | 654 ++++++++++ Store.Framework/Apis/ProductThirdApi.cs | 125 ++ Store.Framework/Apis/ServerHttpBase.cs | 68 + Store.Framework/Apis/ShopApi.cs | 472 +++++++ Store.Framework/Apis/UserApi.cs | 398 ++++++ .../Attributes/HttpTryAttribute.cs | 57 + Store.Framework/Attributes/SleepAttribute.cs | 70 + Store.Framework/ClientInfo.cs | 46 + Store.Framework/Commons/ShopManage.cs | 121 ++ Store.Framework/Commons/SignTool.cs | 70 + Store.Framework/Config.cs | 53 + Store.Framework/Configs/GlobalConfig.cs | 26 + Store.Framework/Configs/IApiServerConfig.cs | 16 + .../Configs/IServiceConfigProvider.cs | 39 + Store.Framework/Configs/ServerConfig.cs | 21 + Store.Framework/Configs/ServiceConfigKeys.cs | 13 + .../Configs/ServiceConfigProvider.cs | 95 ++ .../ServiceConfigRegistrationExtensions.cs | 41 + Store.Framework/Enums/PlatformType.cs | 30 + .../Extensions/DictionaryExtension.cs | 35 + Store.Framework/Extensions/Extensions.cs | 560 ++++++++ .../Extensions/HttpItemExtensions.cs | 68 + .../Extensions/InitialDataMiddleware.cs | 50 + Store.Framework/Helpers/DataOpenSSL.cs | 144 +++ Store.Framework/Helpers/FileHelper.cs | 88 ++ Store.Framework/Helpers/ImageBuild.cs | 1012 +++++++++++++++ .../HttpExtension/AppApiRequestBase.cs | 159 +++ .../HttpExtension/ServerHttpItem.cs | 28 + .../HttpExtension/ServiceRequestBase.cs | 105 ++ .../HttpResultParser/HttpException.cs | 27 + .../HttpResultParser/HttpResultVerify.cs | 150 +++ .../HttpResultParser/IHttpResultVerify.cs | 19 + Store.Framework/Inerterfaces/ICurrentUser.cs | 20 + Store.Framework/Models/Cookies.cs | 102 ++ .../Models/Product/CategoryModel.cs | 24 + Store.Framework/Models/ShopBase.cs | 43 + Store.Framework/Models/UserInfo.cs | 162 +++ .../Services/CacheServerService.cs | 47 + Store.Framework/Services/CommonService.cs | 83 ++ .../Services/CurrentUserService.cs | 37 + Store.Framework/Services/ShopService.cs | 134 ++ Store.Framework/Services/UserService.cs | 132 ++ Store.Framework/Startup.cs | 25 + Store.Framework/Store.Framework.csproj | 23 + Store.Framework/ThreadManage/ThreadManager.cs | 188 +++ .../ThreadManage/ThreadManagerPool.cs | 28 + Store.Framework/ThreadManage/WorkAction.cs | 14 + Store.Framework/ThreadManage/WorkUnit.cs | 29 + Store.Framework/Tools/HardwareTool.cs | 155 +++ Store.Management.Framework/Apis/WXApi.cs | 708 ++++++++++ .../Caches/ImageBase.cs | 30 + .../Caches/ImageCache.cs | 95 ++ Store.Management.Framework/Class1.cs | 6 + .../Configs/ApiServerConfig.cs | 16 + .../Configs/ManagementServiceConfigKeys.cs | 13 + .../Helpers/ImageHelper.cs | 213 +++ Store.Management.Framework/Startup.cs | 38 + .../Store.Management.Framework.csproj | 12 + Store.Product/Class1.cs | 18 + Store.Product/Enums/BlockType.cs | 27 + Store.Product/Enums/FreightTemplateType.cs | 36 + Store.Product/Enums/NextStatus.cs | 27 + Store.Product/Enums/ProductStorageStatus.cs | 18 + Store.Product/Enums/StuffStatus.cs | 11 + .../Models/Product/Image/ContentImage.cs | 24 + .../Models/Product/Image/MainImage.cs | 23 + .../Models/Product/Image/ProductImage.cs | 67 + .../Models/Product/Image/ProductImageType.cs | 13 + .../Models/Product/Image/SkuImage.cs | 21 + .../Models/Product/MainImageCollection.cs | 34 + Store.Product/Models/Product/ProductBase.cs | 139 ++ .../Models/Product/ProductContent.cs | 101 ++ .../Models/Product/ProductPropertys.cs | 51 + Store.Product/Models/Product/Sku/Property.cs | 18 + .../Models/Product/Sku/SkuMapItem.cs | 56 + .../Models/Product/Sku/SkuMapProperty.cs | 26 + Store.Product/Models/Product/Sku/SkuMaps.cs | 40 + .../Models/Product/Sku/SkuProperty.cs | 12 + .../Models/Product/Sku/SkuPropertyItem.cs | 16 + .../Models/Product/Sku/SkuPropertys.cs | 105 ++ .../Image/ImageParameters.cs | 89 ++ .../PublishParameters/Price/Computation.cs | 23 + .../PublishParameters/Price/HighLowPrice.cs | 60 + .../PublishParameters/Price/MultiplesPrice.cs | 67 + .../Price/PriceConfigBase.cs | 39 + .../Price/PriceStrategyFactory.cs | 35 + .../Price/PriceStrategyType.cs | 12 + .../PublishParameters/Price/SectionPrice.cs | 72 ++ .../PublishParameters/ProductStorage.cs | 66 + .../PublishParameters/PublishParameters.cs | 111 ++ .../Models/PublishParameters/Sku/CustomSku.cs | 28 + .../PublishParameters/Sku/SkuParameters.cs | 27 + .../Models/PublishParameters/SwitchParams.cs | 24 + .../Wx/WxProductUploadParam.cs | 263 ++++ Store.Product/Publish/IPublishStep.cs | 27 + Store.Product/Publish/PublishContext .cs | 44 + Store.Product/Publish/PublishPipeline.cs | 49 + Store.Product/Publish/PublishResult.cs | 15 + Store.Product/Publish/PublishStep.cs | 59 + .../Publish/Steps/CheckProductPropertyStep.cs | 17 + .../Publish/Steps/DetailStep/CheckPrice.cs | 35 + .../Publish/Steps/WXPublishParameters.cs | 16 + .../Steps/Wx/AddRandomImageBorderStep.cs | 169 +++ .../Publish/Steps/Wx/AddTitlePrefixStep.cs | 40 + .../Publish/Steps/Wx/BuildSkuInfoStep.cs | 171 +++ .../Steps/Wx/BuildSkuPropertiesStep.cs | 342 +++++ .../Steps/Wx/CalculateShippingTemplateStep.cs | 141 ++ .../Steps/Wx/CommissionPreparationStep.cs | 111 ++ .../Steps/Wx/Convert1688ProductStep.cs | 52 + .../Steps/Wx/ConvertCloudProductStep.cs | 79 ++ .../Steps/Wx/ConvertJstPddProductStep.cs | 54 + .../Steps/Wx/ConvertWxAppProductStep.cs | 45 + .../Steps/Wx/ConvertWxRecycleProductStep.cs | 52 + .../Steps/Wx/ConvertWxShopProductStep.cs | 45 + .../Publish/Steps/Wx/DetectCategoryStep.cs | 165 +++ .../Publish/Steps/Wx/FillMainImagesStep.cs | 77 ++ .../Steps/Wx/FilterDuplicateImagesStep.cs | 87 ++ .../Publish/Steps/Wx/FissionTitleStep.cs | 55 + .../Steps/Wx/GenerateAiProductImageStep.cs | 145 +++ .../Steps/Wx/GenerateAiShortTitleStep.cs | 70 + .../Steps/Wx/GenerateCategoryTemplateStep.cs | 226 ++++ .../Publish/Steps/Wx/GetShopApiTokenStep.cs | 60 + .../Steps/Wx/GetShopShippingTemplateStep.cs | 71 + .../Steps/Wx/InitializeProductDataStep.cs | 363 ++++++ .../Steps/Wx/InitializeUploadProductStep.cs | 47 + .../Publish/Steps/Wx/MIGRATION_NOTES.md | 26 + .../Steps/Wx/MatchProductPropertiesStep.cs | 759 +++++++++++ .../Steps/Wx/PrepareHotlinkImageMapStep.cs | 52 + .../Steps/Wx/PrepareSourceProductStep.cs | 63 + .../Wx/RestoreWxShopSourceProductStep.cs | 50 + .../Steps/Wx/SaveDetailImageHotlinkMapStep.cs | 55 + .../Steps/Wx/SaveDraftHotlinkMapStep.cs | 55 + .../Steps/Wx/SaveMainImageHotlinkMapStep.cs | 55 + .../Publish/Steps/Wx/SaveSkuHotlinkMapStep.cs | 55 + .../Publish/Steps/Wx/SetAfterSaleInfoStep.cs | 244 ++++ .../Publish/Steps/Wx/SetCommissionInfoStep.cs | 46 + .../Publish/Steps/Wx/SetPreSaleInfoStep.cs | 50 + .../Publish/Steps/Wx/SetProductExtCodeStep.cs | 48 + .../Publish/Steps/Wx/SetProductTitleStep.cs | 45 + .../Wx/SetQualificationsAndTagImagesStep.cs | 673 ++++++++++ .../Steps/Wx/SkipUploadedProductStep.cs | 117 ++ .../Steps/Wx/SubmitProductDraftStep.cs | 107 ++ .../Publish/Steps/Wx/SubmitProductStep.cs | 273 ++++ .../Steps/Wx/UploadDetailImagesStep.cs | 181 +++ .../Publish/Steps/Wx/UploadMainImagesStep.cs | 243 ++++ .../Publish/Steps/Wx/UploadVideoStep.cs | 77 ++ .../Publish/Steps/Wx/WXPublishContext.cs | 12 + .../Wx/WxProductPublishPipelineFactory.cs | 67 + .../Steps/Wx/WxProductPublishStepBase.cs | 222 ++++ Store.Product/Store.Product.csproj | 15 + TEMUKJD CEFv138.sln | 88 +- test/Store.Test/Store.Test.csproj | 27 + test/Store.Test/UnitTest1.cs | 32 + 234 files changed, 26994 insertions(+), 1 deletion(-) create mode 100644 Directory.Build.props create mode 100644 MAX.Core/Autofac/AutowiredAttribute.cs create mode 100644 MAX.Core/Autofac/CustomPropertySelector.cs create mode 100644 MAX.Core/Autofac/IDependencyRegistrar.cs create mode 100644 MAX.Core/Autofac/IocManager.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/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/RandomHelper.cs create mode 100644 MAX.Core/Helpers/ReflectionHelper.cs create mode 100644 MAX.Core/Helpers/UriHelper.cs create mode 100644 MAX.Core/Infrastructure/Bootstrap.cs create mode 100644 MAX.Core/Infrastructure/IStartup.cs create mode 100644 MAX.Core/Infrastructure/SingleInstance.cs create mode 100644 MAX.Core/IntPriority.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 MAX.Framework/Commons/KeyValue.cs create mode 100644 MAX.Framework/Configs/ConfigBase.cs create mode 100644 MAX.Framework/Configs/GlobalConfig.cs create mode 100644 MAX.Framework/Controls/Attach/DialogWindowProperty.cs create mode 100644 MAX.Framework/Controls/ConfirmDialog.cs create mode 100644 MAX.Framework/Extensions/StyleExtensions.cs create mode 100644 MAX.Framework/Helpers/ConfigHelper.cs create mode 100644 MAX.Framework/Helpers/FileHelper.cs create mode 100644 MAX.Framework/Helpers/ImageBuild.cs create mode 100644 MAX.Framework/Helpers/ObjectHelper.cs create mode 100644 MAX.Framework/Helpers/StringTypeHelper.cs create mode 100644 MAX.Framework/Helpers/SystemHelper.cs create mode 100644 MAX.Framework/Helpers/WinCmdHelper.cs create mode 100644 MAX.Framework/Helpers/XmlHelper.cs create mode 100644 MAX.Framework/Interfaces/IAppConfig.cs create mode 100644 MAX.Framework/Interfaces/IAppInitialize.cs create mode 100644 MAX.Framework/Interfaces/IFolderConfig.cs create mode 100644 MAX.Framework/Logger/NLogger.cs create mode 100644 MAX.Framework/MAX.Framework.csproj create mode 100644 MAX.Framework/PrismExtension/Commands/AsyncDelegateCommand.cs create mode 100644 MAX.Framework/PrismExtension/Commands/AsyncDelegateCommand{T}.cs create mode 100644 MAX.Framework/PrismExtension/Commands/IAsyncCommand.cs create mode 100644 MAX.Framework/PrismExtension/Prism.Autofac/AutofacContainerExtension.cs create mode 100644 MAX.Framework/PrismExtension/Prism.Autofac/IAutofacContainerExtension.cs create mode 100644 MAX.Framework/PrismExtension/Prism.Autofac/IPreRegisterTypes.cs create mode 100644 MAX.Framework/PrismExtension/Prism.Autofac/PrismApplication.cs create mode 100644 MAX.Framework/PrismExtension/Prism.Autofac/PrismBootstrapper.cs create mode 100644 MAX.Framework/PrismExtension/Prism.Autofac/PrismIocExtensions.cs create mode 100644 MAX.Framework/PrismExtension/RegionManagerExtension.cs create mode 100644 MAX.Framework/Startup.cs create mode 100644 Store.Framework/Agent/AgentConfig.cs create mode 100644 Store.Framework/Apis/ApiResponse.cs create mode 100644 Store.Framework/Apis/CommonApi.cs create mode 100644 Store.Framework/Apis/OrderApi.cs create mode 100644 Store.Framework/Apis/PriceApi.cs create mode 100644 Store.Framework/Apis/ProductApi.cs create mode 100644 Store.Framework/Apis/ProductThirdApi.cs create mode 100644 Store.Framework/Apis/ServerHttpBase.cs create mode 100644 Store.Framework/Apis/ShopApi.cs create mode 100644 Store.Framework/Apis/UserApi.cs create mode 100644 Store.Framework/Attributes/HttpTryAttribute.cs create mode 100644 Store.Framework/Attributes/SleepAttribute.cs create mode 100644 Store.Framework/ClientInfo.cs create mode 100644 Store.Framework/Commons/ShopManage.cs create mode 100644 Store.Framework/Commons/SignTool.cs create mode 100644 Store.Framework/Config.cs create mode 100644 Store.Framework/Configs/GlobalConfig.cs create mode 100644 Store.Framework/Configs/IApiServerConfig.cs create mode 100644 Store.Framework/Configs/IServiceConfigProvider.cs create mode 100644 Store.Framework/Configs/ServerConfig.cs create mode 100644 Store.Framework/Configs/ServiceConfigKeys.cs create mode 100644 Store.Framework/Configs/ServiceConfigProvider.cs create mode 100644 Store.Framework/Configs/ServiceConfigRegistrationExtensions.cs create mode 100644 Store.Framework/Enums/PlatformType.cs create mode 100644 Store.Framework/Extensions/DictionaryExtension.cs create mode 100644 Store.Framework/Extensions/Extensions.cs create mode 100644 Store.Framework/Extensions/HttpItemExtensions.cs create mode 100644 Store.Framework/Extensions/InitialDataMiddleware.cs create mode 100644 Store.Framework/Helpers/DataOpenSSL.cs create mode 100644 Store.Framework/Helpers/FileHelper.cs create mode 100644 Store.Framework/Helpers/ImageBuild.cs create mode 100644 Store.Framework/HttpExtension/AppApiRequestBase.cs create mode 100644 Store.Framework/HttpExtension/ServerHttpItem.cs create mode 100644 Store.Framework/HttpExtension/ServiceRequestBase.cs create mode 100644 Store.Framework/HttpResultParser/HttpException.cs create mode 100644 Store.Framework/HttpResultParser/HttpResultVerify.cs create mode 100644 Store.Framework/HttpResultParser/IHttpResultVerify.cs create mode 100644 Store.Framework/Inerterfaces/ICurrentUser.cs create mode 100644 Store.Framework/Models/Cookies.cs create mode 100644 Store.Framework/Models/Product/CategoryModel.cs create mode 100644 Store.Framework/Models/ShopBase.cs create mode 100644 Store.Framework/Models/UserInfo.cs create mode 100644 Store.Framework/Services/CacheServerService.cs create mode 100644 Store.Framework/Services/CommonService.cs create mode 100644 Store.Framework/Services/CurrentUserService.cs create mode 100644 Store.Framework/Services/ShopService.cs create mode 100644 Store.Framework/Services/UserService.cs create mode 100644 Store.Framework/Startup.cs create mode 100644 Store.Framework/Store.Framework.csproj create mode 100644 Store.Framework/ThreadManage/ThreadManager.cs create mode 100644 Store.Framework/ThreadManage/ThreadManagerPool.cs create mode 100644 Store.Framework/ThreadManage/WorkAction.cs create mode 100644 Store.Framework/ThreadManage/WorkUnit.cs create mode 100644 Store.Framework/Tools/HardwareTool.cs create mode 100644 Store.Management.Framework/Apis/WXApi.cs create mode 100644 Store.Management.Framework/Caches/ImageBase.cs create mode 100644 Store.Management.Framework/Caches/ImageCache.cs create mode 100644 Store.Management.Framework/Class1.cs create mode 100644 Store.Management.Framework/Configs/ApiServerConfig.cs create mode 100644 Store.Management.Framework/Configs/ManagementServiceConfigKeys.cs create mode 100644 Store.Management.Framework/Helpers/ImageHelper.cs create mode 100644 Store.Management.Framework/Startup.cs create mode 100644 Store.Management.Framework/Store.Management.Framework.csproj create mode 100644 Store.Product/Class1.cs create mode 100644 Store.Product/Enums/BlockType.cs create mode 100644 Store.Product/Enums/FreightTemplateType.cs create mode 100644 Store.Product/Enums/NextStatus.cs create mode 100644 Store.Product/Enums/ProductStorageStatus.cs create mode 100644 Store.Product/Enums/StuffStatus.cs create mode 100644 Store.Product/Models/Product/Image/ContentImage.cs create mode 100644 Store.Product/Models/Product/Image/MainImage.cs create mode 100644 Store.Product/Models/Product/Image/ProductImage.cs create mode 100644 Store.Product/Models/Product/Image/ProductImageType.cs create mode 100644 Store.Product/Models/Product/Image/SkuImage.cs create mode 100644 Store.Product/Models/Product/MainImageCollection.cs create mode 100644 Store.Product/Models/Product/ProductBase.cs create mode 100644 Store.Product/Models/Product/ProductContent.cs create mode 100644 Store.Product/Models/Product/ProductPropertys.cs create mode 100644 Store.Product/Models/Product/Sku/Property.cs create mode 100644 Store.Product/Models/Product/Sku/SkuMapItem.cs create mode 100644 Store.Product/Models/Product/Sku/SkuMapProperty.cs create mode 100644 Store.Product/Models/Product/Sku/SkuMaps.cs create mode 100644 Store.Product/Models/Product/Sku/SkuProperty.cs create mode 100644 Store.Product/Models/Product/Sku/SkuPropertyItem.cs create mode 100644 Store.Product/Models/Product/Sku/SkuPropertys.cs create mode 100644 Store.Product/Models/PublishParameters/Image/ImageParameters.cs create mode 100644 Store.Product/Models/PublishParameters/Price/Computation.cs create mode 100644 Store.Product/Models/PublishParameters/Price/HighLowPrice.cs create mode 100644 Store.Product/Models/PublishParameters/Price/MultiplesPrice.cs create mode 100644 Store.Product/Models/PublishParameters/Price/PriceConfigBase.cs create mode 100644 Store.Product/Models/PublishParameters/Price/PriceStrategyFactory.cs create mode 100644 Store.Product/Models/PublishParameters/Price/PriceStrategyType.cs create mode 100644 Store.Product/Models/PublishParameters/Price/SectionPrice.cs create mode 100644 Store.Product/Models/PublishParameters/ProductStorage.cs create mode 100644 Store.Product/Models/PublishParameters/PublishParameters.cs create mode 100644 Store.Product/Models/PublishParameters/Sku/CustomSku.cs create mode 100644 Store.Product/Models/PublishParameters/Sku/SkuParameters.cs create mode 100644 Store.Product/Models/PublishParameters/SwitchParams.cs create mode 100644 Store.Product/Models/PublishParameters/Wx/WxProductUploadParam.cs create mode 100644 Store.Product/Publish/IPublishStep.cs create mode 100644 Store.Product/Publish/PublishContext .cs create mode 100644 Store.Product/Publish/PublishPipeline.cs create mode 100644 Store.Product/Publish/PublishResult.cs create mode 100644 Store.Product/Publish/PublishStep.cs create mode 100644 Store.Product/Publish/Steps/CheckProductPropertyStep.cs create mode 100644 Store.Product/Publish/Steps/DetailStep/CheckPrice.cs create mode 100644 Store.Product/Publish/Steps/WXPublishParameters.cs create mode 100644 Store.Product/Publish/Steps/Wx/AddRandomImageBorderStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/AddTitlePrefixStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/BuildSkuInfoStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/BuildSkuPropertiesStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/CalculateShippingTemplateStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/CommissionPreparationStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/Convert1688ProductStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/ConvertCloudProductStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/ConvertJstPddProductStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/ConvertWxAppProductStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/ConvertWxRecycleProductStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/ConvertWxShopProductStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/DetectCategoryStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/FillMainImagesStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/FilterDuplicateImagesStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/FissionTitleStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/GenerateAiProductImageStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/GenerateAiShortTitleStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/GenerateCategoryTemplateStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/GetShopApiTokenStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/GetShopShippingTemplateStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/InitializeProductDataStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/InitializeUploadProductStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/MIGRATION_NOTES.md create mode 100644 Store.Product/Publish/Steps/Wx/MatchProductPropertiesStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/PrepareHotlinkImageMapStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/PrepareSourceProductStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/RestoreWxShopSourceProductStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/SaveDetailImageHotlinkMapStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/SaveDraftHotlinkMapStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/SaveMainImageHotlinkMapStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/SaveSkuHotlinkMapStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/SetAfterSaleInfoStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/SetCommissionInfoStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/SetPreSaleInfoStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/SetProductExtCodeStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/SetProductTitleStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/SetQualificationsAndTagImagesStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/SkipUploadedProductStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/SubmitProductDraftStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/SubmitProductStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/UploadDetailImagesStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/UploadMainImagesStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/UploadVideoStep.cs create mode 100644 Store.Product/Publish/Steps/Wx/WXPublishContext.cs create mode 100644 Store.Product/Publish/Steps/Wx/WxProductPublishPipelineFactory.cs create mode 100644 Store.Product/Publish/Steps/Wx/WxProductPublishStepBase.cs create mode 100644 Store.Product/Store.Product.csproj create mode 100644 test/Store.Test/Store.Test.csproj create mode 100644 test/Store.Test/UnitTest1.cs diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..67c28ed --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,24 @@ + + + + + net462 + $(DefaultTargetFramework) + 12.0 + true + false + true + annotations + CS1591 + enable + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/MAX.Core/Autofac/AutowiredAttribute.cs b/MAX.Core/Autofac/AutowiredAttribute.cs new file mode 100644 index 0000000..f82ae6a --- /dev/null +++ b/MAX.Core/Autofac/AutowiredAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace MAX.Core.Autofac +{ + /// + /// 自动填充属性特性 + /// + [AttributeUsage(AttributeTargets.Property)] + public class AutowiredAttribute : Attribute + { + } +} diff --git a/MAX.Core/Autofac/CustomPropertySelector.cs b/MAX.Core/Autofac/CustomPropertySelector.cs new file mode 100644 index 0000000..452d089 --- /dev/null +++ b/MAX.Core/Autofac/CustomPropertySelector.cs @@ -0,0 +1,23 @@ +using Autofac.Core; +using System.Linq; +using System.Reflection; + +namespace MAX.Core.Autofac +{ + /// + /// autofac 找出所有属性上是否摸个特性 + /// + public class CustomPropertySelector : IPropertySelector + { + public bool InjectProperty(PropertyInfo propertyInfo, object instance) + { + if (propertyInfo.CustomAttributes.Any(iterator => iterator.AttributeType == typeof(AutowiredAttribute))) + { + + } + //需要一个判断 + //;如果标记的有CustomPropertyAttribute特性返回True:返回true;就构造实例 + return propertyInfo.CustomAttributes.Any(iterator => iterator.AttributeType == typeof(AutowiredAttribute)); + } + } +} diff --git a/MAX.Core/Autofac/IDependencyRegistrar.cs b/MAX.Core/Autofac/IDependencyRegistrar.cs new file mode 100644 index 0000000..e3f7478 --- /dev/null +++ b/MAX.Core/Autofac/IDependencyRegistrar.cs @@ -0,0 +1,9 @@ +using Autofac; + +namespace MAX.Core.Autofac +{ + public interface IDependencyRegistrar + { + void Register(ContainerBuilder builder); + } +} diff --git a/MAX.Core/Autofac/IocManager.cs b/MAX.Core/Autofac/IocManager.cs new file mode 100644 index 0000000..f604681 --- /dev/null +++ b/MAX.Core/Autofac/IocManager.cs @@ -0,0 +1,107 @@ +using Autofac; +using Autofac.Core; +using System; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MAX.Core.DependencyInjection; + +/// +/// 容器 +/// +public static partial class IocManager +{ + /// + /// 默认容器 + /// + public static IContainer Container { get; internal set; } + public static void SetContainer(IContainer container) + { + Container = container; + } + + /// + /// 确定指定的服务在上下文中是否可用。 + /// + /// + /// + public static bool IsRegistered() + { + return Container.IsRegistered(); + } + /// + /// 确定指定的服务在上下文中是否可用。 + /// + /// + /// + public static bool IsRegistered(Type serverType) + { + return Container.IsRegistered(serverType); + } + + public static TService Resolve() + { + if (Container.IsRegistered()) + { + return Container.Resolve(); + } + return default; + } + public static object Resolve(Type serviceType) + { + if (Container.IsRegistered(serviceType)) + { + return Container.Resolve(serviceType); + } + return default; + } + public static TService Resolve(params Parameter[] parameters) + { + if (Container.IsRegistered()) + { + return Container.Resolve(parameters); + } + return default; + } + /// + /// 获取所有注册 + /// + /// + /// + public static TService[] ResolveAll() + { + return ResolveAll(typeof(TService)) as TService[]; + } + /// + /// 获取所有注册 + /// + /// + /// + public static object[] ResolveAll(Type type) + { + Type enumerableOfType = typeof(IEnumerable<>).MakeGenericType(type); + return (object[])Container.ResolveService(new TypedService(enumerableOfType)); + } + + public static Type GetRegistrationType(string key) + { + foreach (IComponentRegistration registration in Container.ComponentRegistry.Registrations) + { + foreach (Service service in registration.Services) + { + if (service is KeyedService ks) + { + if (key.Equals(ks.ServiceKey.ToString(), StringComparison.Ordinal)) + { + if (Container.ComponentRegistry.TryGetServiceRegistration(service, out ServiceRegistration serviceRegistration)) + { + return serviceRegistration.Registration.Target.Activator.LimitType; + } + } + } + } + } + return null; + } +} 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..e42df01 --- /dev/null +++ b/MAX.Core/Caches/MemoryCache.cs @@ -0,0 +1,168 @@ + +using MAX.Core.DependencyInjection; +using MAX.Core.Infrastructure; +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 SingleInstanceCache : 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/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..b2f5755 --- /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 +//{ +// /// +// /// 注册单例: 实现 +// /// +// 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..2429709 --- /dev/null +++ b/MAX.Core/DependencyInjection/LazyServiceProvider.cs @@ -0,0 +1,36 @@ +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..1bd0d39 --- /dev/null +++ b/MAX.Core/Helpers/EnvironmentHelper.cs @@ -0,0 +1,71 @@ +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() + { +#if RELEASE + // Release 构建:只允许生产环境,不允许其他环境 + return "Production"; +#else + // 1. 检查环境变量 + var envVar = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? + Environment.GetEnvironmentVariable("ENVIRONMENT"); + + if (!string.IsNullOrEmpty(envVar)) + { + return envVar; + } + + + // 检查当前机器名 + string machineName = Environment.MachineName.ToLower(); + if (machineName.Contains("dev") || + machineName.Contains("test")) + { + return machineName.Contains("TEST") ? "Test" : "Development"; + } + + // 4. 默认返回 Production + return "Production"; +#endif + } + + // 获取环境描述 + 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..f85098f --- /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; + /// + /// 设置或获取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/RandomHelper.cs b/MAX.Core/Helpers/RandomHelper.cs new file mode 100644 index 0000000..6f04560 --- /dev/null +++ b/MAX.Core/Helpers/RandomHelper.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MAX.Core.Helpers +{ + public sealed class RandomHelper + { + private static readonly char[] RandChar = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; + private static Random rand = new Random(unchecked((int)DateTime.Now.Ticks)); + private static int seed = 1; + + private RandomHelper() + { + } + + /// + /// 根据规则随机生成字符串 + /// + /// 样式:"?"表明一个字符,"#"表明一个一位数字,"*"表明一个字符串或一个一位数字 + /// 随机字符串 + public static string GetRandStringByPattern(string pattern) + { + if (!pattern.Contains("#") && !pattern.Contains("?") && !pattern.Contains("*")) + { + return pattern; + } + + char[] nums = pattern.ToCharArray(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < nums.Length; i++) + { + switch (nums[i]) + { + case '?': + nums[i] = RandChar[rand.Next(10, 62)]; + break; + case '#': + nums[i] = RandChar[rand.Next(0, 10)]; + break; + case '*': + nums[i] = RandChar[rand.Next(62)]; + break; + default: + break; + } + + sb.Append(nums[i]); + } + + return sb.ToString(); + } + /// + /// 生成随机的数值 + /// + /// 随机数的上界 + /// 随机的数值 + public static int GetRandomNumber(int max) + { + return GetRandomNumber(0, max); + } + /// + /// 生成随机的数值 + /// + /// 随机数可取该下界值 + /// 随机数的上界 + /// 随机的数值 + public static int GetRandomNumber(int min, int max) + { + int num = 0; + Random ro = new Random(unchecked(seed * (int)DateTime.Now.Ticks)); + num = ro.Next(min, max); + seed++; + return num; + } + + /// + /// 获取指定长度和字符的随机字符串 + /// 经过调用 Random 类的 Next() 方法,先得到一个大于或等于 0 而小于 pwdchars 长度的整数 + /// 以该数做为索引值,从可用字符串中随机取字符,以指定的密码长度为循环次数 + /// 依次链接取得的字符,最后即获得所需的随机密码串了。 + /// + /// 随机字符串里包含的字符 + /// 随机字符串的长度 + /// 随机产生的字符串 + public static string GetRandomString(string pwdchars, int pwdlen) + { + StringBuilder tmpstr = new StringBuilder(); + int randNum; + + for (int i = 0; i < pwdlen; i++) + { + randNum = rand.Next(pwdchars.Length); + tmpstr.Append(pwdchars[randNum]); + } + + return tmpstr.ToString(); + } + + /// + /// 获取指定长度的随机字符串 + /// + /// 随机字符串的长度 + /// 随机产生的字符串 + public static string GetRandomString(int pwdlen) + { + return GetRandomString("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_*", pwdlen); + } + + /// + /// 获取指定长度的纯字母随机字符串 + /// + /// 数字串长度 + /// 纯字母随机字符串 + public static string GetRandWord(int pwdlen) + { + return GetRandomString("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", pwdlen); + } + + /// + /// 获取指定长度的纯数字随机数字串 + /// + /// 数字串长度 + /// 纯数字随机数字串 + public static string GetRandomLengthNumString(int intlong) + { + StringBuilder w = new StringBuilder(string.Empty); + + for (int i = 0; i < intlong; i++) + { + w.Append(rand.Next(10)); + } + return w.ToString(); + } + + /// + /// 获取按照年月时分秒随机数生成的文件名 + /// + /// 随机文件名 + public static string GetFileRndName() + { + return DateTime.Now.ToString("yyyyMMddHHmmss", CultureInfo.CurrentCulture) + GetRandomString("0123456789", 4); + } + } +} diff --git a/MAX.Core/Helpers/ReflectionHelper.cs b/MAX.Core/Helpers/ReflectionHelper.cs new file mode 100644 index 0000000..e08efdf --- /dev/null +++ b/MAX.Core/Helpers/ReflectionHelper.cs @@ -0,0 +1,586 @@ +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设置显示名称,使用DescriptionAttribute设置描述 + /// + /// 类型 + public static string GetDisplayNameOrDescription() + { + return GetDisplayNameOrDescription(Common.GetType()); + } + + /// + /// 获取属性显示名称或描述,使用DisplayAttribute或DisplayNameAttribute设置显示名称,使用DescriptionAttribute设置描述 + /// + public static string GetDisplayNameOrDescription(MemberInfo member) + { + return GetDescription(member); + } + + /// + /// 查找类型列表 + /// + /// 查找类型 + /// 待查找的程序集列表 + 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/Infrastructure/Bootstrap.cs b/MAX.Core/Infrastructure/Bootstrap.cs new file mode 100644 index 0000000..63a2a12 --- /dev/null +++ b/MAX.Core/Infrastructure/Bootstrap.cs @@ -0,0 +1,42 @@ + +using Autofac; +using MAX.Core.Autofac; +using MAX.Core.DependencyInjection; +using MAX.Core.Helpers; +using MAX.Core.Infrastructure; +using MAX.Core.Reflections; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace LittleCow.Core.Infrastructure +{ + public class Bootstrap + { + public static void Builder() + { + var Builder = new ContainerBuilder(); + Assembly[] assemblies = AppTypeFinder.Assemblies.ToArray(); + // Make sure any not specifically registered concrete type can resolve. + IEnumerable startupList = ReflectionHelper.GetInstancesByInterface(assemblies); + int index = 1; + foreach (IStartup item in startupList.OrderByDescending(a => a.Priority)) + { + Debug.WriteLine($"Startup - {index++} : {item.GetType()}"); + item.Initialize(); + } + // Make sure any not specifically registered concrete type can resolve. + IEnumerable ListDependency = ReflectionHelper.GetInstancesByInterface(assemblies); + foreach (IDependencyRegistrar item in ListDependency) + { + item.Register(Builder); + } + IocManager.SetContainer(Builder.Build()); + } + } + +} diff --git a/MAX.Core/Infrastructure/IStartup.cs b/MAX.Core/Infrastructure/IStartup.cs new file mode 100644 index 0000000..635cf03 --- /dev/null +++ b/MAX.Core/Infrastructure/IStartup.cs @@ -0,0 +1,15 @@ +using MAX.Core.Autofac; +using MAX.Core.DependencyInjection; + +namespace MAX.Core.Infrastructure; + +/// +/// 启动器:实现 ,由宿主项目统一发现并按 排序执行。 +/// +public interface IStartup : IDependencyRegistrar, IPriority +{ + /// + /// 初始化 + /// + void Initialize(); +} diff --git a/MAX.Core/Infrastructure/SingleInstance.cs b/MAX.Core/Infrastructure/SingleInstance.cs new file mode 100644 index 0000000..b6eb50a --- /dev/null +++ b/MAX.Core/Infrastructure/SingleInstance.cs @@ -0,0 +1,42 @@ +using System; +using System.Reflection; + +namespace MAX.Core.Infrastructure; + +/// +/// 单例,需要在当前类中定义 +/// +/// +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/IntPriority.cs b/MAX.Core/IntPriority.cs new file mode 100644 index 0000000..b4fc563 --- /dev/null +++ b/MAX.Core/IntPriority.cs @@ -0,0 +1,74 @@ +namespace MAX.Core; + +/// +/// 优先级(优先级越高越先执行) +/// +public interface IPriority +{ + /// + /// 优先级(优先级越高越先执行) + /// + int Priority { get; } +} +/// +/// 优先级 +/// +/// +/// +public static class IntPriority +{ + /// + /// 最高优先级 (5000) + /// + public const int Highest = 5000; + + /// + /// 非常高优先级 (4000) + /// + public const int VeryHigh = 4000; + + /// + /// 较高优先级 (3000) + /// + public const int Higher = 3000; + + /// + /// 高优先级 (2000) + /// + public const int High = 2000; + + /// + /// 高于正常优先级 (1000) + /// + public const int AboveNormal = 1000; + + /// + /// 正常优先级 (0) + /// + public const int Normal = 0; + + /// + /// 低于正常优先级 (-1000) + /// + public const int BelowNormal = -1000; + + /// + /// 低优先级 (-2000) + /// + public const int Low = -2000; + + /// + /// 较低优先级 (-3000) + /// + public const int Lower = -3000; + + /// + /// 非常低优先级 (-4000) + /// + public const int VeryLow = -4000; + + /// + /// 最低优先级 (-5000) + /// + public const int Lowest = -5000; +} \ No newline at end of file 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..456a96b --- /dev/null +++ b/MAX.Core/Logger/ICustomLogger.cs @@ -0,0 +1,12 @@ +using MAX.Core.Infrastructure; + +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..16ab907 --- /dev/null +++ b/MAX.Core/MAX.Core.csproj @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/MAX.Core/Reflections/AppTypeFinder.cs b/MAX.Core/Reflections/AppTypeFinder.cs new file mode 100644 index 0000000..826ce99 --- /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.", "Store."]; + } + + 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..a941c95 --- /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; + } + } + + /// + /// 从当前应用程序域获取程序集列表 + /// + private 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..e0acd27 --- /dev/null +++ b/MAX.Core/Startup.cs @@ -0,0 +1,74 @@ +/*** + * _ooOoo_ + * o8888888o + * 88" . "88 + * (| -_- |) + * O\ = /O + * ____/`---'\____ + * . ' \\| |// `. + * / \\||| : |||// \ + * / _||||| -:- |||||- \ + * | | \\\ - /// | | + * | \_| ''\---/'' | | + * \ .-\__ `-` ___/-. / + * ___`. .' /--.--\ `. . __ + * ."" '< `.___\_<|>_/___.' >'"". + * | | : `- \`.;`\ _ /`;.`/ - ` : | | + * \ \ `-. \_ __\ /__ _/ .-` / / + * ======`-.____`-.___\_____/___.-`____.-'====== + * `=---=' + * + * ............................................. + * 佛祖保佑 永无BUG + * 快加工资 不改需求 + */ + +using Autofac; +using MAX.Core.DependencyInjection; +using MAX.Core.Infrastructure; +using MAX.Core.Logger; +using MAX.Core.Reflections; +using MediatR; +using System.Reflection; + +namespace MAX.Core; + +public class Startup : IStartup +{ + public int Priority => int.MaxValue; + + public void Initialize() + { + + } + + public void Register(ContainerBuilder builder) + { + builder.RegisterInstance(new ConsoleLogger()).As().SingleInstance(); + + #region 注册MediatR + Assembly[] assemblies = AppTypeFinder.Assemblies.ToArray(); + + //builder.RegisterType().As().AsSelf().SingleInstance(); + + builder.RegisterAssemblyTypes(assemblies) + .AsClosedTypesOf(typeof(INotificationHandler<>)) + .AsImplementedInterfaces() + .AsSelf() + .InstancePerLifetimeScope(); + + builder.RegisterAssemblyTypes(assemblies) + .AsClosedTypesOf(typeof(IRequestHandler<>)) + .AsImplementedInterfaces() + .AsSelf() + .InstancePerLifetimeScope(); + + builder.Register(ctx => + { + var c = ctx.Resolve(); + return t => c.Resolve(t); + }).SingleInstance(); + + #endregion + } +} diff --git a/MAX.Framework/Commons/KeyValue.cs b/MAX.Framework/Commons/KeyValue.cs new file mode 100644 index 0000000..e03b094 --- /dev/null +++ b/MAX.Framework/Commons/KeyValue.cs @@ -0,0 +1,35 @@ +using System.Text; + +namespace MAX.Framework.Commons +{ + public struct KeyValue + { + public KeyValue(string key, string value) + { + Key = key; + Value = value; + } + + public string Key { get; set; } + public string Value { get; set; } + + public override string ToString() + { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.Append('['); + if (Key != null) + { + stringBuilder.Append(Key.ToString()); + } + + stringBuilder.Append(", "); + if (Value != null) + { + stringBuilder.Append(Value.ToString()); + } + + stringBuilder.Append(']'); + return stringBuilder.ToString(); + } + } +} diff --git a/MAX.Framework/Configs/ConfigBase.cs b/MAX.Framework/Configs/ConfigBase.cs new file mode 100644 index 0000000..30fb3b0 --- /dev/null +++ b/MAX.Framework/Configs/ConfigBase.cs @@ -0,0 +1,46 @@ +using Newtonsoft.Json; +using ConfigHelper = MAX.Framework.Helpers.ConfigHelper; + +namespace MAX.Framework.Configs +{ + public abstract class ConfigBase + { + [JsonIgnore] + public abstract string ConfigName { get; } + } + /// + /// 设置 + /// + public abstract class ConfigBase : ConfigBase where TConfig : ConfigBase, new() + { + private static TConfig mySelf;//= default(T); + public static TConfig Instance + { + get + { + if (mySelf == null) + { + mySelf = new TConfig(); + } + + return mySelf; + } + } + + public static TConfig LoadConfig() + { + try + { + TConfig config = ConfigHelper.LoadConfig(); + mySelf = config; + return mySelf; + } + catch { } + return default; + } + public void SaveConfig() + { + ConfigHelper.SaveConfig(ConfigName, this); + } + } +} diff --git a/MAX.Framework/Configs/GlobalConfig.cs b/MAX.Framework/Configs/GlobalConfig.cs new file mode 100644 index 0000000..294a819 --- /dev/null +++ b/MAX.Framework/Configs/GlobalConfig.cs @@ -0,0 +1,28 @@ +using System; +using System.IO; + +namespace MAX.Framework +{ + /// + /// 全局配置 + /// + public class GlobalConfig + { + /// + /// 数据库名 + /// + public const string DbName = "db.db"; + + /// + /// app启动路径 + /// + public static string AppDirectory + { + get + { + return AppDomain.CurrentDomain.BaseDirectory; + } + } + + } +} diff --git a/MAX.Framework/Controls/Attach/DialogWindowProperty.cs b/MAX.Framework/Controls/Attach/DialogWindowProperty.cs new file mode 100644 index 0000000..de2308b --- /dev/null +++ b/MAX.Framework/Controls/Attach/DialogWindowProperty.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace MAX.Framework.Controls.Attach; + +/// +/// 弹出窗口属性值 +/// +public class DialogWindowProperty +{ + public static readonly DependencyProperty WindowStartupLocationProperty = DependencyProperty.RegisterAttached( + "WindowStartupLocation", typeof(WindowStartupLocation), typeof(DialogWindowProperty), new PropertyMetadata(WindowStartupLocation.CenterScreen)); + public static void SetWindowStartupLocation(DependencyObject element, WindowStartupLocation value) + => element.SetValue(WindowStartupLocationProperty, value); + public static WindowStartupLocation GetWindowStartupLocation(DependencyObject element) + => (WindowStartupLocation)element.GetValue(WindowStartupLocationProperty); + + + public static readonly DependencyProperty ResizeModeProperty = DependencyProperty.RegisterAttached( + "ResizeMode", typeof(ResizeMode), typeof(DialogWindowProperty), new PropertyMetadata(ResizeMode.CanResize)); + public static void SetResizeMode(DependencyObject element, ResizeMode value) + => element.SetValue(ResizeModeProperty, value); + public static ResizeMode GetResizeMode(DependencyObject element) + => (ResizeMode)element.GetValue(ResizeModeProperty); + + + + public static readonly DependencyProperty WidthProperty = DependencyProperty.RegisterAttached( + "Width", typeof(double), typeof(DialogWindowProperty), new PropertyMetadata(double.NaN)); + public static void SetWidth(DependencyObject element, double value) + => element.SetValue(WidthProperty, value); + public static double GetWidth(DependencyObject element) + => (double)element.GetValue(WidthProperty); + + + + public static readonly DependencyProperty HeightProperty = DependencyProperty.RegisterAttached( + "Height", typeof(double), typeof(DialogWindowProperty), new PropertyMetadata(double.NaN)); + public static void SetHeight(DependencyObject element, double value) + => element.SetValue(HeightProperty, value); + public static double GetHeight(DependencyObject element) + => (double)element.GetValue(HeightProperty); +} diff --git a/MAX.Framework/Controls/ConfirmDialog.cs b/MAX.Framework/Controls/ConfirmDialog.cs new file mode 100644 index 0000000..610d819 --- /dev/null +++ b/MAX.Framework/Controls/ConfirmDialog.cs @@ -0,0 +1,290 @@ +using MAX.Core.Commons; +using Prism.Commands; +using Prism.Mvvm; +using Prism.Services.Dialogs; +using System; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Input; +using System.Windows.Media; + +namespace MAX.Framework.Controls; + +public static class IDialogServiceExtension +{ + public static void ShowDialog(this IDialogService dialogService, string name, TParameter parameter, Action callback) where TParameter : class + { + dialogService.ShowDialog(name, new DialogParameters + { + {"parameter", parameter}, + }, callback); + } + public static void Show(this IDialogService dialogService, string name, TParameter parameter, Action callback) where TParameter : class + { + dialogService.Show(name, new DialogParameters + { + {"parameter", parameter}, + }, callback); + } +} + + +/// +/// 确定取消对话框 ViewModel +/// +public class ConfirmDialogViewModel : BindableBase, IDialogAware +{ + public ConfirmDialogViewModel() + { + + } + + private string title = ""; + private bool _enableConfirm = true; + private bool _ShowConfirm = true; + private bool _ShowCancel = true; + private bool _ShowErrorMsg = true; + private string _ErrorMsg = ""; + private string confirmText = "确定"; + private string cancelText = "取消"; + private string backText = "返回"; + private bool allowEscape = true; + private bool allowEnter = true; + private bool isLoading = false; + + public string Title + { + get => title; + protected set => SetProperty(ref title, value); + } + + public bool AllowEscape + { + get => allowEscape; + set => SetProperty(ref allowEscape, value); + } + + public bool AllowEnter + { + get => allowEnter; + set => SetProperty(ref allowEnter, value); + } + + public bool IsLoading + { + get => isLoading; + set => SetProperty(ref isLoading, value, () => + { + AllowEscape = !value; + AllowEnter = !value; + EnableConfirm = !value; + }); + } + // + // + // + private SolidColorBrush _Underpaint = new SolidColorBrush(Color.FromRgb(238, 238, 238)); + /// + /// 背景底色 + /// + public SolidColorBrush Underpaint + { + get => _Underpaint; + set => SetProperty(ref _Underpaint, value); + } + + public event Action RequestClose; + protected void OnRequestClose(IDialogResult result) + { + RequestClose?.Invoke(result); + } + + public virtual bool CanCloseDialog() + { + return true; + } + + public virtual void OnDialogClosed() + { + + } + + public virtual void OnDialogOpened(IDialogParameters parameters) + { + + } + + public bool EnableConfirm + { + get => _enableConfirm; + set => SetProperty(ref _enableConfirm, value); + } + + public bool ShowConfirm + { + get => _ShowConfirm; + set => SetProperty(ref _ShowConfirm, value); + } + public bool ShowCancel + { + get => _ShowCancel; + set => SetProperty(ref _ShowCancel, value); + } + public bool ShowErrorMsg + { + get => _ShowErrorMsg; + set => SetProperty(ref _ShowErrorMsg, value); + } + + public string ConfirmText + { + get => confirmText; + set => SetProperty(ref confirmText, value); + } + public string CancelText + { + get => cancelText; + set => SetProperty(ref cancelText, value); + } + public string BackText + { + get => backText; + set => SetProperty(ref backText, value); + } + + /// + /// 错误提示 + /// + public string ErrorMsg + { + get => _ErrorMsg; + set => SetProperty(ref _ErrorMsg, value); + } + + public virtual ICommand ConfirmCommand => new DelegateCommand(_Confirm); + + protected virtual void _Confirm() + { + DialogResult ret = Confirm(); + if (ret != null) + { + RequestClose?.Invoke(ret); + } + } + + protected virtual DialogResult Confirm() + { + return new DialogResult(ButtonResult.OK); + } + + public virtual ICommand CancelCommand => new DelegateCommand(_Cancel); + + protected virtual void _Cancel() + { + DialogResult ret = Cancel(); + if (ret != null) + { + RequestClose?.Invoke(ret); + } + else + { + RequestClose?.Invoke(new DialogResult(ButtonResult.Cancel)); + } + } + protected virtual DialogResult Cancel() + { + return new DialogResult(ButtonResult.Cancel); + } + + public virtual void Loaded(object content) + { + var bb = Application.Current.TryFindResource("BackgroundBrush") as SolidColorBrush; + if (bb != null) + { + Underpaint = bb; + } + + + } + + public class AsyncConfirmDialogViewModel : ConfirmDialogViewModel + { + public override ICommand ConfirmCommand => new AsyncDelegateCommand(_Confirm); + + protected new async Task _Confirm() + { + DialogResult ret = await Confirm(); + if (ret != null) + { + OnRequestClose(ret); + } + } + + protected new virtual async Task Confirm() + { + return await Task.FromResult(new DialogResult(ButtonResult.OK)); + } + } + + public interface IDialogClassParameters + { + + } +} +/// 确定取消对话框 +/// +/// 参数Model +public class ConfirmDialogViewModel : ConfirmDialogViewModel where TParameter : class +{ + protected TParameter Args { get; set; } + public override void OnDialogOpened(IDialogParameters parameters) + { + base.OnDialogOpened(parameters); + if (parameters.TryGetValue("parameter", out TParameter parameter)) + { + Args = parameter; + } + } +} + +public abstract class ConfirmDialogViewModel : ConfirmDialogViewModel where TParameter : class where TResult : DialogResult/*, new()*/ +{ + protected TParameter Args { get; set; } + public override void OnDialogOpened(IDialogParameters parameters) + { + base.OnDialogOpened(parameters); + if (parameters.TryGetValue("parameter", out TParameter parameter)) + { + Args = parameter; + } + } + protected override void _Confirm() + { + TResult ret = Confirm(); + if (ret != null) + { + base.OnRequestClose(ret); + } + } + protected new abstract TResult Confirm(); + + protected override void _Cancel() + { + TResult ret = Cancel(); + if (ret != null) + { + base.OnRequestClose(ret); + } + else + { + base.OnRequestClose(new DialogResult(ButtonResult.Cancel)); + } + } + protected new abstract TResult Cancel(); +} + + +//public abstract class ConfirmDialogParameterViewModel : ConfirmDialogViewModel +//{ +// public abstract class Parameter; +//} \ No newline at end of file diff --git a/MAX.Framework/Extensions/StyleExtensions.cs b/MAX.Framework/Extensions/StyleExtensions.cs new file mode 100644 index 0000000..208c9d2 --- /dev/null +++ b/MAX.Framework/Extensions/StyleExtensions.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace MAX.Framework.Extensions; + +public static class StyleExtensions +{ + /// + /// 合并指定的 。 + /// + /// + /// + public static void Merge(this Style style1, Style style2) + { + if (style1 == null) + { + throw new ArgumentNullException("style1"); + } + if (style2 == null) + { + throw new ArgumentNullException("style2"); + } + + if (style1.TargetType.IsAssignableFrom(style2.TargetType)) + { + style1.TargetType = style2.TargetType; + } + + if (style2.BasedOn != null) + { + style1.Merge(style2.BasedOn); + } + + foreach (SetterBase currentSetter in style2.Setters) + { + style1.Setters.Add(currentSetter); + } + + + foreach (TriggerBase currentTrigger in style2.Triggers) + { + style1.Triggers.Add(currentTrigger); + } + + // This code is only needed when using DynamicResources. + foreach (object key in style2.Resources.Keys) + { + style1.Resources[key] = style2.Resources[key]; + } + } + + /// + /// 集合是否有值。 + /// + /// + /// + /// + public static bool HasValue(this IEnumerable source) + { + return source != null && source.Any(); + } + + public static ResourceDictionary FindResourceDictionary(this Style style, object resourceKey) + { + if (style.Resources != null && style.Resources.Contains(resourceKey)) + { + return style.Resources; + } + if (style.BasedOn != null) + { + return style.BasedOn.FindResourceDictionary(resourceKey); + } + return null; + } + + public static void Merge(this ResourceDictionary resourceDictionary, ResourceDictionary mergee) + { + if (resourceDictionary != null && mergee != null && mergee.Count > 0) + { + foreach (var key in mergee.Keys) + { + if (!resourceDictionary.Contains(key)) + { + resourceDictionary.Add(key, mergee[key]); + } + } + } + } +} diff --git a/MAX.Framework/Helpers/ConfigHelper.cs b/MAX.Framework/Helpers/ConfigHelper.cs new file mode 100644 index 0000000..42825d9 --- /dev/null +++ b/MAX.Framework/Helpers/ConfigHelper.cs @@ -0,0 +1,74 @@ +using MAX.Core.DependencyInjection; +using MAX.Framework.Configs; +using MAX.Framework.Interfaces; +using Newtonsoft.Json; +using System; +using System.IO; +using System.Text; + +namespace MAX.Framework.Helpers +{ + /// + /// 配置 + /// + public class ConfigHelper + { + private static IFolderConfig _folderConfig = LazyServiceProvider.Instance.LazyGetService(); + public static string prvategp = "4Z-zy_VP";//pIuEFX-0-OZm%tP2qtKw + public static T LoadConfig() where T : ConfigBase + { + string filename = Activator.CreateInstance().ConfigName; + return LoadConfig(filename); + } + + public static T LoadConfig(string fileName) where T : ConfigBase + { + try + { + string filePath = GetConfigUserPath(fileName); + if (File.Exists(filePath)) + { + string content = File.ReadAllText(filePath, Encoding.UTF8); + return JsonConvert.DeserializeObject(content); + } + } + catch { } + return default; + } + + public static void SaveConfig(T config) where T : ConfigBase + { + string filename = Activator.CreateInstance().ConfigName; + SaveConfig(filename, config); + } + + public static void SaveConfig(string fileName, T config) where T : ConfigBase + { + try + { + string filePath = GetConfigUserPath(fileName); + FileHelper.CreateDirectory(filePath); + string content = JsonConvert.SerializeObject(config); + File.WriteAllText(filePath, content, Encoding.UTF8); + } + catch { } + } + + public static void SaveConfig(string fileName, string content) + { + try + { + string filePath = GetConfigUserPath(fileName); + FileHelper.CreateDirectory(filePath); + File.WriteAllText(filePath, content, Encoding.UTF8); + } + catch { } + } + + public static string GetConfigUserPath(string fileName) + { + string filePath = Path.Combine(_folderConfig.LocalAppFolder, "config", fileName); + return filePath; + } + } +} diff --git a/MAX.Framework/Helpers/FileHelper.cs b/MAX.Framework/Helpers/FileHelper.cs new file mode 100644 index 0000000..a554782 --- /dev/null +++ b/MAX.Framework/Helpers/FileHelper.cs @@ -0,0 +1,181 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Text; + +namespace MAX.Framework.Helpers +{ + /// + /// 文件操作帮助类 + /// + public class FileHelper + { + /// + /// 获取文件MD5值 + /// + /// + /// + public static string GetFileMd5(string FilePath) + { + try + { + FileStream get_file = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.Read); + System.Security.Cryptography.MD5CryptoServiceProvider get_md5 = new System.Security.Cryptography.MD5CryptoServiceProvider(); + byte[] hash_byte = get_md5.ComputeHash(get_file); + string resule = BitConverter.ToString(hash_byte); + resule = resule.Replace("-", ""); + get_file.Close(); + return resule; + } + catch (Exception e) + { + return e.ToString(); + } + } + + /// + /// 读取所有内容 相对路径Or绝对路径 + /// + /// + /// + public static string ReadAllText(string file) + { + if (!Path.IsPathRooted(file)) + { + file = Path.Combine(GlobalConfig.AppDirectory, file); + } + if (File.Exists(file)) + { + return _ReadAllText(file, Encoding.UTF8); + } + return string.Empty; + } + /// + /// 读取所有内容 绝对路径 + /// + /// + /// + public static string ReadAbsAllText(string file) + { + return _ReadAllText(file, Encoding.UTF8); + } + /// + /// 读取所有内容 相对路径 + /// + /// + /// + public static string ReadPartialAllText(string file) + { + string _file = Path.Combine(GlobalConfig.AppDirectory, file); + return _ReadAllText(_file, Encoding.UTF8); + } + + private static string _ReadAllText(string file, Encoding encoding) + { + if (File.Exists(file)) + { + return File.ReadAllText(file, encoding); + } + else + { + throw new Exception($"缺失文件{file}"); + } + } + + /// + /// 创建目录 + /// + /// + public static void CreateDirectory(string dir) + { + if (Path.HasExtension(dir)) + { + dir = Path.GetDirectoryName(dir); + } + if (!Directory.Exists(dir)) + { + Directory.CreateDirectory(dir); + } + } + + public static bool DeleteDirectory(string path) + { + try + { + Directory.Delete(path, true); + return true; + } + catch (Exception ex) + { + Debug.WriteLine(ex); + return false; + } + } + + public static void CopyDirectory(string sourceDir, string destinationDir, bool recursive = true) + { + // Get information about the source directory + DirectoryInfo dir = new DirectoryInfo(sourceDir); + + // Check if the source directory exists + if (!dir.Exists) + { + throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}"); + } + + // Cache directories before we start copying + DirectoryInfo[] dirs = dir.GetDirectories(); + + // Create the destination directory + Directory.CreateDirectory(destinationDir); + + // Get the files in the source directory and copy to the destination directory + foreach (FileInfo file in dir.GetFiles()) + { + string targetFilePath = Path.Combine(destinationDir, file.Name); + file.CopyTo(targetFilePath); + } + + // If recursive and copying subdirectories, recursively call this method + if (recursive) + { + foreach (DirectoryInfo subDir in dirs) + { + string newDestinationDir = Path.Combine(destinationDir, subDir.Name); + CopyDirectory(subDir.FullName, newDestinationDir, true); + } + } + } + + + /// + /// 获取文件夹大小 + /// + /// + /// + public static long GetDirectorySize(string path) + { + long result = 0; + try + { + if (Directory.Exists(path)) + { + dynamic fso = Activator.CreateInstance(Type.GetTypeFromProgID("Scripting.FileSystemObject")); + dynamic fldr = fso.GetFolder(path); + result = (long)fldr.size; + } + else + { + result = 0; + } + } + catch (Exception ex) + { + result = 0; + } + + return result; + } + + } +} diff --git a/MAX.Framework/Helpers/ImageBuild.cs b/MAX.Framework/Helpers/ImageBuild.cs new file mode 100644 index 0000000..d43803a --- /dev/null +++ b/MAX.Framework/Helpers/ImageBuild.cs @@ -0,0 +1,1071 @@ +//using MAX.Core.Helpers; +//using System; +//using System.Collections.Generic; +//using System.Drawing; +//using System.Drawing.Drawing2D; +//using System.Drawing.Imaging; +//using System.IO; + +//namespace MAX.Framework.Helpers +//{ +// public class ImageBuild +// { +// public byte[] Img; +// public int Width = 0, Height = 0; +// public Image InitImage = null; +// public string format = ""; +// public int PicQuality = 0;//图片质量 +// public bool CanUser +// { +// get { return format == ".jpg" || format == ".png"; } +// } + +// public ImageBuild(byte[] image) +// { +// if (image == null || image.Length == 0) +// { +// return; +// } + +// Img = image; +// try +// { +// if (image.Length > 3145728) +// { +// return; +// } +// MemoryStream ms = new MemoryStream(Img); +// InitImage = Image.FromStream(ms); +// ImageFormat f = GetImageFormat(InitImage, out format); +// ms.Close(); +// ms.Dispose(); +// } +// catch (Exception e) +// { +// throw; +// } +// if (InitImage == null) +// { +// return; +// } + +// Width = InitImage.Width; +// Height = InitImage.Height; +// } +// /// +// /// 裁剪为指定大小,不符合则返回null +// /// +// /// +// /// +// /// +// public byte[] CutToSize(int width, int height) +// { +// if (InitImage == null) +// { +// return null; +// } + +// int w = InitImage.Width; +// int h = InitImage.Height; +// if (w < width || h < height) +// { +// return null; +// } +// int x = 0, y = 0, nw = w, nh = h; +// int tx = 0, ty = 0, tw = width, th = height; +// double f = w * 1.0 / width; +// double t = h * 1.0 / height; +// if (f == t)//无需变化 +// { +// } +// else if (f > t)//宽度较大 +// { +// x = (int)((f - t) / 2 * width); +// nw = w - (x * 2); +// } +// else//高度较大 +// { +// y = (int)((t - f) / 2 * height); +// nh = h - (y * 2); +// } +// Rectangle fromR = new Rectangle(x, y, nw, nh); +// Rectangle toR = new Rectangle(tx, ty, tw, th); +// Bitmap pickedImage = new Bitmap(width, height); + +// Graphics pickedG = Graphics.FromImage(pickedImage); +// pickedG.InterpolationMode = InterpolationMode.Low; +// pickedG.SmoothingMode = SmoothingMode.HighSpeed; +// pickedG.DrawImage(InitImage, toR, fromR, GraphicsUnit.Pixel); +// //转图片后转二进制数组 +// Image initImagex = (Image)pickedImage.Clone(); +// MemoryStream ms = new MemoryStream(); +// ImageCodecInfo icc = GetCodeInfo("1.jpg"); +// long quality = 80; +// EncoderParameters ep = new EncoderParameters(1); +// ep.Param[0] = new EncoderParameter(Encoder.Quality, quality); +// initImagex.Save(ms, icc, ep); +// byte[] bytes = ms.ToArray(); +// return bytes; +// } +// /// +// /// 添加线条 +// /// +// /// +// public void AddLineToImage(ref byte[] img) +// { +// MemoryStream ms = new MemoryStream(img); +// Bitmap image = (Bitmap)Image.FromStream(ms); +// ms.Close(); +// int width = image.Width; +// int height = image.Height; +// if (width < 100 || height < 100) +// { +// return; +// } + +// int r = RandomHelper.GetRandomNumber(100, 256); +// int b = RandomHelper.GetRandomNumber(100, 256); +// int g = RandomHelper.GetRandomNumber(100, 256); +// int h = RandomHelper.GetRandomNumber(1, 10); +// Color c = Color.FromArgb(r, g, b); +// for (int i = 0; i < h; i++) +// { +// for (int j = 0; j < height; j++) +// { +// image.SetPixel(i, j, c); +// } + +// for (int j = 0; j < width; j++) +// { +// image.SetPixel(j, i, c); +// } +// } +// for (int i = height - h; i < height; i++) +// { +// for (int j = 0; j < width; j++) +// { +// image.SetPixel(j, i, c); +// } +// } +// for (int i = width - h; i < width; i++) +// { +// for (int j = 0; j < height; j++) +// { +// image.SetPixel(i, j, c); +// } +// } +// ImageCodecInfo icc = GetCodeInfo("1.jpg"); +// long quality = 75; +// ms = new MemoryStream(); +// EncoderParameters ep = new EncoderParameters(1); +// ep.Param[0] = new EncoderParameter(Encoder.Quality, quality); +// image.Save(ms, icc, ep); +// img = ms.ToArray(); +// //FileBase.WriteBytes("1.jpg", bytes); +// } + + +// /// +// /// 在图片四周区域随机选取8*8像素,微调像素值 +// /// +// /// +// public void FineTuningImageToRandomArea(ref byte[] img) +// { +// try +// { +// MemoryStream ms = new MemoryStream(img); +// Bitmap image = (Bitmap)Image.FromStream(ms); +// int iW = image.Width; +// int iH = image.Height; +// if (iW <= 32 || iH <= 32) +// { +// return; +// } + +// int x = 0; +// int y = 0; +// Random rand = new Random(); +// int r = rand.Next(1, 4); +// switch (r) +// { +// case 1: +// x = rand.Next(0, (iW / 4) - 8); +// y = rand.Next(0, iH - 8); +// break; +// case 2: +// x = rand.Next((iW / 4) - 8, iW - 8); +// y = rand.Next(0, (iH / 4) - 8); +// break; +// case 3: +// x = rand.Next((iW / 4) - 8, iW - 8); +// y = rand.Next(3 * iH / 4, iH - 8); +// break; +// case 4: +// x = rand.Next(3 * iW / 4, iW - 8); +// y = rand.Next((iH / 4) - 8, 3 * iH / 4); +// break; +// } +// List> lstlstPixes = new List>(); +// int tmpx = x; +// int tmpy = y; +// for (int jj = 0; jj < 4; jj++) +// { +// List lstPixes = new List +// { +// new PixPoint { X = tmpx, Y = tmpy } +// }; +// for (int i = 1; i < 8 - (jj * 2); i++) +// { +// PixPoint ppx = new PixPoint { X = x, Y = ++tmpy }; +// lstPixes.Add(ppx); +// PixPoint ppy = new PixPoint { X = ++tmpx, Y = y }; +// lstPixes.Add(ppy); +// } +// lstlstPixes.Add(lstPixes); +// if (jj == 3) +// { +// break; +// } + +// tmpx = ++x; +// tmpy = ++y; +// } + +// for (int jj = 3; jj >= 0; jj--) +// { + +// List lstPixes = new List(); +// tmpx = ++x; +// tmpy = ++y; +// lstPixes.Add(new PixPoint { X = tmpx, Y = tmpy }); +// for (int i = 1; i < 7 - (jj * 2); i++) +// { +// PixPoint ppx = new PixPoint { X = x, Y = --tmpy }; +// lstPixes.Add(ppx); +// PixPoint ppy = new PixPoint { X = --tmpx, Y = y }; +// lstPixes.Add(ppy); +// } +// lstlstPixes[jj].AddRange(lstPixes); +// } + +// int inc = 0; +// foreach (List lstpix in lstlstPixes) +// { +// inc++; +// foreach (PixPoint pix in lstpix) +// { +// Color color = image.GetPixel(pix.X, pix.Y); +// int newR, newG, newB; +// newR = color.R + inc > 255 ? 255 : color.R + inc; +// newG = color.G + inc > 255 ? 255 : color.G + inc; +// newB = color.B + inc > 255 ? 255 : color.B + inc; +// Color c = Color.FromArgb(newR, newG, newB); +// image.SetPixel(pix.X, pix.Y, c); +// } +// } +// ImageCodecInfo icc = GetCodeInfo("1.jpg"); +// //long quality = 75; +// long quality = PicQuality;//之前固定值 现在改为设置的数值 +// ms = new MemoryStream(); +// EncoderParameters ep = new EncoderParameters(1); +// ep.Param[0] = new EncoderParameter(Encoder.Quality, quality); +// image.Save(ms, icc, ep); +// img = ms.ToArray(); +// //TODO:Need Test +// //image.Dispose(); +// ms.Close(); +// } +// catch (Exception ex) +// { +// throw; +// } +// } + +// /// +// /// 输出固定大小图片,背景白色填充,返回JPG格式二进制代码 +// /// +// /// +// /// +// /// 最大尺寸KB +// /// +// public byte[] BuildNewSize(int width, int height, int maxSize, string codePath = "1.jpg") +// { +// if (InitImage == null) +// { +// return null; +// } + +// int w = InitImage.Width; +// int h = InitImage.Height; +// int x = 0, y = 0, nw = w, nh = h; +// int tx = 0, ty = 0, tw = width, th = height; +// //先缩放 +// if (w <= width && h <= height)//无需缩放 +// { +// tx = (width - w) / 2; +// tw = w; +// ty = (height - h) / 2; +// th = h; +// } +// else if (w < width && h > height) +// { +// tx = (width - w) / 2; +// tw = w; +// y = (h - height) / 2; +// nh = height; +// } +// else if (w > width && h < height) +// { +// ty = (height - h) / 2; +// th = h; +// x = (w - width) / 2; +// nw = width; +// } +// else +// { +// double f = w * 1.0 / width; +// double t = h * 1.0 / height; +// if (f == t)//无需变化 +// { +// } +// else if (f > t)//宽度较大 +// { +// x = (int)((f - t) / 2 * width); +// nw = w - (x * 2); +// } +// else//高度较大 +// { +// y = (int)((t - f) / 2 * height); +// nh = h - (y * 2); +// } +// } +// Rectangle fromR = new Rectangle(x, y, nw, nh); +// Rectangle toR = new Rectangle(tx, ty, tw, th); +// Bitmap pickedImage = new Bitmap(width, height); +// for (int i = 0; i < pickedImage.Width; i++) +// { +// for (int j = 0; j < pickedImage.Height; j++) +// { +// Color color = Color.FromArgb(255, 255, 255); +// pickedImage.SetPixel(i, j, color); +// } +// } +// Graphics pickedG = Graphics.FromImage(pickedImage); +// pickedG.InterpolationMode = InterpolationMode.Low; +// pickedG.SmoothingMode = SmoothingMode.HighSpeed; +// pickedG.DrawImage(InitImage, toR, fromR, GraphicsUnit.Pixel); +// //转图片后转二进制数组 +// Image initImagex = (Image)pickedImage.Clone(); +// MemoryStream ms = new MemoryStream(); +// ImageCodecInfo icc = GetCodeInfo(codePath); + +// //long quality = maxSize == 1024 ? 75l : 85; +// long quality = PicQuality;//之前固定值 现在改为设置的数值 +// EncoderParameters ep = new EncoderParameters(1); +// ep.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality); +// initImagex.Save(ms, icc, ep); +// byte[] bytes = ms.ToArray(); +// if (bytes.Length > maxSize * 1024) +// { +// ms = new MemoryStream(); +// ep.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 75L); +// initImagex.Save(ms, icc, ep); +// bytes = ms.ToArray(); +// } +// if (bytes.Length > maxSize * 1024) +// { +// ms = new MemoryStream(); +// ep.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 55L); +// initImagex.Save(ms, icc, ep); +// bytes = ms.ToArray(); +// } +// //TODO:Need Test +// //pickedImage.Dispose(); +// //pickedG.Dispose(); +// //initImagex.Dispose(); +// //ms.Close(); +// if (bytes.Length > maxSize * 1024) +// { +// return null; +// } +// //ImageCodecInfo icc = GetCodeInfo("1.jpg"); +// //initImagex.Save("22.jpg", icc, ep); +// return bytes; +// } +// /// +// /// 根据路劲得出图片编码信息 +// /// +// /// +// /// +// public ImageCodecInfo GetCodeInfo(string path) +// { +// string exe = path.Split('.')[path.Split('.').Length - 1].ToLower(); +// ImageCodecInfo[] icis = ImageCodecInfo.GetImageEncoders(); +// ImageCodecInfo ici = null, defCodecInfo = null; +// foreach (ImageCodecInfo i in icis) +// { +// if (i.MimeType == "image/jpeg") +// { +// defCodecInfo = i; +// } + +// if ((exe == "jpg" || exe == "jpeg") && i.MimeType == "image/jpeg") +// { +// ici = i; +// } +// if (exe == "bmp" && i.MimeType == "image/bmp") +// { +// ici = i; +// } +// if (exe == "png" && i.MimeType == "image/png") +// { +// ici = i; +// } +// if (exe == "gif" && i.MimeType == "image/gif") +// { +// ici = i; +// } +// } +// if (ici == null) +// { +// ici = defCodecInfo; +// } + +// return ici; +// } + +// /// +// /// 获取Image图片格式 +// /// +// /// +// /// +// /// +// private ImageFormat GetImageFormat(Image _img, out string format) +// { +// if (_img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Jpeg)) +// { +// format = ".jpg"; +// return System.Drawing.Imaging.ImageFormat.Jpeg; +// } +// if (_img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Gif)) +// { +// format = ".gif"; +// return System.Drawing.Imaging.ImageFormat.Gif; +// } +// if (_img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Png)) +// { +// format = ".png"; +// return System.Drawing.Imaging.ImageFormat.Png; +// } +// if (_img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Bmp)) +// { +// format = ".bmp"; +// return System.Drawing.Imaging.ImageFormat.Bmp; +// } +// format = string.Empty; +// return null; +// } + + +// #region 随机裁四边--10个像素以内 +// public void CutPicture(ref byte[] file) +// { +// MemoryStream ms = new MemoryStream(file); +// Bitmap image = (Bitmap)Image.FromStream(ms); + +// Random radom = new Random(); +// int cutPixl = radom.Next(1, 5); //左边切除的像素 +// int cutPixr = radom.Next(1, 5); //右边切除的像素 +// int cutPixt = radom.Next(1, 5); //顶部切除的像素 +// int cutPixb = radom.Next(1, 5); //底部切除的像素 + +// Image img = BytesToImage(file); +// int iniWidth = img.Width; +// int iniHeight = img.Height; + +// try +// { +// Bitmap bitmap = StartCutImage(file, cutPixl, cutPixr, iniWidth - cutPixl - cutPixt, iniHeight - cutPixr - cutPixb, iniWidth, iniHeight); + +// image = bitmap; + +// ImageCodecInfo icc = GetCodeInfo("1.jpg"); +// //long quality = 75; +// long quality = PicQuality;//之前固定值 现在改为设置的数值 +// ms = new MemoryStream(); +// EncoderParameters ep = new EncoderParameters(1); +// ep.Param[0] = new EncoderParameter(Encoder.Quality, quality); +// image.Save(ms, icc, ep); +// // image.Save(ms,ImageFormat.Jpeg); +// file = ms.ToArray(); +// //TODO:Need Test +// //img.Dispose(); +// //bitmap.Dispose(); +// //image.Dispose(); +// ms.Close(); +// } +// catch (Exception) +// { + +// } + +// } + +// /// +// /// 剪裁 +// /// +// /// 原始Bitmap +// /// 开始坐标X +// /// 开始坐标Y +// /// 宽度 +// /// 高度 +// /// 剪裁后的Bitmap +// public static Bitmap StartCutImage(byte[] bytes, int StartX, int StartY, int iWidth, int iHeight, int iniWidth, int iniHeight) +// { +// Image image = BytesToImage(bytes); +// Bitmap b = new Bitmap(image); +// if (bytes == null) +// { +// return null; +// } + +// try +// { +// Bitmap bmpOut = new Bitmap(iWidth, iHeight, PixelFormat.Format24bppRgb);//裁剪成小一号的图片 +// //Bitmap bmpOut = new Bitmap(w, h, PixelFormat.Format24bppRgb);//保持原大小,裁剪部分用指定的颜色填充 +// Graphics graphics = Graphics.FromImage(bmpOut); +// //graphics.Clear(Color.White);//裁剪部分用指定的颜色填充 +// graphics.DrawImage(b, new Rectangle(0, 0, iWidth, iHeight), new Rectangle(StartX, StartY, iWidth, iHeight), GraphicsUnit.Pixel); +// graphics.Dispose(); +// bmpOut = new Bitmap(bmpOut, iniWidth, iniHeight);//图片保存原始的大小尺寸 +// return bmpOut; +// } +// catch +// { +// return null; +// } + +// } + +// /// +// /// 字节数组转换为图片 +// /// +// /// 字节数组 +// /// 图片 +// public static Image BytesToImage(byte[] buffer) +// { +// MemoryStream ms = new MemoryStream(buffer, 0, buffer.Length); +// Image img = Image.FromStream(ms); +// ms.Dispose(); +// return img; +// } + +// /// +// /// 图片对象转字节数组 +// /// +// /// 图片路径 +// /// 图片字节数组 +// private byte[] ImageToBytes(string imagePath) +// { +// FileStream fs = new FileStream(imagePath, FileMode.Open); +// byte[] imgBytes = new byte[fs.Length]; +// fs.Read(imgBytes, 0, imgBytes.Length); +// fs.Close(); +// return imgBytes; +// //var bf = new BinaryFormatter(); +// //bf.Serialize(ms,imagePath); +// //ms.Close(); +// //return ms.ToArray(); + +// } + +// #endregion + +// #region 添加文字水印 +// public void AddWaterMark(ref byte[] file, string shopName = "") +// { +// MemoryStream ms = new MemoryStream(file); +// Bitmap image = (Bitmap)Image.FromStream(ms); + +// try +// { +// Bitmap bitmap = StartAddWaterMark(file, shopName); +// image = bitmap; + +// ImageCodecInfo icc = GetCodeInfo("1.jpg"); +// //long quality = 75; +// long quality = PicQuality;//之前固定值 现在改为设置的数值 +// ms = new MemoryStream(); +// EncoderParameters ep = new EncoderParameters(1); +// ep.Param[0] = new EncoderParameter(Encoder.Quality, quality); +// image.Save(ms, icc, ep); +// file = ms.ToArray(); +// //image.Dispose(); +// //bitmap.Dispose(); +// ms.Close(); +// } +// catch (Exception) +// { +// throw; +// } + +// } + +// /// +// /// 检测是否是白底图 +// /// +// /// 图片byte数组 +// /// true-白底图 false-非白底图 +// public static bool CheckImageIsWhiteBackground(byte[] bytesImg) +// { +// MemoryStream ms = new MemoryStream(bytesImg); +// Bitmap image = (Bitmap)Image.FromStream(ms); +// int iWhitePixNum = 0; +// int iTotalPix = 0; +// try +// { +// int iWidth = image.Width; +// int iHeight = image.Height; +// int iInpectWidth = 20; +// //统计三窄边白色像素数量 +// for (int i = 0; i < iInpectWidth; i++) +// { +// for (int j = 0; j < iHeight; j++) +// { +// Color cl = image.GetPixel(i, j); +// iTotalPix++; +// if (cl.Name.Equals("ffffffff")) +// { +// iWhitePixNum++; +// } +// } +// } +// for (int i = iWidth - iInpectWidth; i < iWidth; i++) +// { +// for (int j = iInpectWidth; j < iHeight; j++) +// { +// Color cl = image.GetPixel(i, j); +// iTotalPix++; +// if (cl.Name.Equals("ffffffff")) +// { +// iWhitePixNum++; +// } +// } +// } +// for (int i = iInpectWidth; i < iWidth; i++) +// { +// for (int j = 0; j < iInpectWidth; j++) +// { +// Color cl = image.GetPixel(i, j); +// iTotalPix++; +// if (cl.Name.Equals("ffffffff")) +// { +// iWhitePixNum++; +// } +// } +// } +// } +// catch (Exception ex) +// { +// throw; +// } +// finally +// { +// ms.Close(); +// image.Dispose(); +// } +// //如果三窄边的白色像素数量占比高于85%,则判定为白底图 +// return Decimal.Divide(iWhitePixNum, iTotalPix) > (decimal)0.85; +// } + +// /// +// /// 添加图片水印 +// /// +// /// +// /// +// /// 水印开始X坐标(自动扣除图片宽度) +// /// 水印开始Y坐标(自动扣除图片高度 +// /// 透明度 0-1 +// /// 文件后缀名 +// /// +// public void AddLogoWaterMark(ref byte[] file, Image watermark, float opacity, string externName, bool bFill = true) +// { +// MemoryStream ms = new MemoryStream(file); +// Bitmap image = (Bitmap)Image.FromStream(ms); +// Bitmap bitmap = new Bitmap(image, image.Width, image.Height); //改为固定 + +// Graphics g = Graphics.FromImage(bitmap); + +// //下面定义一个矩形区域 +// float rectWidth = watermark.Width + 10; +// float rectHeight = watermark.Height + 10; +// //声明矩形域 + +// RectangleF textArea; +// if (bFill) +// { +// textArea = new RectangleF(0, 0, image.Width, image.Height); +// } +// else +// { +// int height = watermark.Height; +// int width = watermark.Width; +// height = 80; +// width = 80; +// textArea = new RectangleF(0, 0, width, height); +// } +// Bitmap w_bitmap = ChangeOpacity(watermark, opacity); + +// g.DrawImage(w_bitmap, 0, 0, 80, 80); +// ms = new MemoryStream(); +// //保存图片 +// switch (externName) +// { +// case ".jpg": +// bitmap.Save(ms, ImageFormat.Jpeg); +// break; +// case ".gif": +// bitmap.Save(ms, ImageFormat.Gif); +// break; +// case ".png": +// bitmap.Save(ms, ImageFormat.Png); +// break; +// default: +// bitmap.Save(ms, ImageFormat.Jpeg); +// break; +// } + +// file = ms.ToArray(); +// ms.Close(); +// g.Dispose(); +// bitmap.Dispose(); +// } + +// /// +// /// 改变图片的透明度 +// /// +// /// 图片 +// /// 透明度 +// /// +// public Bitmap ChangeOpacity(Image img, float opacityvalue) +// { + +// float[][] nArray ={ new float[] {1, 0, 0, 0, 0}, +// new float[] {0, 1, 0, 0, 0}, +// new float[] {0, 0, 1, 0, 0}, +// new float[] {0, 0, 0, opacityvalue, 0}, +// new float[] {0, 0, 0, 0, 1}}; +// ColorMatrix matrix = new ColorMatrix(nArray); +// ImageAttributes attributes = new ImageAttributes(); +// attributes.SetColorMatrix(matrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap); +// Image srcImage = img; +// Bitmap resultImage = new Bitmap(srcImage.Width, srcImage.Height); +// Graphics g = Graphics.FromImage(resultImage); +// g.DrawImage(srcImage, new Rectangle(0, 0, srcImage.Width, srcImage.Height), 0, 0, srcImage.Width, srcImage.Height, GraphicsUnit.Pixel, attributes); +// //g.DrawImage(srcImage, 0, 0, srcImage.Width, srcImage.Height) ; +// //g.DrawImage(srcImage, 0, 0, 80,80); +// g.Dispose(); +// attributes.Dispose(); +// return resultImage; +// } + +// //private static Font crFont = new Font("微软雅黑", 30, FontStyle.Bold); +// /// +// /// +// /// +// /// +// /// 水印文字 +// /// +// public static Bitmap StartAddWaterMark(byte[] bytes, string text = "") +// { +// if (bytes == null) +// { +// return null; +// } + +// Image image = BytesToImage(bytes); +// Bitmap bitmap = new Bitmap(image); +// try +// { + +// int width = bitmap.Width, height = bitmap.Height; + +// Graphics g = Graphics.FromImage(bitmap); + +// g.DrawImage(bitmap, 0, 0); + +// g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High; + +// g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; + +// g.DrawImage(image, new Rectangle(0, 0, width, height), 0, 0, width, height, GraphicsUnit.Pixel); + +// Font crFont = new Font("微软雅黑", 30, FontStyle.Bold); +// SizeF crSize = new SizeF(); +// crSize = g.MeasureString(text, crFont); + +// //背景位置(去掉了. 如果想用可以自己调一调 位置.) +// //g.FillRectangle(new SolidBrush(Color.FromArgb(200, 255, 255, 255)), (width - crSize.Width) / 2, (height - crSize.Height) / 2, crSize.Width, crSize.Height); + +// SolidBrush semiTransBrush = new SolidBrush(Color.FromArgb(120, 177, 171, 171));//177, 171, 171 + + +// //文字水印随机加在一遍 +// Random rdom = new Random(); +// int rdLocation = rdom.Next(1, 5);//1:左边 2:右边 3:上边 4:下边 + +// if (rdLocation == 1) +// { +// #region 左边 +// //将原点移动 到图片中点 +// g.TranslateTransform(0, (height / 2) + crSize.Height); +// //以原点为中心 转 -45度 +// g.RotateTransform(-90); + +// g.DrawString(text, crFont, semiTransBrush, new PointF(0, 0));//文字所加的位置 +// #endregion +// } +// else if (rdLocation == 2) +// { +// #region 右边 +// //将原点移动 到图片中点 +// g.TranslateTransform(width - (crSize.Width / 2), (height / 2) + crSize.Height); +// //以原点为中心 转 -45度 +// g.RotateTransform(-90); + +// g.DrawString(text, crFont, semiTransBrush, new PointF(0, 0));//文字所加的位置 +// #endregion +// } +// else if (rdLocation == 3) +// { +// #region 上边 +// ////将原点移动 到图片中点 +// //g.TranslateTransform(width / 2 - crSize.Width, 0); +// ////以原点为中心 转 -45度 +// //g.RotateTransform(-0); + +// g.DrawString(text, crFont, semiTransBrush, new PointF((width / 2) - (crSize.Width / 2), 0));//文字所加的位置 +// #endregion +// } +// else if (rdLocation == 4) +// { +// #region 下边 +// ////将原点移动 到图片中点 +// //g.TranslateTransform(width / 2 - crSize.Width, height - crSize.Height * 3); +// ////以原点为中心 转 -0度 +// ////g.RotateTransform(-0); + +// g.DrawString(text, crFont, semiTransBrush, new PointF((width / 2) - (crSize.Width / 2), height - crSize.Height));//文字所加的位置 +// #endregion +// } +// else +// { +// #region 上边 +// g.DrawString(text, crFont, semiTransBrush, new PointF((width / 2) - (crSize.Width / 2), 0));//文字所加的位置 +// #endregion +// } +// //crFont.Dispose(); +// g.Dispose(); +// //image.Dispose(); +// //semiTransBrush.Dispose(); +// ////将原点移动 到图片中点 +// //g.TranslateTransform(width / 2, height / 2); +// ////以原点为中心 转 -45度 +// //g.RotateTransform(-45); + +// //g.DrawString(text, crFont, semiTransBrush, new PointF(width / 2 - crSize.Width / 2, 0));//文字所加的位置 + + +// } +// catch (Exception e) +// { +// throw; +// } + +// return bitmap; +// } +// #endregion + +// /// +// /// 获取两张图片的相似度 +// /// +// /// 图片1 +// /// 图片2 +// /// +// public static string GetSimilarityForTwoImgs(Image image1, Image image2) +// { +// string sImg1Hash = GetHash(image1); +// string sImg2Hash = GetHash(image2); +// return (Math.Round(((64.0 * 64.0) - CalcSimularDegree(sImg1Hash, sImg2Hash)) / (64.0 * 64.0), 4) * 100) + "%"; +// } + +// private static int CalcSimularDegree(string a, string b) +// { +// if (a.Length != b.Length) +// { +// throw new ArgumentException(); +// } + +// int count = 0; +// for (int i = 0; i < a.Length; i++) +// { +// if (a[i] != b[i]) +// { +// count++; +// } +// } +// return count; +// } + +// private static string GetHash(Image sourceImage) +// { +// Image image = ReduceSize(sourceImage); +// byte[] grayValues = ReduceColor(image); +// byte average = CalcAverage(grayValues); +// string sRes = ComputeBits(grayValues, average); +// image.Dispose(); +// image = null; +// return sRes; +// } + +// private static string ComputeBits(byte[] grayValues, byte average) +// { +// char[] result = new char[grayValues.Length]; +// for (int i = 0; i < grayValues.Length; i++) +// { +// if (grayValues[i] < average) +// { +// result[i] = '0'; +// } +// else +// { +// result[i] = '1'; +// } +// } +// return new string(result); +// } + +// private static byte CalcAverage(byte[] grayValues) +// { +// int sum = 0; +// for (int i = 0; i < grayValues.Length; i++) +// { +// sum += grayValues[i]; +// } +// return System.Convert.ToByte(sum / grayValues.Length); +// } + +// private static byte[] ReduceColor(Image image) +// { +// Bitmap bitmap = new Bitmap(image); +// byte[] grayValues = new byte[image.Width * image.Height]; + +// for (int i = 0; i < image.Width; i++) +// { +// for (int j = 0; j < image.Height; j++) +// { +// Color color = bitmap.GetPixel(i, j); +// byte grayValue = (byte)(((color.R * 30) + (color.G * 59) + (color.B * 11)) / 100); +// grayValues[(i * image.Width) + j] = grayValue; +// } +// } +// bitmap.Dispose(); +// return grayValues; +// } + +// private static Image ReduceSize(Image image) +// { +// Image image1 = image.GetThumbnailImage(64, 64, () => +// { +// return false; +// }, IntPtr.Zero); +// return image1; +// } + +// //byte[] 转换 Bitmap +// public static Bitmap BytesToBitmap(byte[] Bytes) +// { +// MemoryStream stream = null; +// try +// { +// stream = new MemoryStream(Bytes); +// return new Bitmap(new Bitmap(stream)); +// } +// catch (ArgumentNullException ex) +// { +// throw ex; +// } +// catch (ArgumentException ex) +// { +// throw ex; +// } +// finally +// { +// stream.Close(); +// } +// } +// //Bitmap转byte[] +// public static byte[] BitmapToBytes(Bitmap Bitmap) +// { +// MemoryStream ms = null; +// try +// { +// ms = new MemoryStream(); +// Bitmap.Save(ms, Bitmap.RawFormat); +// byte[] byteImage = new Byte[ms.Length]; +// byteImage = ms.ToArray(); +// return byteImage; +// } +// catch (ArgumentNullException ex) +// { +// throw ex; +// } +// finally +// { +// ms.Close(); +// } +// } +// public static Image ResizeImage(Image imgToResize, Size size) +// { +// //获取图片宽度 +// int sourceWidth = imgToResize.Width; +// //获取图片高度 +// int sourceHeight = imgToResize.Height; + +// float nPercent = 0; +// float nPercentW = 0; +// float nPercentH = 0; +// //计算宽度的缩放比例 +// nPercentW = size.Width / (float)sourceWidth; +// //计算高度的缩放比例 +// nPercentH = size.Height / (float)sourceHeight; + +// if (nPercentH < nPercentW) +// { +// nPercent = nPercentH; +// } +// else +// { +// nPercent = nPercentW; +// } +// //期望的宽度 +// int destWidth = (int)(sourceWidth * nPercent); +// //期望的高度 +// int destHeight = (int)(sourceHeight * nPercent); + +// Bitmap b = new Bitmap(destWidth, destHeight); +// Graphics g = Graphics.FromImage(b); +// g.InterpolationMode = InterpolationMode.HighQualityBicubic; +// //绘制图像 +// g.DrawImage(imgToResize, 0, 0, destWidth, destHeight); +// g.Dispose(); +// return b; +// } + +// private class PixPoint +// { +// public int X { get; set; } +// public int Y { get; set; } +// } +// } +//} diff --git a/MAX.Framework/Helpers/ObjectHelper.cs b/MAX.Framework/Helpers/ObjectHelper.cs new file mode 100644 index 0000000..dac2387 --- /dev/null +++ b/MAX.Framework/Helpers/ObjectHelper.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; + +namespace MAX.Framework.Helpers +{ + public static class ObjectHelper + { + private static readonly ConcurrentDictionary CachedObjectProperties = + new ConcurrentDictionary(); + + public static void TrySetProperty( + TObject obj, + Expression> propertySelector, + Func valueFactory, + params Type[] ignoreAttributeTypes + ) + { + TrySetProperty(obj, propertySelector, x => valueFactory(), ignoreAttributeTypes); + } + + public static void TrySetProperty( + TObject obj, + Expression> propertySelector, + Func valueFactory, + params Type[] ignoreAttributeTypes + ) + { + string cacheKey = + $"{obj?.GetType().FullName}-" + + $"{propertySelector}-" + + $"{(ignoreAttributeTypes != null ? "-" + string.Join("-", ignoreAttributeTypes.Select(x => x.FullName)) : "")}"; + + PropertyInfo property = CachedObjectProperties.GetOrAdd( + cacheKey, + (name) => + { + if (propertySelector.Body.NodeType != ExpressionType.MemberAccess) + { + return null; + } + + MemberExpression memberExpression = (MemberExpression)propertySelector.Body; + + PropertyInfo propertyInfo = obj?.GetType() + .GetProperties() + .FirstOrDefault( + x => + x.Name == memberExpression.Member.Name + && x.GetSetMethod(true) != null + ); + + if (propertyInfo == null) + { + return null; + } + + if ( + ignoreAttributeTypes != null + && ignoreAttributeTypes.Any( + ignoreAttribute => propertyInfo.IsDefined(ignoreAttribute, true) + ) + ) + { + return null; + } + + return propertyInfo; + } + ); + + property?.SetValue(obj, valueFactory(obj)); + } + } +} diff --git a/MAX.Framework/Helpers/StringTypeHelper.cs b/MAX.Framework/Helpers/StringTypeHelper.cs new file mode 100644 index 0000000..a6d617c --- /dev/null +++ b/MAX.Framework/Helpers/StringTypeHelper.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MAX.Framework.Helpers; +public class StringTypeHelper +{ + public static object ConvertToObject(string input, out Type type) + { + type = typeof(string); + object value = input; + + // 1. 检查布尔值(支持多种表示形式) + if (IsBoolean(input, out bool boolValue)) + { + type = typeof(bool); + value = boolValue; + return value; + } + + // 2. 检查整数 + if (int.TryParse(input, NumberStyles.Integer, CultureInfo.InvariantCulture, out int intValue)) + { + type = typeof(int); + value = intValue; + return value; + } + + // 3. 检查无符号整数 + if (ulong.TryParse(input, NumberStyles.Integer, CultureInfo.InvariantCulture, out ulong ulongValue)) + { + type = typeof(ulong); + value = ulongValue; + return value; + } + + // 4. 检查双精度浮点数 + if (double.TryParse(input, NumberStyles.Number | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent, + CultureInfo.InvariantCulture, out double doubleValue)) + { + type = typeof(double); + value = doubleValue; + return value; + } + + // 5. 检查十进制数 + if (decimal.TryParse(input, NumberStyles.Number | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent, + CultureInfo.InvariantCulture, out decimal decimalValue)) + { + type = typeof(decimal); + value = decimalValue; + return value; + } + + // 6. 检查日期时间 + if (IsDateTime(input, out DateTime dateTimeValue)) + { + type = typeof(DateTime); + value = dateTimeValue; + return value; + } + + // 7. 检查null + if (input == null) + { + type = typeof(string); + value = null; + return value; + } + + // 8. 默认返回字符串类型 + type = typeof(string); + return value; + } + + + private static bool IsBoolean(string input, out bool boolValue) + { + boolValue = false; + // 支持多种布尔值表示形式 + if (input.Equals(bool.TrueString, StringComparison.OrdinalIgnoreCase) || + input.Equals(bool.FalseString, StringComparison.OrdinalIgnoreCase) || + input.Equals("t", StringComparison.OrdinalIgnoreCase) || + input.Equals("f", StringComparison.OrdinalIgnoreCase) || + input.Equals("1", StringComparison.Ordinal) || + input.Equals("0", StringComparison.Ordinal) || + input.Equals("yes", StringComparison.OrdinalIgnoreCase) || + input.Equals("no", StringComparison.OrdinalIgnoreCase) || + input.Equals("真", StringComparison.Ordinal) || + input.Equals("假", StringComparison.Ordinal)) + { + boolValue = input.Equals(bool.TrueString, StringComparison.OrdinalIgnoreCase) || + input.Equals("t", StringComparison.OrdinalIgnoreCase) || + input.Equals("1", StringComparison.Ordinal) || + input.Equals("yes", StringComparison.OrdinalIgnoreCase) || + input.Equals("真", StringComparison.Ordinal); + return true; + } + + return false; + } + + private static bool IsDateTime(string input, out DateTime dateTimeValue) + { + dateTimeValue = default; + // 首先尝试使用默认的DateTime.TryParse进行解析 + if (DateTime.TryParse(input, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime dateTime)) + { + dateTimeValue = dateTime; + return true; + } + + // 如果解析失败,尝试使用不同的格式字符串进行解析 + string[] formats = { + "yyyy-MM-dd", + "yyyy/MM/dd", + "yyyy.MM.dd", + "yyyyMMdd", + "yyyy-MM-dd HH:mm", + "yyyy/MM/dd HH:mm", + "yyyy.MM.dd HH:mm", + "yyyy-MM-dd HH:mm:ss", + "yyyy/MM/dd HH:mm:ss", + "yyyy.MM.dd HH:mm:ss", + "yyyy-MM-dd HH:mm:ss.fff", + "yyyy-MM-ddTHH:mm:ss.fffK" // 支持ISO 8601格式带时区 + }; + + // 使用TryParseExact尝试解析,提供多种格式字符串 + foreach (string format in formats) + { + if (DateTime.TryParseExact(input, format, CultureInfo.InvariantCulture, DateTimeStyles.None, out dateTime)) + { + dateTimeValue = dateTime; + return true; + } + } + + return false; + } +} diff --git a/MAX.Framework/Helpers/SystemHelper.cs b/MAX.Framework/Helpers/SystemHelper.cs new file mode 100644 index 0000000..ba9a7c7 --- /dev/null +++ b/MAX.Framework/Helpers/SystemHelper.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Security.Principal; +using System.Text; +using System.Threading.Tasks; + +namespace MAX.Framework.Helpers; + +public class SystemHelper +{ + public static bool IsAdministratorSafe + { + get + { + try + { + return IsAdministrator; + } + catch (Exception ex) + { + Debug.WriteLine(ex); + return false; + } + } + } + + + public static bool IsAdministrator + { + get + { + WindowsIdentity current = WindowsIdentity.GetCurrent(); + WindowsPrincipal windowsPrincipal = new(current); + return windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator); + } + } +} \ No newline at end of file diff --git a/MAX.Framework/Helpers/WinCmdHelper.cs b/MAX.Framework/Helpers/WinCmdHelper.cs new file mode 100644 index 0000000..f63ce4b --- /dev/null +++ b/MAX.Framework/Helpers/WinCmdHelper.cs @@ -0,0 +1,157 @@ +using MAX.Core.Commons; +using System; +using System.Diagnostics; +using System.Text; + +namespace MAX.Framework.Helpers +{ + public class WinCmdHelper + { + #region 执行Dos命令 + + /// + /// 执行Dos命令 + /// + /// Dos命令及参数 + /// 是否显示cmd窗口 + /// 执行完毕后是否关闭cmd进程 + /// 成功返回true,失败返回false + public static Result ExcuteDosCommand(string cmd, string workdir, bool isShowCmdWindow = false, bool isWaitForExit = false, bool isCloseCmdProcess = true) + { + Result result = new Result(); + try + { + StringBuilder errString = new StringBuilder(); + StringBuilder outString = new StringBuilder(); + Process p = new Process(); + p.StartInfo.FileName = "cmd"; + p.StartInfo.WorkingDirectory = workdir; + p.StartInfo.Arguments = "/c " + cmd; + p.StartInfo.StandardOutputEncoding = Encoding.UTF8; + p.StartInfo.StandardErrorEncoding = Encoding.UTF8; + p.StartInfo.UseShellExecute = false; + p.StartInfo.RedirectStandardOutput = true; + p.StartInfo.RedirectStandardError = true; + p.StartInfo.CreateNoWindow = !isShowCmdWindow; + p.OutputDataReceived += new DataReceivedEventHandler(delegate (object sender, DataReceivedEventArgs e) + { + if (!string.IsNullOrEmpty(e.Data)) + { + outString.AppendLine(e.Data); + Debug.WriteLine(e.Data); + } + }); + p.ErrorDataReceived += new DataReceivedEventHandler(delegate (object sender, DataReceivedEventArgs e) + { + if (!string.IsNullOrEmpty(e.Data)) + { + errString.AppendLine(e.Data); + } + }); + p.Start(); + p.BeginOutputReadLine(); + p.BeginErrorReadLine(); + if (isWaitForExit) + { + p.WaitForExit(); + } + + if (isCloseCmdProcess) + { + p.Close(); + } + if (errString.Length <= 0) + { + result.Success = true; + result.Data = outString.ToString(); + } + Console.WriteLine(string.Format("成功执行Dos命令[{0}]!", cmd)); + return result; + } + catch (Exception ex) + { + Console.WriteLine("执行命令失败,请检查输入的命令是否正确:" + ex.Message, ex); + result.Msg = ex.Message; + } + + return result; + } + + private static void P_ErrorDataReceived(object sender, DataReceivedEventArgs e) + { + throw new NotImplementedException(); + } + + #endregion + + #region 判断指定的进程是否在运行中 + + /// + /// 判断指定的进程是否在运行中 + /// + /// 要判断的进程名称,不包括扩展名exe + /// 进程文件的完整路径 + /// 存在返回true,否则返回false + public static bool CheckProcessExists(string processName, string processFileName) + { + Process[] processes = Process.GetProcessesByName(processName); + foreach (Process p in processes) + { + if (!string.IsNullOrEmpty(processFileName)) + { + if (processFileName == p?.MainModule?.FileName) + { + return true; + } + } + else + { + return true; + } + } + return false; + } + + #endregion + + #region 结束指定的windows进程 + + /// + /// 结束指定的windows进程,如果进程存在 + /// + /// 进程名称,不包含扩展名 + /// 进程文件完整路径,如果为空则删除所有进程名为processName参数值的进程 + public static bool KillProcessExists(string processName, string processFileName) + { + try + { + Process[] processes = Process.GetProcessesByName(processName); + foreach (Process p in processes) + { + if (!string.IsNullOrEmpty(processFileName)) + { + if (processFileName == p.MainModule.FileName) + { + p.Kill(); + p.Close(); + } + } + else + { + p.Kill(); + p.Close(); + } + } + Console.WriteLine(string.Format("成功结束[{0}]进程!", processes)); + return true; + } + catch (Exception ex) + { + Console.WriteLine("结束指定的Widnows进程异常:" + ex.Message, ex); + return false; + } + } + + #endregion + } +} diff --git a/MAX.Framework/Helpers/XmlHelper.cs b/MAX.Framework/Helpers/XmlHelper.cs new file mode 100644 index 0000000..8d3b6d6 --- /dev/null +++ b/MAX.Framework/Helpers/XmlHelper.cs @@ -0,0 +1,292 @@ + +using System.Xml; +using System.Data; +using System.IO; +using System; + +namespace MAX.Framework.Helpers +{ + /// + /// Xml的操作公共类 + /// + public class XmlHelper + { + #region 字段定义 + /// + /// /// XML文件的物理路径 + /// + private string _filePath = string.Empty; + /// + /// Xml文档 + /// + private XmlDocument _xml; + /// + /// XML的根节点 + /// + private XmlElement _element; + #endregion + + #region 构造方法 + /// + /// 实例化XmlHelper对象 + /// + /// Xml文件的相对路径 + public XmlHelper(string xmlFilePath) + { + //获取XML文件的绝对路径 + _filePath = xmlFilePath; + //创建一个XML对象 + _xml = new XmlDocument(); + if (File.Exists(xmlFilePath)) + { + //加载XML文件 + _xml.Load(xmlFilePath); + } + _element = _xml.DocumentElement; + } + public XmlHelper(Stream stream) + { + //创建一个XML对象 + _xml = new XmlDocument(); + if (stream != null) + { + //加载XML + _xml.Load(stream); + } + _element = _xml.DocumentElement; + } + #endregion + + + + #region 获取指定XPath表达式的节点对象 + /// + /// 获取指定XPath表达式的节点对象 + /// + /// XPath表达式, + /// 范例1: @"Skill/First/SkillItem", 等效于 @"//Skill/First/SkillItem" + /// 范例2: @"Table[USERNAME='a']" , []表示筛选,USERNAME是Table下的一个子节点. + /// 范例3: @"ApplyPost/Item[@itemName='岗位编号']",@itemName是Item节点的属性. + /// + public XmlNode GetNode(string xPath) + { + //返回XPath节点 + return _element.SelectSingleNode(xPath); + } + + /// + /// 获取指定XPath表达式的节点对象 + /// + /// XPath表达式, + /// 范例1: @"Skill/First/SkillItem", 等效于 @"//Skill/First/SkillItem" + /// 范例2: @"Table[USERNAME='a']" , []表示筛选,USERNAME是Table下的一个子节点. + /// 范例3: @"ApplyPost/Item[@itemName='岗位编号']",@itemName是Item节点的属性. + /// + public XmlNodeList GetNodes(string xPath) + { + //返回XPath节点 + return _element.SelectNodes(xPath); + } + #endregion + + #region 获取指定XPath表达式节点的值 + /// + /// 获取指定XPath表达式节点的值 + /// + /// XPath表达式, + /// 范例1: @"Skill/First/SkillItem", 等效于 @"//Skill/First/SkillItem" + /// 范例2: @"Table[USERNAME='a']" , []表示筛选,USERNAME是Table下的一个子节点. + /// 范例3: @"ApplyPost/Item[@itemName='岗位编号']",@itemName是Item节点的属性. + /// + public string GetValue(string xPath) + { + //返回XPath节点的值 + string value = _element.SelectSingleNode(xPath)?.InnerText ?? ""; + return value; + } + public T GetValue(string xPath, T? def = default) + { + string value = GetValue(xPath); + if (value == null) + return def; + else + return (T)Convert.ChangeType(value, typeof(T)); + } + #endregion + + #region 获取指定XPath表达式节点的属性值 + /// + /// 获取指定XPath表达式节点的属性值 + /// + /// XPath表达式, + /// 范例1: @"Skill/First/SkillItem", 等效于 @"//Skill/First/SkillItem" + /// 范例2: @"Table[USERNAME='a']" , []表示筛选,USERNAME是Table下的一个子节点. + /// 范例3: @"ApplyPost/Item[@itemName='岗位编号']",@itemName是Item节点的属性. + /// + /// 属性名 + public string GetAttributeValue(string xPath, string attributeName) + { + //返回XPath节点的属性值 + return _element.SelectSingleNode(xPath).Attributes[attributeName].Value; + } + #endregion + + #region 新增节点 + /// + /// 1. 功能:新增节点。 + /// 2. 使用条件:将任意节点插入到当前Xml文件中。 + /// + /// 要插入的Xml节点 + public void AppendNode(XmlNode xmlNode) + { + //导入节点 + XmlNode node = _xml.ImportNode(xmlNode, true); + + //将节点插入到根节点下 + _element.AppendChild(node); + } + + /// + /// 1. 功能:新增节点。 + /// 2. 使用条件:将DataSet中的第一条记录插入Xml文件中。 + /// + /// DataSet的实例,该DataSet中应该只有一条记录 + public void AppendNode(DataSet ds) + { + //创建XmlDataDocument对象 + XmlDataDocument xmlDataDocument = new XmlDataDocument(ds); + + //导入节点 + XmlNode node = xmlDataDocument.DocumentElement.FirstChild; + + //将节点插入到根节点下 + AppendNode(node); + } + #endregion + + #region 删除节点 + /// + /// 删除指定XPath表达式的节点 + /// + /// XPath表达式, + /// 范例1: @"Skill/First/SkillItem", 等效于 @"//Skill/First/SkillItem" + /// 范例2: @"Table[USERNAME='a']" , []表示筛选,USERNAME是Table下的一个子节点. + /// 范例3: @"ApplyPost/Item[@itemName='岗位编号']",@itemName是Item节点的属性. + /// + public void RemoveNode(string xPath) + { + //获取要删除的节点 + XmlNode node = _xml.SelectSingleNode(xPath); + + //删除节点 + _element.RemoveChild(node); + } + #endregion //删除节点 + + #region 保存XML文件 + /// + /// 保存XML文件 + /// + public void Save() + { + if (string.IsNullOrEmpty(_filePath)) + { + throw new Exception("Xml文件路径不能为空!"); + } + //保存XML文件 + _xml.Save(_filePath); + } + public void Save(string filePath) + { + //保存XML文件 + _xml.Save(filePath); + } + #endregion //保存XML文件 + + #region 静态方法 + + #region 创建根节点对象 + /// + /// 创建根节点对象 + /// + /// Xml文件的相对路径 + private static XmlElement CreateRootElement(string xmlFilePath) + { + //定义变量,表示XML文件的绝对路径 + string filePath = ""; + + //获取XML文件的绝对路径 + filePath = xmlFilePath; + + //创建XmlDocument对象 + XmlDocument xmlDocument = new XmlDocument(); + //加载XML文件 + xmlDocument.Load(filePath); + + //返回根节点 + return xmlDocument.DocumentElement; + } + #endregion + + #region 获取指定XPath表达式节点的值 + /// + /// 获取指定XPath表达式节点的值 + /// + /// Xml文件的相对路径 + /// XPath表达式, + /// 范例1: @"Skill/First/SkillItem", 等效于 @"//Skill/First/SkillItem" + /// 范例2: @"Table[USERNAME='a']" , []表示筛选,USERNAME是Table下的一个子节点. + /// 范例3: @"ApplyPost/Item[@itemName='岗位编号']",@itemName是Item节点的属性. + /// + public static string GetValue(string xmlFilePath, string xPath) + { + //创建根对象 + XmlElement rootElement = CreateRootElement(xmlFilePath); + + //返回XPath节点的值 + return rootElement.SelectSingleNode(xPath).InnerText; + } + #endregion + + #region 获取指定XPath表达式节点的属性值 + /// + /// 获取指定XPath表达式节点的属性值 + /// + /// Xml文件的相对路径 + /// XPath表达式, + /// 范例1: @"Skill/First/SkillItem", 等效于 @"//Skill/First/SkillItem" + /// 范例2: @"Table[USERNAME='a']" , []表示筛选,USERNAME是Table下的一个子节点. + /// 范例3: @"ApplyPost/Item[@itemName='岗位编号']",@itemName是Item节点的属性. + /// + /// 属性名 + public static string GetAttributeValue(string xmlFilePath, string xPath, string attributeName) + { + //创建根对象 + XmlElement rootElement = CreateRootElement(xmlFilePath); + + //返回XPath节点的属性值 + return rootElement.SelectSingleNode(xPath).Attributes[attributeName].Value; + } + #endregion + + #endregion + + + } + + public static class XmlHelExtension + { + public static string GetAttributeValue(this XmlNode node, string attributeName) + { + return node.Attributes[attributeName].Value; + } + public static T GetAttributeValue(this XmlNode node, string attributeName) + { + var value = node.Attributes[attributeName].Value; + if (string.IsNullOrEmpty(value)) + return default(T); + return (T)Convert.ChangeType(value, typeof(T)); + + } + } +} diff --git a/MAX.Framework/Interfaces/IAppConfig.cs b/MAX.Framework/Interfaces/IAppConfig.cs new file mode 100644 index 0000000..2582f88 --- /dev/null +++ b/MAX.Framework/Interfaces/IAppConfig.cs @@ -0,0 +1,17 @@ +using MAX.Core.DependencyInjection; + +namespace MAX.Framework.Interfaces +{ + public interface IAppConfig : ISingletonDependency + { + /// + /// 公司名称 + /// + string CompanyName { get; } + /// + /// 软件名称 + /// + string AppName { get; } + T GetValue(string key, T defaultValue = default); + } +} diff --git a/MAX.Framework/Interfaces/IAppInitialize.cs b/MAX.Framework/Interfaces/IAppInitialize.cs new file mode 100644 index 0000000..100f94b --- /dev/null +++ b/MAX.Framework/Interfaces/IAppInitialize.cs @@ -0,0 +1,38 @@ +using MAX.Core; +using MAX.Core.DependencyInjection; +using System.Threading.Tasks; + +namespace MAX.Framework.Interfaces +{ + /// + /// 初始化接口 + /// + public interface IAppInitialize : ISingletonDependency, IPriority + { + /// + /// 描述 + /// + string Description { get; } + + /// + /// App 初始化数据 + /// + void AppInitialize(); + } + + /// + /// 初始化接口(异步) + /// + public interface IAppInitializeAsync : ISingletonDependency, IPriority + { + /// + /// 描述 + /// + string Description { get; } + + /// + /// App 初始化数据 + /// + Task AppInitializeAsync(); + } +} diff --git a/MAX.Framework/Interfaces/IFolderConfig.cs b/MAX.Framework/Interfaces/IFolderConfig.cs new file mode 100644 index 0000000..26a6a58 --- /dev/null +++ b/MAX.Framework/Interfaces/IFolderConfig.cs @@ -0,0 +1,20 @@ +using MAX.Core.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MAX.Framework.Interfaces +{ + public interface IFolderConfig: ISingletonDependency + { + /// + /// 执行目录 + /// + string LocalFolder { get; } + string LocalAppFolder { get; } + string TargetFolder { get; } + string LoggingFolder { get; } + } +} diff --git a/MAX.Framework/Logger/NLogger.cs b/MAX.Framework/Logger/NLogger.cs new file mode 100644 index 0000000..b219141 --- /dev/null +++ b/MAX.Framework/Logger/NLogger.cs @@ -0,0 +1,153 @@ +using MAX.Core.DependencyInjection; +using MAX.Core.Infrastructure; +using MAX.Core.Logger; +using MAX.Framework.Interfaces; +using NLog; +using NLog.Targets; +using System; +using LLogLevel = MAX.Core.Logger.LogLevel; +using NLogLevel = NLog.LogLevel; + +namespace MAX.Framework.Logger +{ + public class NLogger : ILoggerFacade, ISingletonDependency + { + private readonly ILogger _log; + public static ILoggerFacade Instance => LazyServiceProvider.Instance.LazyGetService(); + public NLogger(IFolderConfig folderConfig = null) + { + if (folderConfig != null) + { + Target target = new FileTarget("log1") + { + FileName = $"{folderConfig.LoggingFolder}\\{DateTime.Now:yyyy-MM-dd}.txt", + }; + var config = new NLog.Config.LoggingConfiguration(); + config.AddRule(NLogLevel.Debug, NLogLevel.Fatal, target); + + LogManager.Configuration = config; + } + _log = LogManager.GetLogger("GlobalLogger"); + } + + public bool IsDebugEnabled { get; private set; } = false; + + public void Debug(Exception ex, string message) + { + Execute(NLogLevel.Debug, ex, message); + } + + public void Debug(string message) + { + Execute(NLogLevel.Debug, message); + } + public void Error(Exception ex) + { + Execute(NLogLevel.Error, ex); + } + public void Error(Exception ex, string message) + { + Execute(NLogLevel.Error, ex, message); + } + + public void Error(string message) + { + Execute(NLogLevel.Error, message); + } + + public void Fatal(Exception ex, string message) + { + Execute(NLogLevel.Fatal, ex, message); + } + + public void Fatal(string message) + { + Execute(NLogLevel.Fatal, message); + } + + public void Info(Exception ex, string message) + { + Execute(NLogLevel.Info, ex, message); + } + + public void Info(string message) + { + Execute(NLogLevel.Info, message); + } + + public void Warn(Exception ex, string message) + { + Execute(NLogLevel.Warn, ex, message); + } + + public void Warn(string message) + { + Execute(NLogLevel.Warn, message); + } + public void Log(string message, LLogLevel level) + { + NLogLevel lv = NLogLevel.Info; + switch (level) + { + case LLogLevel.Trace: + lv = NLogLevel.Trace; + break; + case LLogLevel.Debug: + lv = NLogLevel.Debug; + break; + case LLogLevel.Info: + lv = NLogLevel.Info; + break; + case LLogLevel.Warn: + lv = NLogLevel.Warn; + break; + case LLogLevel.Error: + lv = NLogLevel.Error; + break; + case LLogLevel.Fatal: + lv = NLogLevel.Fatal; + break; + case LLogLevel.None: + lv = NLogLevel.Off; + break; + } + Execute(lv, message); + } + /// + /// 是否启用 + /// + private bool Enabled(NLogLevel level) + { + if (level > NLogLevel.Debug) + { + return true; + } + + return IsDebugEnabled; + } + + /// + /// 执行 + /// + private void Execute(NLogLevel level, Exception ex, string message = "") + { + if (Enabled(level)) + { + _log.Log(level, ex, message, null); + } + } + + + /// + /// 执行 + /// + private void Execute(NLogLevel level, string message) + { + if (Enabled(level)) + { + _log.Log(level, message); + } + } + + } +} diff --git a/MAX.Framework/MAX.Framework.csproj b/MAX.Framework/MAX.Framework.csproj new file mode 100644 index 0000000..f438796 --- /dev/null +++ b/MAX.Framework/MAX.Framework.csproj @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MAX.Framework/PrismExtension/Commands/AsyncDelegateCommand.cs b/MAX.Framework/PrismExtension/Commands/AsyncDelegateCommand.cs new file mode 100644 index 0000000..8a7a4e9 --- /dev/null +++ b/MAX.Framework/PrismExtension/Commands/AsyncDelegateCommand.cs @@ -0,0 +1,243 @@ +using System.Linq.Expressions; +using System; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Input; +using Prism.Properties; + + +namespace Prism.Commands +{ + + /// + /// Provides an implementation of the + /// + public class AsyncDelegateCommand : DelegateCommandBase, IAsyncCommand + { + private bool _enableParallelExecution = false; + private bool _isExecuting = false; + private readonly Func _executeMethod; + private Func _canExecuteMethod; + private Func _getCancellationToken = () => CancellationToken.None; + + /// + /// Creates a new instance of with the to invoke on execution. + /// + /// The to invoke when is called. + public AsyncDelegateCommand(Func executeMethod) +#if NET6_0_OR_GREATER + : this (c => executeMethod().WaitAsync(c), () => true) +#else + : this(c => executeMethod(), () => true) +#endif + { + + } + + /// + /// Creates a new instance of with the to invoke on execution. + /// + /// The to invoke when is called. + public AsyncDelegateCommand(Func executeMethod) + : this(executeMethod, () => true) + { + + } + + /// + /// Creates a new instance of with the to invoke on execution + /// and a to query for determining if the command can execute. + /// + /// The to invoke when is called. + /// The delegate to invoke when is called + public AsyncDelegateCommand(Func executeMethod, Func canExecuteMethod) +#if NET6_0_OR_GREATER + : this(c => executeMethod().WaitAsync(c), canExecuteMethod) +#else + : this(c => executeMethod(), canExecuteMethod) +#endif + { + } + + /// + /// Creates a new instance of with the to invoke on execution + /// and a to query for determining if the command can execute. + /// + /// The to invoke when is called. + /// The delegate to invoke when is called + public AsyncDelegateCommand(Func executeMethod, Func canExecuteMethod) + : base() + { + if (executeMethod == null || canExecuteMethod == null) + throw new ArgumentNullException(nameof(executeMethod), "Neither the executeMethod nor the canExecuteMethod delegates can be null"); + + _executeMethod = executeMethod; + _canExecuteMethod = canExecuteMethod; + } + + /// + /// Gets the current state of the AsyncDelegateCommand + /// + public bool IsExecuting + { + get => _isExecuting; + private set + { + _isExecuting = value; + OnCanExecuteChanged(); + } + } + + /// + /// Executes the command. + /// + public async Task Execute(CancellationToken? cancellationToken = null) + { + var token = cancellationToken ?? _getCancellationToken(); + try + { + if (!_enableParallelExecution && IsExecuting) + return; + + IsExecuting = true; + await _executeMethod(token) + .ConfigureAwait(false); + } + catch + { + throw; + + } + finally + { + IsExecuting = false; + } + } + + /// + /// Determines if the command can be executed. + /// + /// Returns if the command can execute,otherwise returns . + public bool CanExecute() + { + try + { + if (!_enableParallelExecution && IsExecuting) + return false; + + return _canExecuteMethod?.Invoke() ?? true; + } + catch (Exception ex) + { + return false; + } + } + + /// + /// Handle the internal invocation of + /// + /// Command Parameter + protected override async void Execute(object parameter) + { + // We don't want to wrap this in a try/catch because we already handle + // or mean to rethrow the exception in the call with the CancellationToken. + var cancellationToken = _getCancellationToken(); + await Execute(cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Handle the internal invocation of + /// + /// + /// if the Command Can Execute, otherwise + protected override bool CanExecute(object parameter) + { + return CanExecute(); + } + + /// + /// Enables Parallel Execution of Async Tasks + /// + /// The current instance of . + public AsyncDelegateCommand EnableParallelExecution() + { + _enableParallelExecution = true; + return this; + } + + /// + /// Sets the based on the specified timeout. + /// + /// A specified timeout. + /// The current instance of . + public AsyncDelegateCommand CancelAfter(TimeSpan timeout) => + CancellationTokenSourceFactory(() => new CancellationTokenSource(timeout).Token); + + /// + /// Provides a delegate callback to provide a default CancellationToken when the Command is invoked. + /// + /// The default Factory. + /// The current instance of . + public AsyncDelegateCommand CancellationTokenSourceFactory(Func factory) + { + _getCancellationToken = factory; + return this; + } + + /// + /// Observes a property that implements INotifyPropertyChanged, and automatically calls DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications. + /// + /// The object type containing the property specified in the expression. + /// The property expression. Example: ObservesProperty(() => PropertyName). + /// The current instance of . + public AsyncDelegateCommand ObservesProperty(Expression> propertyExpression) + { + ObservesPropertyInternal(propertyExpression); + return this; + } + + /// + /// Observes a property that is used to determine if this command can execute, and if it implements INotifyPropertyChanged it will automatically call DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications. + /// + /// The property expression. Example: ObservesCanExecute(() => PropertyName). + /// The current instance of . + public AsyncDelegateCommand ObservesCanExecute(Expression> canExecuteExpression) + { + _canExecuteMethod = canExecuteExpression.Compile(); + ObservesPropertyInternal(canExecuteExpression); + return this; + } + + /// + /// Provides the ability to connect a delegate to catch exceptions encountered by CanExecute or the Execute methods of the DelegateCommand + /// + /// TThe callback when a specific exception is encountered + /// The current instance of . + public AsyncDelegateCommand Catch(Action @catch) + where TException : Exception + { + return this; + } + + /// + /// Provides the ability to connect a delegate to catch exceptions encountered by CanExecute or the Execute methods of the DelegateCommand + /// + /// The generic / default callback when an exception is encountered + /// The current instance of . + public AsyncDelegateCommand Catch(Action @catch) + { + return this; + } + + Task IAsyncCommand.ExecuteAsync(object parameter) + { + return Execute(_getCancellationToken()); + } + + Task IAsyncCommand.ExecuteAsync(object parameter, CancellationToken cancellationToken) + { + return Execute(cancellationToken); + } + } +} \ No newline at end of file diff --git a/MAX.Framework/PrismExtension/Commands/AsyncDelegateCommand{T}.cs b/MAX.Framework/PrismExtension/Commands/AsyncDelegateCommand{T}.cs new file mode 100644 index 0000000..55d6aec --- /dev/null +++ b/MAX.Framework/PrismExtension/Commands/AsyncDelegateCommand{T}.cs @@ -0,0 +1,284 @@ +using System.Linq.Expressions; +using System; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Input; +using System.Reflection.Emit; +using System.Resources; + +namespace Prism.Commands +{ + /// + /// Provides an implementation of the with a generic parameter type. + /// + /// + public class AsyncDelegateCommand : DelegateCommandBase, IAsyncCommand + { + private bool _enableParallelExecution = false; + private bool _isExecuting = false; + private readonly Func _executeMethod; + private Func _canExecuteMethod; + private Func _getCancellationToken = () => CancellationToken.None; + + /// + /// Creates a new instance of with the to invoke on execution. + /// + /// The to invoke when is called. + public AsyncDelegateCommand(Func executeMethod) +#if NET6_0_OR_GREATER + : this((p,t) => executeMethod(p).WaitAsync(t), _ => true) +#else + : this((p, t) => executeMethod(p), _ => true) +#endif + { + + } + + /// + /// Creates a new instance of with the to invoke on execution. + /// + /// The to invoke when is called. + public AsyncDelegateCommand(Func executeMethod) + : this(executeMethod, _ => true) + { + + } + + /// + /// Creates a new instance of with the to invoke on execution + /// and a to query for determining if the command can execute. + /// + /// The to invoke when is called. + /// The delegate to invoke when is called + public AsyncDelegateCommand(Func executeMethod, Func canExecuteMethod) +#if NET6_0_OR_GREATER + : this((p, c) => executeMethod(p).WaitAsync(c), canExecuteMethod) +#else + : this((p, c) => executeMethod(p), canExecuteMethod) +#endif + { + + } + + /// + /// Creates a new instance of with the to invoke on execution + /// and a to query for determining if the command can execute. + /// + /// The to invoke when is called. + /// The delegate to invoke when is called + public AsyncDelegateCommand(Func executeMethod, Func canExecuteMethod) + : base() + { + if (executeMethod == null || canExecuteMethod == null) + throw new ArgumentNullException(nameof(executeMethod), "Neither the executeMethod nor the canExecuteMethod delegates can be null"); + + + _executeMethod = executeMethod; + _canExecuteMethod = canExecuteMethod; + } + + /// + /// Gets the current state of the AsyncDelegateCommand + /// + public bool IsExecuting + { + get => _isExecuting; + private set + { + _isExecuting = value; + OnCanExecuteChanged(); + } + } + + /// + /// Executes the command. + /// + public async Task Execute(T parameter, CancellationToken? cancellationToken = null) + { + var token = cancellationToken ?? _getCancellationToken(); + + try + { + if (!_enableParallelExecution && IsExecuting) + return; + + IsExecuting = true; + await _executeMethod(parameter, token) + .ConfigureAwait(false); + } + catch (Exception ex) + { + throw; + } + finally + { + IsExecuting = false; + } + } + + /// + /// Determines if the command can be executed. + /// + /// Returns if the command can execute,otherwise returns . + public bool CanExecute(T parameter) + { + try + { + if (!_enableParallelExecution && IsExecuting) + return false; + + return _canExecuteMethod?.Invoke(parameter) ?? true; + } + catch (Exception ex) + { + + throw; + + return false; + } + } + + /// + /// Handle the internal invocation of + /// + /// Command Parameter + protected override async void Execute(object parameter) + { + var cancellationToken = _getCancellationToken(); + T parameterAsT; + try + { + parameterAsT = (T)parameter; + } + catch (Exception ex) + { + throw; + } + + // If we had an exception casting the parameter to T , + // we would have already returned. We want to surface any + // exceptions thrown by the Execute method. + await Execute(parameterAsT, cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Handle the internal invocation of + /// + /// + /// if the Command Can Execute, otherwise + protected override bool CanExecute(object parameter) + { + try + { + return CanExecute((T)parameter); + } + catch (Exception ex) + { + throw; + } + } + + /// + /// Enables Parallel Execution of Async Tasks + /// + /// The current instance of . + public AsyncDelegateCommand EnableParallelExecution() + { + _enableParallelExecution = true; + return this; + } + + /// + /// Sets the based on the specified timeout. + /// + /// A specified timeout. + /// The current instance of . + public AsyncDelegateCommand CancelAfter(TimeSpan timeout) => + CancellationTokenSourceFactory(() => new CancellationTokenSource(timeout).Token); + + /// + /// Provides a delegate callback to provide a default CancellationToken when the Command is invoked. + /// + /// The default Factory. + /// The current instance of . + public AsyncDelegateCommand CancellationTokenSourceFactory(Func factory) + { + _getCancellationToken = factory; + return this; + } + + /// + /// Observes a property that implements INotifyPropertyChanged, and automatically calls DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications. + /// + /// The type of the return value of the method that this delegate encapsulates + /// The property expression. Example: ObservesProperty(() => PropertyName). + /// The current instance of . + public AsyncDelegateCommand ObservesProperty(Expression> propertyExpression) + { + ObservesPropertyInternal(propertyExpression); + return this; + } + + /// + /// Observes a property that is used to determine if this command can execute, and if it implements INotifyPropertyChanged it will automatically call DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications. + /// + /// The property expression. Example: ObservesCanExecute(() => PropertyName). + /// The current instance of . + public AsyncDelegateCommand ObservesCanExecute(Expression> canExecuteExpression) + { + Expression> expression = Expression.Lambda>(canExecuteExpression.Body, Expression.Parameter(typeof(T), "o")); + _canExecuteMethod = expression.Compile(); + ObservesPropertyInternal(canExecuteExpression); + return this; + } + + /// + /// Provides the ability to connect a delegate to catch exceptions encountered by CanExecute or the Execute methods of the DelegateCommand + /// + /// TThe callback when a specific exception is encountered + /// The current instance of . + public AsyncDelegateCommand Catch(Action @catch) + where TException : Exception + { + return this; + } + + /// + /// Provides the ability to connect a delegate to catch exceptions encountered by CanExecute or the Execute methods of the DelegateCommand + /// + /// The generic / default callback when an exception is encountered + /// The current instance of . + public AsyncDelegateCommand Catch(Action @catch) + { + return this; + } + + async Task IAsyncCommand.ExecuteAsync(object parameter) + { + try + { + // If T is not nullable this may throw an exception + await Execute((T)parameter, _getCancellationToken()); + } + catch (Exception ex) + { + throw; + + } + } + + async Task IAsyncCommand.ExecuteAsync(object parameter, CancellationToken cancellationToken) + { + try + { + // If T is not nullable this may throw an exception + await Execute((T)parameter, cancellationToken); + } + catch (Exception ex) + { + throw; + } + } + } +} \ No newline at end of file diff --git a/MAX.Framework/PrismExtension/Commands/IAsyncCommand.cs b/MAX.Framework/PrismExtension/Commands/IAsyncCommand.cs new file mode 100644 index 0000000..72a86cd --- /dev/null +++ b/MAX.Framework/PrismExtension/Commands/IAsyncCommand.cs @@ -0,0 +1,28 @@ +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace Prism.Commands +{ + + /// + /// Provides an abstraction layer for custom controls which want to make use of Async Commands + /// + public interface IAsyncCommand : ICommand + { + /// + /// Executes the Command with a specified parameter and the Default . + /// + /// The Command Parameter + /// An Asynchronous Task + Task ExecuteAsync(object parameter); + + /// + /// Executes the Command with a specified parameter and using a + /// + /// The Command Parameter + /// The . + /// An Asynchronous Task + Task ExecuteAsync(object parameter, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/MAX.Framework/PrismExtension/Prism.Autofac/AutofacContainerExtension.cs b/MAX.Framework/PrismExtension/Prism.Autofac/AutofacContainerExtension.cs new file mode 100644 index 0000000..7223baa --- /dev/null +++ b/MAX.Framework/PrismExtension/Prism.Autofac/AutofacContainerExtension.cs @@ -0,0 +1,334 @@ +using Autofac; +using Autofac.Core; +using Autofac.Features.ResolveAnything; +using MAX.Core; +using MAX.Core.Autofac; +using MAX.Core.DependencyInjection; +using MAX.Core.Helpers; +using MAX.Core.Infrastructure; +using MAX.Core.Reflections; +using MAX.Framework.Interfaces; +using MAX.Framework.Prism.Autofac; +using Prism.Ioc; +using Prism.Ioc.Internals; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; + +namespace Prism.Autofac +{ + public class AutofacContainerExtension : IContainerExtension, IContainerInfo + { + private AutofacScopedProvider _currentScope; + public readonly ContainerBuilder Builder; + + public IContainer Instance { get; private set; } + + public IScopedProvider CurrentScope => _currentScope; + public AutofacContainerExtension() + { + Builder = new ContainerBuilder(); + string currentContainer = "CurrentContainer"; + Builder.RegisterInstance(this).Named(currentContainer, typeof(AutofacContainerExtension)); + Builder.RegisterInstance(this).As(); + Builder.RegisterInstance(this).As(); + //设置过滤AOP + //IAspectConfiguration configuration = new AspectConfiguration(); + //foreach (var item in AppTypeFinder.Assemblies) + //{ + // string _namespace = item.FullName; + // if (!Regex.IsMatch(_namespace, "^MAX", RegexOptions.IgnoreCase | RegexOptions.Compiled)) + // { + // configuration.NonAspectPredicates.AddNamespace(_namespace); + // } + //} + //Builder.RegisterDynamicProxy(configuration); + } + public AutofacContainerExtension(ContainerBuilder builder) + { + Builder = builder; + string currentContainer = "CurrentContainer"; + //Builder.RegisterDynamicProxy(); + Builder.RegisterInstance(this).Named(currentContainer, typeof(AutofacContainerExtension)); + Builder.RegisterInstance(this).As(); + Builder.RegisterInstance(this).As(); + } + public IScopedProvider CreateScope() + { + return CreateScopeInternal(); + } + protected IScopedProvider CreateScopeInternal() + { + _currentScope = new AutofacScopedProvider(Instance); + return _currentScope; + } + public void FinalizeExtension() + { + if (Instance != null) + { + return; + } + + Assembly[] assemblies = AppTypeFinder.Assemblies.ToArray(); + + // Make sure any not specifically registered concrete type can resolve. + IEnumerable startupList = ReflectionHelper.GetInstancesByInterface(assemblies); + int index = 1; + foreach (IStartup item in startupList.OrderByDescending(a => a.Priority)) + { + Debug.WriteLine($"Startup - {index++} : {item.GetType()}"); + item.Initialize(); + } + + // Make sure any not specifically registered concrete type can resolve. + IEnumerable ListDependency = ReflectionHelper.GetInstancesByInterface(assemblies); + foreach (IDependencyRegistrar item in ListDependency) + { + item.Register(Builder); + } + + IEnumerable PreRegisterTypes = ReflectionHelper.GetInstancesByInterface(assemblies); + foreach (IPreRegisterTypes item in PreRegisterTypes) + { + item.RegisterTypes(this); + } + + Builder.RegisterSource(new AnyConcreteTypeNotAlreadyRegisteredSource()); + Instance = Builder.Build(); + IocManager.SetContainer(Instance); + + } + + public bool IsRegistered(Type type) + { + return Instance.IsRegistered(type); + } + + public bool IsRegistered(Type type, string name) + { + return Instance.IsRegisteredWithName(name, type); + } + + public IContainerRegistry Register(Type from, Type to) + { + Builder.RegisterType(to).As(from); + return this; + } + + public IContainerRegistry Register(Type from, Type to, string name) + { + Builder.RegisterType(to).Named(name, from); + return this; + } + + + + public IContainerRegistry RegisterInstance(Type type, object instance) + { + Builder.RegisterInstance(instance).As(type); + return this; + } + + public IContainerRegistry RegisterInstance(Type type, object instance, string name) + { + Builder.RegisterInstance(instance).Named(name, type); + return this; + } + + public IContainerRegistry RegisterSingleton(Type from, Type to) + { + Builder.RegisterType(to).As(from).SingleInstance(); + return this; + } + + public IContainerRegistry RegisterSingleton(Type from, Type to, string name) + { + Builder.RegisterType(to).Named(name, from).SingleInstance(); + return this; + } + + public IContainerRegistry RegisterSingleton(Type type, Func factoryMethod) + { + Debugger.Break(); + throw new NotImplementedException(); + } + + public IContainerRegistry RegisterSingleton(Type type, Func factoryMethod) + { + Debugger.Break(); + throw new NotImplementedException(); + } + + public object Resolve(Type type) + { + return Instance.Resolve(type); + } + + public object Resolve(Type type, params (Type Type, object Instance)[] parameters) + { + List listps = new List(); + foreach ((Type Type, object Instance) item in parameters) + { + TypedParameter ps = new TypedParameter(item.Type, item.Instance); + listps.Add(ps); + } + return Instance.Resolve(type, listps); + } + + public object Resolve(Type type, string name) + { + return Instance.ResolveNamed(name, type); + } + + public object Resolve(Type type, string name, params (Type Type, object Instance)[] parameters) + { + List listps = new List(); + foreach ((Type Type, object Instance) item in parameters) + { + TypedParameter ps = new TypedParameter(item.Type, item.Instance); + listps.Add(ps); + } + return Instance.ResolveNamed(name, type, listps); + } + + public IContainerRegistry Register(Type type, Func factoryMethod) + { + Debugger.Break(); + throw new NotImplementedException(); + } + + public IContainerRegistry Register(Type type, Func factoryMethod) + { + Debugger.Break(); + throw new NotImplementedException(); + } + public IContainerRegistry RegisterMany(Type type, params Type[] serviceTypes) + { + Debugger.Break(); + throw new NotImplementedException(); + } + + public IContainerRegistry RegisterManySingleton(Type type, params Type[] serviceTypes) + { + Debugger.Break(); + throw new NotImplementedException(); + } + + public IContainerRegistry RegisterScoped(Type from, Type to) + { + Debugger.Break(); + throw new NotImplementedException(); + } + + public IContainerRegistry RegisterScoped(Type type, Func factoryMethod) + { + Debugger.Break(); + throw new NotImplementedException(); + } + + public IContainerRegistry RegisterScoped(Type type, Func factoryMethod) + { + Debugger.Break(); + throw new NotImplementedException(); + } + + public Type GetRegistrationType(string key) + { + Type retType = null; + //First try friendly name registration. If not found, try type registration + foreach (IComponentRegistration registration in Instance.ComponentRegistry.Registrations) + { + //System.Diagnostics.Debug.WriteLine(registration.ToString()); + foreach (Service service in registration.Services) + { + //System.Diagnostics.Debug.WriteLine(service.ToString()); + if (service is KeyedService ks) + { + if (key.Equals(ks.ServiceKey.ToString(), StringComparison.Ordinal)) + { + if (Instance.ComponentRegistry.TryGetServiceRegistration(service, out ServiceRegistration serviceRegistration)) + { + retType = serviceRegistration.Registration.Target.Activator.LimitType; + } + } + } + } + } + + //Instance.TryResolveService + //retType = Instance.ResolveNamed(key).GetType(); + if (retType == null) + { + retType = Instance.ResolveNamed(key).GetType(); + if (retType == null) + { + retType = typeof(System.Object); + } + } + return retType; + } + + public Type GetRegistrationType(Type serviceType) + { + Debugger.Break(); + throw new NotImplementedException(); + } + + private class AutofacScopedProvider : IScopedProvider + { + private readonly IContainer _container; + + public bool IsAttached { get; set; } + + public IScopedProvider CurrentScope => this; + + public IScopedProvider CreateScope() + { + return this; + } + public AutofacScopedProvider(IContainer container) + { + _container = container; + } + + public void Dispose() + { + _container.Dispose(); + } + + public object Resolve(Type type) + { + return _container.Resolve(type); + } + + public object Resolve(Type type, params (Type Type, object Instance)[] parameters) + { + List listps = new List(); + foreach ((Type Type, object Instance) item in parameters) + { + TypedParameter ps = new TypedParameter(item.Type, item.Instance); + listps.Add(ps); + } + return _container.Resolve(type, listps); + } + + public object Resolve(Type type, string name) + { + return _container.ResolveNamed(name, type); + } + + public object Resolve(Type type, string name, params (Type Type, object Instance)[] parameters) + { + List list = new List(); + foreach ((Type Type, object Instance) item in parameters) + { + TypedParameter ps = new TypedParameter(item.Type, item.Instance); + list.Add(ps); + } + return _container.ResolveNamed(name, type, list); + } + } + } +} \ No newline at end of file diff --git a/MAX.Framework/PrismExtension/Prism.Autofac/IAutofacContainerExtension.cs b/MAX.Framework/PrismExtension/Prism.Autofac/IAutofacContainerExtension.cs new file mode 100644 index 0000000..9428a2a --- /dev/null +++ b/MAX.Framework/PrismExtension/Prism.Autofac/IAutofacContainerExtension.cs @@ -0,0 +1,16 @@ +using Autofac; +using Prism.Ioc; + +namespace Prism.Autofac +{ + /// + /// Provides customization extensions for Autofac Applications + /// + public interface IAutofacContainerExtension : IContainerExtension + { + /// + /// Gets the used to register the services for the application + /// + ContainerBuilder Builder { get; } + } +} diff --git a/MAX.Framework/PrismExtension/Prism.Autofac/IPreRegisterTypes.cs b/MAX.Framework/PrismExtension/Prism.Autofac/IPreRegisterTypes.cs new file mode 100644 index 0000000..3a0f748 --- /dev/null +++ b/MAX.Framework/PrismExtension/Prism.Autofac/IPreRegisterTypes.cs @@ -0,0 +1,9 @@ +using Prism.Ioc; + +namespace MAX.Framework.Prism.Autofac +{ + public interface IPreRegisterTypes + { + void RegisterTypes(IContainerRegistry containerRegistry); + } +} diff --git a/MAX.Framework/PrismExtension/Prism.Autofac/PrismApplication.cs b/MAX.Framework/PrismExtension/Prism.Autofac/PrismApplication.cs new file mode 100644 index 0000000..adf50e7 --- /dev/null +++ b/MAX.Framework/PrismExtension/Prism.Autofac/PrismApplication.cs @@ -0,0 +1,44 @@ +using Prism.Ioc; +using System; +using System.Windows; +using AutofacCore = Autofac.Core; + +namespace Prism.Autofac +{ + /// + /// Base application class that uses as it's container. + /// + public abstract class PrismApplication : PrismApplicationBase + { + /// + /// Create a new used by Prism. + /// + /// A new . + protected override IContainerExtension CreateContainerExtension() + { + return new AutofacContainerExtension(); + } + + /// + /// Registers the s of the Exceptions that are not considered + /// root exceptions by the . + /// + protected override void RegisterFrameworkExceptionTypes() + { + ExceptionExtensions.RegisterFrameworkExceptionType(typeof(AutofacCore.DependencyResolutionException)); + ExceptionExtensions.RegisterFrameworkExceptionType(typeof(AutofacCore.Registration.ComponentNotRegisteredException)); + + } + + protected override void Initialize() + { + base.Initialize(); + } + + + protected override Window CreateShell() + { + return null; + } + } +} diff --git a/MAX.Framework/PrismExtension/Prism.Autofac/PrismBootstrapper.cs b/MAX.Framework/PrismExtension/Prism.Autofac/PrismBootstrapper.cs new file mode 100644 index 0000000..b449cd1 --- /dev/null +++ b/MAX.Framework/PrismExtension/Prism.Autofac/PrismBootstrapper.cs @@ -0,0 +1,31 @@ +using Prism.Ioc; +using System; +using AutofacCore = Autofac.Core; + +namespace Prism.Autofac +{ + /// + /// Base bootstrapper class that uses as it's container. + /// + public abstract class PrismBootstrapper : PrismBootstrapperBase + { + /// + /// Create a new used by Prism. + /// + /// A new . + protected override IContainerExtension CreateContainerExtension() + { + return new AutofacContainerExtension(); + } + + /// + /// Registers the s of the Exceptions that are not considered + /// root exceptions by the . + /// + protected override void RegisterFrameworkExceptionTypes() + { + ExceptionExtensions.RegisterFrameworkExceptionType(typeof(AutofacCore.DependencyResolutionException)); + ExceptionExtensions.RegisterFrameworkExceptionType(typeof(AutofacCore.Registration.ComponentNotRegisteredException)); + } + } +} diff --git a/MAX.Framework/PrismExtension/Prism.Autofac/PrismIocExtensions.cs b/MAX.Framework/PrismExtension/Prism.Autofac/PrismIocExtensions.cs new file mode 100644 index 0000000..a0d99ac --- /dev/null +++ b/MAX.Framework/PrismExtension/Prism.Autofac/PrismIocExtensions.cs @@ -0,0 +1,40 @@ +using Autofac; +using Prism.Ioc; + +namespace Prism.Autofac +{ + /// + /// Extensions help get the underlying + /// + public static class PrismIocExtensions + { + /// + /// Gets the from the + /// + /// The current + /// The underlying + public static IContainer GetContainer(this IContainerProvider containerProvider) + { + return ((IContainerExtension)containerProvider).Instance; + } + + /// + /// Gets the from the + /// + /// The current + /// The underlying + public static IContainer GetContainer(this IContainerRegistry containerRegistry) + { + return ((IContainerExtension)containerRegistry).Instance; + } + /// + /// Gets the used to register services. + /// + /// + /// The current + public static ContainerBuilder GetBuilder(this IContainerRegistry registry) + { + return ((IAutofacContainerExtension)registry).Builder; + } + } +} diff --git a/MAX.Framework/PrismExtension/RegionManagerExtension.cs b/MAX.Framework/PrismExtension/RegionManagerExtension.cs new file mode 100644 index 0000000..65dc602 --- /dev/null +++ b/MAX.Framework/PrismExtension/RegionManagerExtension.cs @@ -0,0 +1,35 @@ +using Prism.Regions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using Autofac; +using Autofac.Core; +using System.Windows.Input; +using MAX.Core.DependencyInjection; +namespace MAX.Framework.PrismExtension; +public static class RegionManagerExtension +{ + public static FrameworkElement GetView(this IRegionManager regionManager, string regionName, string target) + { + var region = regionManager.Regions[regionName]; + if (region != null) + { + + var viewType = IocManager.GetRegistrationType(target); + foreach (var viewItem in region.Views) + { + if (viewItem.GetType() == viewType) + { + if (viewItem is FrameworkElement fk) + { + return fk; + } + } + } + } + return null; + } +} diff --git a/MAX.Framework/Startup.cs b/MAX.Framework/Startup.cs new file mode 100644 index 0000000..aed526f --- /dev/null +++ b/MAX.Framework/Startup.cs @@ -0,0 +1,59 @@ +using Autofac; +using MAX.Core; +using MAX.Core.Autofac; +using MAX.Core.DependencyInjection; +using MAX.Core.Infrastructure; +using MAX.Core.Logger; +using MAX.Core.Reflections; +using MAX.Framework.Logger; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; + +namespace MAX.Framework; + +public class Startup : IStartup +{ + public int Priority => int.MaxValue - 1; + + public void Initialize() + { + + } + + public void Register(ContainerBuilder builder) + { + #region 注册所有IOC接口 + + Assembly[] assemblies = AppTypeFinder.Assemblies.ToArray(); + + Type typeSingleton = typeof(ISingletonDependency); + builder.RegisterAssemblyTypes(assemblies) + .Where(a => + typeSingleton.IsAssignableFrom(a) + && a.IsClass + && !a.IsAbstract + ) + .AsImplementedInterfaces() + .AsSelf() + .SingleInstance(); + + + Type typeTransient = typeof(ITransientDependency); + builder.RegisterAssemblyTypes(assemblies) + .Where(a => + typeTransient.IsAssignableFrom(a) + && a.IsClass + && !a.IsAbstract + ) + .AsImplementedInterfaces() + .AsSelf() + .InstancePerDependency(); + + + #endregion + + } +} diff --git a/Store.Framework/Agent/AgentConfig.cs b/Store.Framework/Agent/AgentConfig.cs new file mode 100644 index 0000000..8bf107a --- /dev/null +++ b/Store.Framework/Agent/AgentConfig.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Client.Agent +{ + /// + /// 代理配置 + /// + public class AgentConfig + { + /// + /// 代理码 + /// + public static string AgentCode { get; set; } + } +} diff --git a/Store.Framework/Apis/ApiResponse.cs b/Store.Framework/Apis/ApiResponse.cs new file mode 100644 index 0000000..b2e352d --- /dev/null +++ b/Store.Framework/Apis/ApiResponse.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Framework.Apis; + +public class ApiResponse +{ + public int Code { get; set; } + + public string Msg { get; set; } + + public T Data { get; set; } +} diff --git a/Store.Framework/Apis/CommonApi.cs b/Store.Framework/Apis/CommonApi.cs new file mode 100644 index 0000000..ffb1e71 --- /dev/null +++ b/Store.Framework/Apis/CommonApi.cs @@ -0,0 +1,241 @@ +using MAX.Core.DependencyInjection; +using MAX.Core.Helpers; +using Store.Client.HttpExtension; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Client.Apis +{ + /// + /// 公共基础数据Api + /// + public partial class CommonApi : AppApiRequestBase, ITransientDependency + { + #region 公共基础 + /// + /// 获取服务器时间校准时间 + /// + public DateTime GetServerTime() + { + HttpResult result = ServerRequest(new ServerHttpItem { URL = $"{_config.Host}/time.php" }); + if (result.StatusCode == HttpStatusCode.OK) + { + if (DateTime.TryParse(result.Html, out DateTime dt)) + { + return dt; + } + else + { + throw new Exception("连接服务器失败,将导致无法登录"); + } + + } + return DateTime.Now; + } + + /// + /// 获取签名验证配置 + /// + public string GetAuthConfig() + { + ServerHttpItem item = new ServerHttpItem(); + item.URL = $"{_config.Host}?obj=api/config&act=auth_config"; + HttpResult result = ServerRequest(item); + if (result.StatusCode == HttpStatusCode.OK) + { + return result.Html; + } + return ""; + } + + + /// + /// 获取缓存服务集群负载比例 + /// + public string GetCacheServerRatio() + { + + ServerHttpItem item = new ServerHttpItem(); + item.URL = _config.Host + "?" + _config.ObjUrl + "config&act=cache_service_mode"; + item.Method = MethodType.Post; + //item.SignUser(); + HttpResult result = ServerRequest(item); + if (result.StatusCode == HttpStatusCode.OK) + { + return result.Html; + } + return ""; + } + #endregion + + ///// + ///// 获取服务器图片缓存 + ///// + ///// + ///// + //public HttpResult GetImageCache(string imgUrl) + //{ + // long id = MD5Helper.GetLong(imgUrl); + // ServerHttpItem item = new ServerHttpItem(); + // item.Method = MethodType.Post; + // item.URL = _config.Host + "?" + _config.ObjUrl + "add_img"; + // item.Postdata = "act=get&table=" + Math.Abs(id % 1024) + "&id=" + id; + // return ServerRequest(item); + //} + + #region 默认服务接口 + /// + /// common&act=get_agent_ip + /// + public HttpResult GetAgentIp(string postdata = "") + { + return Post("common&act=get_agent_ip", postdata); + } + + /// + /// config&act=get_service_url + /// + public HttpResult GetServiceUrl(string postdata = "") + { + return Post("config&act=get_service_url", postdata); + } + + /// + /// cookies&act=save + /// + public HttpResult SaveCookies(string postdata = "") + { + return Post("cookies&act=save", postdata); + } + + /// + /// data_change&act=get_cache_title + /// + public HttpResult GetCacheTitle(string postdata = "") + { + return Post("data_change&act=get_cache_title", postdata); + } + + /// + /// data_change&act=get_pid + /// + public HttpResult GetPid(string postdata = "") + { + return Post("data_change&act=get_pid", postdata); + } + + /// + /// decode&act=get + /// + public HttpResult GetDecode(string postdata = "") + { + return Post("decode&act=get", postdata); + } + + /// + /// decode&act=getx + /// + public HttpResult GetDecodeList(string postdata = "") + { + return Post("decode&act=getx", postdata); + } + + /// + /// get_taobao_sign&act=get_tb_sign + /// + public HttpResult GetTaobaoSign(string postdata = "") + { + return Post("get_taobao_sign&act=get_tb_sign", postdata); + } + + /// + /// nav_group_list + /// + public HttpResult GetNavGroupList(string postdata = "") + { + return Post("nav_group_list", postdata); + } + + /// + /// oem_config&act=get_config_info + /// + public HttpResult GetOemConfigInfo(string postdata = "") + { + return Post("oem_config&act=get_config_info", postdata); + } + + /// + /// oem_config&act=get_version + /// + public HttpResult GetOemVersion(string postdata = "") + { + return Post("oem_config&act=get_version", postdata); + } + + /// + /// regedit + /// + public HttpResult Regedit(string postdata = "") + { + return PostPublic("regedit", postdata); + } + + /// + /// reset_password + /// + public HttpResult ResetPassword(string postdata = "") + { + return PostPublic("reset_password", postdata); + } + + /// + /// sign + /// + public HttpResult GetPackageSign(string postdata = "") + { + return Post("sign", postdata); + } + + /// + /// sign&act=get&type={type}&client_id={clientId} + /// + public HttpResult GetSign(string type, string clientId, string postdata = "") + { + return Post("sign&act=get&type=" + type + "&client_id=" + clientId, postdata); + } + + /// + /// sms_gate + /// + public HttpResult SmsGate(string postdata = "") + { + return PostPublic("sms_gate", postdata); + } + + /// + /// upid&act=getx&page={page} + /// + public HttpResult GetUpidList(int page, string postdata = "") + { + return Post("upid&act=getx&page=" + page, postdata); + } + + #endregion + + #region 默认兼容接口 + /// + /// update/get_update_new + /// + public HttpResult DefaultUpdateGetUpdateNew(string postdata = "") + { + return PostDefault("update/get_update_new", postdata); + } + + #endregion + + } +} diff --git a/Store.Framework/Apis/OrderApi.cs b/Store.Framework/Apis/OrderApi.cs new file mode 100644 index 0000000..c36e41a --- /dev/null +++ b/Store.Framework/Apis/OrderApi.cs @@ -0,0 +1,128 @@ +using MAX.Core.DependencyInjection; +using MAX.Core.Helpers; +using Store.Client.HttpExtension; + +namespace Store.Client.Apis +{ + public partial class OrderApi : AppApiRequestBase, ITransientDependency + { + #region 默认服务接口 + /// + /// order/add_order + /// + public HttpResult AddOrder(string postdata = "") + { + return Post("order/add_order", postdata); + } + + /// + /// order/add_order_code_file + /// + public HttpResult AddOrderCodeFile(string postdata = "") + { + return Post("order/add_order_code_file", postdata); + } + + /// + /// order/get_order_code_file + /// + public HttpResult GetOrderCodeFile(string postdata = "") + { + return Post("order/get_order_code_file", postdata); + } + + /// + /// order/get_order_list + /// + public HttpResult GetOrderList(string postdata = "") + { + return Post("order/get_order_list", postdata); + } + + /// + /// order/save_order_code_file_link + /// + public HttpResult SaveOrderCodeFileLink(string postdata = "") + { + return Post("order/save_order_code_file_link", postdata); + } + + /// + /// order/update_order_status + /// + public HttpResult UpdateOrderStatus(string postdata = "") + { + return Post("order/update_order_status", postdata); + } + + /// + /// order/update_order_status1 + /// + public HttpResult UpdateOrderStatus1(string postdata = "") + { + return Post("order/update_order_status1", postdata); + } + + /// + /// order/upload_order + /// + public HttpResult UploadOrder(string postdata = "") + { + return Post("order/upload_order", postdata); + } + + /// + /// pay_group_order&act=get_confirm_order&buy_type={buyType}&page={page} + /// + public HttpResult GetPayGroupConfirmOrder(string buyType, int page, string postdata = "") + { + return Post("pay_group_order&act=get_confirm_order&buy_type=" + buyType + "&page=" + page, postdata); + } + + /// + /// pay_group_order&act=reset_status + /// + public HttpResult ResetPayGroupOrderStatus(string postdata = "") + { + return Post("pay_group_order&act=reset_status", postdata); + } + + /// + /// pay_group_order&act=update&buy_type={buyType} + /// + public HttpResult UpdatePayGroupOrder(string buyType, string postdata = "") + { + return Post("pay_group_order&act=update&buy_type=" + buyType, postdata); + } + + #endregion + + #region 微信服务接口 + /// + /// order/get_erp_order_list + /// + public HttpResult WxGetErpOrderList(string postdata = "") + { + return PostWx("order/get_erp_order_list", postdata); + } + + /// + /// order/wx_del_erp_transit_warehouse_order + /// + public HttpResult WxDelErpTransitWarehouseOrder(string postdata = "") + { + return PostWx("order/wx_del_erp_transit_warehouse_order", postdata); + } + + /// + /// order/wx_get_transit_order + /// + public HttpResult WxGetTransitOrder(string postdata = "") + { + return PostWx("order/wx_get_transit_order", postdata); + } + + #endregion + + } +} diff --git a/Store.Framework/Apis/PriceApi.cs b/Store.Framework/Apis/PriceApi.cs new file mode 100644 index 0000000..e697dc5 --- /dev/null +++ b/Store.Framework/Apis/PriceApi.cs @@ -0,0 +1,80 @@ +using MAX.Core.DependencyInjection; +using MAX.Core.Helpers; +using Store.Client.HttpExtension; + +namespace Store.Client.Apis +{ + public partial class PriceApi : AppApiRequestBase, ITransientDependency + { + #region 默认服务接口 + /// + /// price/add_price + /// + public HttpResult AddPrice(string postdata = "") + { + return Post("price/add_price", postdata); + } + + /// + /// price/add_purchase_price + /// + public HttpResult AddPurchasePrice(string postdata = "") + { + return Post("price/add_purchase_price", postdata); + } + + /// + /// price/del_db_price + /// + public HttpResult DelDbPrice(string postdata = "") + { + return Post("price/del_db_price", postdata); + } + + /// + /// price/get_price_list + /// + public HttpResult GetPriceList(string postdata = "") + { + return Post("price/get_price_list", postdata); + } + + /// + /// price/get_purchase_price_list + /// + public HttpResult GetPurchasePriceList(string postdata = "") + { + return Post("price/get_purchase_price_list", postdata); + } + + /// + /// price/mdy_db_price + /// + public HttpResult MdyDbPrice(string postdata = "") + { + return Post("price/mdy_db_price", postdata); + } + + /// + /// price/mdy_purchase_price + /// + public HttpResult MdyPurchasePrice(string postdata = "") + { + return Post("price/mdy_purchase_price", postdata); + } + + #endregion + + #region 拼多多服务接口 + /// + /// price/add_ext_code_price + /// + public HttpResult PddAddExtCodePrice(string postdata = "") + { + return PostPdd("price/add_ext_code_price", postdata); + } + + #endregion + + } +} diff --git a/Store.Framework/Apis/ProductApi.cs b/Store.Framework/Apis/ProductApi.cs new file mode 100644 index 0000000..cfdb8d3 --- /dev/null +++ b/Store.Framework/Apis/ProductApi.cs @@ -0,0 +1,654 @@ +using MAX.Core.DependencyInjection; +using MAX.Core.Helpers; +using Store.Client.HttpExtension; + +namespace Store.Client.Apis +{ + public partial class ProductApi : AppApiRequestBase, ITransientDependency + { + #region 默认服务接口 + /// + /// product/add_cate_size_weight_config_v2 + /// + public HttpResult AddCateSizeWeightConfigV2(string postdata = "") + { + return Post("product/add_cate_size_weight_config_v2", postdata); + } + + /// + /// product/add_product + /// + public HttpResult AddProduct(string postdata = "") + { + return Post("product/add_product", postdata); + } + + /// + /// product/add_sku_image + /// + public HttpResult AddSkuImage(string postdata = "") + { + return Post("product/add_sku_image", postdata); + } + + /// + /// product/add_sku_img + /// + public HttpResult AddSkuImg(string postdata = "") + { + return Post("product/add_sku_img", postdata); + } + + /// + /// product/add_sku_img_replace + /// + public HttpResult AddSkuImgReplace(string postdata = "") + { + return Post("product/add_sku_img_replace", postdata); + } + + /// + /// product/add_user_select_product + /// + public HttpResult AddUserSelectProduct(string postdata = "") + { + return Post("product/add_user_select_product", postdata); + } + + /// + /// product/admin_get_user_select_product_list + /// + public HttpResult AdminGetUserSelectProductList(string postdata = "") + { + return Post("product/admin_get_user_select_product_list", postdata); + } + + /// + /// product/del_db_product + /// + public HttpResult DelDbProduct(string postdata = "") + { + return Post("product/del_db_product", postdata); + } + + /// + /// product/del_select_product + /// + public HttpResult DelSelectProduct(string postdata = "") + { + return Post("product/del_select_product", postdata); + } + + /// + /// product/del_sku_image + /// + public HttpResult DelSkuImage(string postdata = "") + { + return Post("product/del_sku_image", postdata); + } + + /// + /// product/get_cate_size_weight_config_v2 + /// + public HttpResult GetCateSizeWeightConfigV2(string postdata = "") + { + return Post("product/get_cate_size_weight_config_v2", postdata); + } + + /// + /// product/get_pdd_prod_wx_complate_list + /// + public HttpResult GetPddProdWxComplateList(string postdata = "") + { + return Post("product/get_pdd_prod_wx_complate_list", postdata); + } + + /// + /// product/get_pdd_product_cates + /// + public HttpResult GetPddProductCates(string postdata = "") + { + return Post("product/get_pdd_product_cates", postdata); + } + + /// + /// product/get_pdd_product_detail_list + /// + public HttpResult GetPddProductDetailList(string postdata = "") + { + return Post("product/get_pdd_product_detail_list", postdata); + } + + /// + /// product/get_pdd_product_list_random + /// + public HttpResult GetPddProductListRandom(string postdata = "") + { + return Post("product/get_pdd_product_list_random", postdata); + } + + /// + /// product/get_product_detail_list + /// + public HttpResult GetProductDetailList(string postdata = "") + { + return Post("product/get_product_detail_list", postdata); + } + + /// + /// product/get_product_group_list + /// + public HttpResult GetProductGroupList(string postdata = "") + { + return Post("product/get_product_group_list", postdata); + } + + /// + /// product/get_product_list + /// + public HttpResult GetProductList(string postdata = "") + { + return Post("product/get_product_list", postdata); + } + + /// + /// product/get_product_list_random + /// + public HttpResult GetProductListRandom(string postdata = "") + { + return Post("product/get_product_list_random", postdata); + } + + /// + /// product/get_random_company_product_config + /// + public HttpResult GetRandomCompanyProductConfig(string postdata = "") + { + return Post("product/get_random_company_product_config", postdata); + } + + /// + /// product/get_select_prod_db_id_map_draft_id_list + /// + public HttpResult GetSelectProdDbIdMapDraftIdList(string postdata = "") + { + return Post("product/get_select_prod_db_id_map_draft_id_list", postdata); + } + + /// + /// product/get_select_prod_map_up_spu_id_list + /// + public HttpResult GetSelectProdMapUpSpuIdList(string postdata = "") + { + return Post("product/get_select_prod_map_up_spu_id_list", postdata); + } + + /// + /// product/get_sku_image_list + /// + public HttpResult GetSkuImageList(string postdata = "") + { + return Post("product/get_sku_image_list", postdata); + } + + /// + /// product/get_sku_img_replace_list + /// + public HttpResult GetSkuImgReplaceList(string postdata = "") + { + return Post("product/get_sku_img_replace_list", postdata); + } + + /// + /// product/get_sku_replace_img_list_random + /// + public HttpResult GetSkuReplaceImgListRandom(string postdata = "") + { + return Post("product/get_sku_replace_img_list_random", postdata); + } + + /// + /// product/get_user_select_product_list + /// + public HttpResult GetUserSelectProductList(string postdata = "") + { + return Post("product/get_user_select_product_list", postdata); + } + + /// + /// product/is_get_wx_recommend_hot_prod + /// + public HttpResult IsGetWxRecommendHotProd(string postdata = "") + { + return Post("product/is_get_wx_recommend_hot_prod", postdata); + } + + /// + /// product/mdy_base64_img_url + /// + public HttpResult MdyBase64ImgUrl(string postdata = "") + { + return Post("product/mdy_base64_img_url", postdata); + } + + /// + /// product/mdy_base64_img_use_count + /// + public HttpResult MdyBase64ImgUseCount(string postdata = "") + { + return Post("product/mdy_base64_img_use_count", postdata); + } + + /// + /// product/mdy_best_sale_prod + /// + public HttpResult MdyBestSaleProd(string postdata = "") + { + return Post("product/mdy_best_sale_prod", postdata); + } + + /// + /// product/mdy_db_product + /// + public HttpResult MdyDbProduct(string postdata = "") + { + return Post("product/mdy_db_product", postdata); + } + + /// + /// product/mdy_product + /// + public HttpResult MdyProduct(string postdata = "") + { + return Post("product/mdy_product", postdata); + } + + /// + /// product/mdy_product_category + /// + public HttpResult MdyProductCategory(string postdata = "") + { + return Post("product/mdy_product_category", postdata); + } + + /// + /// product/mdy_product_select_notes + /// + public HttpResult MdyProductSelectNotes(string postdata = "") + { + return Post("product/mdy_product_select_notes", postdata); + } + + /// + /// product/mdy_sku_img_replace + /// + public HttpResult MdySkuImgReplace(string postdata = "") + { + return Post("product/mdy_sku_img_replace", postdata); + } + + /// + /// product/mdy_user_select_product_status + /// + public HttpResult MdyUserSelectProductStatus(string postdata = "") + { + return Post("product/mdy_user_select_product_status", postdata); + } + + /// + /// product/set_random_company_product_config + /// + public HttpResult SetRandomCompanyProductConfig(string postdata = "") + { + return Post("product/set_random_company_product_config", postdata); + } + + /// + /// product/set_select_product_sub_sa + /// + public HttpResult SetSelectProductSubSa(string postdata = "") + { + return Post("product/set_select_product_sub_sa", postdata); + } + + /// + /// product/wx_add_mdy_after_sale + /// + public HttpResult WxAddMdyAfterSale(string postdata = "") + { + return Post("product/wx_add_mdy_after_sale", postdata); + } + + /// + /// product/wx_del_address + /// + public HttpResult WxDelAddress(string postdata = "") + { + return Post("product/wx_del_address", postdata); + } + + /// + /// product/wx_del_qualification_image + /// + public HttpResult WxDelQualificationImage(string postdata = "") + { + return Post("product/wx_del_qualification_image", postdata); + } + + /// + /// product/wx_get_after_sale + /// + public HttpResult WxGetAfterSale(string postdata = "") + { + return Post("product/wx_get_after_sale", postdata); + } + + /// + /// product/wx_get_after_sale_list + /// + public HttpResult WxGetAfterSaleList(string postdata = "") + { + return Post("product/wx_get_after_sale_list", postdata); + } + + /// + /// product/wx_get_qualification_image + /// + public HttpResult WxGetQualificationImage(string postdata = "") + { + return Post("product/wx_get_qualification_image", postdata); + } + + /// + /// product/wx_get_random_after_sale_list + /// + public HttpResult WxGetRandomAfterSaleList(string postdata = "") + { + return Post("product/wx_get_random_after_sale_list", postdata); + } + + #endregion + + #region 拼多多服务接口 + /// + /// product/add_jst_sku + /// + public HttpResult PddAddJstSku(string postdata = "") + { + return PostPdd("product/add_jst_sku", postdata); + } + + /// + /// product/add_mdy_jst_product_ban + /// + public HttpResult PddAddMdyJstProductBan(string postdata = "") + { + return PostPdd("product/add_mdy_jst_product_ban", postdata); + } + + /// + /// product/add_prod_down_sku + /// + public HttpResult PddAddProdDownSku(string postdata = "") + { + return PostPdd("product/add_prod_down_sku", postdata); + } + + /// + /// product/add_prod_wx_complate + /// + public HttpResult PddAddProdWxComplate(string postdata = "") + { + return PostPdd("product/add_prod_wx_complate", postdata); + } + + /// + /// product/add_product + /// + public HttpResult PddAddProduct(string postdata = "") + { + return PostPdd("product/add_product", postdata); + } + + /// + /// product/add_product_groups + /// + public HttpResult PddAddProductGroups(string postdata = "") + { + return PostPdd("product/add_product_groups", postdata); + } + + /// + /// product/add_product_notes + /// + public HttpResult PddAddProductNotes(string postdata = "") + { + return PostPdd("product/add_product_notes", postdata); + } + + /// + /// product/add_relation + /// + public HttpResult PddAddRelation(string postdata = "") + { + return PostPdd("product/add_relation", postdata); + } + + /// + /// product/del_db_product + /// + public HttpResult PddDelDbProduct(string postdata = "") + { + return PostPdd("product/del_db_product", postdata); + } + + /// + /// product/del_product_groups + /// + public HttpResult PddDelProductGroups(string postdata = "") + { + return PostPdd("product/del_product_groups", postdata); + } + + /// + /// product/del_product_notes + /// + public HttpResult PddDelProductNotes(string postdata = "") + { + return PostPdd("product/del_product_notes", postdata); + } + + /// + /// product/get_jst_product_coid_list + /// + public HttpResult PddGetJstProductCoidList(string postdata = "") + { + return PostPdd("product/get_jst_product_coid_list", postdata); + } + + /// + /// product/get_jst_sku_list + /// + public HttpResult PddGetJstSkuList(string postdata = "") + { + return PostPdd("product/get_jst_sku_list", postdata); + } + + /// + /// product/get_prod_down_sku_lst_by_sku_codes + /// + public HttpResult PddGetProdDownSkuLstBySkuCodes(string postdata = "") + { + return PostPdd("product/get_prod_down_sku_lst_by_sku_codes", postdata); + } + + /// + /// product/get_product_detail_list + /// + public HttpResult PddGetProductDetailList(string postdata = "") + { + return PostPdd("product/get_product_detail_list", postdata); + } + + /// + /// product/get_product_groups + /// + public HttpResult PddGetProductGroups(string postdata = "") + { + return PostPdd("product/get_product_groups", postdata); + } + + /// + /// product/get_product_list + /// + public HttpResult PddGetProductList(string postdata = "") + { + return PostPdd("product/get_product_list", postdata); + } + + /// + /// product/get_product_notes + /// + public HttpResult PddGetProductNotes(string postdata = "") + { + return PostPdd("product/get_product_notes", postdata); + } + + /// + /// product/get_product_spu_id_list + /// + public HttpResult PddGetProductSpuIdList(string postdata = "") + { + return PostPdd("product/get_product_spu_id_list", postdata); + } + + /// + /// product/get_relation + /// + public HttpResult PddGetRelation(string postdata = "") + { + return PostPdd("product/get_relation", postdata); + } + + /// + /// product/mdy_db_product + /// + public HttpResult PddMdyDbProduct(string postdata = "") + { + return PostPdd("product/mdy_db_product", postdata); + } + + /// + /// product/mdy_jst_sku + /// + public HttpResult PddMdyJstSku(string postdata = "") + { + return PostPdd("product/mdy_jst_sku", postdata); + } + + /// + /// product/mdy_product + /// + public HttpResult PddMdyProduct(string postdata = "") + { + return PostPdd("product/mdy_product", postdata); + } + + /// + /// product/mdy_violation_prod + /// + public HttpResult PddMdyViolationProd(string postdata = "") + { + return PostPdd("product/mdy_violation_prod", postdata); + } + + /// + /// product/wx_api_category_classify + /// + public HttpResult PddWxApiCategoryClassify(string postdata = "") + { + return PostPdd("product/wx_api_category_classify", postdata); + } + + /// + /// product/wx_api_get_all_cate + /// + public HttpResult PddWxApiGetAllCate(string postdata = "") + { + return PostPdd("product/wx_api_get_all_cate", postdata); + } + + #endregion + + #region 微信服务接口 + /// + /// product/e57_get_freight_template + /// + public HttpResult E57GetFreightTemplate(string postdata = "") + { + return PostWx("product/e57_get_freight_template", postdata); + } + + /// + /// product/get_hot_product_detail_list + /// + public HttpResult WxGetHotProductDetailList(string postdata = "") + { + return PostWx("product/get_hot_product_detail_list", postdata); + } + + /// + /// product/get_hot_product_list + /// + public HttpResult WxGetHotProductList(string postdata = "") + { + return PostWx("product/get_hot_product_list", postdata); + } + + /// + /// product/wx_add_mdy_qualification_image + /// + public HttpResult WxAddMdyQualificationImage(string postdata = "") + { + return PostWx("product/wx_add_mdy_qualification_image", postdata); + } + + /// + /// product/wx_add_mdy_shipping_template + /// + public HttpResult WxAddMdyShippingTemplate(string postdata = "") + { + return PostWx("product/wx_add_mdy_shipping_template", postdata); + } + + /// + /// product/wx_get_product_latest_buy_amount_list + /// + public HttpResult WxGetProductLatestBuyAmountList(string postdata = "") + { + return PostWx("product/wx_get_product_latest_buy_amount_list", postdata); + } + + /// + /// product/wx_get_shipping_template + /// + public HttpResult WxGetShippingTemplate(string postdata = "") + { + return PostWx("product/wx_get_shipping_template", postdata); + } + + #endregion + + #region 微信图片服务接口 + /// + /// product/wx_api_upload_image + /// + public HttpResult WxApiUploadImage(string postdata = "") + { + return PostWxImg("product/wx_api_upload_image", postdata); + } + + #endregion + + } +} diff --git a/Store.Framework/Apis/ProductThirdApi.cs b/Store.Framework/Apis/ProductThirdApi.cs new file mode 100644 index 0000000..a6c4ad9 --- /dev/null +++ b/Store.Framework/Apis/ProductThirdApi.cs @@ -0,0 +1,125 @@ +using MAX.Core.DependencyInjection; +using MAX.Core.Helpers; +using Store.Client.HttpExtension; + +namespace Store.Client.Apis +{ + public partial class ProductThirdApi : AppApiRequestBase, ITransientDependency + { + #region 默认服务接口 + /// + /// product_third/add_product + /// + public HttpResult AddProduct(string postdata = "") + { + return Post("product_third/add_product", postdata); + } + + /// + /// product_third/add_user_select_product + /// + public HttpResult AddUserSelectProduct(string postdata = "") + { + return Post("product_third/add_user_select_product", postdata); + } + + /// + /// product_third/admin_get_user_select_product_list + /// + public HttpResult AdminGetUserSelectProductList(string postdata = "") + { + return Post("product_third/admin_get_user_select_product_list", postdata); + } + + /// + /// product_third/del_select_product + /// + public HttpResult DelSelectProduct(string postdata = "") + { + return Post("product_third/del_select_product", postdata); + } + + /// + /// product_third/get_product_detail_list + /// + public HttpResult GetProductDetailList(string postdata = "") + { + return Post("product_third/get_product_detail_list", postdata); + } + + /// + /// product_third/get_product_detail_list_bak + /// + public HttpResult GetProductDetailListBak(string postdata = "") + { + return Post("product_third/get_product_detail_list_bak", postdata); + } + + /// + /// product_third/get_product_list + /// + public HttpResult GetProductList(string postdata = "") + { + return Post("product_third/get_product_list", postdata); + } + + /// + /// product_third/get_select_prod_db_id_map_draft_id_list + /// + public HttpResult GetSelectProdDbIdMapDraftIdList(string postdata = "") + { + return Post("product_third/get_select_prod_db_id_map_draft_id_list", postdata); + } + + /// + /// product_third/get_user_select_product_list + /// + public HttpResult GetUserSelectProductList(string postdata = "") + { + return Post("product_third/get_user_select_product_list", postdata); + } + + /// + /// product_third/mdy_db_product + /// + public HttpResult MdyDbProduct(string postdata = "") + { + return Post("product_third/mdy_db_product", postdata); + } + + /// + /// product_third/mdy_product_category + /// + public HttpResult MdyProductCategory(string postdata = "") + { + return Post("product_third/mdy_product_category", postdata); + } + + /// + /// product_third/mdy_product_select_notes + /// + public HttpResult MdyProductSelectNotes(string postdata = "") + { + return Post("product_third/mdy_product_select_notes", postdata); + } + + /// + /// product_third/mdy_user_select_product_status + /// + public HttpResult MdyUserSelectProductStatus(string postdata = "") + { + return Post("product_third/mdy_user_select_product_status", postdata); + } + + /// + /// product_third/set_select_product_sub_sa + /// + public HttpResult SetSelectProductSubSa(string postdata = "") + { + return Post("product_third/set_select_product_sub_sa", postdata); + } + + #endregion + + } +} diff --git a/Store.Framework/Apis/ServerHttpBase.cs b/Store.Framework/Apis/ServerHttpBase.cs new file mode 100644 index 0000000..4adeb8d --- /dev/null +++ b/Store.Framework/Apis/ServerHttpBase.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Text; + +namespace Store.Client.Apis +{ + ///// + ///// 服务器 + ///// + //public class ServerHttpBase : ITransientDependency + //{ + + // /// + // /// 日志 + // /// + // [Autowired] + // protected ILog _log { get; set; } + + // /// + // /// 服务器配置 + // /// + // [Autowired] + // protected IServerConfig _config { get; set; } + + // public ServerHttpBase() + // { + + // } + + + + // /// + // /// 服务器请求 + // /// + // /// + // /// + // public HttpResult ServerRequest(HttpItem item) + // { + // if (!item.URL.StartsWith("http")) + // { + // item.URL += _config.Host + item.URL; + // } + // item.Encoding = Encoding.UTF8; + // // ////item + // //#if PROXY + // // item.ProxyIp = "127.0.0.1:8080"; + // //#endif + // HttpHelper http = new HttpHelper(); + // HttpResult result = http.GetHtml(item); + // if (result.StatusCode == HttpStatusCode.OK) + // { + // if (!string.IsNullOrEmpty(result.Html)) + // { + // if (result.Html[0] == 65279) + // { + // result.Html = result.Html.Remove(0, 1); + // } + // } + // } + // else + // { + + // } + // return result; + // } + //} +} diff --git a/Store.Framework/Apis/ShopApi.cs b/Store.Framework/Apis/ShopApi.cs new file mode 100644 index 0000000..033f421 --- /dev/null +++ b/Store.Framework/Apis/ShopApi.cs @@ -0,0 +1,472 @@ +using MAX.Core.DependencyInjection; +using MAX.Core.Helpers; +using Store.Client.HttpExtension; + +namespace Store.Client.Apis +{ + public partial class ShopApi : AppApiRequestBase, ITransientDependency + { + #region 默认服务接口 + /// + /// pdd_shop_temp&act=goup + /// + public HttpResult GoupPddShopTemp(string postdata = "") + { + return Post("pdd_shop_temp&act=goup", postdata); + } + + /// + /// pdd_shop_tempx&act=get_shop_cookies_list + /// + public HttpResult GetPddShopCookiesList(string postdata = "") + { + return Post("pdd_shop_tempx&act=get_shop_cookies_list", postdata); + } + + /// + /// shop&act=get_shop_token + /// + public HttpResult GetShopToken(string postdata = "") + { + return Post("shop&act=get_shop_token", postdata); + } + + /// + /// shop&act=update_mall_id + /// + public HttpResult UpdateMallId(string postdata = "") + { + return Post("shop&act=update_mall_id", postdata); + } + + /// + /// shop/active_jit_card_code + /// + public HttpResult ActiveJitCardCode(string postdata = "") + { + return Post("shop/active_jit_card_code", postdata); + } + + /// + /// shop/add_mdy_express_optimize_remark + /// + public HttpResult AddMdyExpressOptimizeRemark(string postdata = "") + { + return Post("shop/add_mdy_express_optimize_remark", postdata); + } + + /// + /// shop/add_mdy_express_optimize_rst + /// + public HttpResult AddMdyExpressOptimizeRst(string postdata = "") + { + return Post("shop/add_mdy_express_optimize_rst", postdata); + } + + /// + /// shop/add_mdy_shop_client_info + /// + public HttpResult AddMdyShopClientInfo(string postdata = "") + { + return Post("shop/add_mdy_shop_client_info", postdata); + } + + /// + /// shop/add_shop + /// + public HttpResult AddShop(string postdata = "") + { + return Post("shop/add_shop", postdata); + } + + /// + /// shop/add_waybill + /// + public HttpResult AddWaybill(string postdata = "") + { + return Post("shop/add_waybill", postdata); + } + + /// + /// shop/admin_add_shop + /// + public HttpResult AdminAddShop(string postdata = "") + { + return Post("shop/admin_add_shop", postdata); + } + + /// + /// shop/admin_del_shop + /// + public HttpResult AdminDelShop(string postdata = "") + { + return Post("shop/admin_del_shop", postdata); + } + + /// + /// shop/admin_get_shop_list + /// + public HttpResult AdminGetShopList(string postdata = "") + { + return Post("shop/admin_get_shop_list", postdata); + } + + /// + /// shop/admin_mdy_shop + /// + public HttpResult AdminMdyShop(string postdata = "") + { + return Post("shop/admin_mdy_shop", postdata); + } + + /// + /// shop/create_shop_card_code + /// + public HttpResult CreateShopCardCode(string postdata = "") + { + return Post("shop/create_shop_card_code", postdata); + } + + /// + /// shop/del_shop + /// + public HttpResult DelShop(string postdata = "") + { + return Post("shop/del_shop", postdata); + } + + /// + /// shop/del_shop_card_code + /// + public HttpResult DelShopCardCode(string postdata = "") + { + return Post("shop/del_shop_card_code", postdata); + } + + /// + /// shop/del_waybill + /// + public HttpResult DelWaybill(string postdata = "") + { + return Post("shop/del_waybill", postdata); + } + + /// + /// shop/e57_get_wx_shop_token + /// + public HttpResult E57GetWxShopToken(string postdata = "") + { + return Post("shop/e57_get_wx_shop_token", postdata); + } + + /// + /// shop/e57_get_wx_tokens + /// + public HttpResult E57GetWxTokens(string postdata = "") + { + return Post("shop/e57_get_wx_tokens", postdata); + } + + /// + /// shop/get_express_optimize_remark + /// + public HttpResult GetExpressOptimizeRemark(string postdata = "") + { + return Post("shop/get_express_optimize_remark", postdata); + } + + /// + /// shop/get_express_optimize_rst + /// + public HttpResult GetExpressOptimizeRst(string postdata = "") + { + return Post("shop/get_express_optimize_rst", postdata); + } + + /// + /// shop/get_shop_card_code_list + /// + public HttpResult GetShopCardCodeList(string postdata = "") + { + return Post("shop/get_shop_card_code_list", postdata); + } + + /// + /// shop/get_shop_list + /// + public HttpResult GetShopList(string postdata = "") + { + return Post("shop/get_shop_list", postdata); + } + + /// + /// shop/get_waybill + /// + public HttpResult GetWaybill(string postdata = "") + { + return Post("shop/get_waybill", postdata); + } + + /// + /// shop/get_waybill_no_user_name + /// + public HttpResult GetWaybillNoUserName(string postdata = "") + { + return Post("shop/get_waybill_no_user_name", postdata); + } + + /// + /// shop/get_weidian_shop + /// + public HttpResult GetWeidianShop(string postdata = "") + { + return Post("shop/get_weidian_shop", postdata); + } + + /// + /// shop/mdy_anti_content + /// + public HttpResult MdyAntiContent(string postdata = "") + { + return Post("shop/mdy_anti_content", postdata); + } + + /// + /// shop/mdy_shop + /// + public HttpResult MdyShop(string postdata = "") + { + return Post("shop/mdy_shop", postdata); + } + + /// + /// shop/mdy_shop_ads_cookie_and_login_status + /// + public HttpResult MdyShopAdsCookieAndLoginStatus(string postdata = "") + { + return Post("shop/mdy_shop_ads_cookie_and_login_status", postdata); + } + + /// + /// shop/mdy_shop_cookie_and_login_status + /// + public HttpResult MdyShopCookieAndLoginStatus(string postdata = "") + { + return Post("shop/mdy_shop_cookie_and_login_status", postdata); + } + + /// + /// shop/select_waybill + /// + public HttpResult SelectWaybill(string postdata = "") + { + return Post("shop/select_waybill", postdata); + } + + /// + /// shop/set_shop_sub_sa + /// + public HttpResult SetShopSubSa(string postdata = "") + { + return Post("shop/set_shop_sub_sa", postdata); + } + + /// + /// shop/update_add_prod_remaining + /// + public HttpResult UpdateAddProdRemaining(string postdata = "") + { + return Post("shop/update_add_prod_remaining", postdata); + } + + /// + /// shop/update_used_waybill + /// + public HttpResult UpdateUsedWaybill(string postdata = "") + { + return Post("shop/update_used_waybill", postdata); + } + + /// + /// shop/wx_add_mdy_29wx_pdd_shop + /// + public HttpResult WxAddMdy29wxPddShop(string postdata = "") + { + return Post("shop/wx_add_mdy_29wx_pdd_shop", postdata); + } + + /// + /// shop/wx_add_mdy_buy_url_notes + /// + public HttpResult WxAddMdyBuyUrlNotes(string postdata = "") + { + return Post("shop/wx_add_mdy_buy_url_notes", postdata); + } + + /// + /// shop/wx_add_mdy_order_address + /// + public HttpResult WxAddMdyOrderAddress(string postdata = "") + { + return Post("shop/wx_add_mdy_order_address", postdata); + } + + /// + /// shop/wx_add_mdy_shop_default_address + /// + public HttpResult WxAddMdyShopDefaultAddress(string postdata = "") + { + return Post("shop/wx_add_mdy_shop_default_address", postdata); + } + + /// + /// shop/wx_del_shop_default_address + /// + public HttpResult WxDelShopDefaultAddress(string postdata = "") + { + return Post("shop/wx_del_shop_default_address", postdata); + } + + /// + /// shop/wx_get_29wx_pdd_shop + /// + public HttpResult WxGet29wxPddShop(string postdata = "") + { + return Post("shop/wx_get_29wx_pdd_shop", postdata); + } + + /// + /// shop/wx_get_buy_url_notes + /// + public HttpResult WxGetBuyUrlNotes(string postdata = "") + { + return Post("shop/wx_get_buy_url_notes", postdata); + } + + /// + /// shop/wx_get_order_address + /// + public HttpResult WxGetOrderAddress(string postdata = "") + { + return Post("shop/wx_get_order_address", postdata); + } + + /// + /// shop/wx_get_shop_default_address_list + /// + public HttpResult WxGetShopDefaultAddressList(string postdata = "") + { + return Post("shop/wx_get_shop_default_address_list", postdata); + } + + /// + /// shop/wx_mdy_29wx_pdd_cookie_and_login_status + /// + public HttpResult WxMdy29wxPddCookieAndLoginStatus(string postdata = "") + { + return Post("shop/wx_mdy_29wx_pdd_cookie_and_login_status", postdata); + } + + /// + /// taobao_id&act=add + /// + public HttpResult AddTaobaoId(string postdata = "") + { + return Post("taobao_id&act=add", postdata); + } + + /// + /// taobao_id&act=get + /// + public HttpResult GetTaobaoId(string postdata = "") + { + return Post("taobao_id&act=get", postdata); + } + + #endregion + + #region 拼多多服务接口 + /// + /// shop/add_vdian + /// + public HttpResult PddAddVdian(string postdata = "") + { + return PostPdd("shop/add_vdian", postdata); + } + + /// + /// shop/add_weidian_shop + /// + public HttpResult PddAddWeidianShop(string postdata = "") + { + return PostPdd("shop/add_weidian_shop", postdata); + } + + /// + /// shop/admin_get_shop_list + /// + public HttpResult PddAdminGetShopList(string postdata = "") + { + return PostPdd("shop/admin_get_shop_list", postdata); + } + + /// + /// shop/del_weidian_shop + /// + public HttpResult PddDelWeidianShop(string postdata = "") + { + return PostPdd("shop/del_weidian_shop", postdata); + } + + /// + /// shop/e57_get_pdd_shop_token + /// + public HttpResult PddE57GetPddShopToken(string postdata = "") + { + return PostPdd("shop/e57_get_pdd_shop_token", postdata); + } + + /// + /// shop/get_pdd_client_info + /// + public HttpResult PddGetPddClientInfo(string postdata = "") + { + return PostPdd("shop/get_pdd_client_info", postdata); + } + + /// + /// shop/get_shop_client_info + /// + public HttpResult PddGetShopClientInfo(string postdata = "") + { + return PostPdd("shop/get_shop_client_info", postdata); + } + + /// + /// shop/mdy_vdian + /// + public HttpResult PddMdyVdian(string postdata = "") + { + return PostPdd("shop/mdy_vdian", postdata); + } + + /// + /// shop/mdy_vdian_cookie + /// + public HttpResult PddMdyVdianCookie(string postdata = "") + { + return PostPdd("shop/mdy_vdian_cookie", postdata); + } + + /// + /// shop/mdy_weidian_shop_cookie + /// + public HttpResult PddMdyWeidianShopCookie(string postdata = "") + { + return PostPdd("shop/mdy_weidian_shop_cookie", postdata); + } + + #endregion + + } +} diff --git a/Store.Framework/Apis/UserApi.cs b/Store.Framework/Apis/UserApi.cs new file mode 100644 index 0000000..5e3d559 --- /dev/null +++ b/Store.Framework/Apis/UserApi.cs @@ -0,0 +1,398 @@ +using MAX.Core.DependencyInjection; +using MAX.Core.Extensions; +using MAX.Core.Helpers; +using Store.Client.Agent; +using Store.Client.Commons; +using Store.Client.Extensions; +using Store.Client.HttpExtension; +using Store.Framework; +using Store.Framework.Tools; +using System; +using System.Collections.Generic; +using System.Net; +using System.Text; +using System.Text.RegularExpressions; + +namespace Store.Client.Apis +{ + /// + /// 用户操作Api + /// 登录,注册 + /// + public partial class UserApi : AppApiRequestBase, ITransientDependency + { + + public HttpResult Login(string username, string password) + { + _logger.Info("123"); + int r2 = RandomHelper.GetRandomNumber(100000000, 899999999); //生成随机码 + ServerHttpItem item = new ServerHttpItem(); + item.URL = base.BuildServiceUrl("user/user_login"); + item.Method = MethodType.Post; + Dictionary param = new Dictionary + { + { "mobile", username }, + { "r2", r2.ToString() }, + { "password", password }, + { "hw_code", HardwareTool.HardWareCode }, + { "hw_code_new", HardwareTool.HardWareCodeNew }, + { "hw_code_v3", HardwareTool.HardWareCodeV3 }, + { "agent_code", AgentConfig.AgentCode }, + { "version",Config.Version} + //, + //{ "client_ip", _client.Ip }, + //{ "client_addr", _client.Address } + }; + item.Postdata = param.ToUrlParams(); + item.Sign(); + HttpResult result = ServerRequest(item); + if (result.StatusCode == HttpStatusCode.OK) + { + string chCode = Regex.Match(result.Html, "([^<]+)")?.Groups[1]?.Value ?? ""; + if (!string.IsNullOrEmpty(chCode)) + { + string codex = SignTool.Encryption(r2); + if (chCode != codex) + { + result.StatusCode = HttpStatusCode.Unauthorized; + } + } + } + return result; + } + + + + + + #region 默认服务接口 + /// + /// user&act=add_user_lock + /// + public HttpResult AddUserLock(string postdata = "") + { + return Post("user&act=add_user_lock", postdata); + } + + /// + /// user/add_sub_user + /// + public HttpResult AddSubUser(string postdata = "") + { + return Post("user/add_sub_user", postdata); + } + + /// + /// user/add_user + /// + public HttpResult AddUser(string postdata = "") + { + return Post("user/add_user", postdata); + } + + /// + /// user/apply_oem_withdrawal + /// + public HttpResult ApplyOemWithdrawal(string postdata = "") + { + return Post("user/apply_oem_withdrawal", postdata); + } + + /// + /// user/delete_user + /// + public HttpResult DeleteUser(string postdata = "") + { + return Post("user/delete_user", postdata); + } + + /// + /// user/get_all_user + /// + public HttpResult GetAllUser(string postdata = "") + { + return Post("user/get_all_user", postdata); + } + + /// + /// user/get_package_sign + /// + public HttpResult GetPackageSign(string postdata = "") + { + return Post("user/get_package_sign", postdata); + } + + /// + /// user/get_sub_user + /// + public HttpResult GetSubUser(string postdata = "") + { + return Post("user/get_sub_user", postdata); + } + + /// + /// user/get_user_amount + /// + public HttpResult GetUserAmount(string postdata = "") + { + return Post("user/get_user_amount", postdata); + } + + /// + /// user/is_get_return_package + /// + public HttpResult IsGetReturnPackage(string postdata = "") + { + return Post("user/is_get_return_package", postdata); + } + + /// + /// user/oem_audit_withdrawal + /// + public HttpResult OemAuditWithdrawal(string postdata = "") + { + return Post("user/oem_audit_withdrawal", postdata); + } + + /// + /// user/oem_settle_flows + /// + public HttpResult OemSettleFlows(string postdata = "") + { + return Post("user/oem_settle_flows", postdata); + } + + /// + /// user/oem_withdrawals + /// + public HttpResult OemWithdrawals(string postdata = "") + { + return Post("user/oem_withdrawals", postdata); + } + + /// + /// user/update_sub_user + /// + public HttpResult UpdateSubUser(string postdata = "") + { + return Post("user/update_sub_user", postdata); + } + + /// + /// user/update_user + /// + public HttpResult UpdateUser(string postdata = "") + { + return Post("user/update_user", postdata); + } + + /// + /// user/update_user_balance + /// + public HttpResult UpdateUserBalance(string postdata = "") + { + return Post("user/update_user_balance", postdata); + } + + /// + /// user/update_user_points + /// + public HttpResult UpdateUserPoints(string postdata = "") + { + return Post("user/update_user_points", postdata); + } + + /// + /// user/user_login + /// + public HttpResult UserLogin(string postdata = "") + { + return PostPublic("user/user_login", postdata); + } + + /// + /// user_login_cookies&act=get + /// + public HttpResult GetUserLoginCookies(string postdata = "") + { + return Post("user_login_cookies&act=get", postdata); + } + + /// + /// user_login_cookies&act=insert + /// + public HttpResult InsertUserLoginCookies(string postdata = "") + { + return Post("user_login_cookies&act=insert", postdata); + } + + #endregion + + #region 拼多多服务接口 + /// + /// user/add_buyer + /// + public HttpResult PddAddBuyer(string postdata = "") + { + return PostPdd("user/add_buyer", postdata); + } + + /// + /// user/del_buyer + /// + public HttpResult PddDelBuyer(string postdata = "") + { + return PostPdd("user/del_buyer", postdata); + } + + /// + /// user/get_buyer + /// + public HttpResult PddGetBuyer(string postdata = "") + { + return PostPdd("user/get_buyer", postdata); + } + + /// + /// user/get_scm121_cookie + /// + public HttpResult PddGetScm121Cookie(string postdata = "") + { + return PostPdd("user/get_scm121_cookie", postdata); + } + + /// + /// user/mdy_buyer + /// + public HttpResult PddMdyBuyer(string postdata = "") + { + return PostPdd("user/mdy_buyer", postdata); + } + + /// + /// user/mdy_buyer_cookie + /// + public HttpResult PddMdyBuyerCookie(string postdata = "") + { + return PostPdd("user/mdy_buyer_cookie", postdata); + } + + /// + /// user/update_scm121_cookie + /// + public HttpResult PddUpdateScm121Cookie(string postdata = "") + { + return PostPdd("user/update_scm121_cookie", postdata); + } + + #endregion + + #region 微信服务接口 + /// + /// user/add_mdy_oem + /// + public HttpResult WxAddMdyOem(string postdata = "") + { + return PostWx("user/add_mdy_oem", postdata); + } + + /// + /// user/del_oem + /// + public HttpResult WxDelOem(string postdata = "") + { + return PostWx("user/del_oem", postdata); + } + + /// + /// user/get_all_user + /// + public HttpResult WxGetAllUser(string postdata = "") + { + return PostWx("user/get_all_user", postdata); + } + + /// + /// user/get_e57_authorization + /// + public HttpResult WxGetE57Authorization(string postdata = "") + { + return PostWx("user/get_e57_authorization", postdata); + } + + /// + /// user/get_oem + /// + public HttpResult WxGetOem(string postdata = "") + { + return PostWx("user/get_oem", postdata); + } + + /// + /// user/get_oem_config_info + /// + public HttpResult WxGetOemConfigInfo(string postdata = "") + { + return PostWx("user/get_oem_config_info", postdata); + } + + /// + /// user/get_oem_version + /// + public HttpResult WxGetOemVersion(string postdata = "") + { + return PostWx("user/get_oem_version", postdata); + } + + /// + /// user/get_top_up_qrcode + /// + public HttpResult WxGetTopUpQrcode(string postdata = "") + { + return PostWx("user/get_top_up_qrcode", postdata); + } + + /// + /// user/get_user_balance + /// + public HttpResult WxGetUserBalance(string postdata = "") + { + return PostWx("user/get_user_balance", postdata); + } + + /// + /// user/get_user_points + /// + public HttpResult WxGetUserPoints(string postdata = "") + { + return PostWx("user/get_user_points", postdata); + } + + /// + /// user/get_wx_sale_assistant_cookie + /// + public HttpResult WxGetWxSaleAssistantCookie(string postdata = "") + { + return PostWx("user/get_wx_sale_assistant_cookie", postdata); + } + + /// + /// user/update_top_up_qrcode + /// + public HttpResult WxUpdateTopUpQrcode(string postdata = "") + { + return PostWx("user/update_top_up_qrcode", postdata); + } + + /// + /// user/update_wx_sale_assistant_cookie + /// + public HttpResult WxUpdateWxSaleAssistantCookie(string postdata = "") + { + return PostWx("user/update_wx_sale_assistant_cookie", postdata); + } + + #endregion + + } +} diff --git a/Store.Framework/Attributes/HttpTryAttribute.cs b/Store.Framework/Attributes/HttpTryAttribute.cs new file mode 100644 index 0000000..eccf76c --- /dev/null +++ b/Store.Framework/Attributes/HttpTryAttribute.cs @@ -0,0 +1,57 @@ +//using MAX.Core.Helpers; +//using Polly; +//using Store.Framework.HttpResultParser; +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using System.Threading.Tasks; + +//namespace Store.Framework.Attributes +//{ +// /// +// /// Http请求请求失败重新尝试 HttpStatusCode 状态不为Ok一律重新尝试 +// /// +// [AttributeUsage(AttributeTargets.Method)] +// public class HttpTryAttribute : AbstractInterceptorAttribute +// { +// /// +// /// 最大尝试次数 +// /// +// public int MaxRetryTimes { get; set; } = 3; +// /// +// /// 重新尝试延迟时间毫秒 +// /// +// public int RetryIntervalMilliseconds { get; set; } = 1000; +// protected Type[] VerifyTypes { get; set; } +// //public HttpTryAttribute() +// //{ + +// //} + +// public HttpTryAttribute(params Type[] verifyTypes) +// { +// VerifyTypes = (verifyTypes ?? new Type[0]); +// } +// public override Task Invoke(AspectContext context, AspectDelegate next) +// { +// var verify = HttpResultVerify.TryVerify(() => +// { +// next(context).Wait(); +// return (HttpResult)context.ReturnValue; +// }, MaxRetryTimes, RetryIntervalMilliseconds); +// if (VerifyTypes != null && VerifyTypes.Count() > 0) +// { +// foreach (Type item in VerifyTypes) +// { +// if (typeof(IHttpResultVerify).IsAssignableFrom(item)) +// { +// verify.UseCheckResult((IHttpResultVerify)Activator.CreateInstance(item)); +// } +// } +// } +// verify.Exec(); +// return Task.FromResult(0); +// } +// } +//} diff --git a/Store.Framework/Attributes/SleepAttribute.cs b/Store.Framework/Attributes/SleepAttribute.cs new file mode 100644 index 0000000..a441388 --- /dev/null +++ b/Store.Framework/Attributes/SleepAttribute.cs @@ -0,0 +1,70 @@ +//using AspectCore.DynamicProxy; +////using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Reflection; +//using System.Text; +//using System.Threading; +//using System.Threading.Tasks; + +//namespace Store.Framework.Attributes +//{ +// /// +// /// Aop延迟时间 +// /// +// /// +// public class SleepAttribute : AopInterceptorAttribute +// { +// /// +// /// 毫秒 +// /// +// public int Millisecond { get; set; } +// /// +// /// 间隔区间 +// /// +// public Tuple ExtentMillisecond { get; set; } +// /// +// /// 执行位置 +// /// +// public ExecOrder Location { get; set; } = ExecOrder.Before; +// public SleepAttribute(int millisecond) +// { +// Millisecond = millisecond; +// } +// public SleepAttribute(int StartMillisecond, int EndMillisecond) +// { +// ExtentMillisecond = new Tuple(StartMillisecond, EndMillisecond); +// } +// public override void BeforeAction(AspectContext context) +// { +// if (Location == ExecOrder.Before) +// Exec(); +// } + +// public override void AfterAction(AspectContext context) +// { +// if (Location == ExecOrder.After) +// Exec(); +// } + +// private void Exec() +// { +// if (ExtentMillisecond != null) +// { +// Millisecond = new Random().Next(ExtentMillisecond.Item1, ExtentMillisecond.Item2); +// } +// Thread.Sleep(Millisecond); +// } +// public enum ExecOrder +// { +// /// +// /// 方法执行前 +// /// +// Before, +// /// +// /// 方法执行后 +// /// +// After +// } +// } +//} diff --git a/Store.Framework/ClientInfo.cs b/Store.Framework/ClientInfo.cs new file mode 100644 index 0000000..cd06bed --- /dev/null +++ b/Store.Framework/ClientInfo.cs @@ -0,0 +1,46 @@ +using MAX.Core.Infrastructure; +using Store.Framework.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Framework +{ + /// + /// 客户端信息 + /// + public class ClientInfo : SingleInstance + { + + public static UserInfo User + { + get + { + return Instance.UserInfo; + } + } + + + + private ClientInfo() + { + + } + /// + /// 客户端IP + /// + public string Ip { get; } + /// + /// 客户端地址 + /// + public string Address { get; set; } + + /// + /// 获取当前登录用户 + /// + public UserInfo UserInfo { get; private set; } + + } +} diff --git a/Store.Framework/Commons/ShopManage.cs b/Store.Framework/Commons/ShopManage.cs new file mode 100644 index 0000000..d97d248 --- /dev/null +++ b/Store.Framework/Commons/ShopManage.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Client.Commons +{ + ///// + ///// 商铺列表信息管理 + ///// + //public abstract class ShopManage : ISingletonDependency, ILoginSuccessed where T : ShopBase + //{ + // /// + // /// 日志 + // /// + // [Autowired] + // protected ILog _log { get; set; } + // [Autowired] + // internal ShopService _shopService { get; set; } + // /// + // /// 私有商铺集合 + // /// + // private List _shopsData = new List(); + // /// + // /// 私有商铺分组 + // /// + // /// + // private List _shopGroups = new List(); + + // /// + // /// 所有商铺 + // /// + // public List Shops + // { + // get + // { + // return _shopsData; + // } + // } + // /// + // /// 商铺分组 + // /// + // public List ShopGroups + // { + // get => _shopGroups; + // } + // /// + // /// 获取随机Cookie + // /// + // /// + // public Cookies GetRandomCookies() + // { + // return Shops[RandomHelper.GetRandomNumber(Shops.Count - 1)].Cookie; + // } + + + // /// + // /// 刷新 + // /// + // public void Refresh() + // { + // LoadServerShops(); + // } + + // void ILoginSuccessed.LoginSuccess(UserInfo user) + // { + // try + // { + // LoadServerShops(); + // } + // catch (Exception ex) + // { + // _log.Error(ex, "加载商铺列表"); + // } + // } + + // /// + // /// 加载服务器商铺 + // /// + // private void LoadServerShops() + // { + // _shopsData = _shopService.GetShops(); + // _getshopGroup(); + // RefreshShopsCookie(); + // } + + // private void RefreshShopsCookie() + // { + // var shops = Shops.Select(a => a as ShopBase).ToList(); + // _shopService.GetShopCookie(shops); + // } + // /// + // /// 检测商铺登录状态 + // /// + // public abstract void CheckShopStatus(T shop); + // //{ + // // //string urlx = "https://myseller.taobao.com/ajaxProxy.do?_ksTS=1548152154745_13&callback=seller_layout_head&action=FrameworkLayoutAction&event_submit_do_layout_data=true"; + // // //string refere = "https://myseller.taobao.com"; + // // //string htmlx = GetPostTaobaoHtml.GetUrl(urlx, refere, true, "", Shop.Cookies); + // // //Shop.MsgForCookiesLogin = htmlx.Contains("\"shopName\":\"") ? "登录成功" : "会话已过期"; + // // //GetTaobaoShops.ShopsTemp.Add(Shop); + // //} + + + // /// + // /// 商铺分组 + // /// + // private void _getshopGroup() + // { + // _shopGroups.Clear(); + // List list = (from a in _shopsData group a by a.GroupName into b select b.Key).ToList(); + // foreach (var item in list) + // { + // _shopGroups.Add(item); + // } + // } + + + //} +} diff --git a/Store.Framework/Commons/SignTool.cs b/Store.Framework/Commons/SignTool.cs new file mode 100644 index 0000000..1e176fa --- /dev/null +++ b/Store.Framework/Commons/SignTool.cs @@ -0,0 +1,70 @@ +using MAX.Core.Cryptography; +using MAX.Core.Helpers; +using Store.Client; +using Store.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Client.Commons +{ + /// + /// 签名工具 + /// + public class SignTool + { + /// + /// 获取签名 + /// + /// + public static string GetSign() + { + long time = GetReviseUnix(); + string s = Encryption(time); + return "time=" + time + "&code=" + s + "&NbCodeVersion=1"; + } + /// + /// 用户签名 + /// + /// + public static string GetUserSign() + { + return GetUserSign("", ""); + } + /// + /// 获取当前用户签名 + /// + /// + /// + /// + public static string GetUserSign(string shopId, string shopName) + { + long time = GetReviseUnix(); + string time1 = Encryption(time); + return "time=" + time + "&code=" + time1 + "&NbCodeVersion=1" + "&uid=" + ClientInfo.User.UserId + "&key=" + ClientInfo.User.UserKey + + "&cid=" + ClientInfo.User.CompanyId + "&company=" + UrlHelper.Encode(ClientInfo.User.CompanyName) + + "&sid=" + shopId + "&shop=" + UrlHelper.Encode(shopName); + } + + /// + /// 获取校正后的时间戳 + /// + /// + public static long GetReviseUnix() + { + return DateHelper.GetCurrentUnixTimestampInSeconds() + ServerConfig.TimeDifferent; + } + /// + /// 签名算法 + /// + /// 随机数 + /// 加密签名结果 + public static string Encryption(long s) + { + string value = $"{s}sdhewu*/fhAAue48485ew5+5%@3485esGFFHFD"; + return MD5Helper.Md5_32(value); + } + } +} diff --git a/Store.Framework/Config.cs b/Store.Framework/Config.cs new file mode 100644 index 0000000..81644fe --- /dev/null +++ b/Store.Framework/Config.cs @@ -0,0 +1,53 @@ +using MAX.Framework.Helpers; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Framework; + +public static class Config +{ + public const string AppVersion = "1312"; + + public static bool IsRelease + { + get + { +#if RELEASE + return true; +#else + return false; +#endif + } + } + + public static Guid SessionID { get; } + public static DateTime AppStartTime { get; } + public static string Version => AppVersion; + static Config() + { + SessionID = Guid.NewGuid(); + AppStartTime = DateTime.Now; + + //FileVersionInfo info = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location); + //Version = new VersionStruct(info.FileMajorPart, info.ProductMinorPart, info.FileBuildPart, info.FilePrivatePart); + + } + public static string GetFullVersionString() + { + static IEnumerable GetStrings() + { + yield return Version.ToString(); + yield return IsRelease ? "Release" : "Debug"; + if (SystemHelper.IsAdministratorSafe) + { + yield return "管理员权限"; + } + } + return string.Join(" - ", GetStrings()); + } +} diff --git a/Store.Framework/Configs/GlobalConfig.cs b/Store.Framework/Configs/GlobalConfig.cs new file mode 100644 index 0000000..248ac17 --- /dev/null +++ b/Store.Framework/Configs/GlobalConfig.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Framework.Configs +{ + /// + /// 全局配置 + /// + public class GlobalConfig + { + + /// + /// app启动路径 + /// + public static string AppDirectory + { + get + { + return AppDomain.CurrentDomain.BaseDirectory; + } + } + } +} diff --git a/Store.Framework/Configs/IApiServerConfig.cs b/Store.Framework/Configs/IApiServerConfig.cs new file mode 100644 index 0000000..a0af532 --- /dev/null +++ b/Store.Framework/Configs/IApiServerConfig.cs @@ -0,0 +1,16 @@ +using System; + +namespace Store.Client +{ + public interface IApiServerConfig + { + /// + /// 服务器地址 + /// + string Host { get; } + /// + /// 项目地址 + /// + string ObjUrl { get; } + } +} diff --git a/Store.Framework/Configs/IServiceConfigProvider.cs b/Store.Framework/Configs/IServiceConfigProvider.cs new file mode 100644 index 0000000..7296cad --- /dev/null +++ b/Store.Framework/Configs/IServiceConfigProvider.cs @@ -0,0 +1,39 @@ +//namespace Store.Client +//{ +// /// +// /// 根据 DI 注册 Key 获取服务配置。 +// /// Keyed DI 配置作为初始值,登录后获取到的服务地址可以覆盖初始值。 +// /// +// public interface IServiceConfigProvider +// { +// /// +// /// 获取指定 Key 的服务配置。 +// /// +// IServerConfig Get(string key); + +// /// +// /// 尝试获取指定 Key 的服务配置。 +// /// +// bool TryGet(string key, out IServerConfig config); + +// /// +// /// 设置或覆盖指定 Key 的完整服务配置。 +// /// +// void Set(string key, IServerConfig config); + +// /// +// /// 设置或覆盖指定 Key 的服务配置。 +// /// +// void Set(string key, string host, string objUrl); + +// /// +// /// 只更新指定 Key 的 Host,保留原有 ObjUrl。 +// /// +// void SetHost(string key, string host); + +// /// +// /// 删除运行时覆盖配置。删除后会回退到 Keyed DI 初始配置。 +// /// +// bool Remove(string key); +// } +//} \ No newline at end of file diff --git a/Store.Framework/Configs/ServerConfig.cs b/Store.Framework/Configs/ServerConfig.cs new file mode 100644 index 0000000..e8098bc --- /dev/null +++ b/Store.Framework/Configs/ServerConfig.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Client +{ + internal class ServerConfig + { + /// + /// 服务器时间差 + /// + public static long TimeDifferent { get; internal set; } + /// + /// 是否使用新签名算法 + /// + public static bool IsNewSignatureAuth { get; internal set; } = true; + + } +} diff --git a/Store.Framework/Configs/ServiceConfigKeys.cs b/Store.Framework/Configs/ServiceConfigKeys.cs new file mode 100644 index 0000000..315339b --- /dev/null +++ b/Store.Framework/Configs/ServiceConfigKeys.cs @@ -0,0 +1,13 @@ +namespace Store.Client +{ + /// + /// 服务配置默认 Key。 + /// + public static class ServiceConfigKeys + { + /// + /// 基础 Store API 使用的默认配置 Key。 + /// + public const string Default = "Default"; + } +} \ No newline at end of file diff --git a/Store.Framework/Configs/ServiceConfigProvider.cs b/Store.Framework/Configs/ServiceConfigProvider.cs new file mode 100644 index 0000000..1023f39 --- /dev/null +++ b/Store.Framework/Configs/ServiceConfigProvider.cs @@ -0,0 +1,95 @@ +//using Autofac.Core.Registration; +//using Autofac.Features.Indexed; +//using MAX.Core.DependencyInjection; +//using MAX.Core.Infrastructure; +//using System; +//using System.Collections.Concurrent; +//using System.Collections.Generic; + +//namespace Store.Client +//{ +// /// +// /// 从 Autofac Keyed Service 中获取服务配置。 +// /// 登录后服务端下发的新地址会保存在运行时配置中,并优先于 Keyed DI 初始配置。 +// /// +// public sealed class ServiceConfigProvider : IServiceConfigProvider, ISingletonDependency +// { +// private readonly IIndex _configs; +// private readonly ConcurrentDictionary _runtimeConfigs = new ConcurrentDictionary(); + +// public ServiceConfigProvider(IIndex configs) +// { +// _configs = configs ?? throw new ArgumentNullException(nameof(configs)); +// } + +// public IApiServerConfig Get(string key) +// { +// if (TryGet(key, out IApiServerConfig config)) +// { +// return config; +// } + +// throw new InvalidOperationException($"未注册 Key 为“{key}”的 IApiServerConfig。"); +// } + +// public bool TryGet(string key, out IApiServerConfig config) +// { +// config = null; +// if (string.IsNullOrWhiteSpace(key)) +// { +// return false; +// } + +// if (_runtimeConfigs.TryGetValue(key, out config)) +// { +// return true; +// } + +// try +// { +// config = _configs[key]; +// return config != null; +// } +// catch (KeyNotFoundException) +// { +// return false; +// } +// catch (ComponentNotRegisteredException) +// { +// return false; +// } +// } + +// public void Set(string key, IApiServerConfig config) +// { +// if (string.IsNullOrWhiteSpace(key)) +// { +// throw new ArgumentException("服务配置 Key 不能为空。", nameof(key)); +// } +// if (config == null) +// { +// throw new ArgumentNullException(nameof(config)); +// } + +// _runtimeConfigs[key] = config; +// } + +// public void Set(string key, string host, string objUrl) +// { +// Set(key, new ServiceConfig(host, objUrl)); +// } + +// public void SetHost(string key, string host) +// { +// string objUrl = TryGet(key, out IApiServerConfig config) +// ? config.ObjUrl +// : string.Empty; +// Set(key, new ServiceConfig(host, objUrl)); +// } + +// public bool Remove(string key) +// { +// return !string.IsNullOrWhiteSpace(key) && _runtimeConfigs.TryRemove(key, out _); +// } +// } +//} \ No newline at end of file diff --git a/Store.Framework/Configs/ServiceConfigRegistrationExtensions.cs b/Store.Framework/Configs/ServiceConfigRegistrationExtensions.cs new file mode 100644 index 0000000..0290cc4 --- /dev/null +++ b/Store.Framework/Configs/ServiceConfigRegistrationExtensions.cs @@ -0,0 +1,41 @@ +//using Autofac; +//using System; + +//namespace Store.Client +//{ +// /// +// /// IServerConfig 的 Keyed DI 注册扩展。 +// /// +// public static class ServiceConfigRegistrationExtensions +// { +// /// +// /// 注册指定 Key 的服务配置。 +// /// +// public static ContainerBuilder RegisterServiceConfig(this ContainerBuilder builder, string key, IServerConfig config) +// { +// if (builder == null) +// { +// throw new ArgumentNullException(nameof(builder)); +// } +// if (string.IsNullOrWhiteSpace(key)) +// { +// throw new ArgumentException("服务配置 Key 不能为空。", nameof(key)); +// } +// if (config == null) +// { +// throw new ArgumentNullException(nameof(config)); +// } + +// builder.RegisterInstance(config).Keyed(key); +// return builder; +// } + +// /// +// /// 注册指定 Key 的服务配置。 +// /// +// public static ContainerBuilder RegisterServiceConfig(this ContainerBuilder builder, string key, string host, string objUrl) +// { +// return builder.RegisterServiceConfig(key, new ServiceConfig(host, objUrl)); +// } +// } +//} \ No newline at end of file diff --git a/Store.Framework/Enums/PlatformType.cs b/Store.Framework/Enums/PlatformType.cs new file mode 100644 index 0000000..bae1550 --- /dev/null +++ b/Store.Framework/Enums/PlatformType.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Framework.Enums; + +/// +/// 平台类型 +/// +public enum PlatformType +{ + /// + /// 未知 + /// + [Description("未知")] + None = 0, + /// + /// 多多 + /// + [Description("多多")] + PDD = 1, + /// + /// 微信 + /// + [Description("微信")] + WX = 2, +} diff --git a/Store.Framework/Extensions/DictionaryExtension.cs b/Store.Framework/Extensions/DictionaryExtension.cs new file mode 100644 index 0000000..c3e1305 --- /dev/null +++ b/Store.Framework/Extensions/DictionaryExtension.cs @@ -0,0 +1,35 @@ +using MAX.Core.Cryptography; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Store.Framework.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 StringBuilder(""); + 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/Store.Framework/Extensions/Extensions.cs b/Store.Framework/Extensions/Extensions.cs new file mode 100644 index 0000000..7210d3a --- /dev/null +++ b/Store.Framework/Extensions/Extensions.cs @@ -0,0 +1,560 @@ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using System.Web; + +namespace Store.Framework.Extensions; + + +public static class Extensions +{ + public static long ParseToLong(this object obj) + { + try + { + string text = ParseToString(obj); + if (string.IsNullOrEmpty(text)) + { + return 0L; + } + return long.Parse(text); + } + catch + { + return 0L; + } + } + + public static long ParseToLong(this string str, long defaultValue) + { + try + { + return long.Parse(str); + } + catch + { + return defaultValue; + } + } + + public static int ParseToInt(this object str) + { + try + { + return Convert.ToInt32(str); + } + catch + { + return 0; + } + } + + public static int DecimalPointParseToInt(this object str) + { + try + { + string text = ParseToString(str); + int num = text.IndexOf('.'); + if (num > 0) + { + text = text.Substring(0, num); + } + return Convert.ToInt32(text); + } + catch + { + return 0; + } + } + + public static int ParseToInt(this object str, int defaultValue) + { + if (str == null) + { + return defaultValue; + } + try + { + return Convert.ToInt32(str); + } + catch + { + return defaultValue; + } + } + + public static short ParseToShort(this object obj) + { + try + { + return short.Parse(obj.ToString()); + } + catch + { + return 0; + } + } + + public static short ParseToShort(this object str, short defaultValue) + { + try + { + return short.Parse(str.ToString()); + } + catch + { + return defaultValue; + } + } + + public static decimal ParseToDecimal(this object str, decimal defaultValue) + { + try + { + return decimal.Parse(str.ToString()); + } + catch + { + return defaultValue; + } + } + + public static decimal ParseToDecimal(this object str) + { + try + { + return decimal.Parse(str.ToString()); + } + catch + { + return 0m; + } + } + + public static bool ParseToBool(this object str) + { + try + { + return bool.Parse(str.ToString()); + } + catch + { + return false; + } + } + + public static bool ParseToBool(this object str, bool result) + { + try + { + return bool.Parse(str.ToString()); + } + catch + { + return result; + } + } + + public static float ParseToFloat(this object str) + { + try + { + return float.Parse(str.ToString()); + } + catch + { + return 0f; + } + } + + public static float ParseToFloat(this object str, float result) + { + try + { + return float.Parse(str.ToString()); + } + catch + { + return result; + } + } + + public static Guid ParseToGuid(this string str) + { + try + { + return new Guid(str); + } + catch + { + return Guid.Empty; + } + } + + public static DateTime ParseToDateTime(this string str) + { + try + { + if (string.IsNullOrWhiteSpace(str)) + { + return DateTime.MinValue; + } + if (str.Contains("-") || str.Contains("/")) + { + return DateTime.Parse(str); + } + return str.Length switch + { + 4 => DateTime.ParseExact(str, "yyyy", CultureInfo.CurrentCulture), + 6 => DateTime.ParseExact(str, "yyyyMM", CultureInfo.CurrentCulture), + 8 => DateTime.ParseExact(str, "yyyyMMdd", CultureInfo.CurrentCulture), + 10 => DateTime.ParseExact(str, "yyyyMMddHH", CultureInfo.CurrentCulture), + 12 => DateTime.ParseExact(str, "yyyyMMddHHmm", CultureInfo.CurrentCulture), + 14 => DateTime.ParseExact(str, "yyyyMMddHHmmss", CultureInfo.CurrentCulture), + _ => DateTime.ParseExact(str, "yyyyMMddHHmmss", CultureInfo.CurrentCulture), + }; + } + catch + { + return DateTime.MinValue; + } + } + + public static DateTime ParseToDateTime(this string str, DateTime? defaultValue) + { + try + { + if (string.IsNullOrWhiteSpace(str)) + { + return defaultValue.GetValueOrDefault(); + } + if (str.Contains("-") || str.Contains("/")) + { + return DateTime.Parse(str); + } + return str.Length switch + { + 4 => DateTime.ParseExact(str, "yyyy", CultureInfo.CurrentCulture), + 6 => DateTime.ParseExact(str, "yyyyMM", CultureInfo.CurrentCulture), + 8 => DateTime.ParseExact(str, "yyyyMMdd", CultureInfo.CurrentCulture), + 10 => DateTime.ParseExact(str, "yyyyMMddHH", CultureInfo.CurrentCulture), + 12 => DateTime.ParseExact(str, "yyyyMMddHHmm", CultureInfo.CurrentCulture), + 14 => DateTime.ParseExact(str, "yyyyMMddHHmmss", CultureInfo.CurrentCulture), + _ => DateTime.ParseExact(str, "yyyyMMddHHmmss", CultureInfo.CurrentCulture), + }; + } + catch + { + return defaultValue.GetValueOrDefault(); + } + } + + public static string ParseToString(this object obj) + { + try + { + if (obj == null) + { + return string.Empty; + } + return obj.ToString(); + } + catch + { + return string.Empty; + } + } + + public static string ParseToStrings(this object obj, string split = ",") + { + try + { + if (obj is IEnumerable values) + { + return string.Join(split, values); + } + return obj.ToString(); + } + catch + { + return string.Empty; + } + } + + public static double ParseToDouble(this object obj) + { + try + { + return double.Parse(obj.ToString()); + } + catch + { + return 0.0; + } + } + + public static double ParseToDouble(this object str, double defaultValue) + { + try + { + return double.Parse(str.ToString()); + } + catch + { + return defaultValue; + } + } + + public static T[] SplitToArray(this string value, string split = ",") + { + if (string.IsNullOrEmpty(value)) + { + return null; + } + return CastSuper(value.Split(new string[1] { split }, StringSplitOptions.RemoveEmptyEntries)).ToArray(); + } + + public static T[] SplitToArray(this string value, string[] split) + { + if (string.IsNullOrEmpty(value)) + { + return null; + } + return CastSuper(value.Split(split, StringSplitOptions.RemoveEmptyEntries)).ToArray(); + } + + public static List SplitToList(this string value, string split = ",") + { + if (string.IsNullOrEmpty(value)) + { + return new List(); + } + return CastSuper(value.Split(new string[1] { split }, StringSplitOptions.RemoveEmptyEntries)).ToList(); + } + + public static List SplitToList(this string value, string[] split) + { + if (string.IsNullOrEmpty(value)) + { + return new List(); + } + return CastSuper(value.Split(split, StringSplitOptions.RemoveEmptyEntries)).ToList(); + } + + public static IEnumerable CastSuper(this IEnumerable source) + { + foreach (object item in source) + { + yield return (TResult)Convert.ChangeType(item, typeof(TResult)); + } + } + + + + + public static Dictionary EnumToDictionary(this Type enumType) + { + Dictionary dictionary = new Dictionary(); + Type typeFromHandle = typeof(DescriptionAttribute); + FieldInfo[] fields = enumType.GetFields(); + int num = 0; + string empty = string.Empty; + FieldInfo[] array = fields; + foreach (FieldInfo fieldInfo in array) + { + if (fieldInfo.FieldType.IsEnum) + { + num = (int)enumType.InvokeMember(fieldInfo.Name, BindingFlags.GetField, null, null, null); + object[] customAttributes = fieldInfo.GetCustomAttributes(typeFromHandle, inherit: true); + if (customAttributes.Length != 0) + { + DescriptionAttribute descriptionAttribute = (DescriptionAttribute)customAttributes[0]; + empty = descriptionAttribute.Description; + } + else + { + empty = fieldInfo.Name; + } + dictionary.Add(num, empty); + } + } + return dictionary.OrderBy((KeyValuePair p) => p.Key).ToDictionary((KeyValuePair p) => p.Key, (KeyValuePair p) => p.Value); + } + + public static string GetDescription(this Enum enumType) + { + FieldInfo field = enumType.GetType().GetField(enumType.ToString()); + if (field != null) + { + DescriptionAttribute[] array = (DescriptionAttribute[])field.GetCustomAttributes(typeof(DescriptionAttribute), inherit: false); + if (array.Length != 0) + { + return array[0].Description; + } + } + return enumType.ToString(); + } + + public static string GetDescriptionByEnum(this object obj) + { + Enum enumType = Enum.Parse(typeof(T), ParseToString(obj)) as Enum; + return GetDescription(enumType); + } + + public static int GetEnumValueByDescription(this string description) + { + Dictionary source = EnumToDictionary(typeof(T)); + return (from p in source + where p.Value == description + select p.Key).FirstOrDefault(); + } + + public static int GetEnumValueByName(this string name) + { + return (int)Enum.Parse(typeof(T), name); + } + + public static object GetEnumNameByValue(this int value) + { + return Enum.ToObject(typeof(T), value); + } + + public static Exception GetOriginalException(this Exception ex) + { + if (ex.InnerException == null) + { + return ex; + } + return GetOriginalException(ex.InnerException); + } + + public static bool IsNullOrEmpty(this object value) + { + if (value != null && !string.IsNullOrEmpty(ParseToString(value))) + { + return false; + } + return true; + } + + public static bool IsNullOrZero(this object value) + { + if (value == null || string.IsNullOrEmpty(ParseToString(value)) || ParseToString(value).Trim() == "0") + { + return true; + } + return false; + } + + public static bool IsNumericType(this object value) + { + if (value == null) + { + return false; + } + TypeCode typeCode = Type.GetTypeCode(value.GetType()); + TypeCode typeCode2 = typeCode; + if ((uint)(typeCode2 - 5) <= 10u) + { + return true; + } + return false; + } + + public static bool IsStringType(this object value) + { + if (value == null) + { + return false; + } + TypeCode typeCode = Type.GetTypeCode(value.GetType()); + TypeCode typeCode2 = typeCode; + if (typeCode2 == TypeCode.String) + { + return true; + } + return false; + } + + public static bool IsNumber(this string value) + { + return RegexHelper.IsNumber(value); + } + + public static bool IsAlphabet(this string value) + { + return RegexHelper.IsAlphabet(value); + } + + public static bool IsAlphabetOrNumber(this string value) + { + return RegexHelper.IsAlphabetOrNumber(value); + } + + + +} +public class RegexHelper +{ + public static MatchCollection GetMatchCollection(string html, string regex) + { + if (string.IsNullOrEmpty(html)) + { + return null; + } + return Regex.Matches(html, regex, RegexOptions.IgnoreCase); + } + + public static string GetMatch(string html, string regex, int index = 0) + { + if (string.IsNullOrEmpty(html)) + { + return string.Empty; + } + MatchCollection matchCollection = Regex.Matches(html, regex, RegexOptions.IgnoreCase); + if (matchCollection.Count > 0) + { + return matchCollection[index].Groups[1].Value.ToString(); + } + return string.Empty; + } + + public static string GetMatchWithSingleLine(string html, string regex) + { + if (string.IsNullOrEmpty(html)) + { + return string.Empty; + } + MatchCollection matchCollection = Regex.Matches(html, regex, RegexOptions.IgnoreCase | RegexOptions.Singleline); + if (matchCollection.Count > 0) + { + return matchCollection[0].Groups[1].ToString(); + } + return string.Empty; + } + + public static bool IsNumber(string str) + { + Regex regex = new Regex("^[0-9\\.]+$"); + return regex.IsMatch(str); + } + + public static bool IsAlphabet(string str) + { + Regex regex = new Regex("^[A-Za-z]+$"); + return regex.IsMatch(str); + } + + public static bool IsAlphabetOrNumber(string str) + { + Regex regex = new Regex("^[A-Za-z0-9]+$"); + return regex.IsMatch(str); + } +} + diff --git a/Store.Framework/Extensions/HttpItemExtensions.cs b/Store.Framework/Extensions/HttpItemExtensions.cs new file mode 100644 index 0000000..33c3718 --- /dev/null +++ b/Store.Framework/Extensions/HttpItemExtensions.cs @@ -0,0 +1,68 @@ +using MAX.Core.Helpers; +using Store.Client.Commons; +using Store.Client.HttpExtension; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Client.Extensions +{ + /// + /// 请求扩展 + /// + public static class HttpItemExtensions + { + /// + /// 增加签名 + /// + /// + public static void Sign(this ServerHttpItem item) + { + SetSign(item, SignTool.GetSign()); + } + /// + /// 增加用户签名 + /// + /// + public static void SignUser(this ServerHttpItem item) + { + item.SignUser("", ""); + } + /// + /// 增加用户签名 + /// + /// + /// + /// + public static void SignUser(this ServerHttpItem item, string shopId, string shopName) + { + SetSign(item, SignTool.GetUserSign(shopId, shopName)); + } + private static void SetSign(ServerHttpItem item, string sign) + { + if (item.Method == MethodType.Get) + { + if (item.URL.Contains("?")) + { + item.URL += "&"; + } + else + { + item.URL += "?"; + } + item.URL += sign; + } + else + { + if (item.Postdata.Length > 0) + { + item.Postdata += "&"; + } + item.Postdata += sign; + } + item.IsSign = true; + } + } +} diff --git a/Store.Framework/Extensions/InitialDataMiddleware.cs b/Store.Framework/Extensions/InitialDataMiddleware.cs new file mode 100644 index 0000000..380f1ce --- /dev/null +++ b/Store.Framework/Extensions/InitialDataMiddleware.cs @@ -0,0 +1,50 @@ +//using Autofac; +////////////////using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using System.Threading.Tasks; + +//namespace LittleCow.Framework.Extensions +//{ + //public static class InitialDataMiddlewareExtensions + //{ + // /// + // /// 加载所有初始化信息 + // /// + // /// + // /// + // public static LittleBootstrap UseInitData(this LittleBootstrap engine) + // { + // engine.UseMiddleware(); + // return engine; + // } + //} + //public class InitialDataMiddleware : IEngine, ILoginSuccessed + //{ + // private IInitialLoading[] listInit = new IInitialLoading[0]; + // public void Build() + // { + // listInit = IocManager.ResolveAll(); + // listInit = listInit.OrderBy(a => a.Order).ToArray(); + // foreach (IInitialLoading item in listInit) + // { + // item.AppInit(); + // } + + // } + + // public void Use() + // { + + // } + + // void ILoginSuccessed.LoginSuccess(UserInfo user) + // { + // foreach (IInitialLoading item in listInit) + // { + // item.LoginInit(); + // } + // } + //} +//} diff --git a/Store.Framework/Helpers/DataOpenSSL.cs b/Store.Framework/Helpers/DataOpenSSL.cs new file mode 100644 index 0000000..0f2311a --- /dev/null +++ b/Store.Framework/Helpers/DataOpenSSL.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Client.Helpers +{ + public class DataOpenSSL + { + public static byte[] key = new byte[32] { 145, 232, 156, 201, 201, 0, 42, 184, 222, 111, 109, 49, 226, 169, 9, 44, 151, 148, 143, 120, 62, 84, 30, 170, 127, 242, 20, 141, 231, 33, 230, 6 }; + public static byte[] iv = new byte[16] { 113, 6, 75, 177, 160, 112, 73, 39, 178, 147, 82, 107, 186, 58, 144, 187 }; + + public static string EncryptString(string plainText) + { + try + { + + // Instantiate a new Aes object to perform string symmetric encryption + Aes encryptor = Aes.Create(); + + encryptor.Mode = CipherMode.CBC; + + // Set key and IV + byte[] aesKey = new byte[32]; + Array.Copy(key, 0, aesKey, 0, 32); + encryptor.Key = aesKey; + encryptor.IV = iv; + + // Instantiate a new MemoryStream object to contain the encrypted bytes + MemoryStream memoryStream = new MemoryStream(); + + // Instantiate a new encryptor from our Aes object + ICryptoTransform aesEncryptor = encryptor.CreateEncryptor(); + + // Instantiate a new CryptoStream object to process the data and write it to the + // memory stream + CryptoStream cryptoStream = new CryptoStream(memoryStream, aesEncryptor, CryptoStreamMode.Write); + + // Convert the plainText string into a byte array + byte[] plainBytes = Encoding.UTF8.GetBytes(plainText); + + // Encrypt the input plaintext string + cryptoStream.Write(plainBytes, 0, plainBytes.Length); + + // Complete the encryption process + cryptoStream.FlushFinalBlock(); + + // Convert the encrypted data from a MemoryStream to a byte array + byte[] cipherBytes = memoryStream.ToArray(); + + // Close both the MemoryStream and the CryptoStream + memoryStream.Close(); + cryptoStream.Close(); + + // Convert the encrypted byte array to a base64 encoded string + string cipherText = Convert.ToBase64String(cipherBytes, 0, cipherBytes.Length); + + + cipherText = cipherText.Replace("+", "-").Replace("/", "_").Replace("=", ""); + + // Return the encrypted data as a string + return cipherText; + + } + catch (Exception) + { + + //throw; + } + return plainText; + } + + public static string DecryptString(string cipherText) + { + cipherText = cipherText.Replace("-", "+").Replace("_", "/"); + var x1 = cipherText.Length % 4; + if (x1 > 0) + { + cipherText = cipherText + "====".Substring(x1); + } + // Instantiate a new Aes object to perform string symmetric encryption + Aes encryptor = Aes.Create(); + + encryptor.Mode = CipherMode.CBC; + + // Set key and IV + byte[] aesKey = new byte[32]; + Array.Copy(key, 0, aesKey, 0, 32); + encryptor.Key = aesKey; + encryptor.IV = iv; + + // Instantiate a new MemoryStream object to contain the encrypted bytes + MemoryStream memoryStream = new MemoryStream(); + + // Instantiate a new encryptor from our Aes object + ICryptoTransform aesDecryptor = encryptor.CreateDecryptor(); + + // Instantiate a new CryptoStream object to process the data and write it to the + // memory stream + CryptoStream cryptoStream = new CryptoStream(memoryStream, aesDecryptor, CryptoStreamMode.Write); + + // Will contain decrypted plaintext + string plainText = String.Empty; + + try + { + // Convert the ciphertext string into a byte array + byte[] cipherBytes = Convert.FromBase64String(cipherText); + + // Decrypt the input ciphertext string + cryptoStream.Write(cipherBytes, 0, cipherBytes.Length); + + // Complete the decryption process + cryptoStream.FlushFinalBlock(); + + // Convert the decrypted data from a MemoryStream to a byte array + byte[] plainBytes = memoryStream.ToArray(); + + // Convert the decrypted byte array to string + plainText = Encoding.UTF8.GetString(plainBytes, 0, plainBytes.Length); + } + catch (Exception) + { + + //throw; + } + finally + { + // Close both the MemoryStream and the CryptoStream + memoryStream.Close(); + cryptoStream.Close(); + } + + // Return the decrypted data as a string + return plainText; + } + + + + } +} diff --git a/Store.Framework/Helpers/FileHelper.cs b/Store.Framework/Helpers/FileHelper.cs new file mode 100644 index 0000000..952589b --- /dev/null +++ b/Store.Framework/Helpers/FileHelper.cs @@ -0,0 +1,88 @@ +using Store.Framework.Configs; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Framework.Helpers +{ + /// + /// 文件操作帮助类 + /// + public class FileHelper + { + /// + /// 获取文件MD5值 + /// + /// + /// + public static string GetFileMd5(string FilePath) + { + try + { + FileStream get_file = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.Read); + System.Security.Cryptography.MD5CryptoServiceProvider get_md5 = new System.Security.Cryptography.MD5CryptoServiceProvider(); + byte[] hash_byte = get_md5.ComputeHash(get_file); + string resule = BitConverter.ToString(hash_byte); + resule = resule.Replace("-", ""); + get_file.Close(); + return resule; + } + catch (Exception e) + { + return e.ToString(); + } + } + + /// + /// 读取所有内容 相对路径Or绝对路径 + /// + /// + /// + public static string ReadAllText(string file) + { + if (!Path.IsPathRooted(file)) + { + file = Path.Combine(GlobalConfig.AppDirectory, file); + } + if (File.Exists(file)) + { + return _ReadAllText(file, Encoding.UTF8); + } + return string.Empty; + } + /// + /// 读取所有内容 绝对路径 + /// + /// + /// + public static string ReadAbsAllText(string file) + { + return _ReadAllText(file, Encoding.UTF8); + } + /// + /// 读取所有内容 相对路径 + /// + /// + /// + public static string ReadPartialAllText(string file) + { + string _file = Path.Combine(GlobalConfig.AppDirectory, file); + return _ReadAllText(_file, Encoding.UTF8); + } + + private static string _ReadAllText(string file, Encoding encoding) + { + if (File.Exists(file)) + { + return File.ReadAllText(file, encoding); + } + else + { + throw new Exception($"缺失文件{file}"); + } + } + } +} diff --git a/Store.Framework/Helpers/ImageBuild.cs b/Store.Framework/Helpers/ImageBuild.cs new file mode 100644 index 0000000..6e68977 --- /dev/null +++ b/Store.Framework/Helpers/ImageBuild.cs @@ -0,0 +1,1012 @@ +using MAX.Core.Helpers; +using MAX.Framework.Logger; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.IO; + +namespace Store.Framework.Helpers +{ + public class ImageBuild + { + public byte[] Img; + public int Width = 0, Height = 0; + public Image InitImage = null; + public string format = ""; + public int PicQuality = 0;//图片质量 + public bool CanUser + { + get { return format == ".jpg" || format == ".png"; } + } + + public ImageBuild(byte[] image) + { + if (image == null || image.Length == 0) + return; + Img = image; + try + { + if (image.Length > 3145728) + { + return; + } + MemoryStream ms = new MemoryStream(Img); + InitImage = Image.FromStream(ms); + var f = GetImageFormat(InitImage, out format); + ms.Close(); + ms.Dispose(); + } + catch (Exception e) + { + NLogger.Instance.Error(e.Message); + } + if (InitImage == null) + return; + Width = InitImage.Width; + Height = InitImage.Height; + } + /// + /// 裁剪为指定大小,不符合则返回null + /// + /// + /// + /// + public byte[] CutToSize(int width, int height) + { + if (InitImage == null) + return null; + int w = InitImage.Width; + int h = InitImage.Height; + if (w < width || h < height) + { + return null; + } + int x = 0, y = 0, nw = w, nh = h; + int tx = 0, ty = 0, tw = width, th = height; + double f = w * 1.0 / width; + double t = h * 1.0 / height; + if (f == t)//无需变化 + { + } + else if (f > t)//宽度较大 + { + x = (int)(((f - t) / 2) * width); + nw = w - x * 2; + } + else//高度较大 + { + y = (int)((t - f) / 2 * height); + nh = h - y * 2; + } + Rectangle fromR = new Rectangle(x, y, nw, nh); + Rectangle toR = new Rectangle(tx, ty, tw, th); + Bitmap pickedImage = new Bitmap(width, height); + + Graphics pickedG = Graphics.FromImage(pickedImage); + pickedG.InterpolationMode = InterpolationMode.Low; + pickedG.SmoothingMode = SmoothingMode.HighSpeed; + pickedG.DrawImage(InitImage, toR, fromR, GraphicsUnit.Pixel); + //转图片后转二进制数组 + Image initImagex = (Image)pickedImage.Clone(); + MemoryStream ms = new MemoryStream(); + ImageCodecInfo icc = GetCodeInfo("1.jpg"); + long quality = 80; + EncoderParameters ep = new EncoderParameters(1); + ep.Param[0] = new EncoderParameter(Encoder.Quality, quality); + initImagex.Save(ms, icc, ep); + byte[] bytes = ms.ToArray(); + return bytes; + } + /// + /// 添加线条 + /// + /// + public void AddLineToImage(ref byte[] img) + { + MemoryStream ms = new MemoryStream(img); + var image = (Bitmap)Image.FromStream(ms); + ms.Close(); + int width = image.Width; + int height = image.Height; + if (width < 100 || height < 100) + return; + int r = RandomHelper.GetRandomNumber(100, 256); + int b = RandomHelper.GetRandomNumber(100, 256); + int g = RandomHelper.GetRandomNumber(100, 256); + int h = RandomHelper.GetRandomNumber(1, 10); + var c = Color.FromArgb(r, g, b); + for (int i = 0; i < h; i++) + { + for (int j = 0; j < height; j++) + image.SetPixel(i, j, c); + for (int j = 0; j < width; j++) + image.SetPixel(j, i, c); + } + for (int i = height - h; i < height; i++) + { + for (int j = 0; j < width; j++) + image.SetPixel(j, i, c); + } + for (int i = width - h; i < width; i++) + { + for (int j = 0; j < height; j++) + image.SetPixel(i, j, c); + } + ImageCodecInfo icc = GetCodeInfo("1.jpg"); + long quality = 75; + ms = new MemoryStream(); + EncoderParameters ep = new EncoderParameters(1); + ep.Param[0] = new EncoderParameter(Encoder.Quality, quality); + image.Save(ms, icc, ep); + img = ms.ToArray(); + //FileBase.WriteBytes("1.jpg", bytes); + } + + + /// + /// 在图片四周区域随机选取8*8像素,微调像素值 + /// + /// + public void FineTuningImageToRandomArea(ref byte[] img) + { + try + { + MemoryStream ms = new MemoryStream(img); + var image = (Bitmap)Image.FromStream(ms); + int iW = image.Width; + int iH = image.Height; + if (iW <= 32 || iH <= 32) return; + int x = 0; + int y = 0; + Random rand = new Random(); + int r = rand.Next(1, 4); + switch (r) + { + case 1: + x = rand.Next(0, iW / 4 - 8); + y = rand.Next(0, iH - 8); + break; + case 2: + x = rand.Next(iW / 4 - 8, iW - 8); + y = rand.Next(0, iH / 4 - 8); + break; + case 3: + x = rand.Next(iW / 4 - 8, iW - 8); + y = rand.Next(3 * iH / 4, iH - 8); + break; + case 4: + x = rand.Next(3 * iW / 4, iW - 8); + y = rand.Next(iH / 4 - 8, 3 * iH / 4); + break; + } + List> lstlstPixes = new List>(); + int tmpx = x; + int tmpy = y; + for (int jj = 0; jj < 4; jj++) + { + List lstPixes = new List(); + lstPixes.Add(new PixPoint { X = tmpx, Y = tmpy }); + for (int i = 1; i < 8 - jj * 2; i++) + { + PixPoint ppx = new PixPoint { X = x, Y = ++tmpy }; + lstPixes.Add(ppx); + PixPoint ppy = new PixPoint { X = ++tmpx, Y = y }; + lstPixes.Add(ppy); + } + lstlstPixes.Add(lstPixes); + if (jj == 3) break; + tmpx = ++x; + tmpy = ++y; + } + + for (int jj = 3; jj >= 0; jj--) + { + + List lstPixes = new List(); + tmpx = ++x; + tmpy = ++y; + lstPixes.Add(new PixPoint { X = tmpx, Y = tmpy }); + for (int i = 1; i < 7 - jj * 2; i++) + { + PixPoint ppx = new PixPoint { X = x, Y = --tmpy }; + lstPixes.Add(ppx); + PixPoint ppy = new PixPoint { X = --tmpx, Y = y }; + lstPixes.Add(ppy); + } + lstlstPixes[jj].AddRange(lstPixes); + } + + int inc = 0; + foreach (var lstpix in lstlstPixes) + { + inc++; + foreach (var pix in lstpix) + { + Color color = image.GetPixel(pix.X, pix.Y); + int newR, newG, newB; + newR = color.R + inc > 255 ? 255 : color.R + inc; + newG = color.G + inc > 255 ? 255 : color.G + inc; + newB = color.B + inc > 255 ? 255 : color.B + inc; + var c = Color.FromArgb(newR, newG, newB); + image.SetPixel(pix.X, pix.Y, c); + } + } + ImageCodecInfo icc = GetCodeInfo("1.jpg"); + //long quality = 75; + long quality = PicQuality;//之前固定值 现在改为设置的数值 + ms = new MemoryStream(); + EncoderParameters ep = new EncoderParameters(1); + ep.Param[0] = new EncoderParameter(Encoder.Quality, quality); + image.Save(ms, icc, ep); + img = ms.ToArray(); + //TODO:Need Test + //image.Dispose(); + ms.Close(); + } + catch (Exception ex) + { + NLogger.Instance.Error(ex); + } + } + + /// + /// 输出固定大小图片,背景白色填充,返回JPG格式二进制代码 + /// + /// + /// + /// 最大尺寸KB + /// + public byte[] BuildNewSize(int width, int height, int maxSize, string codePath = "1.jpg") + { + if (InitImage == null) + return null; + int w = InitImage.Width; + int h = InitImage.Height; + int x = 0, y = 0, nw = w, nh = h; + int tx = 0, ty = 0, tw = width, th = height; + //先缩放 + if (w <= width && h <= height)//无需缩放 + { + tx = (width - w) / 2; + tw = w; + ty = (height - h) / 2; + th = h; + } + else if (w < width && h > height) + { + tx = (width - w) / 2; + tw = w; + y = (h - height) / 2; + nh = height; + } + else if (w > width && h < height) + { + ty = (height - h) / 2; + th = h; + x = (w - width) / 2; + nw = width; + } + else + { + double f = w * 1.0 / width; + double t = h * 1.0 / height; + if (f == t)//无需变化 + { + } + else if (f > t)//宽度较大 + { + x = (int)(((f - t) / 2) * width); + nw = w - x * 2; + } + else//高度较大 + { + y = (int)((t - f) / 2 * height); + nh = h - y * 2; + } + } + Rectangle fromR = new Rectangle(x, y, nw, nh); + Rectangle toR = new Rectangle(tx, ty, tw, th); + Bitmap pickedImage = new Bitmap(width, height); + for (int i = 0; i < pickedImage.Width; i++) + { + for (int j = 0; j < pickedImage.Height; j++) + { + Color color = Color.FromArgb(255, 255, 255); + pickedImage.SetPixel(i, j, color); + } + } + Graphics pickedG = Graphics.FromImage(pickedImage); + pickedG.InterpolationMode = InterpolationMode.Low; + pickedG.SmoothingMode = SmoothingMode.HighSpeed; + pickedG.DrawImage(InitImage, toR, fromR, GraphicsUnit.Pixel); + //转图片后转二进制数组 + Image initImagex = (Image)pickedImage.Clone(); + MemoryStream ms = new MemoryStream(); + ImageCodecInfo icc = GetCodeInfo(codePath); + + //long quality = maxSize == 1024 ? 75l : 85; + long quality = PicQuality;//之前固定值 现在改为设置的数值 + EncoderParameters ep = new EncoderParameters(1); + ep.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality); + initImagex.Save(ms, icc, ep); + byte[] bytes = ms.ToArray(); + if (bytes.Length > maxSize * 1024) + { + ms = new MemoryStream(); + ep.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 75L); + initImagex.Save(ms, icc, ep); + bytes = ms.ToArray(); + } + if (bytes.Length > maxSize * 1024) + { + ms = new MemoryStream(); + ep.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 55L); + initImagex.Save(ms, icc, ep); + bytes = ms.ToArray(); + } + //TODO:Need Test + //pickedImage.Dispose(); + //pickedG.Dispose(); + //initImagex.Dispose(); + //ms.Close(); + if (bytes.Length > maxSize * 1024) + { + return null; + } + //ImageCodecInfo icc = GetCodeInfo("1.jpg"); + //initImagex.Save("22.jpg", icc, ep); + return bytes; + } + /// + /// 根据路劲得出图片编码信息 + /// + /// + /// + public ImageCodecInfo GetCodeInfo(string path) + { + string exe = path.Split('.')[path.Split('.').Length - 1].ToLower(); + ImageCodecInfo[] icis = ImageCodecInfo.GetImageEncoders(); + ImageCodecInfo ici = null, defCodecInfo = null; + foreach (ImageCodecInfo i in icis) + { + if (i.MimeType == "image/jpeg") + defCodecInfo = i; + if ((exe == "jpg" || exe == "jpeg") && i.MimeType == "image/jpeg") + { + ici = i; + } + if (exe == "bmp" && i.MimeType == "image/bmp") + { + ici = i; + } + if (exe == "png" && i.MimeType == "image/png") + { + ici = i; + } + if (exe == "gif" && i.MimeType == "image/gif") + { + ici = i; + } + } + if (ici == null) + ici = defCodecInfo; + return ici; + } + + /// + /// 获取Image图片格式 + /// + /// + /// + /// + private System.Drawing.Imaging.ImageFormat GetImageFormat(Image _img, out string format) + { + if (_img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Jpeg)) + { + format = ".jpg"; + return System.Drawing.Imaging.ImageFormat.Jpeg; + } + if (_img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Gif)) + { + format = ".gif"; + return System.Drawing.Imaging.ImageFormat.Gif; + } + if (_img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Png)) + { + format = ".png"; + return System.Drawing.Imaging.ImageFormat.Png; + } + if (_img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Bmp)) + { + format = ".bmp"; + return System.Drawing.Imaging.ImageFormat.Bmp; + } + format = string.Empty; + return null; + } + + + #region 随机裁四边--10个像素以内 + public void CutPicture(ref byte[] file) + { + MemoryStream ms = new MemoryStream(file); + var image = (Bitmap)Image.FromStream(ms); + + Random radom = new Random(); + int cutPixl = radom.Next(1, 5); //左边切除的像素 + int cutPixr = radom.Next(1, 5); //右边切除的像素 + int cutPixt = radom.Next(1, 5); //顶部切除的像素 + int cutPixb = radom.Next(1, 5); //底部切除的像素 + + Image img = BytesToImage(file); + int iniWidth = img.Width; + int iniHeight = img.Height; + + try + { + Bitmap bitmap = StartCutImage(file, cutPixl, cutPixr, iniWidth - cutPixl - cutPixt, iniHeight - cutPixr - cutPixb, iniWidth, iniHeight); + + image = bitmap; + + ImageCodecInfo icc = GetCodeInfo("1.jpg"); + //long quality = 75; + long quality = PicQuality;//之前固定值 现在改为设置的数值 + ms = new MemoryStream(); + EncoderParameters ep = new EncoderParameters(1); + ep.Param[0] = new EncoderParameter(Encoder.Quality, quality); + image.Save(ms, icc, ep); + // image.Save(ms,ImageFormat.Jpeg); + file = ms.ToArray(); + //TODO:Need Test + //img.Dispose(); + //bitmap.Dispose(); + //image.Dispose(); + ms.Close(); + } + catch (Exception) + { + + } + + } + + /// + /// 剪裁 + /// + /// 原始Bitmap + /// 开始坐标X + /// 开始坐标Y + /// 宽度 + /// 高度 + /// 剪裁后的Bitmap + public static Bitmap StartCutImage(byte[] bytes, int StartX, int StartY, int iWidth, int iHeight, int iniWidth, int iniHeight) + { + Image image = BytesToImage(bytes); + Bitmap b = new Bitmap(image); + if (bytes == null) return null; + try + { + Bitmap bmpOut = new Bitmap(iWidth, iHeight, PixelFormat.Format24bppRgb);//裁剪成小一号的图片 + //Bitmap bmpOut = new Bitmap(w, h, PixelFormat.Format24bppRgb);//保持原大小,裁剪部分用指定的颜色填充 + Graphics graphics = Graphics.FromImage(bmpOut); + //graphics.Clear(Color.White);//裁剪部分用指定的颜色填充 + graphics.DrawImage(b, new Rectangle(0, 0, iWidth, iHeight), new Rectangle(StartX, StartY, iWidth, iHeight), GraphicsUnit.Pixel); + graphics.Dispose(); + bmpOut = new System.Drawing.Bitmap(bmpOut, iniWidth, iniHeight);//图片保存原始的大小尺寸 + return bmpOut; + } + catch + { + return null; + } + + } + + /// + /// 字节数组转换为图片 + /// + /// 字节数组 + /// 图片 + public static Image BytesToImage(byte[] buffer) + { + var ms = new MemoryStream(buffer, 0, buffer.Length); + Image img = Image.FromStream(ms); + ms.Dispose(); + return (Image)img; + } + + /// + /// 图片对象转字节数组 + /// + /// 图片路径 + /// 图片字节数组 + private byte[] ImageToBytes(string imagePath) + { + var fs = new FileStream(imagePath, FileMode.Open); + byte[] imgBytes = new byte[fs.Length]; + fs.Read(imgBytes, 0, imgBytes.Length); + fs.Close(); + return imgBytes; + //var bf = new BinaryFormatter(); + //bf.Serialize(ms,imagePath); + //ms.Close(); + //return ms.ToArray(); + + } + + #endregion + + #region 添加文字水印 + public void AddWaterMark(ref byte[] file, string shopName = "") + { + MemoryStream ms = new MemoryStream(file); + var image = (Bitmap)Image.FromStream(ms); + + try + { + Bitmap bitmap = StartAddWaterMark(file, shopName); + image = bitmap; + + ImageCodecInfo icc = GetCodeInfo("1.jpg"); + //long quality = 75; + long quality = PicQuality;//之前固定值 现在改为设置的数值 + ms = new MemoryStream(); + EncoderParameters ep = new EncoderParameters(1); + ep.Param[0] = new EncoderParameter(Encoder.Quality, quality); + image.Save(ms, icc, ep); + file = ms.ToArray(); + //image.Dispose(); + //bitmap.Dispose(); + ms.Close(); + } + catch (Exception) + { + + throw; + } + + } + + /// + /// 检测是否是白底图 + /// + /// 图片byte数组 + /// true-白底图 false-非白底图 + public static bool CheckImageIsWhiteBackground(byte[] bytesImg) + { + MemoryStream ms = new MemoryStream(bytesImg); + var image = (Bitmap)Image.FromStream(ms); + int iWhitePixNum = 0; + int iTotalPix = 0; + try + { + int iWidth = image.Width; + int iHeight = image.Height; + int iInpectWidth = 20; + //统计三窄边白色像素数量 + for (int i = 0; i < iInpectWidth; i++) + { + for (int j = 0; j < iHeight; j++) + { + Color cl = image.GetPixel(i, j); + iTotalPix++; + if (cl.Name.Equals("ffffffff")) + { + iWhitePixNum++; + } + } + } + for (int i = iWidth - iInpectWidth; i < iWidth; i++) + { + for (int j = iInpectWidth; j < iHeight; j++) + { + Color cl = image.GetPixel(i, j); + iTotalPix++; + if (cl.Name.Equals("ffffffff")) + { + iWhitePixNum++; + } + } + } + for (int i = iInpectWidth; i < iWidth; i++) + { + for (int j = 0; j < iInpectWidth; j++) + { + Color cl = image.GetPixel(i, j); + iTotalPix++; + if (cl.Name.Equals("ffffffff")) + { + iWhitePixNum++; + } + } + } + } + catch (Exception ex) + { + NLogger.Instance.Error(ex); + } + finally + { + ms.Close(); + image.Dispose(); + } + //如果三窄边的白色像素数量占比高于85%,则判定为白底图 + return Decimal.Divide(iWhitePixNum, iTotalPix) > (decimal)0.85; + } + + /// + /// 添加图片水印 + /// + /// + /// + /// 水印开始X坐标(自动扣除图片宽度) + /// 水印开始Y坐标(自动扣除图片高度 + /// 透明度 0-1 + /// 文件后缀名 + /// + public void AddLogoWaterMark(ref byte[] file, Image watermark, float opacity, string externName, bool bFill = true) + { + MemoryStream ms = new MemoryStream(file); + var image = (Bitmap)Image.FromStream(ms); + Bitmap bitmap = new Bitmap(image, image.Width, image.Height); //改为固定 + + Graphics g = Graphics.FromImage(bitmap); + + //下面定义一个矩形区域 + float rectWidth = watermark.Width + 10; + float rectHeight = watermark.Height + 10; + //声明矩形域 + + RectangleF textArea; + if (bFill) + { + textArea = new RectangleF(0, 0, image.Width, image.Height); + } + else + { + int height = watermark.Height; + int width = watermark.Width; + height = 80; + width = 80; + textArea = new RectangleF(0, 0, width, height); + } + Bitmap w_bitmap = ChangeOpacity(watermark, opacity); + + g.DrawImage(w_bitmap, 0, 0, 80, 80); + ms = new MemoryStream(); + //保存图片 + switch (externName) + { + case ".jpg": + bitmap.Save(ms, ImageFormat.Jpeg); + break; + case ".gif": + bitmap.Save(ms, ImageFormat.Gif); + break; + case ".png": + bitmap.Save(ms, ImageFormat.Png); + break; + default: + bitmap.Save(ms, ImageFormat.Jpeg); + break; + } + + file = ms.ToArray(); + ms.Close(); + g.Dispose(); + bitmap.Dispose(); + } + + /// + /// 改变图片的透明度 + /// + /// 图片 + /// 透明度 + /// + public Bitmap ChangeOpacity(Image img, float opacityvalue) + { + + float[][] nArray ={ new float[] {1, 0, 0, 0, 0}, + new float[] {0, 1, 0, 0, 0}, + new float[] {0, 0, 1, 0, 0}, + new float[] {0, 0, 0, opacityvalue, 0}, + new float[] {0, 0, 0, 0, 1}}; + ColorMatrix matrix = new ColorMatrix(nArray); + ImageAttributes attributes = new ImageAttributes(); + attributes.SetColorMatrix(matrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap); + Image srcImage = img; + Bitmap resultImage = new Bitmap(srcImage.Width, srcImage.Height); + Graphics g = Graphics.FromImage(resultImage); + g.DrawImage(srcImage, new Rectangle(0, 0, srcImage.Width, srcImage.Height), 0, 0, srcImage.Width, srcImage.Height, GraphicsUnit.Pixel, attributes); + //g.DrawImage(srcImage, 0, 0, srcImage.Width, srcImage.Height) ; + //g.DrawImage(srcImage, 0, 0, 80,80); + g.Dispose(); + attributes.Dispose(); + return resultImage; + } + + //private static Font crFont = new Font("微软雅黑", 30, FontStyle.Bold); + /// + /// + /// + /// + /// 水印文字 + /// + public static Bitmap StartAddWaterMark(byte[] bytes, string text = "") + { + if (bytes == null) return null; + Image image = BytesToImage(bytes); + Bitmap bitmap = new Bitmap(image); + try + { + + int width = bitmap.Width, height = bitmap.Height; + + Graphics g = Graphics.FromImage(bitmap); + + g.DrawImage(bitmap, 0, 0); + + g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High; + + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; + + g.DrawImage(image, new Rectangle(0, 0, width, height), 0, 0, width, height, GraphicsUnit.Pixel); + + Font crFont = new Font("微软雅黑", 30, FontStyle.Bold); + SizeF crSize = new SizeF(); + crSize = g.MeasureString(text, crFont); + + //背景位置(去掉了. 如果想用可以自己调一调 位置.) + //g.FillRectangle(new SolidBrush(Color.FromArgb(200, 255, 255, 255)), (width - crSize.Width) / 2, (height - crSize.Height) / 2, crSize.Width, crSize.Height); + + SolidBrush semiTransBrush = new SolidBrush(Color.FromArgb(120, 177, 171, 171));//177, 171, 171 + + + //文字水印随机加在一遍 + Random rdom = new Random(); + int rdLocation = rdom.Next(1, 5);//1:左边 2:右边 3:上边 4:下边 + + if (rdLocation == 1) + { + #region 左边 + //将原点移动 到图片中点 + g.TranslateTransform(0, height / 2 + crSize.Height); + //以原点为中心 转 -45度 + g.RotateTransform(-90); + + g.DrawString(text, crFont, semiTransBrush, new PointF(0, 0));//文字所加的位置 + #endregion + } + else if (rdLocation == 2) + { + #region 右边 + //将原点移动 到图片中点 + g.TranslateTransform(width - crSize.Width / 2, height / 2 + crSize.Height); + //以原点为中心 转 -45度 + g.RotateTransform(-90); + + g.DrawString(text, crFont, semiTransBrush, new PointF(0, 0));//文字所加的位置 + #endregion + } + else if (rdLocation == 3) + { + #region 上边 + ////将原点移动 到图片中点 + //g.TranslateTransform(width / 2 - crSize.Width, 0); + ////以原点为中心 转 -45度 + //g.RotateTransform(-0); + + g.DrawString(text, crFont, semiTransBrush, new PointF(width / 2 - crSize.Width / 2, 0));//文字所加的位置 + #endregion + } + else if (rdLocation == 4) + { + #region 下边 + ////将原点移动 到图片中点 + //g.TranslateTransform(width / 2 - crSize.Width, height - crSize.Height * 3); + ////以原点为中心 转 -0度 + ////g.RotateTransform(-0); + + g.DrawString(text, crFont, semiTransBrush, new PointF(width / 2 - crSize.Width / 2, height - crSize.Height));//文字所加的位置 + #endregion + } + else + { + #region 上边 + g.DrawString(text, crFont, semiTransBrush, new PointF(width / 2 - crSize.Width / 2, 0));//文字所加的位置 + #endregion + } + //crFont.Dispose(); + g.Dispose(); + //image.Dispose(); + //semiTransBrush.Dispose(); + ////将原点移动 到图片中点 + //g.TranslateTransform(width / 2, height / 2); + ////以原点为中心 转 -45度 + //g.RotateTransform(-45); + + //g.DrawString(text, crFont, semiTransBrush, new PointF(width / 2 - crSize.Width / 2, 0));//文字所加的位置 + + + } + catch (Exception e) + { + NLogger.Instance.Error(e); + } + + return bitmap; + } + #endregion + + /// + /// 获取两张图片的相似度 + /// + /// 图片1 + /// 图片2 + /// + public static string GetSimilarityForTwoImgs(Image image1, Image image2) + { + string sImg1Hash = GetHash(image1); + string sImg2Hash = GetHash(image2); + return Math.Round(((64.0 * 64.0) - CalcSimularDegree(sImg1Hash, sImg2Hash)) / (64.0 * 64.0), 4) * 100 + "%"; + } + + private static int CalcSimularDegree(string a, string b) + { + if (a.Length != b.Length) + throw new ArgumentException(); + int count = 0; + for (int i = 0; i < a.Length; i++) + { + if (a[i] != b[i]) + count++; + } + return count; + } + + private static string GetHash(Image sourceImage) + { + Image image = ReduceSize(sourceImage); + byte[] grayValues = ReduceColor(image); + byte average = CalcAverage(grayValues); + string sRes = ComputeBits(grayValues, average); + image.Dispose(); + image = null; + return sRes; + } + + private static string ComputeBits(byte[] grayValues, byte average) + { + char[] result = new char[grayValues.Length]; + for (int i = 0; i < grayValues.Length; i++) + { + if (grayValues[i] < average) + result[i] = '0'; + else + result[i] = '1'; + } + return new string(result); + } + + private static byte CalcAverage(byte[] grayValues) + { + int sum = 0; + for (int i = 0; i < grayValues.Length; i++) + { + sum += (int)grayValues[i]; + } + return System.Convert.ToByte(sum / grayValues.Length); + } + + private static byte[] ReduceColor(Image image) + { + Bitmap bitmap = new Bitmap(image); + byte[] grayValues = new byte[image.Width * image.Height]; + + for (int i = 0; i < image.Width; i++) + { + for (int j = 0; j < image.Height; j++) + { + Color color = bitmap.GetPixel(i, j); + byte grayValue = (byte)(((color.R * 30 + color.G * 59 + color.B * 11) / 100)); + grayValues[i * image.Width + j] = grayValue; + } + } + bitmap.Dispose(); + return grayValues; + } + + private static Image ReduceSize(Image image) + { + Image image1 = image.GetThumbnailImage(64, 64, () => { + return false; + }, IntPtr.Zero); + return image1; + } + + //byte[] 转换 Bitmap + public static Bitmap BytesToBitmap(byte[] Bytes) + { + MemoryStream stream = null; + try + { + stream = new MemoryStream(Bytes); + return new Bitmap((Image)new Bitmap(stream)); + } + catch (ArgumentNullException ex) + { + throw ex; + } + catch (ArgumentException ex) + { + throw ex; + } + finally + { + stream.Close(); + } + } + //Bitmap转byte[] + public static byte[] BitmapToBytes(Bitmap Bitmap) + { + MemoryStream ms = null; + try + { + ms = new MemoryStream(); + Bitmap.Save(ms, Bitmap.RawFormat); + byte[] byteImage = new Byte[ms.Length]; + byteImage = ms.ToArray(); + return byteImage; + } + catch (ArgumentNullException ex) + { + throw ex; + } + finally + { + ms.Close(); + } + } + public static Image ResizeImage(System.Drawing.Image imgToResize, Size size) + { + //获取图片宽度 + int sourceWidth = imgToResize.Width; + //获取图片高度 + int sourceHeight = imgToResize.Height; + + float nPercent = 0; + float nPercentW = 0; + float nPercentH = 0; + //计算宽度的缩放比例 + nPercentW = ((float)size.Width / (float)sourceWidth); + //计算高度的缩放比例 + nPercentH = ((float)size.Height / (float)sourceHeight); + + if (nPercentH < nPercentW) + nPercent = nPercentH; + else + nPercent = nPercentW; + //期望的宽度 + int destWidth = (int)(sourceWidth * nPercent); + //期望的高度 + int destHeight = (int)(sourceHeight * nPercent); + + Bitmap b = new Bitmap(destWidth, destHeight); + Graphics g = Graphics.FromImage((System.Drawing.Image)b); + g.InterpolationMode = InterpolationMode.HighQualityBicubic; + //绘制图像 + g.DrawImage(imgToResize, 0, 0, destWidth, destHeight); + g.Dispose(); + return (System.Drawing.Image)b; + } + + + class PixPoint + { + public int X { get; set; } + public int Y { get; set; } + } + } +} diff --git a/Store.Framework/HttpExtension/AppApiRequestBase.cs b/Store.Framework/HttpExtension/AppApiRequestBase.cs new file mode 100644 index 0000000..5148529 --- /dev/null +++ b/Store.Framework/HttpExtension/AppApiRequestBase.cs @@ -0,0 +1,159 @@ +using MAX.Core.Autofac; +using MAX.Core.DependencyInjection; +using MAX.Core.Helpers; +using MAX.Core.Logger; +using Store.Client.Extensions; +using System; +using System.Net; +using System.Text; + +namespace Store.Client.HttpExtension +{ + /// + /// Store 基础 API 请求基类。 + /// 项目 API 可以继承该基类并重写 ServiceConfigKey,使用项目自己的服务配置。 + /// + public class AppApiRequestBase + { + /// + /// 日志。 + /// + [Autowired] + protected ILoggerFacade _logger => LazyServiceProvider.Instance.LazyGetService(); + protected IApiServerConfig _config => LazyServiceProvider.Instance.LazyGetService(); + + /// + /// 默认服务请求。 + /// + protected HttpResult Post(string urlpath, string postdata = "") + { + return Request(BuildServiceUrl(urlpath), postdata); + } + + /// + /// 公共签名服务请求。 + /// + protected HttpResult PostPublic(string urlpath, string postdata = "") + { + return Request(BuildServiceUrl(urlpath), postdata, isUserSign: false); + } + + /// + /// 拼多多服务请求。 + /// + protected HttpResult PostPdd(string urlpath, string postdata = "") + { + return Request(BuildServiceUrl("bxx_" + urlpath), postdata); + } + + /// + /// 微信服务请求。 + /// + protected HttpResult PostWx(string urlpath, string postdata = "") + { + return Request(BuildServiceUrl("wxxd_" + urlpath), postdata); + } + + /// + /// 微信图片服务请求。 + /// + protected HttpResult PostWxImg(string urlpath, string postdata = "") + { + return Request(BuildServiceUrl("wxxd_" + urlpath), postdata); + } + + /// + /// 默认兼容服务请求。 + /// + protected HttpResult PostDefault(string urlpath, string postdata = "") + { + return Request(BuildServiceUrl(urlpath), postdata); + } + /// + /// 使用当前 Key 构建服务请求地址。 + /// + protected string BuildServiceUrl(string urlPath) + { + return $"{_config.Host}/wxxd_{urlPath}?obj=29wx/wxxd_{urlPath}"; + } + + + /// + /// 创建并执行服务端请求,默认使用当前用户签名。 + /// + protected HttpResult Request(string url, string postdata = "", MethodType method = MethodType.Post, bool isUserSign = true) + { + ServerHttpItem item = new ServerHttpItem + { + URL = url, + Method = method, + Postdata = postdata ?? string.Empty + }; + + if (isUserSign) + { + item.SignUser(); + } + + return ServerRequest(item); + } + + /// + /// 服务器请求。 + /// + public HttpResult ServerRequest(ServerHttpItem item) + { + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + +#if RELEASE + item.WebProxy = null; +#endif + if (!item.IsSign) + { + if (item.Shop != null) + { + item.SignUser(item.Shop.Id, item.Shop.Name); + } + else + { + item.Sign(); + } + } + + return Send(item); + } + + + /// + /// 执行底层 HTTP 请求。 + /// + protected HttpResult Send(ServerHttpItem item) + { + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + if (string.IsNullOrWhiteSpace(item.URL)) + { + throw new ArgumentException("请求地址不能为空。", nameof(item)); + } + + if (!item.URL.StartsWith("http", StringComparison.OrdinalIgnoreCase)) + { + item.URL = _config.Host + "/" + item.URL.TrimStart('/'); + } + + item.Encoding = Encoding.UTF8; + HttpResult result = HttpHelper.Request(item); + if (result.StatusCode == HttpStatusCode.OK && !string.IsNullOrEmpty(result.Html) && result.Html[0] == 65279) + { + result.Html = result.Html.Remove(0, 1); + } + + return result; + } + } +} \ No newline at end of file diff --git a/Store.Framework/HttpExtension/ServerHttpItem.cs b/Store.Framework/HttpExtension/ServerHttpItem.cs new file mode 100644 index 0000000..6f4f06e --- /dev/null +++ b/Store.Framework/HttpExtension/ServerHttpItem.cs @@ -0,0 +1,28 @@ + +using MAX.Core.Helpers; +using Store.Framework.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Client.HttpExtension +{ + public class ServerHttpItem : HttpItem + { + public ShopBase Shop { get; private set; } + public ServerHttpItem() + { + + } + public ServerHttpItem(ShopBase shop) + { + Shop = shop; + } + /// + /// 是否已签名 + /// + public bool IsSign { get; set; } + } +} diff --git a/Store.Framework/HttpExtension/ServiceRequestBase.cs b/Store.Framework/HttpExtension/ServiceRequestBase.cs new file mode 100644 index 0000000..08f38ac --- /dev/null +++ b/Store.Framework/HttpExtension/ServiceRequestBase.cs @@ -0,0 +1,105 @@ +//using MAX.Core.Autofac; +//using MAX.Core.Helpers; +//using System; +//using System.Net; +//using System.Text; + +//namespace Store.Client.HttpExtension +//{ +// /// +// /// 服务请求公共基类。 +// /// 该基类只负责按 Key 获取服务配置以及执行底层 HTTP 请求,不绑定具体业务签名规则。 +// /// +// public abstract class ServiceRequestBase +// { +// /// +// /// 服务配置提供器。由 Autofac 通过属性注入。 +// /// +// [Autowired] +// protected IServiceConfigProvider ServiceConfigProvider { get; set; } + +// /// +// /// 当前请求类使用的服务配置 Key。 +// /// 项目 API 可以重写该属性以使用自己的服务地址。 +// /// +// protected virtual string ServiceConfigKey => ServiceConfigKeys.Default; + +// /// +// /// 获取当前服务配置。 +// /// +// protected IApiServerConfig GetServiceConfig() +// { +// return GetServiceConfig(ServiceConfigKey); +// } + +// /// +// /// 获取指定 Key 的服务配置。 +// /// +// protected IApiServerConfig GetServiceConfig(string serviceConfigKey) +// { +// if (ServiceConfigProvider == null) +// { +// throw new InvalidOperationException("IServiceConfigProvider 未注入,请通过 DI 容器创建请求服务。"); +// } + +// return ServiceConfigProvider.Get(serviceConfigKey); +// } + +// /// +// /// 使用当前 Key 构建服务请求地址。 +// /// +// protected string BuildServiceUrl(string urlPath) +// { +// return BuildServiceUrl(ServiceConfigKey, urlPath); +// } + +// /// +// /// 使用指定 Key 构建服务请求地址。 +// /// +// protected string BuildServiceUrl(string serviceConfigKey, string urlPath) +// { +// IApiServerConfig config = GetServiceConfig(serviceConfigKey); +// return config.Host + "?" + config.ObjUrl + (urlPath ?? string.Empty); +// } + +// /// +// /// 获取指定 Key 的基础地址。 +// /// +// protected string GetServiceHost(string serviceConfigKey = null) +// { +// IApiServerConfig config = string.IsNullOrWhiteSpace(serviceConfigKey) +// ? GetServiceConfig() +// : GetServiceConfig(serviceConfigKey); +// return config.Host; +// } + +// /// +// /// 执行底层 HTTP 请求。 +// /// +// protected HttpResult Send(ServerHttpItem item) +// { +// if (item == null) +// { +// throw new ArgumentNullException(nameof(item)); +// } +// if (string.IsNullOrWhiteSpace(item.URL)) +// { +// throw new ArgumentException("请求地址不能为空。", nameof(item)); +// } + +// if (!item.URL.StartsWith("http", StringComparison.OrdinalIgnoreCase)) +// { +// item.URL = GetServiceConfig().Host + "/" + item.URL.TrimStart('/'); +// } + +// item.Encoding = Encoding.UTF8; +// HttpResult result = HttpHelper.Request(item); +// if (result.StatusCode == HttpStatusCode.OK && !string.IsNullOrEmpty(result.Html) && result.Html[0] == 65279) +// { +// result.Html = result.Html.Remove(0, 1); +// } + +// return result; +// } +// } +//} \ No newline at end of file diff --git a/Store.Framework/HttpResultParser/HttpException.cs b/Store.Framework/HttpResultParser/HttpException.cs new file mode 100644 index 0000000..d99d45a --- /dev/null +++ b/Store.Framework/HttpResultParser/HttpException.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Framework.HttpResultParser +{ + /// + /// Http异常 + /// + public class HttpException : Exception + { + /// + /// Cookie是否在线 + /// + public bool IsOnline { get; set; } = true; + public HttpException(string message) : base(message) + { + + } + public HttpException(string message, Exception innerException) : base(message, innerException) + { + + } + } +} diff --git a/Store.Framework/HttpResultParser/HttpResultVerify.cs b/Store.Framework/HttpResultParser/HttpResultVerify.cs new file mode 100644 index 0000000..e04261d --- /dev/null +++ b/Store.Framework/HttpResultParser/HttpResultVerify.cs @@ -0,0 +1,150 @@ +using MAX.Core.Helpers; +using Polly; +using Polly.Retry; +using System; +using System.Collections.Generic; +using System.Net; + +namespace Store.Framework.HttpResultParser +{ + public class HttpResultVerify + { + /// + /// 重新尝试请求 + /// + /// 请求方法 + /// 重试次数 + /// 间隔时间 + /// + public static HttpResultVerify TryVerify(Func action, int retryCount = 3, int sleepTime = 1000) + { + return new HttpResultVerify(action, retryCount, sleepTime); + } + private int retryCount = 3; + private int sleepTime = 1000; + private readonly Func _action; + private RetryPolicy retryPolicy; + private List _listUse = new List(); + private List> _addRetTryList = new List>(); + public HttpResultVerify(Func action) + { + _action = action; + retryPolicy = TryHttpExec(_action); + } + public HttpResultVerify(Func action, int RetryCount) : this(action) + { + retryCount = RetryCount; + } + public HttpResultVerify(Func action, int RetryCount, int SleepTime) : this(action, RetryCount) + { + sleepTime = SleepTime; + } + /// + /// 添加检测Http返回 + /// + /// + public void UseCheckResult() where T : IHttpResultVerify, new() + { + _listUse.Add(new T()); + } + /// + /// 添加检测Http返回 + /// + /// + public void UseCheckResult(T checkResult) where T : IHttpResultVerify + { + _listUse.Add(checkResult); + } + /// + /// 添加 是否再次尝试 true 再次尝试 false 不再尝试 + /// + /// + /// + public HttpResultVerify AddRetTry(Func func) + { + _addRetTryList.Add(func); + return this; + } + + /// + /// 执行 + /// + /// + public HttpResult Exec() + { + return retryPolicy.Execute(() => + { + var result = _action(); + foreach (var item in _listUse) + { + result = item.CheckResult(result); + } + return result; + }); + } + + + + + private bool RetTry(HttpResult result) + { + //返回结果状态判断 再次执行 + var ret = false; + ret = result.StatusCode != HttpStatusCode.OK; + foreach (var item in _listUse) + { + ret = item.RetTry(result); + if (ret) + { + break; + } + } + if (!ret) + { + foreach (var item in _addRetTryList) + { + ret = item(result); + if (ret) + { + break; + } + } + } + return ret; + } + /// + /// 尝试执行 + /// + /// + /// + private RetryPolicy TryHttpExec(Func action, int sleepTime = 1000) + { + if (retryCount == 0) + { + retryCount = 3; + } + var retryPolicy = Policy + .Handle(a => + { + if (a.Message == "登录失效") + { + return false; + } + return true; + }) + .OrResult(RetTry) + .WaitAndRetry(retryCount, retryAttempt => + { + return TimeSpan.FromMilliseconds(sleepTime); + } + , (ex, timespan, retryindex, context) => + { + Console.WriteLine($"开始第 {retryindex} 次重试:"); + }); + return retryPolicy; + } + + } + + +} diff --git a/Store.Framework/HttpResultParser/IHttpResultVerify.cs b/Store.Framework/HttpResultParser/IHttpResultVerify.cs new file mode 100644 index 0000000..e23551b --- /dev/null +++ b/Store.Framework/HttpResultParser/IHttpResultVerify.cs @@ -0,0 +1,19 @@ +using MAX.Core.Helpers; + +namespace Store.Framework.HttpResultParser +{ + public interface IHttpResultVerify + { + /// + /// 重新尝试条件 true 再次尝试 false 不重新尝试 + /// + /// + /// + bool RetTry(HttpResult result); + /// + /// 检测请求结果 + /// + /// + HttpResult CheckResult(HttpResult result); + } +} diff --git a/Store.Framework/Inerterfaces/ICurrentUser.cs b/Store.Framework/Inerterfaces/ICurrentUser.cs new file mode 100644 index 0000000..89a3c01 --- /dev/null +++ b/Store.Framework/Inerterfaces/ICurrentUser.cs @@ -0,0 +1,20 @@ +using Store.Framework.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Framework.Inerterfaces; + +public interface ICurrentUser +{ + string UserName { get; } + int GroupId { get; } + bool IsAuthorized(string code); + /// + /// 是否超级管理员 + /// + bool IsSuperAdmin { get; } + UserInfo User { get; } +} diff --git a/Store.Framework/Models/Cookies.cs b/Store.Framework/Models/Cookies.cs new file mode 100644 index 0000000..e3e3738 --- /dev/null +++ b/Store.Framework/Models/Cookies.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Store.Framework.Models +{ + public class Cookies : Dictionary + { + private readonly string oldCookie; + public Cookies(string cookie) + { + oldCookie = cookie; + SplitCookie(oldCookie); + } + + /// + /// Cookie字符串 + /// + public string CookieString + { + get + { + return GetCookieString(); + } + } + + /// + /// 拆分Cookie + /// + private void SplitCookie(string cookie) + { + this.Clear(); + Regex regCookie = new Regex("(?.+?)=(?.+?)(;|$)"); + if (regCookie.IsMatch(cookie)) + { + MatchCollection matchs = regCookie.Matches(cookie); + foreach (Match item in matchs) + { + string key = item.Groups["key"].Value.Trim(); + string value = item.Groups["value"].Value.Trim(); + if (!ContainsKey(key)) + { + this.Add(key.Trim(), value.Trim()); + } + } + } + } + + /// + /// 获取指定值 + /// + /// + /// 默认值 + /// + public string GetValue(string key, string defvalue) + { + if (ContainsKey(key)) + { + return this[key]; + } + return defvalue; + } + /// + /// 添加或修改 + /// + /// + /// + public void AddOrUpdate(string key, string value) + { + if (!this.ContainsKey(key)) + { + Add(key, value); + } + else + { + this[key] = value; + } + } + /// + /// 获取Cookies字符串 + /// + /// + public virtual string GetCookieString() + { + var list = this.Select(a => { return $"{a.Key}={a.Value}"; }); + return string.Join(";", list); + } + + /// + /// 隐式转换 + /// + /// + public static implicit operator string (Cookies cookie) + { + return cookie.GetCookieString(); + } + + } +} diff --git a/Store.Framework/Models/Product/CategoryModel.cs b/Store.Framework/Models/Product/CategoryModel.cs new file mode 100644 index 0000000..e886ab3 --- /dev/null +++ b/Store.Framework/Models/Product/CategoryModel.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Framework.Models.Product; + +/// +/// 商品类目 +/// +public class CategoryModel +{ + public int Id { get; set; } + public string Name { get; set; } + public int ParentId { get; set; } + public int MainCatId { get; set; } + + public List Childs { get; set; } + public CategoryModel() + { + Childs = new List(); + } +} diff --git a/Store.Framework/Models/ShopBase.cs b/Store.Framework/Models/ShopBase.cs new file mode 100644 index 0000000..9223e76 --- /dev/null +++ b/Store.Framework/Models/ShopBase.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Framework.Models +{ + /// + /// 商铺信息 + /// + public class ShopBase + { + public string Id { get; set; } + public string Name { get; set; } + public string Password { get; set; } + /// + /// Cookie信息 + public Cookies Cookie { get; set; } + /// + /// + /// 分组名称 + /// + public string GroupName { get; set; } + + /// + /// 商铺提示信息 + /// + public string Msg { get; set; } + } + /// + /// 商铺信息 + /// + /// 状态码 + public class ShopBase : ShopBase + { + /// + /// 状态 + /// + public TStatus Status { get; set; } + + } +} diff --git a/Store.Framework/Models/UserInfo.cs b/Store.Framework/Models/UserInfo.cs new file mode 100644 index 0000000..699ed08 --- /dev/null +++ b/Store.Framework/Models/UserInfo.cs @@ -0,0 +1,162 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Framework.Models +{ + public class UserInfo + { + [JsonProperty("id")] + public int UserId { get; set; } + + /// + /// 账号 + /// + [JsonProperty("user_name")] + public string UserName { get; set; } + + /// + /// 密码 + /// + [JsonProperty("password")] + public string Password { get; set; } + + /// + /// 客服-4;采购-9;上货-10;数据-11;登录-8 + /// + [JsonProperty("group_id")] + public int GroupId { get; set; } + + /// + /// 注册用户Key + /// + [JsonProperty("user_key")] + public string UserKey { get; set; } + + /// + /// 所在企业ID + /// + [JsonProperty("company_Id")] + public string CompanyId { get; set; } + + /// + /// 企业名称 + /// + [JsonProperty("company_name")] + public string CompanyName { get; set; } + + [JsonProperty("nick_name")] + public string NickName { get; set; } + + [JsonProperty("parent_user_name")] + public string ParentUserName { get; set; } + + [JsonProperty("sa_level")] + public int SaLevel { get; set; } + + [JsonProperty("authority")] + public string UserAuthority { get; set; } + + [JsonProperty("parent_authority")] + public string ParentAuthority { get; set; } + + [JsonProperty("forbidden_page")] + public string ForbiddenPage { get; set; } + + [JsonProperty("mobile")] + public string Mobile { get; set; } + + [JsonProperty("version")] + public int UserVersion { get; set; } + + [JsonProperty("version_s")] + public string UserVersionString { get; set; } + + [JsonProperty("authorized_amount")] + public decimal? AuthorizedAmount { get; set; } + + [JsonProperty("points")] + public int Points { get; set; } + + [JsonProperty("shop_points_per_month")] + public int ShopPointsPerMonth { get; set; } + + [JsonProperty("balance")] + public decimal Balance { get; set; } + + [JsonProperty("recharge_amount_total")] + public decimal RechargeAmountTotal { get; set; } + + [JsonProperty("postage_per_order")] + public decimal? PostagePerOrder { get; set; } + + [JsonProperty("refund_postage_per_order")] + public decimal? RefundPostagePerOrder { get; set; } + + [JsonProperty("gift_oem_amount_per_order")] + public decimal GiftOemAmountPerOrder { get; set; } + + [JsonProperty("oem_id")] + public string OemId { get; set; } + + [JsonProperty("oem_name")] + public string OemName { get; set; } + + [JsonProperty("is_oem_owner")] + public bool IsOemOwner { get; set; } + + [JsonProperty("end")] + public string EndDate { get; set; } + + [JsonProperty("qq_group_num")] + public string QqGroupNum { get; set; } + + [JsonProperty("qq_group_url")] + public string QqGroupUrl { get; set; } + + [JsonProperty("wechat_url")] + public string WechatUrl { get; set; } + + [JsonProperty("pinduoduo")] + public string Pinduoduo { get; set; } + + [JsonProperty("alimama_domain")] + public string AlimamaDomain { get; set; } + + [JsonProperty("alimama_pid")] + public string AlimamaPid { get; set; } + + [JsonProperty("is_lock")] + public int LockUserName { get; set; } + + [JsonProperty("is_weak_password")] + public int WeakPassword { get; set; } + + [JsonProperty("message")] + public string MessageInfo { get; set; } + + [JsonProperty("agent_auth")] + public string AgentAuth { get; set; } + + [JsonProperty("is_import")] + public bool IsImport { get; set; } + + [JsonProperty("card_id")] + public string CardId { get; set; } + + [JsonProperty("is_sa")] + public bool IsSa { get; set; } + + [JsonProperty("add_time")] + public DateTime AddTime { get; set; } + + [JsonProperty("mdy_time")] + public DateTime MdyTime { get; set; } + + [JsonProperty("notes")] + public string Notes { get; set; } + } +} diff --git a/Store.Framework/Services/CacheServerService.cs b/Store.Framework/Services/CacheServerService.cs new file mode 100644 index 0000000..31fa4c2 --- /dev/null +++ b/Store.Framework/Services/CacheServerService.cs @@ -0,0 +1,47 @@ +using MAX.Core.DependencyInjection; +using Newtonsoft.Json.Linq; +using NLog; +using Store.Client.Apis; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Store.Client.Services +{ + /// + /// 缓存服务集群负载 + /// + public class CacheServerService : ITransientDependency + { + private readonly CommonApi _commonApi; + private readonly ILogger _logger; + private readonly CommonApi _commonApi1; + + /// + /// 缓存服务集群负载占比 + /// + private static Dictionary _cacheServerRatio { get; set; } = new Dictionary(); + + public CacheServerService(ILogger logger, CommonApi commonApi) + { + _commonApi = IocManager.Resolve();// commonApi; + _logger = logger; + _commonApi1 = commonApi; + } + + + /// + /// 默认数据 + /// + private void SetDefaultData() + { + _cacheServerRatio.Add("https://temp-new1.taojinhu.com:10019", 5); + _cacheServerRatio.Add("https://t3.1n11.com", 5); + } + + + } +} diff --git a/Store.Framework/Services/CommonService.cs b/Store.Framework/Services/CommonService.cs new file mode 100644 index 0000000..54aeecf --- /dev/null +++ b/Store.Framework/Services/CommonService.cs @@ -0,0 +1,83 @@ +using MAX.Core.DependencyInjection; +using MAX.Core.Helpers; +using Store.Client; +using Store.Client.Apis; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Store.Client.Services +{ + internal class CommonService : ITransientDependency + { + private readonly CommonApi _commonApi; + public CommonService(CommonApi commonApi) + { + _commonApi = commonApi; + } + + + /// + /// 获取签名配置 + /// + public void LoadAuthConfig() + { + var value = _commonApi.GetAuthConfig(); + if (!string.IsNullOrEmpty(value)) + { + ServerConfig.IsNewSignatureAuth = value.Equals("1"); + } + } + /// + /// 获取本地与服务器时间差 + /// + public void LoadServerTimeSpan() + { + DateTime SDateTime = _commonApi.GetServerTime(); + long s = DateHelper.GetUnixTimestampInSeconds(SDateTime); + long a = DateHelper.GetUnixTimestampInSeconds(DateTime.Now); + ServerConfig.TimeDifferent = s - a; + } + /// + /// 获取服务器时间戳 + /// + /// + public static long GetServerUnix() + { + return DateHelper.GetUnixTimestampInSeconds(DateTime.Now) + ServerConfig.TimeDifferent; + } + /// + /// 获取服务器时间 + /// + /// + public static DateTime GetServerDataTime() + { + return DateHelper.ConvertFromUnixTimestampInSeconds(GetServerUnix()); + } + + + ///// + ///// 获取服务器图片缓存 + ///// + ///// + ///// + //public string GetImageCache(string imgUrl) + //{ + // string imgurl = ""; + // HttpResult result = _commonApi.GetImageCache(imgUrl); + // if (result.StatusCode == System.Net.HttpStatusCode.OK) + // { + // result.Html = result.Html.Trim(); + // if (Regex.IsMatch(result.Html, "^http")) + // { + // imgurl = result.Html; + + // } + // } + // return imgurl; + //} + } +} diff --git a/Store.Framework/Services/CurrentUserService.cs b/Store.Framework/Services/CurrentUserService.cs new file mode 100644 index 0000000..e242caa --- /dev/null +++ b/Store.Framework/Services/CurrentUserService.cs @@ -0,0 +1,37 @@ +using MAX.Core.DependencyInjection; +using MediatR; +using Store.Framework.Inerterfaces; +using Store.Framework.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Framework.Services; + +public record UserNotification(UserInfo user) : INotification; +/// +/// 当前用户 +/// +internal class CurrentUserService : ICurrentUser, ISingletonDependency, INotificationHandler +{ + public string UserName => User?.UserName; + + public int GroupId => User.GroupId; + + public UserInfo User { get; private set; } + + public bool IsSuperAdmin => User.IsSa; + + public bool IsAuthorized(string code) + { + return User.UserAuthority.Contains(code); + } + + Task INotificationHandler.Handle(UserNotification notification, CancellationToken cancellationToken) + { + User = notification.user; + return Task.CompletedTask; + } +} diff --git a/Store.Framework/Services/ShopService.cs b/Store.Framework/Services/ShopService.cs new file mode 100644 index 0000000..2091d42 --- /dev/null +++ b/Store.Framework/Services/ShopService.cs @@ -0,0 +1,134 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +//using FkServices = LittleCow.Framework.Services; + +namespace Store.Client.Services +{ + ///// + ///// 商铺服务 + ///// + //internal class ShopService : ITransientDependency + //{ + // private readonly ShopApi _shopApi; + // private readonly IEventBus _eventBus; + + // public ShopService(ShopApi shopApi, IEventBus eventBus) + // { + // _shopApi = shopApi; + // _eventBus = eventBus; + // } + + + // /// + // /// 获取所有商铺信息 + // /// + // /// + // /// + // public List GetShops() where T : ShopBase + // { + // HttpResult result = _shopApi.GetShops(); + // if (result.StatusCode == System.Net.HttpStatusCode.OK) + // { + // StringBuilder sb = new StringBuilder(); + // sb.Append("["); + // Regex regShopList = new Regex("]+?)>"); + // if (regShopList.IsMatch(result.Html)) + // { + // int index = 0; + // MatchCollection matchs = regShopList.Matches(result.Html); + // foreach (Match item in matchs) + // { + // string value = item.Value; + // value = value.Replace("", "\"}"); + // value = value.Replace("':'", "\":\""); + // value = value.Replace("' '", "\",\""); + // if (index > 0) + // { + // sb.Append(","); + // } + // sb.Append(value); + // index++; + + // } + // sb.Append("]"); + // List list = JsonConvert.DeserializeObject>(sb.ToString()); + // list.RemoveAll(a => string.IsNullOrEmpty(a.Name)); + // return list; + // } + // } + // return new List(); + // } + + + // public string GetShopCookie(List shops) + // { + // HttpResult result = _shopApi.GetShopsCookie(shops); + // if (result.StatusCode == System.Net.HttpStatusCode.OK) + // { + // MatchCollection matchs = Regex.Matches(result.Html, @"([\w\W]+?)"); + // if (matchs != null) + // { + // foreach (Match item in matchs) + // { + // string shopId = item.Groups[1].Value; + // string json = item.Groups[2].Value; + // var rootToken = JToken.Parse(json); + // string cookie = rootToken["Cookies"]?.ToString() ?? ""; + // var shop = shops.FirstOrDefault(a => a.Id == shopId); + // if (shop != null) + // { + // shop.Cookie = new Cookies(cookie); + // } + // } + // } + // } + // return null; + // } + + + // /// + // /// 检测商铺登录状态 + // /// + // public void CheckShopStatus(ShopBase shop) + // { + // HttpItem item = new HttpItem(); + // item.URL = "https://myseller.taobao.com/ajaxProxy.do?_ksTS=1548152154745_13&callback=seller_layout_head&action=FrameworkLayoutAction&event_submit_do_layout_data=true"; + // item.Referer = "https://myseller.taobao.com"; + // item.Method = MethodType.Post; + // item.Cookie = shop.Cookie.GetCookieString(); + // HttpResult result = HttpHelper.Request(item); + // if (result.StatusCode == System.Net.HttpStatusCode.OK) + // { + // string a = result.Html.Contains("\"shopName\":\"") ? "登录成功" : "会话已过期"; + // } + // //string urlx = + // //string refere = + // //string htmlx = GetPostTaobaoHtml.GetUrl(urlx, refere, true, "", Shop.Cookies); + // //Shop.MsgForCookiesLogin = htmlx.Contains("\"shopName\":\"") ? "登录成功" : "会话已过期"; + // //GetTaobaoShops.ShopsTemp.Add(Shop); + // } + // /// + // /// 获取值 + // /// + // /// + // /// + // /// + // private string GetUserValue(string html, string reg) + // { + // string temp = Regex.Match(html, @"[""]" + reg + @"[""][\s]*:[\s]*[""]([^""]*?)[""]")?.Value; + // if (string.IsNullOrEmpty(temp)) + // temp = Regex.Match(html, @"[']" + reg + @"['][\s]*:[\s]*[']([^']*?)[']")?.Groups[1].Value; + // if (string.IsNullOrEmpty(temp)) + // temp = Regex.Match(html, @"['""]" + reg + @"['""][\s]*:[\s]*([\d]+)")?.Value; + + // return temp; + // } + //} +} diff --git a/Store.Framework/Services/UserService.cs b/Store.Framework/Services/UserService.cs new file mode 100644 index 0000000..891daa9 --- /dev/null +++ b/Store.Framework/Services/UserService.cs @@ -0,0 +1,132 @@ +using MAX.Core.Commons; +using MAX.Core.Cryptography; +using MAX.Core.DependencyInjection; +using MAX.Core.Helpers; +using Newtonsoft.Json; +using Store.Client.Apis; +using Store.Framework.Apis; +using Store.Framework.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Client.Services +{ + /// + /// 用户操作 + /// + public class UserService : ITransientDependency + { + private readonly UserApi _userApi; + + public UserService(UserApi userApi) + { + _userApi = userApi; + } + + /// + /// 用户登录 + /// + /// + public Result Login(UserInfo user) + { + Result ret = new Result(); + user.Password = MD5Helper.Md5_Lower32(user.Password); + HttpResult result = _userApi.Login(user.UserName, user.Password); + + if (result.StatusCode == HttpStatusCode.Unauthorized) + { + ret.Msg = "登录失败\r\n链接异常请重试"; + return ret; + } + + if (string.IsNullOrEmpty(result.Html)) + { + ret.Msg = "链接服务器失败"; + return ret; + } + + ApiResponse apiResponse = null; + try + { + apiResponse = JsonConvert.DeserializeObject>(result.Html); + } + catch (JsonException) + { + } + + if (apiResponse == null) + { + ret.Msg = result.Html; + return ret; + } + + ret.Code = apiResponse.Code; + if (apiResponse.Code != 200 || apiResponse.Data == null) + { + ret.Msg = string.IsNullOrEmpty(apiResponse.Msg) ? result.Html : apiResponse.Msg; + return ret; + } + + FillUserInfo(user, apiResponse.Data); + ret.Success = true; + ret.Msg = apiResponse.Msg; + ret.Data = result; + return ret; + } + + /// + /// 将接口返回的登录用户信息回填到调用方对象。 + /// + private static void FillUserInfo(UserInfo target, UserInfo source) + { + target.UserId = source.UserId; + target.UserName = source.UserName; + target.Password = source.Password; + target.GroupId = source.GroupId; + target.UserKey = source.UserKey; + target.CompanyId = source.CompanyId; + target.CompanyName = source.CompanyName; + target.NickName = source.NickName; + target.ParentUserName = source.ParentUserName; + target.SaLevel = source.SaLevel; + target.UserAuthority = source.UserAuthority; + target.ParentAuthority = source.ParentAuthority; + target.ForbiddenPage = source.ForbiddenPage; + target.Mobile = source.Mobile; + target.UserVersion = source.UserVersion; + target.UserVersionString = source.UserVersionString; + target.AuthorizedAmount = source.AuthorizedAmount; + target.Points = source.Points; + target.ShopPointsPerMonth = source.ShopPointsPerMonth; + target.Balance = source.Balance; + target.RechargeAmountTotal = source.RechargeAmountTotal; + target.PostagePerOrder = source.PostagePerOrder; + target.RefundPostagePerOrder = source.RefundPostagePerOrder; + target.GiftOemAmountPerOrder = source.GiftOemAmountPerOrder; + target.OemId = source.OemId; + target.OemName = source.OemName; + target.IsOemOwner = source.IsOemOwner; + target.EndDate = source.EndDate; + target.QqGroupNum = source.QqGroupNum; + target.QqGroupUrl = source.QqGroupUrl; + target.WechatUrl = source.WechatUrl; + target.Pinduoduo = source.Pinduoduo; + target.AlimamaDomain = source.AlimamaDomain; + target.AlimamaPid = source.AlimamaPid; + target.LockUserName = source.LockUserName; + target.WeakPassword = source.WeakPassword; + target.MessageInfo = source.MessageInfo; + target.AgentAuth = source.AgentAuth; + target.IsImport = source.IsImport; + target.CardId = source.CardId; + target.IsSa = source.IsSa; + target.AddTime = source.AddTime; + target.MdyTime = source.MdyTime; + target.Notes = source.Notes; + } + } +} diff --git a/Store.Framework/Startup.cs b/Store.Framework/Startup.cs new file mode 100644 index 0000000..65a440c --- /dev/null +++ b/Store.Framework/Startup.cs @@ -0,0 +1,25 @@ +using Autofac; +using MAX.Core; +using MAX.Core.Infrastructure; +using Store.Client; +using Store.Framework.Tools; + +namespace Store.Framework +{ + public class Startup : IStartup + { + public int Priority => IntPriority.Normal; + + public void Initialize() + { + HardwareTool.GetMacCode(); + } + + public void Register(ContainerBuilder builder) + { + //builder.RegisterType() + // .As() + // .SingleInstance(); + } + } +} \ No newline at end of file diff --git a/Store.Framework/Store.Framework.csproj b/Store.Framework/Store.Framework.csproj new file mode 100644 index 0000000..7c7a22d --- /dev/null +++ b/Store.Framework/Store.Framework.csproj @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Store.Framework/ThreadManage/ThreadManager.cs b/Store.Framework/ThreadManage/ThreadManager.cs new file mode 100644 index 0000000..87923dd --- /dev/null +++ b/Store.Framework/ThreadManage/ThreadManager.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Store.Framework.ThreadManage +{ + /// + /// Ï̹߳ÜÀí + /// + public class ThreadManager + { + //private int _sleep; + private Thread _listen; + private List _actions = new List(); + private CancellationTokenSource cancellationToken = new CancellationTokenSource(); + private int _countThreads = 10; + /// + /// ¼ä¸ôÃëÊý + /// + public int Interval { get; set; } = 1; + /// + /// Ï߳ÌÊý + /// + public int CountThreads + { + get => _countThreads; + set + { + _countThreads = value; + ThreadPool.SetMinThreads(_countThreads, _countThreads); + ThreadPool.SetMaxThreads(_countThreads, _countThreads); + } + } + /// + /// ÊǷñÍê³É״̬ + /// + public bool Completed { get; private set; } + /// + /// ÕýÔÚÔËÐеÄÏ߳ÌÊýÁ¿ + /// + public int CountActiveThreads + { + get + { + return -1; + } + } + + /// + /// ʵÀý ĬÈÏÏ߳Ì10 ¼ä¸ô 1Ãë + /// + public ThreadManager() + { + + } + /// + /// + /// + /// Ï߳ÌÊý + public ThreadManager(int countThreads) + { + CountThreads = countThreads; + } + /// + /// + /// + /// Ï߳ÌÊý + /// ¼ä¸ôʱ¼ä + public ThreadManager(int countThreads, int interval) + { + CountThreads = countThreads; + Interval = interval; + } + + private void Listen() + { + Completed = false; + cancellationToken = new CancellationTokenSource(); + _listen = new Thread(() => + { + while (!cancellationToken.IsCancellationRequested && _actions.Count > 0) + { + DateTime time = DateTime.Now; + int countCompeleted = 0; + for (int i = 0; i < _actions.Count; i++) + { + var action = _actions[i]; + if (action.Completed) countCompeleted++; + if (!action.Completed && !action.IsWork && CountThreads > CountActiveThreads) + //if (((time >= action.EndTime && action.Loop) || (!action.Completed && !action.Loop)) && !action.IsWork && CountThreads > CountActiveThreads) + { + action.IsWork = true; + ThreadPool.QueueUserWorkItem(o => + { + WorkUnit unit = (WorkUnit)o; + unit.Action.Invoke(unit); + unit.IsWork = false; + unit.Completed = true; //!unit.Loop; + }, action); + } + //int subTime = (int)(action.EndTime - time).TotalMilliseconds; + //if (subTime < _sleep) + //{ + // if (subTime < 0) + // { + // _sleep = 0; + // } + // else + // { + // _sleep = subTime; + // } + //} + } + Completed = countCompeleted == _actions.Count; + //Thread.Sleep(_sleep); + //_sleep = int.MaxValue; + Thread.Sleep(1000 * Interval); + } + }); + _listen.IsBackground = true; + _listen.Start(); + } + + /// + /// Clear all tasks. + /// + public void Clear() => _actions.Clear(); + /// + /// Start Thread Manager. + /// + public async void Start() + { + await Task.Run(() => + { + //Console.WriteLine("123"); + Listen(); + } + + ); + } + /// + /// Stop Thread Manager. + /// + public void Stop() + { + Completed = true; + if (cancellationToken != null && !cancellationToken.IsCancellationRequested) + { + cancellationToken.Cancel(); + cancellationToken.Dispose(); + } + if (_listen != null) + { + _listen.Abort(); + } + } + /// + /// Waiting for work to finish + /// + public void Wait() + { + while (!Completed) { Thread.Sleep(100); } + } + + /// + /// Ìí¼ÓÈÎÎñ + /// + /// ίÍÐÈÎÎñ + /// Repeat the task. + public void AddTask(WorkAction action, object arg = null) + { + WorkUnit unit = new WorkUnit(); + unit.Action = action; + unit.Completed = false; + unit.Argument = arg; + _actions.Add(unit); + } + + public void AddTask(Action action) + { + WorkUnit unit = new WorkUnit(); + unit.Action = a => action.Invoke(); + unit.Completed = false; + _actions.Add(unit); + } + } +} \ No newline at end of file diff --git a/Store.Framework/ThreadManage/ThreadManagerPool.cs b/Store.Framework/ThreadManage/ThreadManagerPool.cs new file mode 100644 index 0000000..52bbd54 --- /dev/null +++ b/Store.Framework/ThreadManage/ThreadManagerPool.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Framework.ThreadManage +{ + public static class ThreadManagerPool + { + private static ThreadManager _manager; + + public static int CountThread { get; set; } + public static int CountActiveThread { get => _manager.CountActiveThreads; } + + static ThreadManagerPool() + { + _manager = new ThreadManager(CountThread); + _manager.Start(); + } + + public static void AddTask(WorkAction action) + { + _manager.AddTask(action, false); + } + } + +} \ No newline at end of file diff --git a/Store.Framework/ThreadManage/WorkAction.cs b/Store.Framework/ThreadManage/WorkAction.cs new file mode 100644 index 0000000..08bbdb9 --- /dev/null +++ b/Store.Framework/ThreadManage/WorkAction.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Framework.ThreadManage +{ + /// + /// 工作委托 + /// + /// + public delegate void WorkAction(WorkUnit unit); +} diff --git a/Store.Framework/ThreadManage/WorkUnit.cs b/Store.Framework/ThreadManage/WorkUnit.cs new file mode 100644 index 0000000..b866547 --- /dev/null +++ b/Store.Framework/ThreadManage/WorkUnit.cs @@ -0,0 +1,29 @@ +using System; +/// +/// Represents an instance of a task. +/// +namespace Store.Framework.ThreadManage +{ + /// + /// ¹¤×÷µ¥Ïî + /// + public class WorkUnit + { + internal bool IsWork { get; set; } = false; + internal DateTime EndTime { get; set; } = DateTime.Now; + internal WorkAction Action { get; set; } + internal bool Completed { get; set; } + internal object Argument { get; set; } + + //public void Wait(int time) + //{ + // EndTime = DateTime.Now.AddMilliseconds(time); + // IsWork = false; + //} + + public T GetArg() + { + return (T)Argument; + } + } +} \ No newline at end of file diff --git a/Store.Framework/Tools/HardwareTool.cs b/Store.Framework/Tools/HardwareTool.cs new file mode 100644 index 0000000..3491b2f --- /dev/null +++ b/Store.Framework/Tools/HardwareTool.cs @@ -0,0 +1,155 @@ +using MAX.Core.Cryptography; +using System; +using System.Management; + +namespace Store.Framework.Tools +{ + /// + /// 系统信息 + /// + public class HardwareTool + { + + /// + /// 机器硬件码 + /// + public static string HardWareCode { get; private set; } = ""; + + /// + /// 机器硬件码新版 + /// + public static string HardWareCodeNew { get; private set; } = ""; + + /// + /// 机器硬件码v3 + /// + public static string HardWareCodeV3 { get; private set; } = ""; + /// + /// 硬件信息 + /// + public static string MacInfo { get; private set; } = ""; + /// + /// 获取机器码 + /// + /// + internal static void GetMacCode() + { + + string sHostName = GethostName(); + string sCpuSn = GetCPUSerialNumber(); + string sDiskSn = GetDiskSerialNumber(); + string sDiskSnNew = GetFirstDiskSnAndSignature(); + string sOs = Environment.OSVersion.VersionString; + + HardWareCode = MD5Helper.Md5_32(sHostName + sCpuSn + sDiskSn + sOs); + HardWareCodeNew = MD5Helper.Md5_32(sCpuSn + sDiskSn); + HardWareCodeV3 = MD5Helper.Md5_32(sCpuSn + sDiskSnNew); + + } + + + /// + /// 取机器名 + /// + /// + private static string GethostName() + { + string sHostName = System.Net.Dns.GetHostName(); + MacInfo += "|HostName:" + sHostName; + return sHostName; + } + + /// + /// 获取cpu序列号 + /// + /// + private static string GetCPUSerialNumber() + { + string cpuSerialNumber = string.Empty; + ManagementClass mc = new ManagementClass("Win32_Processor"); + ManagementObjectCollection moc = mc.GetInstances(); + foreach (ManagementObject mo in moc) + { + cpuSerialNumber = mo["ProcessorId"].ToString(); + MacInfo += "|CPU:" + mo["ProcessorId"].ToString() + "," + mo.Properties["Manufacturer"].Value.ToString().Trim() + "," + mo.Properties["Name"].Value.ToString().Trim(); + break; + } + mc.Dispose(); + moc.Dispose(); + return cpuSerialNumber; + } + + /// + /// 获取硬盘序列号 + /// + /// + private static string GetDiskSerialNumber() + { + ManagementObjectSearcher mos = new ManagementObjectSearcher(); + mos.Query = new SelectQuery("Win32_DiskDrive", "", new string[] { "PNPDeviceID", "Signature" }); + ManagementObjectCollection myCollection = mos.Get(); + ManagementObjectCollection.ManagementObjectEnumerator em = myCollection.GetEnumerator(); + em.MoveNext(); + ManagementBaseObject moo = em.Current; + string id = ""; + if (moo.Properties["signature"].Value != null) + { + id = moo.Properties["signature"].Value.ToString().Trim(); + } + MacInfo += "|DiskSignature:" + id; + return id; + } + + /// + /// 获取硬盘序列号-New + /// + /// + private static string GetFirstDiskSnAndSignature() + { + ManagementObjectSearcher mos = new ManagementObjectSearcher(); + mos.Query = new SelectQuery("Win32_DiskDrive", "Index=0", new string[] { "PNPDeviceID", "SerialNumber", "Signature" }); + ManagementObjectCollection myCollection = mos.Get(); + ManagementObjectCollection.ManagementObjectEnumerator em = myCollection.GetEnumerator(); + em.MoveNext(); + ManagementBaseObject moo = em.Current; + string sn = ""; + string signature = ""; + if (moo.Properties["SerialNumber"].Value != null) + { + sn = moo.Properties["SerialNumber"].Value.ToString().Trim(); + } + if (moo.Properties["Signature"].Value != null) + { + signature = moo.Properties["Signature"].Value.ToString().Trim(); + if (signature.Equals("0")) + { + signature = ""; + } + } + MacInfo += "|Disk:" + sn + "," + signature + "," + moo.Properties["PNPDeviceID"].Value.ToString().Trim(); + return sn + signature; + + + } + + /// + /// 获取网卡硬件地址 + /// + /// + private static string GetMoAddress() + { + string MoAddress = " "; + using (ManagementClass mc = new ManagementClass("Win32_NetworkAdapterConfiguration")) + { + ManagementObjectCollection moc2 = mc.GetInstances(); + foreach (ManagementObject mo in moc2) + { + //if ((bool)mo["IPEnabled"] == true) + MoAddress = mo["MacAddress"].ToString(); + mo.Dispose(); + } + } + return MoAddress.ToString(); + } + } +} diff --git a/Store.Management.Framework/Apis/WXApi.cs b/Store.Management.Framework/Apis/WXApi.cs new file mode 100644 index 0000000..b8e1b9c --- /dev/null +++ b/Store.Management.Framework/Apis/WXApi.cs @@ -0,0 +1,708 @@ +using MAX.Core.DependencyInjection; +using MAX.Core.Helpers; +using Store.Client.HttpExtension; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Framework; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Store.Management.Framework.Apis; + +public class WXApi : ITransientDependency +{ + private const string Host = "https://store.weixin.qq.com"; + private const string WxUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"; + private const string E57UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36"; + private const string JsonAccept = "application/json, text/plain, */*"; + private const string HtmlAccept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"; + private const string JsonContentType = "application/json"; + private const string WxAcceptLanguage = "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6"; + private const string E57AcceptLanguage = "zh-CN,zh;q=0.9"; + + /// + /// 执行底层 HTTP 请求。 + /// + protected HttpResult Send(HttpItem item) + { + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + if (string.IsNullOrWhiteSpace(item.URL)) + { + throw new ArgumentException("请求地址不能为空。", nameof(item)); + } +#if RELEASE + item.WebProxy = null; +#endif + + if (!item.URL.StartsWith("http", StringComparison.OrdinalIgnoreCase)) + { + item.URL = Host + "/" + item.URL.TrimStart('/'); + } + item.Encoding = Encoding.UTF8; + if (item.PostEncoding == null) + { + item.PostEncoding = Encoding.UTF8; + } + HttpResult result = HttpHelper.Request(item); + return result; + } + + public HttpResult WeiXinAssistantPostResponse(string cookie, string url, string data, string referer) + { + HttpItem item = CreateWxItem(cookie, url, referer); + AddCommonWxHeaders(item, string.Empty, GetCookieValue(cookie, "talent_magic")); + PrepareJsonPost(item, data); + return Send(item); + } + + public HttpResult WeiXinPostResponse(string cookie, string url, string data, string referer) + { + HttpItem item = CreateWxItem(cookie, url, referer); + AddCommonWxHeaders(item, GetCookieValue(cookie, "biz_magic"), string.Empty); + PrepareJsonPost(item, data); + return Send(item); + } + + public HttpResult WeiXinTalentOpenKfPostResponse(string cookie, string url, string data, string referer) + { + HttpItem item = CreateWxItem(cookie, url, referer); + AddCommonWxHeaders(item, GetCookieValue(cookie, "biz_magic"), string.Empty); + item.Header["user_scene"] = "1002"; + item.Header["kf_scene"] = "6"; + item.Header["logistics_magic"] = string.Empty; + PrepareJsonPost(item, data); + return Send(item); + } + + public HttpResult WeiXinImgPostResponse(string cookie, string url, string data, string disK, long disT, string ext, string openid, string referer = "https://store.weixin.qq.com/") + { + HttpItem item = CreateWxItem(cookie, url, referer); + item.Header["sec-ch-ua-platform"] = "\"Windows\""; + item.Header["sec-ch-ua"] = "\"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\""; + item.Header["sec-ch-ua-mobile"] = "?0"; + item.Header["X-Requested-With"] = "XMLHttpRequest"; + item.Header["X-Arguments"] = "dis_k=" + disK + "&dis_t=" + disT.ToString(CultureInfo.InvariantCulture) + "&ext=" + ext + "&apptype=160&filetype=20304&openid=" + openid + "&groupid=0&taskid=" + Guid.NewGuid().ToString(); + item.Header["Origin"] = Host; + item.Header["Sec-Fetch-Site"] = "same-site"; + item.Header["Sec-Fetch-Mode"] = "cors"; + item.Header["Sec-Fetch-Dest"] = "empty"; + item.Header["Accept-Encoding"] = "gzip"; + item.Header["Accept-Language"] = "zh-CN,zh;q=0.9"; + item.Method = MethodType.Post; + item.ContentType = JsonContentType; + item.PostDataType = PostDataType.Byte; + item.PostdataByte = System.Convert.FromBase64String(data ?? string.Empty); + return Send(item); + } + + public HttpResult WeiXinUploadResponse(string cookie, string url, string body, string boundaryStr, string referer) + { + HttpItem item = CreateWxItem(GetBizCookies(cookie), url, referer); + AddCommonWxHeaders(item, GetCookieValue(cookie, "biz_magic"), string.Empty, includeMobileHeader: false); + item.ContentType = "multipart/form-data; boundary=" + boundaryStr; + item.Method = MethodType.Post; + item.PostDataType = PostDataType.Byte; + item.PostdataByte = BuildMultipartBody(body); + return Send(item); + } + + public HttpResult WeiXinGetResponse(string cookie, string url, string referer = "", bool isGetDocument = false) + { + string cookies = GetBizCookies(cookie); + HttpItem item = CreateWxItem(cookies, url, isGetDocument ? string.Empty : referer, isGetDocument ? HtmlAccept : JsonAccept); + if (isGetDocument) + { + item.Header["sec-ch-ua"] = "\"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\""; + item.Header["sec-ch-ua-mobile"] = "?0"; + item.Header["sec-ch-ua-platform"] = "\"Windows\""; + item.Header["Upgrade-Insecure-Requests"] = "1"; + item.Header["Sec-Fetch-Site"] = "none"; + item.Header["Sec-Fetch-Mode"] = "navigate"; + item.Header["Sec-Fetch-User"] = "?1"; + item.Header["Sec-Fetch-Dest"] = "document"; + } + else + { + AddCommonWxHeaders(item, GetCookieValue(cookie, "biz_magic"), string.Empty); + } + return Send(item); + } + + public HttpResult kdniaoPostResponse(string url, string data, string referer, Dictionary reqDic, string kdnAuth = null, string kdnAuthKey = "Auth") + { + HttpItem item = CreateWxItem(string.Empty, url, referer, "application/json, text/javascript, */*; q=0.01"); + item.Header["sec-ch-ua-platform"] = "\"Windows\""; + item.Header["sec-ch-ua"] = "\"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\""; + item.Header["sec-ch-ua-mobile"] = "?0"; + item.Header["Origin"] = "https://56.kdniao.com"; + item.Header["Accept-Language"] = "zh-CN,zh;q=0.9"; + item.Header["Sec-Fetch-Site"] = "same-site"; + item.Header["Sec-Fetch-Mode"] = "cors"; + item.Header["Sec-Fetch-Dest"] = "empty"; + if (reqDic != null) + { + foreach (string key in reqDic.Keys) + { + if (item.Header.AllKeys.Contains(key, StringComparer.OrdinalIgnoreCase)) + { + continue; + } + item.Header[key] = reqDic[key]?.ToString() ?? string.Empty; + } + } + item.Header["Accept-Encoding"] = "gzip"; + item.Method = MethodType.Post; + item.ContentType = "application/x-www-form-urlencoded"; + item.Postdata = data ?? string.Empty; + item.WebProxy = null; + return Send(item); + } + + public HttpResult e57PostResponse(string cookie, string url, string data, string referer, string authorization = null) + { + string token = ResolveAuthorization(cookie, authorization); + HttpItem item = CreateE57Item(cookie, token, url, referer); + item.Method = MethodType.Post; + item.ContentType = JsonContentType; + item.Postdata = data ?? string.Empty; + return Send(item); + } + + public HttpResult e57GetResponse(string cookie, string url, string referer) + { + string token = ResolveAuthorization(cookie, null); + HttpItem item = CreateE57Item(cookie, token, url, referer); + return Send(item); + } + + #region WXUpload 业务接口 + + public HttpResult searchProduct(string cookie, string extCode, string extCode2 = "") + { + string url = "https://store.weixin.qq.com/shop-faas/mmchannelstradeproductcore/cgi/goods/searchProduct?" + + "token=&lang=zh_CN&pageSize=20&productStatus[]=-1&productSource=[1,16,32]&searchSource=1&useNew=true&pageNum=1&status[]=5&spuCode=" + extCode; + if (!string.IsNullOrEmpty(extCode2)) + { + url += "," + extCode2; + } + return WeiXinGetResponse(cookie, url, "https://store.weixin.qq.com/shop/goods/list?currentTab=PRODUCT_STATUS_ON"); + } + + public HttpResult searchProductWithSpuId(string cookie, string spuId) + { + string url = "https://store.weixin.qq.com/shop-faas/mmchannelstradeproductcore/cgi/goods/searchProduct?" + + "token=&lang=zh_CN&pageSize=20&productStatus[]=-1&productSource=[1,16,32]&searchSource=1&useNew=true&pageNum=1&status[]=-1&spuId=" + spuId; + return WeiXinGetResponse(cookie, url, "https://store.weixin.qq.com/shop/goods/list"); + } + + public HttpResult searchProductWithTitle(string cookie, string urlEncodeTitle) + { + string url = "https://store.weixin.qq.com/shop-faas/mmchannelstradeproductcore/cgi/goods/searchProduct?" + + "token=&lang=zh_CN&pageSize=20&productStatus[]=-1&productSource=[1,16,32]&searchSource=1&useNew=true&pageNum=1&status[]=-1&title=" + urlEncodeTitle; + return WeiXinGetResponse(cookie, url, "https://store.weixin.qq.com/shop/goods/list"); + } + + public HttpResult getUploadTempImgAuth(string cookie, int uploadScene = 3) + { + string url = "https://store.weixin.qq.com/shop-faas/mmecchannelspicture/cgi/getUploadTempImgAuth?token=&lang=zh_CN&uploadScene=" + uploadScene; + return WeiXinGetResponse(cookie, url, "https://store.weixin.qq.com/shop/goods/entry"); + } + + public HttpResult adtimageupload(string cookie, string openid, string imgBase64, string disK, long disT, string ext) + { + return WeiXinImgPostResponse(cookie, "https://westorealpha.video.qq.com/adtimageupload", imgBase64, disK, disT, ext, openid, "https://store.weixin.qq.com/"); + } + + public HttpResult getProductClassify(string cookie, string goodsName, List goodImgUrlsLst) + { + string boundaryStr = CreateBoundary(); + string url = "https://store.weixin.qq.com/shop-faas/cgi/common/productClassify?from=B&title=" + WebUtility.UrlEncode(goodsName) + "&token=&lang=zh_CN"; + string referer = "https://store.weixin.qq.com/shop/goods/entry"; + string body = "--" + boundaryStr + "\r\n"; + foreach (string imgFullPath in goodImgUrlsLst ?? new List()) + { + string fileName = Path.GetFileName(imgFullPath); + body += "Content-Disposition: form-data; name=\"file\"; filename=\"" + fileName + "\"\r\n" + + "Content-Type: image/jpeg\r\n\r\n" + + "" + imgFullPath + "\r\n--" + boundaryStr; + } + body += "--\r\n"; + return WeiXinUploadResponse(cookie, url, body, boundaryStr, referer); + } + + public HttpResult getCategoryOnShelf(string cookie, List catIdLst, string brandId = "2100000000") + { + string url = "https://store.weixin.qq.com/shop-faas/mmchannelstradecategory/cgi/product/getCategoryOnShelfV2?token=&lang=zh_CN"; + string body = "{\"catIdList\":[" + string.Join(",", catIdLst) + "],\"brandId\":" + brandId + "}"; + return WeiXinPostResponse(cookie, url, body, "https://store.weixin.qq.com/shop/goods/entry"); + } + + public HttpResult getMixBrand(string cookie, string catId = "547591") + { + string url = "https://store.weixin.qq.com/shop-faas/mmchannelstradecategory/cgi/getMixBrand?token=&lang=zh_CN"; + string body = "{\"categoryId\":\"" + catId + "\"}"; + return WeiXinPostResponse(cookie, url, body, "https://store.weixin.qq.com/shop/goods/entry"); + } + + public HttpResult batchCheckCategoryRelation(string cookie, long catId) + { + string url = "https://store.weixin.qq.com/shop-faas/mmchannelstradecategory/cgi/product/batchCheckCategoryRelation?token=&lang=zh_CN"; + string body = "{\"categoryIdList\":[" + catId + "]}"; + return WeiXinPostResponse(cookie, url, body, "https://store.weixin.qq.com/shop/goods/entry"); + } + + public HttpResult addFinderCategoryV2(string cookie, long catId) + { + string url = "https://store.weixin.qq.com/shop-faas/mmchannelstradecategory/cgi/addFinderCategoryV2?token=&lang=zh_CN"; + string body = "{\"categoryInfo\":{\"categories\":[{\"id\":\"" + catId + "\"}]},\"brandIdList\":[],\"leafCategoryId\":\"" + catId + "\",\"reuse\":0,\"applyType\":1}"; + return WeiXinPostResponse(cookie, url, body, "https://store.weixin.qq.com/shop/goods/entry"); + } + + public HttpResult getSingleCategoryData(string cookie, long catId, List catIdLst) + { + string catIds = BuildQuotedArray(catIdLst); + string url = "https://store.weixin.qq.com/shop-faas/mmchannelstradecategory/cgi/getSingleCategoryData?token=&lang=zh_CN&categoryId=" + catId; + string referer = "https://store.weixin.qq.com/shop/brandAndCat/category/apply?defaultCategoryIds=" + WebUtility.UrlEncode(catIds) + "&source=goods&type=apply"; + return WeiXinGetResponse(cookie, url, referer); + } + + public HttpResult wxCateAuth(string cookie, List catIdLst, string requestJson) + { + string catIds = BuildQuotedArray(catIdLst); + string url = "https://store.weixin.qq.com/shop-faas/mmchannelstradecategory/cgi/addFinderCategoryV2?token=&lang=zh_CN"; + string referer = "https://store.weixin.qq.com/shop/brandAndCat/category/apply?defaultCategoryIds=" + WebUtility.UrlEncode(catIds) + "&source=goods&type=apply"; + return WeiXinPostResponse(cookie, url, requestJson, referer); + } + + public HttpResult genProductShortTitle(string cookie, string requestJson) + { + string url = "https://store.weixin.qq.com/shop-faas/mmecnodeproductupload/common/cgi/goods/genProductShortTitle?token=&lang=zh_CN"; + return WeiXinPostResponse(cookie, url, requestJson, "https://store.weixin.qq.com/shop/goods/entry"); + } + + public HttpResult getFreightTemplate(string cookie) + { + string url = "https://store.weixin.qq.com/shop-faas/mmecnodeproductupload/express/cgi/goods/getFreightTemplate?token=&lang=zh_CN"; + return WeiXinGetResponse(cookie, url, "https://store.weixin.qq.com/shop/goods/entry"); + } + + public HttpResult getAddress(string cookie, string searchText = "") + { + string url = "https://store.weixin.qq.com/shop-faas/mmchannelstradeexpress/cgi/getAddress?token=&lang=zh_CN"; + if (!string.IsNullOrEmpty(searchText)) + { + url = "https://store.weixin.qq.com/shop-faas/mmchannelstradeexpress/cgi/getAddress?searchStr=" + WebUtility.UrlEncode(searchText) + "&token=&lang=zh_CN"; + } + return WeiXinGetResponse(cookie, url, "https://store.weixin.qq.com/shop/goods/entry"); + } + + public HttpResult getAddressById(string cookie, string addressId) + { + string url = "https://store.weixin.qq.com/shop-faas/mmchannelstradeexpress/cgi/getAddress?offset=0&limit=20&includeBlock=1&addressId=" + addressId + "&token=&lang=zh_CN"; + return WeiXinGetResponse(cookie, url, "https://store.weixin.qq.com/shop/address/expressAddressMgmt"); + } + + public HttpResult uploadProductDraft(string cookie, string productJson) + { + string url = "https://store.weixin.qq.com/shop-faas/mmecnodeproductupload/common/cgi/goods/uploadProductDraft?token=&lang=zh_CN"; + productJson = SetJsonProperty(productJson, "type", 1); + return WeiXinPostResponse(cookie, url, WrapData(productJson), "https://store.weixin.qq.com/shop/goods/entry"); + } + + public HttpResult upProductV2(string cookie, string productJson) + { + string url = "https://store.weixin.qq.com/shop-faas/mmchannelstradeproductcore/cgi/goods/upProductV2?token=&lang=zh_CN"; + return WeiXinPostResponse(cookie, url, WrapData(productJson), "https://store.weixin.qq.com/shop/goods/entry"); + } + + public HttpResult checkProductUpload(string cookie, string productId) + { + string url = "https://store.weixin.qq.com/shop-faas/mmchannelstradeproductcore/cgi/goods/checkUploadProductDraftStatus?token=&lang=zh_CN&productKey=%7B%22productId%22:" + productId + "%7D&preview=false"; + string referer = "https://store.weixin.qq.com/shop/goods/entry?productId=" + productId + "&isUpdateAuditFree=0&source=list_edit&continueEditInfo=" + + "{%22isEditingContinued%22:false,%22lastEditDraftTime%22:0,%22listingTime%22:0}"; + return WeiXinGetResponse(cookie, url, referer); + } + + public HttpResult SearchProductUpload(string cookie, string productId) + { + string url = "https://store.weixin.qq.com/shop-faas/mmchannelstradeproductcore/cgi/goods/searchProduct?token=&lang=zh_CN&pageSize=20&productStatus[]=-1" + + "&productSource=[1,16,32]&searchSource=1&useNew=true&fromProductManager=1&pageNum=1&status[]=2&spuId=" + productId; + return WeiXinGetResponse(cookie, url, "https://store.weixin.qq.com/shop/goods/list?currentTab=PRODUCT_STATUS_EDIT"); + } + + public HttpResult upProductJObjectV2(string cookie, string prodStr) + { + string url = "https://store.weixin.qq.com/shop-faas/mmchannelstradeproductcore/cgi/goods/upProductV2?token=&lang=zh_CN"; + return WeiXinPostResponse(cookie, url, WrapData(prodStr), "https://store.weixin.qq.com/shop/goods/entry"); + } + + public HttpResult getEditProductV2(string cookie, string productId) + { + string url = "https://store.weixin.qq.com/shop-faas/mmchannelstradeproductcore/cgi/goods/getEditProductV2?token=&lang=zh_CN&productKey=%7B%22productId%22:" + productId + "%7D&needRealStock=true&preview=false&release=false"; + string referer = "https://store.weixin.qq.com/shop/goods/entry?productId=" + productId + "&isUpdateAuditFree=0&source=list_edit&continueEditInfo={%22isEditingContinued%22:false,%22lastEditDraftTime%22:0,%22listingTime%22:0}"; + return WeiXinGetResponse(cookie, url, referer); + } + + public HttpResult initUploadPart(string cookie, string submitJson, string prodId, int time1, int time2) + { + string url = "https://store.weixin.qq.com/shop-faas/mmecnodefiletransfer/filecos/initUploadPart?token=&lang=zh_CN"; + string referer = BuildAuditFreeReferer(prodId, time1, time2); + return WeiXinPostResponse(cookie, url, submitJson, referer); + } + + public HttpResult listingAuditFree(string cookie, string productJson, string prodId, int time1, int time2) + { + string url = "https://store.weixin.qq.com/shop-faas/mmchannelstradeproductcore/cgi/goods/listingAuditFree?token=&lang=zh_CN"; + string referer = BuildAuditFreeReferer(prodId, time1, time2); + return WeiXinPostResponse(cookie, url, WrapData(productJson), referer); + } + + public HttpResult uploadPartBinary(string cookie, string initFileName, string blobPath, string uploadId, string prodId, int time1, int time2) + { + string boundaryStr = CreateBoundary(); + string url = "https://store.weixin.qq.com/shop-faas/mmecnodefiletransfer/filecos/uploadPartBinary?token=&lang=zh_CN"; + string referer = BuildAuditFreeReferer(prodId, time1, time2); + string body = "--" + boundaryStr + "\r\n" + + "Content-Disposition: form-data; name=\"name\"\r\n\r\n" + initFileName + "\r\n" + + "--" + boundaryStr + "\r\n" + + "Content-Disposition: form-data; name=\"uploadId\"\r\n\r\n" + uploadId + "\r\n" + + "--" + boundaryStr + "\r\n" + + "Content-Disposition: form-data; name=\"partSeq\"\r\n\r\n1\r\n" + + "--" + boundaryStr + "\r\n" + + "Content-Disposition: form-data; name=\"file\"; filename=\"blob\"\r\n" + + "Content-Type: application/octet-stream\r\n\r\n" + + "" + blobPath + "\r\n--" + boundaryStr + "--\r\n"; + return WeiXinUploadResponse(cookie, url, body, boundaryStr, referer); + } + + public HttpResult completeUploadPart(string cookie, string completeJson, string prodId, int time1, int time2) + { + string url = "https://store.weixin.qq.com/shop-faas/mmecnodefiletransfer/filecos/completeUploadPart?token=&lang=zh_CN"; + string referer = BuildAuditFreeReferer(prodId, time1, time2); + return WeiXinPostResponse(cookie, url, completeJson, referer); + } + + public HttpResult addAddress(string cookie, string provinceName, string cityName, string countyName, string detailInfo, string userName, string telNumber) + { + string url = "https://store.weixin.qq.com/shop-faas/mmchannelstradeexpress/address/addAddress?token=&lang=zh_CN"; + string address = JsonConvert.SerializeObject(new + { + addressInfo = new + { + provinceName, + cityName, + countyName, + detailInfo, + userName, + telNumber, + location = new JObject() + }, + defaultSend = 0, + defaultRecv = 0 + }); + return WeiXinPostResponse(cookie, url, WrapData(address), "https://store.weixin.qq.com/shop/address/expressAddressMgmt"); + } + + public HttpResult GetApiServiceList(string cookie) + { + string url = "https://store.weixin.qq.com/shop-faas/mmecnodeservicemarket/service/GetMyServiceList?offset=0&limit=20&status=2&token=&lang=zh_CN"; + return WeiXinGetResponse(cookie, url, "https://store.weixin.qq.com/shop/servicemarket/myServices?status=2"); + } + + public HttpResult GetAndUpdateWebStatus(string cookie) + { + string url = "https://store.weixin.qq.com/shop-faas/mmchannelstradehome/home/getAndUpdateWebStatus?token=&lang=zh_CN"; + long startMs = ToTimestampMilliseconds(DateTime.Now.Date.AddDays(-1)); + long endMs = ToTimestampMilliseconds(DateTime.Now.Date.AddSeconds(-1)); + string body = "{\"timeQuery\":{\"startMs\":" + startMs + ",\"endMs\":" + endMs + "}}"; + return WeiXinPostResponse(cookie, url, body, "https://store.weixin.qq.com/shop/home"); + } + + public HttpResult addLeagueItemForUpdatingProduct(string cookie, int itemSwitch, int itemRatio, int headSupplierItemSwitch, int headSupplierItemRatio, long prodId, int updType = 2) + { + string url = "https://store.weixin.qq.com/shop-faas/mmecnodeproductupload/common/cgi/goods/addLeagueItemForUpdatingProduct?token=&lang=zh_CN"; + string body = "{\"itemSwitch\":" + itemSwitch + ",\"itemRatio\":" + itemRatio + ",\"headSupplierPublicItemSwitch\":" + headSupplierItemSwitch + + ",\"headSupplierPublicItemServiceRatio\":" + headSupplierItemRatio + ",\"productId\":" + prodId + ",\"scene\":2,\"updType\":" + updType + "}"; + return WeiXinPostResponse(cookie, url, body, "https://store.weixin.qq.com/shop/home"); + } + + public HttpResult generalProductInfoRecommend(string cookie, string productId, string requestJson) + { + string url = "https://store.weixin.qq.com/shop-faas/mmecnodeproductupload/aiRecommendCompat/cgi/goods/generalProductInfoRecommend?token=&lang=zh_CN"; + string referer = "https://store.weixin.qq.com/shop/goods/entry?productId=" + productId + "&isUpdateAuditFree=0&source=list_edit&continueEditInfo={%22isEditingContinued%22:false,%22lastEditDraftTime%22:0,%22listingTime%22:0}"; + return WeiXinPostResponse(cookie, url, requestJson, referer); + } + + public HttpResult wxGetAssistant(string cookie, string prodId, string pageContext = null, string spu_id = null) + { + var NULL = (object)null; + string url = "https://store.weixin.qq.com/shop-faas/mmeckolpoolnode/ecSearchSelectionSpuItems?token=&lang=zh_CN"; + string referer = "https://store.weixin.qq.com/talent/pool/home"; + if (!string.IsNullOrEmpty(prodId)) + { + referer += "?keyword=" + prodId; + } + object data = new + { + filter = new + { + spuFilter = new + { + monthlySalesRange = NULL, + goodEvaluationRatioRange = NULL, + shopScoreRange = NULL + + }, + spuSort = new + { + sortOrder = 0, + sortType = 0 + + }, + category = NULL + + }, + pageContext = new + { + pageContext = pageContext, + limit = 15 + }, + requestContext = new + { + sessionId = DateHelper.GetUnixTimestampInMilliseconds(DateTime.Now.AddSeconds(-RandomHelper.GetRandomNumber(10, 100))), + requestId = DateHelper.GetUnixTimestampInMilliseconds(DateTime.Now), + parentSearchId = 53, + displayedSpus = + string.IsNullOrEmpty(spu_id) ? NULL : new + { + spuId = spu_id, + } + + + }, + keyword = prodId + }; + + string body = JsonConvert.SerializeObject(data); + var result = WeiXinAssistantPostResponse(cookie, url, body, referer); + return result; + } + + #endregion + + private static HttpItem CreateWxItem(string cookie, string url, string referer, string accept = JsonAccept) + { + return new HttpItem + { + URL = url, + Cookie = cookie ?? string.Empty, + Referer = referer ?? string.Empty, + KeepAlive = true, + Expect100Continue = false, + UserAgent = WxUserAgent, + Accept = accept, + PostEncoding = Encoding.UTF8 + }; + } + + private static HttpItem CreateE57Item(string cookie, string token, string url, string referer) + { + HttpItem item = new HttpItem + { + URL = url, + Cookie = BuildAuthorizationCookie(cookie, token), + KeepAlive = true, + Expect100Continue = false, + UserAgent = E57UserAgent, + Accept = HtmlAccept, + PostEncoding = Encoding.UTF8, + WebProxy = null + }; + if (url != null && url.StartsWith("https", StringComparison.OrdinalIgnoreCase)) + { + item.ProtocolVersion = HttpVersion.Version10; + } + item.Header["sec-ch-ua"] = "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\", \"Google Chrome\";v=\"144\""; + item.Header["sec-ch-ua-mobile"] = "?0"; + item.Header["sec-ch-ua-platform"] = "\"Windows\""; + item.Header["Upgrade-Insecure-Requests"] = "1"; + item.Header["Authorization"] = "Bearer " + token; + item.Header["Sec-Fetch-Site"] = "none"; + item.Header["Sec-Fetch-Mode"] = "navigate"; + item.Header["Sec-Fetch-User"] = "?1"; + item.Header["Sec-Fetch-Dest"] = "document"; + item.Header["Accept-Encoding"] = "gzip"; + item.Header["Accept-Language"] = E57AcceptLanguage; + return item; + } + + private static void AddCommonWxHeaders(HttpItem item, string bizMagic, string talentMagic, bool includeMobileHeader = true) + { + item.Header["wecom_magic"] = string.Empty; + item.Header["sec-ch-ua-platform"] = "\"Windows\""; + item.Header["supplier_magic"] = string.Empty; + item.Header["sec-ch-ua"] = "\"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\""; + if (includeMobileHeader) + { + item.Header["sec-ch-ua-mobile"] = "?0"; + } + item.Header["potter-scene"] = "weixinShop"; + item.Header["biz_magic"] = bizMagic ?? string.Empty; + item.Header["talent_magic"] = talentMagic ?? string.Empty; + item.Header["mcn_magic"] = string.Empty; + item.Header["Origin"] = Host; + item.Header["Sec-Fetch-Site"] = "same-origin"; + item.Header["Sec-Fetch-Mode"] = "cors"; + item.Header["Sec-Fetch-Dest"] = "empty"; + item.Header["Accept-Encoding"] = "gzip"; + item.Header["Accept-Language"] = WxAcceptLanguage; + } + + private static void PrepareJsonPost(HttpItem item, string data) + { + item.Method = MethodType.Post; + item.ContentType = JsonContentType; + item.Postdata = data ?? string.Empty; + } + + private static string CreateBoundary() + { + return "----WebKitFormBoundary" + Guid.NewGuid().ToString("N").Substring(0, 16); + } + + private static string BuildQuotedArray(List values) + { + return "[\"" + string.Join("\",\"", values ?? new List()) + "\"]"; + } + + private static string WrapData(string data) + { + return JsonConvert.SerializeObject(new { data = data ?? string.Empty }); + } + + private static string SetJsonProperty(string json, string propertyName, JToken value) + { + if (string.IsNullOrWhiteSpace(json)) + { + return json; + } + try + { + JObject obj = JObject.Parse(json); + obj[propertyName] = value; + return obj.ToString(Formatting.None); + } + catch + { + return json; + } + } + + private static string BuildAuditFreeReferer(string prodId, int time1, int time2) + { + return "https://store.weixin.qq.com/shop/goods/entry?productId=" + prodId + + "&isUpdateAuditFree=1&source=list_audit_free&continueEditInfo=" + + "{%22isEditingContinued%22:true,%22lastEditDraftTime%22:" + time1 + "," + + "%22listingTime%22:" + time2 + "}"; + } + + private static long ToTimestampMilliseconds(DateTime time) + { + DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1)); + return (long)(time - startTime).TotalMilliseconds; + } + + private static string GetCookieValue(string cookie, string name) + { + if (string.IsNullOrWhiteSpace(cookie)) + { + return string.Empty; + } + foreach (string part in cookie.Split(';')) + { + string value = part.Trim(); + int index = value.IndexOf('='); + if (index <= 0) + { + continue; + } + string key = value.Substring(0, index).Trim(); + if (key.Equals(name, StringComparison.OrdinalIgnoreCase)) + { + return value.Substring(index + 1).Trim(); + } + } + return string.Empty; + } + + private static string GetBizCookies(string cookie) + { + if (string.IsNullOrWhiteSpace(cookie)) + { + return string.Empty; + } + return string.Join(";", cookie + .Split(';') + .Select(part => part.Trim()) + .Where(part => + { + int index = part.IndexOf('='); + return index > 0 && part.Substring(0, index).Trim().StartsWith("biz", StringComparison.OrdinalIgnoreCase); + })); + } + + private static string ResolveAuthorization(string cookie, string authorization) + { + if (!string.IsNullOrWhiteSpace(authorization)) + { + string value = authorization.Trim(); + return value.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase) + ? value.Substring("Bearer ".Length).Trim() + : value; + } + string token = GetCookieValue(cookie, "Admin-Token"); + if (!string.IsNullOrWhiteSpace(token)) + { + return token; + } + return string.IsNullOrWhiteSpace(cookie) ? string.Empty : cookie.Trim(); + } + + private static string BuildAuthorizationCookie(string cookie, string token) + { + if (!string.IsNullOrWhiteSpace(cookie) && cookie.IndexOf('=') >= 0) + { + return cookie; + } + return string.IsNullOrWhiteSpace(token) ? string.Empty : "Admin-Token=" + token; + } + + private static byte[] BuildMultipartBody(string body) + { + if (string.IsNullOrEmpty(body)) + { + return Array.Empty(); + } + using (MemoryStream stream = new MemoryStream()) + { + foreach (string part in Regex.Split(body, @"")) + { + byte[] bytes = File.Exists(part) + ? File.ReadAllBytes(part) + : Encoding.UTF8.GetBytes(part.Replace("\r\n", "\n").Replace("\r", "\n").Replace("\n", "\r\n")); + stream.Write(bytes, 0, bytes.Length); + } + return stream.ToArray(); + } + } +} diff --git a/Store.Management.Framework/Caches/ImageBase.cs b/Store.Management.Framework/Caches/ImageBase.cs new file mode 100644 index 0000000..ee492ad --- /dev/null +++ b/Store.Management.Framework/Caches/ImageBase.cs @@ -0,0 +1,30 @@ +using Newtonsoft.Json; +using Store.Management.Framework.Helpers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Management.Framework.Caches; + +public class StoreImageBase +{ + /// + /// 图片源路径 + /// + public string ImgUrl { get; set; } + /// + /// 上传后图片路径 + /// + public string UploadImgUrl { get; set; } + + /// + /// 图片流 + /// + [JsonIgnore] + public byte[] ImgByte + { + get;set; + } +} diff --git a/Store.Management.Framework/Caches/ImageCache.cs b/Store.Management.Framework/Caches/ImageCache.cs new file mode 100644 index 0000000..7ab1638 --- /dev/null +++ b/Store.Management.Framework/Caches/ImageCache.cs @@ -0,0 +1,95 @@ +using MAX.Core.Caches; +using Store.Management.Framework.Helpers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Management.Framework.Caches; + +/// +/// 图片缓存// +/// +internal class ImageCache : SingleInstanceCache +{ + + private ImageCache() + { + + } + public override void Clear() + { + base.Clear(); + GC.Collect(); + } + + + + public byte[] GetBytes(string key) + { + var item = Get(key); + if (item != null) + { + return item.ImgByte; + } + return null; + } + + public string GetUploadUrl(string key) + { + var item = Get(key); + if (item != null) + { + return item.UploadImgUrl; + } + return ""; + } + + + + + + /// + /// 添加缓存 + /// + /// + /// 图片流 + /// + public bool TryAdd(string key, byte[] file) + { + var item = Get(key); + if (item == null) + { + item = new StoreImageBase(); + item.ImgUrl = key; + } + item.ImgByte = file; + return base.TryAdd(key, item); + } + /// + /// 添加缓存 + /// + /// + /// 图片上传后服务器地址 + /// + public bool TryAdd(string key, string uploadUrl) + { + var item = Get(key); + if (item == null) + { + item = new StoreImageBase(); + item.ImgUrl = key; + } + if (item.ImgByte == null) + { + item.UploadImgUrl = uploadUrl; + } + return base.TryAdd(key, item); + } + + public bool TryAdd(StoreImageBase img) + { + return base.TryAdd(img.ImgUrl, img); + } +} diff --git a/Store.Management.Framework/Class1.cs b/Store.Management.Framework/Class1.cs new file mode 100644 index 0000000..f37df3f --- /dev/null +++ b/Store.Management.Framework/Class1.cs @@ -0,0 +1,6 @@ +namespace Store.Management.Framework; + +public class Class1 +{ + +} diff --git a/Store.Management.Framework/Configs/ApiServerConfig.cs b/Store.Management.Framework/Configs/ApiServerConfig.cs new file mode 100644 index 0000000..7cb45e7 --- /dev/null +++ b/Store.Management.Framework/Configs/ApiServerConfig.cs @@ -0,0 +1,16 @@ +using MAX.Core.DependencyInjection; +using Store.Client; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Management.Framework.Configs; + +internal class ApiServerConfig : IApiServerConfig, ISingletonDependency +{ + public string Host => "http://1.13.195.131:9000"; + + public string ObjUrl => "29wx/wxxd_"; +} diff --git a/Store.Management.Framework/Configs/ManagementServiceConfigKeys.cs b/Store.Management.Framework/Configs/ManagementServiceConfigKeys.cs new file mode 100644 index 0000000..766e748 --- /dev/null +++ b/Store.Management.Framework/Configs/ManagementServiceConfigKeys.cs @@ -0,0 +1,13 @@ +namespace Store.Management.Framework.Configs +{ + /// + /// Management 项目的服务配置 Key。 + /// + public static class ManagementServiceConfigKeys + { + /// + /// Management 默认服务配置。 + /// + public const string Default = "Management"; + } +} \ No newline at end of file diff --git a/Store.Management.Framework/Helpers/ImageHelper.cs b/Store.Management.Framework/Helpers/ImageHelper.cs new file mode 100644 index 0000000..2ad3a20 --- /dev/null +++ b/Store.Management.Framework/Helpers/ImageHelper.cs @@ -0,0 +1,213 @@ +using MAX.Core.Commons; +using MAX.Core.Helpers; +using MAX.Framework.Logger; +using Store.Management.Framework.Caches; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Management.Framework.Helpers; + +public class ImageHelper +{ + ///// + ///// 判断是否二维码 + ///// + ///// + ///// + //public static bool CheckQrCode(byte[] file) + //{ + + // return GetQRCode(file, out int width, out int height, out Image initImage); + //} + ///// + ///// 获取二维码 + ///// + ///// + ///// + //public static bool GetQRCode(byte[] file, out int width, out int height, out Image initImage) + //{ + // initImage = null; + // width = 0; + // height = 0; + // try + // { + // MemoryStream ms = new MemoryStream(file); + // initImage = Image.FromStream(ms); + // ms.Close(); + // width = initImage.Width; + // height = initImage.Height; + // if (initImage.Width < 10 || initImage.Height < 10) + // { + // initImage.Dispose(); + // initImage = null; + // return false; + // } + + // BarcodeReader reader = new BarcodeReader(); + // var result = reader.Decode((Bitmap)initImage); + // if (result == null || string.IsNullOrEmpty(result.Text)) + // { + // reader = null; + // result = null; + // return false; + // } + // reader = null; + // result = null; + // initImage.Dispose(); + // initImage = null; + // } + // catch + // { + // return false; + // } + // return true; + //} + + /// + /// 图片大小固定 + /// + /// + /// + /// + public static void FixedSize(bool IsMainImg, ref int width, ref int height) + { + if (IsMainImg) + { + if (width >= 800 && height >= 800) + { + width = 800; + height = 800; + } + else + { + if (width > 640) + { + width = 640; + height = height * 640 / width; + } + if (height > 1920) + height = 1920; + } + } + + else + { + if (width > 640) + { + width = 640; + height = height * 640 / width; + } + if (height > 1920) + height = 1920; + } + } + + /// + /// 下载图片 并缓存 + /// + /// + public static byte[] DownloadImage(string imgUrl) + { + if (string.IsNullOrEmpty(imgUrl)) + { + return null; + } + imgUrl = imgUrl.Trim(); + if (imgUrl.StartsWith("//")) + { + imgUrl = "http:" + imgUrl; + } + byte[] data = ImageCache.Instance.GetBytes(imgUrl); + if (data == null) + { + data = HttpHelper.DownloadFile(imgUrl); + if (data != null) + { + ImageCache.Instance.TryAdd(imgUrl, data); + } + } + return data; + } + + /// + /// 上传图片 + /// + /// http请求参数 + /// 文件bytes + /// 上传关键字 + /// 文件名 + /// 附加参数 + public static Result UploadImage(HttpItem item, byte[] file, string fileKeyName = "file", string fileName = "", Dictionary param = null) + { + Result result = new Result(); + Result resultHttp = UploadImageHttpResult(item, file, fileKeyName, fileName, param); + result.Success = resultHttp.Success; + if (result.Success) + { + result.Data = resultHttp.Data.Html; + } + return result; + } + public static Result UploadImageHttpResult(HttpItem item, byte[] file, string fileKeyName = "file", string fileName = "", Dictionary param = null) + { + Result result = new Result(); + try + { + // 边界符 + var boundary = "----" + Guid.NewGuid().ToString("N"); + + if (string.IsNullOrEmpty(fileName)) + { + fileName = Guid.NewGuid().ToString("N").Substring(0, 4) + ".jpg"; + } + item.PostEncoding = Encoding.UTF8; + item.Method = MethodType.Post; + item.PostDataType = PostDataType.Byte; + item.ContentType = "multipart/form-data; boundary=" + boundary; + var memStream = new MemoryStream(); + + if (param != null) + { + foreach (var key in param.Keys) + { + string value = param[key]; + memStream.WriteString("--" + boundary); + memStream.WriteString($"Content-Disposition: form-data; name=\"{key}\"" + "\r\n"); + memStream.WriteString(value); + } + } + memStream.WriteString("--" + boundary); + memStream.WriteString($"Content-Disposition: form-data; name=\"{fileKeyName}\"; filename=\"{fileName}\""); + memStream.WriteString("Content-Type: image/jpeg\r\n"); + memStream.Write(file, 0, file.Length); + memStream.WriteString("\r\n"); + memStream.WriteString("--" + boundary + "--"); + var tempBuffer = new byte[memStream.Length]; + memStream.Read(tempBuffer, 0, tempBuffer.Length); + memStream.Close(); + item.PostdataByte = memStream.ToArray(); + var httpresult = HttpHelper.Request(item); + result.Success = true; + result.Data = httpresult; + } + catch (Exception ex) + { + NLogger.Instance.Error(ex, "Http上传图片错误"); + result.Msg = ex.Message; + } + return result; + } +} + +static class StreamExtension +{ + public static void WriteString(this MemoryStream memStream, string value) + { + byte[] valuebyte = Encoding.UTF8.GetBytes(value + "\r\n"); + memStream.Write(valuebyte, 0, valuebyte.Length); + } +} diff --git a/Store.Management.Framework/Startup.cs b/Store.Management.Framework/Startup.cs new file mode 100644 index 0000000..fc06e1f --- /dev/null +++ b/Store.Management.Framework/Startup.cs @@ -0,0 +1,38 @@ +using Autofac; +using MAX.Core; +using MAX.Core.Infrastructure; +using Store.Client; +using Store.Management.Framework.Configs; + +namespace Store.Management.Framework +{ + /// + /// Management 项目启动注册。 + /// + public class Startup : IStartup + { + private const string ManagementHost = "http://1.13.195.131:9000"; + private const string ApiObjUrl = "obj=api&act="; + // PostWx 会追加 wxxd_,最终 obj 为 29wx/wxxd_{urlPath}。 + private const string ManagementObjUrl = "obj=29wx/"; + + public int Priority => IntPriority.Normal; + + public void Initialize() + { + } + + public void Register(ContainerBuilder builder) + { + //builder + // .RegisterServiceConfig( + // ServiceConfigKeys.Default, + // ManagementHost, + // ApiObjUrl) + // .RegisterServiceConfig( + // ManagementServiceConfigKeys.Default, + // ManagementHost, + // ManagementObjUrl); + } + } +} \ No newline at end of file diff --git a/Store.Management.Framework/Store.Management.Framework.csproj b/Store.Management.Framework/Store.Management.Framework.csproj new file mode 100644 index 0000000..14bafe8 --- /dev/null +++ b/Store.Management.Framework/Store.Management.Framework.csproj @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/Store.Product/Class1.cs b/Store.Product/Class1.cs new file mode 100644 index 0000000..1cc5854 --- /dev/null +++ b/Store.Product/Class1.cs @@ -0,0 +1,18 @@ +using Store.Product.Publish.Steps.Wx; + +namespace Store.Product; + +public class Class1 +{ + public Class1() + { + var p = new Progress(P_ProgressChanged); + new WXPublishContext().Progress = p; + + } + + private void P_ProgressChanged(string value) + { + // 更新 UI + } +} diff --git a/Store.Product/Enums/BlockType.cs b/Store.Product/Enums/BlockType.cs new file mode 100644 index 0000000..1f6f574 --- /dev/null +++ b/Store.Product/Enums/BlockType.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Product.Enums +{ + /// + /// 封禁类型 + /// + public enum BlockType + { + /// + /// 根类目封禁 + /// + CatgoryRoot, + /// + /// 当前类目封禁 + /// + Category, + /// + /// 店铺封禁 + /// + Shop, + } +} diff --git a/Store.Product/Enums/FreightTemplateType.cs b/Store.Product/Enums/FreightTemplateType.cs new file mode 100644 index 0000000..320ac22 --- /dev/null +++ b/Store.Product/Enums/FreightTemplateType.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Product.Enums +{ + /// + /// 运费模板设置方式 + /// + public enum FreightTemplateType + { + /// + /// 复制上家模板 + /// + [Description("复制上家模板")] + Copy = 0, + /// + /// 随机本店模板 + /// + [Description("随机本店模板")] + Random = 1, + /// + /// 固定本店模板 + /// + [Description("固定本店模板")] + Fixed = 2, + /// + /// 指定模板 + /// + [Description("指定本店模板")] + Specified = 3 + } +} diff --git a/Store.Product/Enums/NextStatus.cs b/Store.Product/Enums/NextStatus.cs new file mode 100644 index 0000000..5b2df63 --- /dev/null +++ b/Store.Product/Enums/NextStatus.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Product.Enums +{ + /// + /// 上传结果状态 + /// + public enum UpResultStatus + { + /// + /// 继续下一个 + /// + Next, + /// + /// 跳出本次上传 + /// + Return, + /// + /// 结束所有上传 + /// + Finished + } +} diff --git a/Store.Product/Enums/ProductStorageStatus.cs b/Store.Product/Enums/ProductStorageStatus.cs new file mode 100644 index 0000000..66f1e96 --- /dev/null +++ b/Store.Product/Enums/ProductStorageStatus.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Product.Enums +{ + /// + /// 商品存储状态 + /// + public enum ProductStorageStatus + { + 立刻上架 = 0, + 定时上架 = 1, + 放入仓库 = 2 + } +} diff --git a/Store.Product/Enums/StuffStatus.cs b/Store.Product/Enums/StuffStatus.cs new file mode 100644 index 0000000..75c3397 --- /dev/null +++ b/Store.Product/Enums/StuffStatus.cs @@ -0,0 +1,11 @@ +namespace Store.Product.Enums +{ + /// + /// 商品状态 + /// + public enum StuffStatus + { + 全新 = 5, + 二手 = 6 + } +} \ No newline at end of file diff --git a/Store.Product/Models/Product/Image/ContentImage.cs b/Store.Product/Models/Product/Image/ContentImage.cs new file mode 100644 index 0000000..cd424a6 --- /dev/null +++ b/Store.Product/Models/Product/Image/ContentImage.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Product.Models.Product.Image +{ + /// + /// 商品详情图片 + /// + public class ContentImage : ProductImage + { + //public override ImageType ImageType => ImageType.Content; + + public ContentImage(string url) : base(url) + { + Gid = Guid.NewGuid().ToString(); + } + public string Gid { get; } + + + } +} diff --git a/Store.Product/Models/Product/Image/MainImage.cs b/Store.Product/Models/Product/Image/MainImage.cs new file mode 100644 index 0000000..cf04e54 --- /dev/null +++ b/Store.Product/Models/Product/Image/MainImage.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Product.Models.Product.Image +{ + public class MainImage : ProductImage + { + public override ProductImageType ImageType => ProductImageType.Main; + public MainImage() + { + + } + public MainImage(string url) : base(url) + { + + } + + + } +} diff --git a/Store.Product/Models/Product/Image/ProductImage.cs b/Store.Product/Models/Product/Image/ProductImage.cs new file mode 100644 index 0000000..1525fd1 --- /dev/null +++ b/Store.Product/Models/Product/Image/ProductImage.cs @@ -0,0 +1,67 @@ +using Newtonsoft.Json; +using Store.Management.Framework.Helpers; +using System.Drawing; + +namespace Store.Product.Models.Product.Image +{ + /// + /// 商品图片 + /// + public class ProductImage + { + + public ProductImage() + { + + } + public ProductImage(string url) + { + ImgUrl = url; + } + //public ProductImage(string url, ImageType type) + //{ + // ImgUrl = url; + // ImageType = type; + //} + /// + /// 图片类型 + /// + public virtual ProductImageType ImageType { get; protected set; } = ProductImageType.None; + /// + /// 文件名 + /// + public string FileName { get; set; } + /// + /// 图片源路径 + /// + public string ImgUrl { get; set; } + + private byte[] _imgbyte; + /// + /// 图片流 + /// + [JsonIgnore] + public byte[] ImgByte + { + get + { + if (_imgbyte == null) + { + _imgbyte = ImageHelper.DownloadImage(ImgUrl); + } + return _imgbyte; + } + set + { + _imgbyte = value; + } + } + /// + /// 上传后图片路径 + /// + public string UploadImgUrl { get; set; } + + + + } +} diff --git a/Store.Product/Models/Product/Image/ProductImageType.cs b/Store.Product/Models/Product/Image/ProductImageType.cs new file mode 100644 index 0000000..0e7f504 --- /dev/null +++ b/Store.Product/Models/Product/Image/ProductImageType.cs @@ -0,0 +1,13 @@ +namespace Store.Product.Models.Product.Image +{ + /// + /// 图片类型 + /// + public enum ProductImageType + { + None, + Main, + Sku, + Content + } +} diff --git a/Store.Product/Models/Product/Image/SkuImage.cs b/Store.Product/Models/Product/Image/SkuImage.cs new file mode 100644 index 0000000..fc68721 --- /dev/null +++ b/Store.Product/Models/Product/Image/SkuImage.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Product.Models.Product.Image +{ + public class SkuImage : ProductImage + { + public override ProductImageType ImageType => ProductImageType.Sku; + public SkuImage() + { + + } + public SkuImage(string url) : base(url) + { + + } + } +} diff --git a/Store.Product/Models/Product/MainImageCollection.cs b/Store.Product/Models/Product/MainImageCollection.cs new file mode 100644 index 0000000..0edcc1d --- /dev/null +++ b/Store.Product/Models/Product/MainImageCollection.cs @@ -0,0 +1,34 @@ +using Store.Product.Models.Product.Image; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Product.Models.Product +{ + /// + /// 主图集合 + /// + public class MainImageCollection : List + { + /// + /// 随机排序 + /// + public void RandomSort() + { + Random random = new Random(); + List newList = new List(); + foreach (ProductImage item in this.Take(4)) + { + newList.Insert(random.Next(newList.Count + 1), item); + } + if (this.Count == 5) + { + newList.Add(this.Last()); + } + this.Clear(); + this.AddRange(newList); + } + } +} diff --git a/Store.Product/Models/Product/ProductBase.cs b/Store.Product/Models/Product/ProductBase.cs new file mode 100644 index 0000000..5671271 --- /dev/null +++ b/Store.Product/Models/Product/ProductBase.cs @@ -0,0 +1,139 @@ +using Store.Product.Models.Product.Sku; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Product.Models.Product; + + /// + /// 商品属性基类 + /// + public class ProductBase + { + private string _gid = Guid.NewGuid().ToString(); + /// + /// 唯一ID + /// + public string Gid + { + get + { + return _gid; + } + } + /// + /// 商品ID + /// + public long Id { get; set; } + /// + /// 标题 + /// + public string Title { get; set; } + + /// + /// 商铺名称 + /// + public string ShopName { get; set; } + /// + /// 类目ID + /// + public int CategoryId { get; set; } + + /// + /// 销售数量 + /// + public int SaleCount { get; set; } + /// + /// 库存数量 + /// + public int StockCount { get; set; } + /// + /// 价格 + /// + public decimal Price { get; set; } + /// + /// 以前价格 | 划线价格 + /// + public decimal BeforePrice { get; set; } + /// + /// 邮费 + /// + public decimal ExpressPrice { get; set; } + /// + /// 折扣 + /// + public decimal Discount { get; set; } + /// + /// 折扣价格 + /// + public decimal DiscountPrice { get; set; } + /// + /// 在线价格 + /// + public decimal OnlinePrice { get; set; } + /// + /// 市场价格 + /// + public decimal MarketPrice { get; set; } + /// + /// 主图 + /// + public string MainImage { get; set; } + + /// + /// 品牌 + /// + public string ShopBrand { get; set; } + /// + /// 是否是促销品 + /// + public bool IsPromotion { get; set; } + + /// + /// 省 + /// + public string State { get; set; } + /// + /// 市 + /// + public string City { get; set; } + /// + /// 内容 + /// + public ProductContent Content { get; set; } = new ProductContent(""); + /// + /// 来源地址 + /// + public virtual string SourceUrl { get; set; } + + /// + /// 商品主图视频Id + /// + public long VideoId { get; set; } + /// + /// 重量 + /// + public int Weight { get; set; } + + /// + /// 商品特性 + /// + public ProductPropertys Propertys { get; set; } = new ProductPropertys(); + + /// + /// SKU 属性值 + /// + public SkuPropertys SkuProperty { get; set; } = new SkuPropertys(); + + /// + /// Sku 属性映射 + /// + public SkuMaps SkuMaps { get; set; } = new SkuMaps(); + /// + /// 主图列表 + /// + public MainImageCollection MainImages { get; set; } = new MainImageCollection(); + + } diff --git a/Store.Product/Models/Product/ProductContent.cs b/Store.Product/Models/Product/ProductContent.cs new file mode 100644 index 0000000..9a46dde --- /dev/null +++ b/Store.Product/Models/Product/ProductContent.cs @@ -0,0 +1,101 @@ +using Store.Product.Models.Product.Image; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Store.Product.Models.Product +{ + /// + /// 商品详情 + /// + public class ProductContent + { + /// + /// 备份数据 + /// + private string _backcontent = ""; + public ProductContent(string content) + { + _backcontent = content; + _content = content; + ConvertContent(); + } + + + private string _content = ""; + public override string ToString() + { + return GetContent(); + } + + /// + /// 详情图片集合 + /// + public List Images { get; } = new List(); + + /// + /// 删除详情图片 + /// + /// + public void DelImage(ContentImage image) + { + var item = Images.FirstOrDefault(a => a.Gid == image.Gid); + if (item != null) + { + string value = GetImgTag(item); + _content.Replace(value, ""); + Images.Remove(item); + } + } + + private void ConvertContent() + { + Regex regImg = new Regex(@"]*?\bsrc[\s]*=[\s]*[""']?[\s]*(?[^""'>]*)[^>]*?/?[\s]*>"); + if (regImg.IsMatch(_backcontent)) + { + MatchCollection matchs = regImg.Matches(_backcontent); + int MatchMax = matchs.Count; + for (int i = MatchMax - 1; i >=0; i--) + { + Match item = matchs[i]; + if (item.Success) + { + Group urlgroup = item.Groups["imgUrl"]; + if (urlgroup.Success) + { + var cimg = new ContentImage(urlgroup.Value); + Images.Add(cimg); + + _content = _content.Remove(item.Index, item.Length); + _content = _content.Insert(item.Index, GetImgTag(cimg)); + } + } + } + } + } + + private string GetContent() + { + var tempContent = _content; + foreach (var item in Images) + { + string value = GetImgTag(item); + string imgString = $""; + tempContent = tempContent.Replace(value, imgString); + } + return tempContent; + } + /// + /// 图片标记 + /// + /// + /// + private string GetImgTag(ContentImage img) + { + return "${" + img.Gid + "}"; + } + } +} diff --git a/Store.Product/Models/Product/ProductPropertys.cs b/Store.Product/Models/Product/ProductPropertys.cs new file mode 100644 index 0000000..6475f2a --- /dev/null +++ b/Store.Product/Models/Product/ProductPropertys.cs @@ -0,0 +1,51 @@ +using MAX.Core.Commons; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Product.Models.Product +{ + /// + /// 商品属性 + /// + public partial class ProductPropertys : List + { + /// + /// 是否存在Key + /// + /// + /// + public bool ContainsKey(string key) + { + return Exists(a => a.Key == key); + } + /// + /// 添加数据 + /// + /// + /// + public void Add(string Key, string Value) + { + if (!ContainsKey(Key)) + { + Add(new CommonKeyValue() { Key = Key, Value = Value }); + } + } + /// + /// 获取值 + /// + /// + /// + public string GetValue(string key) + { + if (ContainsKey(key)) + { + CommonKeyValue p = this.FirstOrDefault(a => a.Key == key); + return p.Value; + } + return ""; + } + } +} diff --git a/Store.Product/Models/Product/Sku/Property.cs b/Store.Product/Models/Product/Sku/Property.cs new file mode 100644 index 0000000..bd409a3 --- /dev/null +++ b/Store.Product/Models/Product/Sku/Property.cs @@ -0,0 +1,18 @@ +namespace Store.Product.Models.Product.Sku +{ + /// + /// 属性值 + /// + public class Property + { + /// + /// Id + /// + public string Id { get; set; } + /// + /// 标题 + /// + public string Name { get; set; } + } + +} diff --git a/Store.Product/Models/Product/Sku/SkuMapItem.cs b/Store.Product/Models/Product/Sku/SkuMapItem.cs new file mode 100644 index 0000000..40ade23 --- /dev/null +++ b/Store.Product/Models/Product/Sku/SkuMapItem.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Store.Product.Models.Product.Sku +{ + /// + /// Sku Map Item + /// + public class SkuMapItem + { + /// + /// 库存数量 + /// + public int StockCount { get; set; } + /// + /// 价格 + /// + public decimal Price { get; set; } + /// + /// 划线价格 + /// + public decimal MarketPrice { get; set; } + /// + /// SkuId + /// + public long SkuId { get; set; } + /// + /// Sku 属性值 + /// + public List Properties { get; set; } = new List(); + + /// + /// Sku 属性标题集合 + /// + public string SkuPropertyTitles + { + get + { + string title = string.Join("},{", Properties.Select(a => a.ToString())); + title = "{" + title + "}"; + return title; + } + } + /// + /// Sku属性Keyu + /// + public string SalePropKey + { + get + { + return string.Join("_", Properties.Select(a => a.SalePropKey)); + } + } + + } +} diff --git a/Store.Product/Models/Product/Sku/SkuMapProperty.cs b/Store.Product/Models/Product/Sku/SkuMapProperty.cs new file mode 100644 index 0000000..76a6d41 --- /dev/null +++ b/Store.Product/Models/Product/Sku/SkuMapProperty.cs @@ -0,0 +1,26 @@ +namespace Store.Product.Models.Product.Sku +{ + /// + /// SkuMap 属性 + /// + public class SkuMapProperty : Property + { + public SkuPropertyItem Item { get; set; } + + public override string ToString() + { + return $"{Name}:{Item.Name}"; + } + /// + /// 属性Key + /// + /// + public string SalePropKey + { + get + { + return (Id + "-" + Item.Id).Replace("p-", ""); + } + } + } +} diff --git a/Store.Product/Models/Product/Sku/SkuMaps.cs b/Store.Product/Models/Product/Sku/SkuMaps.cs new file mode 100644 index 0000000..32afedd --- /dev/null +++ b/Store.Product/Models/Product/Sku/SkuMaps.cs @@ -0,0 +1,40 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using System.Linq; + +namespace Store.Product.Models.Product.Sku +{ + /// + /// Sku Map映射 + /// + public class SkuMaps : List + { + /// + /// 最低价位 + /// + /// + public SkuMapItem SkuLowItem + { + get + { + var price = this.Min(a => a.Price); + return this.FirstOrDefault(a => a.Price == price); + } + } + /// + /// 最高价位 + /// + /// + public SkuMapItem SkuHighItem + { + get + { + var price = this.Max(a => a.Price); + return this.FirstOrDefault(a => a.Price == price); + } + } + } +} diff --git a/Store.Product/Models/Product/Sku/SkuProperty.cs b/Store.Product/Models/Product/Sku/SkuProperty.cs new file mode 100644 index 0000000..259aa5f --- /dev/null +++ b/Store.Product/Models/Product/Sku/SkuProperty.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace Store.Product.Models.Product.Sku +{ + /// + /// SKU 属性值 + /// + public class SkuProperty : Property + { + public List Values { get; set; } = new List(); + } +} diff --git a/Store.Product/Models/Product/Sku/SkuPropertyItem.cs b/Store.Product/Models/Product/Sku/SkuPropertyItem.cs new file mode 100644 index 0000000..553a9f3 --- /dev/null +++ b/Store.Product/Models/Product/Sku/SkuPropertyItem.cs @@ -0,0 +1,16 @@ +using Store.Product.Models.Product.Image; + +namespace Store.Product.Models.Product.Sku +{ + /// + /// Sku属性 values + /// + public class SkuPropertyItem : Property + { + /// + /// 库存 + /// + public int StockCount { get; set; } + public ProductImage SkuImg { get; set; } + } +} diff --git a/Store.Product/Models/Product/Sku/SkuPropertys.cs b/Store.Product/Models/Product/Sku/SkuPropertys.cs new file mode 100644 index 0000000..ec1a1bd --- /dev/null +++ b/Store.Product/Models/Product/Sku/SkuPropertys.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Product.Models.Product.Sku +{ + /// + /// Sku属性 + /// + public class SkuPropertys : List + { + + public SkuPropertys() + { + + } + /// + /// 是否存在Key + /// + /// + /// + public bool ContainsKey(string Id) + { + return Exists(a => a.Id == Id); + } + /// + /// 添加Sku属性 + /// + /// + /// + public void AddSkuProperty(string id, string name) + { + if (!ContainsKey(id)) + { + this.Add(new SkuProperty + { + Id = id, + Name = name, + Values = new List() + }); + } + } + /// + /// 添加Sku属性 包含值 + /// + /// + /// + public void AddSkuPropertyItem(string id, SkuPropertyItem propertyItem) + { + if (propertyItem != null) + { + var item = GetPropertyById(id); + if (!item.Values.Exists(a => a.Id == propertyItem.Id)) + { + item.Values.Add(propertyItem); + } + } + } + //public void AddSkuProperty(string id, string name, SkuPropertyItem propertyItem) + //{ + + //} + + /// + /// 获取Sku属性值 + /// + /// + /// + /// + public SkuMapProperty GetSkuMapProperty(string name, string itemname) + { + SkuProperty p = this.FirstOrDefault(a => a.Name == name); + if (p != null) + { + var item = p.Values.FirstOrDefault(a => a.Name == itemname); + return new SkuMapProperty() + { + Id = p.Id, + Name = p.Name, + Item = item + }; + } + return null; + } + /// + /// 获取值 + /// + /// + /// + public SkuProperty GetPropertyById(string Id) + { + if (ContainsKey(Id)) + { + SkuProperty p = this.FirstOrDefault(a => a.Id == Id); + return p; + } + return null; + } + + + + } +} diff --git a/Store.Product/Models/PublishParameters/Image/ImageParameters.cs b/Store.Product/Models/PublishParameters/Image/ImageParameters.cs new file mode 100644 index 0000000..36a3cd9 --- /dev/null +++ b/Store.Product/Models/PublishParameters/Image/ImageParameters.cs @@ -0,0 +1,89 @@ +using System.IO; + +namespace Store.Product.Models.PublishParameters.Image +{ + /// + /// 上传图片参数 + /// + public class ImageParameters + { + /// + /// 是否随机删除一张详情图 + /// + public bool IsRandomDelDetailImg { get; set; } + /// + /// 是否随机主图 + /// + public bool IsRandomMainImg { get; set; } + /// + /// 复制上家主图视频 + /// + public bool IsUploadVideoByCopy { get; set; } + + /// + /// 上传自己视频空间索引视频 + /// + public SwitchParams IsUploadVideoFromToIndex { get; set; } + /// + /// Logo水印 + /// + public SwitchParams IsLogoWaterMark { get; set; } + /// + /// 图片质量百分比 + /// + public int PicQuality { get; set; } + /// + /// 商铺文字水印 + /// + public bool IsShopNameWaterMark { get; set; } + /// + /// 是否随机属性图充当主图 + /// + public bool IsRandomPropImgFillToMainImg { get; set; } + + /// + /// 简单的电脑详情图 + /// + public bool IsSimpleContentPc { get; set; } + /// + /// 是否删除主图 索引 + /// + public SwitchParams IsDelMainImgByIndex { get; set; } = new SwitchParams(); + + + /// + /// 是否删除详情图 索引 在之前 + /// + public SwitchParams IsDelDetailImgByFromToIndex { get; set; } = new SwitchParams(); + + /// + /// 是否开启 详情图 最大上传 + /// + public SwitchParams IsOnlyUpDetailImgMaxCount { get; set; } = new SwitchParams(); + + /// + /// 主图 白底最后一张 + /// + public bool IsMainImgBackgroundWihiteLast { get; set; } + /// + /// 是否盗链 + /// + public bool IsSteal { get; set; } = false; + + /// + /// 是否保留前x张主图 + /// + public SwitchParams IsKeepMainImgNum { get; set; } = new SwitchParams(); + + public ImageParameters() + { + IsLogoWaterMark = new SwitchParams(); + //string brandPath = Path.Combine(GlobalConfig.AppDirectory + "Brand\\brand.png"); + //if(File.Exists(brandPath)) + //{ + // IsLogoWaterMark.Value = System.Drawing.Image.FromFile(brandPath); + //} + + } + } +} diff --git a/Store.Product/Models/PublishParameters/Price/Computation.cs b/Store.Product/Models/PublishParameters/Price/Computation.cs new file mode 100644 index 0000000..66e9ced --- /dev/null +++ b/Store.Product/Models/PublishParameters/Price/Computation.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Product.Models.PublishParameters.Price +{ + /// + /// 计算操作符 + /// + public enum ComputationType + { + /// + /// 加 + /// + ADD, + /// + /// 减法 + /// + SUB + } +} diff --git a/Store.Product/Models/PublishParameters/Price/HighLowPrice.cs b/Store.Product/Models/PublishParameters/Price/HighLowPrice.cs new file mode 100644 index 0000000..69356f2 --- /dev/null +++ b/Store.Product/Models/PublishParameters/Price/HighLowPrice.cs @@ -0,0 +1,60 @@ +using Store.Product.Models.Product.Sku; +using System.Linq; + +namespace Store.Product.Models.PublishParameters.Price +{ + /// + /// 高低价格配置 + /// + public class HighLowPrice : PriceConfigBase + { + public override PriceStrategyType PriceType => PriceStrategyType.高低价格; + + /// + /// 低价倍数 + /// + public decimal LowMultiples { get; set; } = 1; + /// + /// 其他倍数 + /// + public decimal OtherMultiples { get; set; } = 1; + + + /// + /// 计算价格 + /// + /// + /// + public override decimal ComputedPrice(decimal oldPrice) + { + return oldPrice * OtherMultiples; + } + /// + /// 计算最低价 + /// + /// + /// + public decimal ComputedLowPrice(decimal oldPrice) + { + return oldPrice * LowMultiples; + } + + + public override SkuMaps ComputedPrice(SkuMaps skuMaps) + { + decimal minPrice = skuMaps.Min(a => a.Price); + foreach (var item in skuMaps) + { + if (item.Price == minPrice) + { + ComputedLowPrice(item.Price); + } + else + { + item.Price = ComputedPrice(item.Price); + } + } + return skuMaps; + } + } +} diff --git a/Store.Product/Models/PublishParameters/Price/MultiplesPrice.cs b/Store.Product/Models/PublishParameters/Price/MultiplesPrice.cs new file mode 100644 index 0000000..24e2909 --- /dev/null +++ b/Store.Product/Models/PublishParameters/Price/MultiplesPrice.cs @@ -0,0 +1,67 @@ + +using Store.Product.Models.Product.Sku; + +namespace Store.Product.Models.PublishParameters.Price +{ + /// + /// 翻倍价格配置 + /// + public class MultiplesPrice : PriceConfigBase + { + public override PriceStrategyType PriceType => PriceStrategyType.常规价格; + /// + /// 翻 x 倍 + /// + public int Multiples { get; set; } = 1; + /// + /// 计算 + /// + public ComputationType Computation { get; set; } = ComputationType.ADD; + /// + /// 基础加价 + /// + public decimal AddPrice { get; set; } + public MultiplesPrice() { } + /// + /// 翻倍价格配置 + /// + /// 倍数 + /// 加价 + public MultiplesPrice(int multiples, decimal addprice) + { + Multiples = multiples; + AddPrice = addprice; + } + /// + /// 计算价格 + /// + /// + /// + public override decimal ComputedPrice(decimal oldPrice) + { + var newPrice = oldPrice; + newPrice = oldPrice * Multiples; + switch (Computation) + { + case ComputationType.ADD: + newPrice = newPrice + AddPrice; + break; + case ComputationType.SUB: + newPrice = newPrice - AddPrice; + break; + default: + break; + } + return newPrice; + } + + public override SkuMaps ComputedPrice(SkuMaps skuMaps) + { + foreach (var item in skuMaps) + { + item.Price = ComputedPrice(item.Price); + } + return skuMaps; + } + } +} diff --git a/Store.Product/Models/PublishParameters/Price/PriceConfigBase.cs b/Store.Product/Models/PublishParameters/Price/PriceConfigBase.cs new file mode 100644 index 0000000..4326d76 --- /dev/null +++ b/Store.Product/Models/PublishParameters/Price/PriceConfigBase.cs @@ -0,0 +1,39 @@ + +using Store.Product.Models.Product.Sku; + +namespace Store.Product.Models.PublishParameters.Price +{ + /// + /// 价格配置 + /// + public abstract class PriceConfigBase + { + /// + /// 价格类型 + /// + public abstract PriceStrategyType PriceType { get; } + + + ///// + ///// 快递费是否增加到商品价格 + ///// + //public bool IsExpressAddPrice { get; set; } = false; + ///// + ///// 是否随机增加金额 0-1元 + ///// + //public bool IsRandomAddPrice { get; set; } = false; + + /// + /// 计算价格 + /// + /// + public abstract decimal ComputedPrice(decimal oldPrice); + + /// + /// 计算价格 + /// + /// + public abstract SkuMaps ComputedPrice(SkuMaps skuMaps); + + } +} diff --git a/Store.Product/Models/PublishParameters/Price/PriceStrategyFactory.cs b/Store.Product/Models/PublishParameters/Price/PriceStrategyFactory.cs new file mode 100644 index 0000000..4cc55e0 --- /dev/null +++ b/Store.Product/Models/PublishParameters/Price/PriceStrategyFactory.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Product.Models.PublishParameters.Price +{ + /// + /// + /// + public class PriceStrategyFactory + { + public static PriceConfigBase GetPriceStrategy(PriceStrategyType strategyType) + { + PriceConfigBase priceConfig; + switch (strategyType) + { + case PriceStrategyType.常规价格: + priceConfig = new MultiplesPrice(); + break; + case PriceStrategyType.高低价格: + priceConfig = new HighLowPrice(); + break; + case PriceStrategyType.分段价格: + priceConfig = new SectionPrice(); + break; + default: + priceConfig = null; + break; + } + return priceConfig; + } + } +} diff --git a/Store.Product/Models/PublishParameters/Price/PriceStrategyType.cs b/Store.Product/Models/PublishParameters/Price/PriceStrategyType.cs new file mode 100644 index 0000000..9ff2182 --- /dev/null +++ b/Store.Product/Models/PublishParameters/Price/PriceStrategyType.cs @@ -0,0 +1,12 @@ +namespace Store.Product.Models.PublishParameters.Price +{ + /// + /// 价格策略类型 + /// + public enum PriceStrategyType + { + 常规价格 = 0, + 高低价格 = 1, + 分段价格 = 2 + } +} diff --git a/Store.Product/Models/PublishParameters/Price/SectionPrice.cs b/Store.Product/Models/PublishParameters/Price/SectionPrice.cs new file mode 100644 index 0000000..724ead4 --- /dev/null +++ b/Store.Product/Models/PublishParameters/Price/SectionPrice.cs @@ -0,0 +1,72 @@ +using Store.Product.Models.Product.Sku; +using System.Collections.Generic; +using System.Linq; + +namespace Store.Product.Models.PublishParameters.Price +{ + /// + /// 区间价格配置 + /// + public class SectionPrice : PriceConfigBase + { + public override PriceStrategyType PriceType => PriceStrategyType.分段价格; + + /// + /// 区间列表 + /// + public List SectionList { get; set; } = new List(); + + /// + /// 计算价格 + /// + /// + /// + public override decimal ComputedPrice(decimal oldPrice) + { + var newPrice = oldPrice; + if (SectionList != null && SectionList.Count > 0) + { + foreach (var item in SectionList) + { + if (item.BeginPrice < newPrice && newPrice < item.EndPrice) + { + newPrice = item.ComputedPrice(newPrice); + } + } + if (newPrice == oldPrice) + { + newPrice = SectionList.Last().ComputedPrice(newPrice); + } + } + return newPrice; + } + + public override SkuMaps ComputedPrice(SkuMaps skuMaps) + { + foreach (var item in skuMaps) + { + item.Price = ComputedPrice(item.Price); + } + return skuMaps; + } + + + + /// + /// 指定区间价格 + /// + public class SectionItemPrice : MultiplesPrice + { + /// + /// 开始价格 + /// + public decimal BeginPrice { get; set; } = 0; + /// + /// 结束价格 + /// + public decimal EndPrice { get; set; } = 10000000; + } + } + + +} diff --git a/Store.Product/Models/PublishParameters/ProductStorage.cs b/Store.Product/Models/PublishParameters/ProductStorage.cs new file mode 100644 index 0000000..0360973 --- /dev/null +++ b/Store.Product/Models/PublishParameters/ProductStorage.cs @@ -0,0 +1,66 @@ +using Newtonsoft.Json; +using Store.Product.Enums; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Product.Models.PublishParameters +{ + + /// + /// 商品存储状态 + /// + public class ProductStorage + { + /// + /// 立即上架 + /// + public static ProductStorage OnSale + { + get + { + return new ProductStorage() { Status = ProductStorageStatus.立刻上架 }; + } + } + /// + /// 放入仓库 + /// + public static ProductStorage InStock + { + get + { + return new ProductStorage() { Status = ProductStorageStatus.放入仓库 }; + } + } + /// + /// 定时上架 + /// + /// + public static ProductStorage TimedOnSale(DateTime time) + { + return new ProductStorage { Status = ProductStorageStatus.定时上架, ShelfTime = time }; + } + public ProductStorage() + { + + } + public ProductStorage(ProductStorageStatus status, DateTime? time = null) + { + Status = status; + if (Status == ProductStorageStatus.定时上架) + { + ShelfTime = time; + } + } + /// + /// 发布商品状态 + /// + public ProductStorageStatus Status { get; set; } = ProductStorageStatus.立刻上架; + /// + /// 定时上架时间 + /// + public DateTime? ShelfTime { get; set; } + } +} diff --git a/Store.Product/Models/PublishParameters/PublishParameters.cs b/Store.Product/Models/PublishParameters/PublishParameters.cs new file mode 100644 index 0000000..0d70ef9 --- /dev/null +++ b/Store.Product/Models/PublishParameters/PublishParameters.cs @@ -0,0 +1,111 @@ +using Store.Framework.Models; +using Store.Framework.Models.Product; +using Store.Product.Enums; +using Store.Product.Models.Product; +using Store.Product.Models.PublishParameters.Image; +using Store.Product.Models.PublishParameters.Price; +using Store.Product.Models.PublishParameters.Sku; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Product.Models.PublishParameters; + +public class PublishParameters +{ + /// + /// 价格配置 + /// + public PriceConfigBase PriceStrategy { get; set; } + + + /// + /// 图片上传参数 + /// + public ImageParameters UpImageParams { get; set; } = new ImageParameters(); + + #region 重要参数 + /// + /// Sku 上传参数 + /// + public SkuParameters SkuParams { get; set; } = new SkuParameters(); + + /// 1 + /// 存储类型 上架Or仓库 Stock + /// + public ProductStorage StorageType { get; set; } = new ProductStorage(); + + + /// + /// 店铺上传最大限制 + /// + public int ShopUpMaxCount { get; set; } + + /// + /// 过滤商品价格 + /// + public SwitchParams> IsFilterPrice { get; set; } = new SwitchParams>(false, new Tuple(0, int.MaxValue)); + + /// + /// 运费模板类型 + /// + public FreightTemplateType FreightTemplateType { get; set; } = FreightTemplateType.Fixed; + + ///// + /// 运费模板 + /// + //public FreightTemplateModel FreightTemplate { get; set; } + + /// + /// 标题前缀 + /// + public string TitlePrefix { get; set; } + + #endregion + + + #region 其他参数 + /// + /// 自定义删除标题关键字 + /// + public bool IsRemoveCustomTitleKeywors { get; set; } + /// + /// 自定义过滤标题关键字 + /// + public bool IsFilterCustomTitleKeywors { get; set; } + + /// + /// 自定义类目 + /// + public SwitchParams IsCustomCategory { get; set; } = new SwitchParams(); + /// + /// 自定义描述 + /// + public string CustomDescription { get; set; } + + /// + /// 过滤非法标题 + /// + public bool IsFilterIllegalTitle { get; set; } + /// + /// 过滤不包邮 + /// + public bool IsFilterPackage { get; set; } + + /// + /// 是否设置最低价库存 + /// + public SwitchParams IsSetLowPriceStock { get; set; } = new SwitchParams(); + /// + /// 是否使用代理 + /// + public bool IsProxy { get; set; } + /// + /// 是否自动删除标题违规词语 + /// + public bool IsRemoveIllegalTitle { get; set; } + #endregion + +} diff --git a/Store.Product/Models/PublishParameters/Sku/CustomSku.cs b/Store.Product/Models/PublishParameters/Sku/CustomSku.cs new file mode 100644 index 0000000..2c2d130 --- /dev/null +++ b/Store.Product/Models/PublishParameters/Sku/CustomSku.cs @@ -0,0 +1,28 @@ + +using Store.Product.Models.PublishParameters.Price; + +namespace Store.Product.Models.PublishParameters.Sku +{ + /// + /// 自定义Sku + /// + public class CustomSku + { + /// + /// 标题 + /// + public string Title { get; set; } + /// + /// 翻倍 + /// + public MultiplesPrice Multiples { get; set; } + /// + /// 图片地址 + /// + public string ImagePath { get; set; } + /// + /// 库存 + /// + public int StockCount { get; set; } + } +} diff --git a/Store.Product/Models/PublishParameters/Sku/SkuParameters.cs b/Store.Product/Models/PublishParameters/Sku/SkuParameters.cs new file mode 100644 index 0000000..bf69580 --- /dev/null +++ b/Store.Product/Models/PublishParameters/Sku/SkuParameters.cs @@ -0,0 +1,27 @@ +using Store.Product.Models.PublishParameters; + +namespace Store.Product.Models.PublishParameters.Sku +{ + /// + /// 上传Sku配置 + /// + public class SkuParameters + { + /// + /// Sku名称随机增加特殊字符 + /// + public bool IsSkuPropertyRandomChar { get; set; } + /// + /// 随机删除Sku + /// + public bool IsRandomDelSku { get; set; } + /// + /// 自定义Sku + /// + public SwitchParams IsCustomSku { get; set; } = new SwitchParams(); + /// + /// 是否固定截取 + /// + public bool IsSkuFixedSplit { get; set; } + } +} diff --git a/Store.Product/Models/PublishParameters/SwitchParams.cs b/Store.Product/Models/PublishParameters/SwitchParams.cs new file mode 100644 index 0000000..500bc0a --- /dev/null +++ b/Store.Product/Models/PublishParameters/SwitchParams.cs @@ -0,0 +1,24 @@ +namespace Store.Product.Models.PublishParameters +{ + /// + /// 请求参数 是否开启 和值 + /// + /// + public class SwitchParams + { + /// + /// 是否开启 + /// + public bool IsOpened { get; set; } = false; + /// + /// 值 + /// + public T Value { get; set; } = default; + public SwitchParams() { } + public SwitchParams(bool isopen, T value) + { + IsOpened = isopen; + Value = value; + } + } +} diff --git a/Store.Product/Models/PublishParameters/Wx/WxProductUploadParam.cs b/Store.Product/Models/PublishParameters/Wx/WxProductUploadParam.cs new file mode 100644 index 0000000..88126c9 --- /dev/null +++ b/Store.Product/Models/PublishParameters/Wx/WxProductUploadParam.cs @@ -0,0 +1,263 @@ +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json.Linq; +using Store.Framework.Extensions; +using System; +using System.Collections.Generic; +using System.Drawing; + +namespace Store.Product.Models.PublishParameters.Wx; + +/// +/// Store.Product 新的微信商品上传参数实体。 +/// 说明:这里不修改 AllBase 中已有上传参数实体,当前作为迁移期承载对象复用其字段结构。 +/// +public class WxProductUploadParam +{ + public WxProductUploadSource Source { get; set; } = new(); + public WXUploadProductInitParam InitParameter { get; set; } = new(); + public WXUploadProductParam ProductParameter { get; set; } = new(); + public WxProductPublishState State { get; set; } = new(); + public WxProductUploadOptions Options { get; set; } = new(); + public WxProductUploadRuntime Runtime { get; set; } = new(); + + /// 步骤正常提前结束或最终成功时写入的返回 JSON。 + public string Result { get; set; } + + /// 正常提前结束发布时置为 true,流水线不再执行后续步骤。 + public bool IsCompleted { get; set; } +} + +public class WxProductUploadSource +{ + public ShopLoginInfo Shop { get; set; } + public KJDDBProduct DbProduct { get; set; } + public TemuDBProductSelect SelectProduct { get; set; } + public DateTime StartTime { get; set; } = DateTime.Now; +} + +/// +/// 原 UploadProduct 方法中跨 #region 使用的可变状态。 +/// TODO(迁移): 个别字段当前为 dynamic,需按 Store.Product 最终 DTO 改为明确类型。 +/// +public class WxProductPublishState +{ + public int CommissionRate { get; set; } + public int MinCommissionRate { get; set; } + public int MaxCommissionRate { get; set; } + public int CommissionRateChange { get; set; } + public int? FromCommissionRate { get; set; } + + public WXGetEditProductInfoLite WxEditProdInfo { get; set; } + public JObject ProdJObject { get; set; } = new(); + public bool IsChangeImgDic { get; set; } + public Dictionary WxImgMapDic { get; set; } = new(); + public PDDProductInfo ProdInfo { get; set; } + public int MainImgCount { get; set; } + public bool NeedAddRandomBlock { get; set; } + public bool WantAddRandomBlock { get; set; } + public WXShopTokenInfo Token { get; set; } + public string ProdTitle { get; set; } + public string ExtCode { get; set; } + public List MainGalleries { get; set; } = new(); + public List AiCateIdLst { get; set; } = new(); + public string GetCateRst { get; set; } + public JObject JCateRst { get; set; } + public long LeafCatId { get; set; } + public WxCatBoundInfo Bound { get; set; } + public int MaxBound { get; set; } + public int MinBound { get; set; } + public string SpBrandId { get; set; } + public string SpBrandName { get; set; } + public string ShippingTemplateJson { get; set; } + public int ShipPrice { get; set; } + public string ExpressRst { get; set; } + public List DetailGalleries { get; set; } = new(); + public WXUploadProductInfoRecommendParam RecommendParamRst { get; set; } = new(); + public allAttrParam ProductAttr { get; set; } + public int MaxSkuSpecCnt { get; set; } + + public int SkuIndex { get; set; } + public int Id { get; set; } + public long CatId { get; set; } + public long FCatId { get; set; } + public long ValueCatId { get; set; } + public Dictionary DicSpecParentNameId { get; set; } = new(); + public Dictionary DicSpecParentNameCatId { get; set; } = new(); + public Dictionary DicSpecNameFCatId { get; set; } = new(); + public Dictionary DicSpecNameCatId { get; set; } = new(); + + public string UploadDraftRst { get; set; } + public string CommissionRstStr { get; set; } + public string DefaultAdultBrandId { get; set; } + public bool IsWxNotUseSoftwareDefaultAddressOnce { get; set; } + public List WxShopTokenInfoList { get; set; } = new(); + public List QualificationImages { get; set; } = new(); + public List ForceFilterKeywords { get; set; } = new(); + public List AddPriceRules { get; set; } = new(); + public dynamic WxUploadRule { get; set; } + public string PddProductCates { get; set; } + public dynamic UploadDraftProductList { get; set; } +} + +/// +/// UploadProduct 从窗体控件和配置读取的入参快照。 +/// TODO(迁移): 后续可将这些字段逐步替换成 Store.Product 自己的发布规则模型。 +/// +public class WxProductUploadOptions +{ + public WxProductUploadFormState Form { get; set; } = new(); +} + +/// +/// 原方法依赖的窗体操作与通用上传能力适配入口。 +/// +public class WxProductUploadRuntime +{ + public object Host { get; set; } + public dynamic Cell { get; set; } = new WxUploadCellState(); + public dynamic CancellationTokenSource { get; set; } = new WxUploadCancellationState(); + + public Func GetAiImg { get; set; } + public Func UploadImgByUrl { get; set; } + public Func UploadImgByBitmap { get; set; } + public GetImgBase64ForApiHandler GetImgBase64ForApi { get; set; } + + public Action SetStatus { get; set; } + public Action Cancel { get; set; } + public Action ShowError { get; set; } +} + +public delegate string GetImgBase64ForApiHandler( + out string errorMsg, + string imgUrl, + bool needAddRandomBlock, + bool wantAddRandomBlock, + int imgWidth = 0, + int imgHeight = 0, + bool wantAddRandomBorder = false); + +public class WxUploadCellState +{ + private object _value; + + public object Value + { + get => _value; + set => _value = value; + } +} + +public class WxUploadCancellationState +{ + public Action CancelAction { get; set; } + + public void Cancel() + { + CancelAction?.Invoke(); + } +} + +/// +/// 原窗体控件的状态代理,避免 Store.Product 直接引用 WinForms 控件类型。 +/// +public class WxProductUploadFormState +{ + public dynamic chkFissionGoodsName { get; set; } = new WxControlState(); + public dynamic txtFissionTitle { get; set; } = new WxControlState(); + public dynamic chkSkipUploaded { get; set; } = new WxControlState(); + public dynamic chkCommission { get; set; } = new WxControlState(); + public dynamic cmbCommissionAddOrReduce { get; set; } = new WxControlState(); + public dynamic txtCommissionChangeValue { get; set; } = new WxControlState(); + public dynamic txtMinUpCommission { get; set; } = new WxControlState(); + public dynamic txtMaxUpCommission { get; set; } = new WxControlState(); + public dynamic chkImgRandomBlock { get; set; } = new WxControlState(); + public dynamic chkRandomMainImgOrder { get; set; } = new WxControlState(); + public dynamic chkRandomDetailImgOrder { get; set; } = new WxControlState(); + public dynamic chkFissionRandomPriceChange { get; set; } = new WxControlState(); + public dynamic txtFissionRandomPriceChange { get; set; } = new WxControlState(); + public dynamic chkHighestPriceSku { get; set; } = new WxControlState(); + public dynamic chkLowestPriceSku { get; set; } = new WxControlState(); + public dynamic chkSameSpecPriceUp { get; set; } = new WxControlState(); + public dynamic chkSameSpecPriceDown { get; set; } = new WxControlState(); + public dynamic txtMinPrice { get; set; } = new WxControlState(); + public dynamic txtMaxPrice { get; set; } = new WxControlState(); + public dynamic txtReplaceText { get; set; } = new WxControlState(); + public dynamic chkSkipReplaceText { get; set; } = new WxControlState(); + public dynamic chkRemoveSpecialChar { get; set; } = new WxControlState(); + public dynamic chkUseWXComplateProd { get; set; } = new WxControlState(); + public dynamic chkUseRecommand { get; set; } = new WxControlState(); + public dynamic cmbSpBrandName { get; set; } = new WxControlState(); + public dynamic txtFilterBoundMax { get; set; } = new WxControlState(); + public dynamic txtFilterBoundMin { get; set; } = new WxControlState(); + public dynamic txtMainBorderImgCount { get; set; } = new WxControlState(); + public dynamic txtDetailBorderImgCount { get; set; } = new WxControlState(); + public dynamic chkRandomImgBorder { get; set; } = new WxControlState(); + public dynamic chkAiImage { get; set; } = new WxControlState(); + public dynamic txtAiImgCount { get; set; } = new WxControlState(); + public dynamic chkFreightInsurance { get; set; } = new WxControlState(); + public dynamic chkUseShanghaiReturnOrderAddress { get; set; } = new WxControlState(); + public dynamic chkPreSale { get; set; } = new WxControlState(); + public dynamic chkDefaultBestBefore { get; set; } = new WxControlState(); + public dynamic txtDefaultBestBefore { get; set; } = new WxControlState(); + public dynamic chkTestNoAuditUpload { get; set; } = new WxControlState(); + public dynamic chkUpDraft { get; set; } = new WxControlState(); + public dynamic chkIsSubmitSuccess { get; set; } = new WxControlState(); + public dynamic dgvShop { get; set; } = new WxControlState(); +} + +public class WxControlState +{ + public bool Checked { get; set; } + public string Text { get; set; } = ""; + public decimal Value { get; set; } + public object SelectedValue { get; set; } + public object SelectedItem { get; set; } + public int SelectedIndex { get; set; } = -1; + public dynamic Items { get; set; } = new List(); + + public static WxControlState FromCheckBox(dynamic checkBox) + { + return new WxControlState + { + Checked = checkBox?.Checked == true, + Text = checkBox?.Text ?? "" + }; + } + + public static WxControlState FromTextBox(dynamic textBox) + { + return new WxControlState + { + Text = textBox?.Text ?? "" + }; + } + + public static WxControlState FromComboBox(dynamic comboBox) + { + if (comboBox == null) + { + return new WxControlState(); + } + + return new WxControlState + { + Text = comboBox.Text ?? "", + SelectedValue = comboBox.SelectedValue, + SelectedItem = comboBox.SelectedItem, + SelectedIndex = comboBox.SelectedIndex + }; + } + + public static WxControlState FromNumericUpDown(dynamic numericUpDown) + { + return new WxControlState + { + Value = numericUpDown?.Value ?? 0m, + Text = numericUpDown?.Text ?? "" + }; + } +} \ No newline at end of file diff --git a/Store.Product/Publish/IPublishStep.cs b/Store.Product/Publish/IPublishStep.cs new file mode 100644 index 0000000..fad0b2e --- /dev/null +++ b/Store.Product/Publish/IPublishStep.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Remoting.Contexts; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Product.Publish; + +public interface IPublishStep where TContext : PublishContextBase +{ + /// + /// 备注 + /// + string Description { get; } + /// + /// 执行前检测 true 继续执行,false 跳过 + /// + /// + /// + Task CanExecuteAsync(TContext context, CancellationToken token); + /// + /// 执行 + /// + /// + Task ExecuteAsync(TContext context, CancellationToken token); +} diff --git a/Store.Product/Publish/PublishContext .cs b/Store.Product/Publish/PublishContext .cs new file mode 100644 index 0000000..da9bc3f --- /dev/null +++ b/Store.Product/Publish/PublishContext .cs @@ -0,0 +1,44 @@ +using Store.Framework.Models; +using Store.Product.Models.Product; +using Store.Product.Models.PublishParameters; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish.Steps; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Product.Publish; + +/// +/// 发布上下文 +/// +public class PublishContextBase +{ + /// + /// 商铺 + /// + public ShopBase Shop { get; set; } + /// + /// 商铺名称 + /// + public string ShopName + { + get + { + if (Shop == null) + { + return ""; + } + return Shop.Name; + } + } + + public ProductBase Product { get; set; } + + public WXPublishParameters Parameters { get; set; } = new WXPublishParameters(); + + /// 微信小店商品上传的新上下文参数。 + public WxProductUploadParam WxUpload { get; set; } = new WxProductUploadParam(); +} \ No newline at end of file diff --git a/Store.Product/Publish/PublishPipeline.cs b/Store.Product/Publish/PublishPipeline.cs new file mode 100644 index 0000000..7491788 --- /dev/null +++ b/Store.Product/Publish/PublishPipeline.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Product.Publish; + +public class PublishPipeline where TContext : PublishContextBase +{ + private readonly IEnumerable> _steps; + + public PublishPipeline(IEnumerable> steps) + { + _steps = steps; + } + + public async Task ExecuteAsync( + TContext context, + CancellationToken token = default) + { + try + { + foreach (var step in _steps) + { + token.ThrowIfCancellationRequested(); + + if (await step.CanExecuteAsync(context, token)) + { + await step.ExecuteAsync(context, token); + } + + // 步骤正常提前结束(提交成功/上传到草稿箱)时会写入 Result 并标记完成,此处跳出后续步骤。 + if (context.WxUpload?.IsCompleted == true) + { + break; + } + } + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception) + { + throw; + } + } +} \ No newline at end of file diff --git a/Store.Product/Publish/PublishResult.cs b/Store.Product/Publish/PublishResult.cs new file mode 100644 index 0000000..af7acaf --- /dev/null +++ b/Store.Product/Publish/PublishResult.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Product.Publish; + +public class PublishResult +{ + public bool IsSuccess { get; set; } + public List Logs { get; set; } = new(); + public string? ErrorMessage { get; set; } + //public Product? PublishedProduct { get; set; } +} diff --git a/Store.Product/Publish/PublishStep.cs b/Store.Product/Publish/PublishStep.cs new file mode 100644 index 0000000..0011a90 --- /dev/null +++ b/Store.Product/Publish/PublishStep.cs @@ -0,0 +1,59 @@ +using MAX.Core.DependencyInjection; +using MAX.Core.Logger; +using MAX.Framework.Logger; +using Newtonsoft.Json.Linq; +using Store.Framework.Inerterfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Product.Publish; + +public abstract class PublishStep : IPublishStep, IDisposable where TContext : PublishContextBase +{ + protected ILoggerFacade Looger => LazyServiceProvider.Instance.LazyGetService(); + protected ICurrentUser CurrentUser => LazyServiceProvider.Instance.LazyGetService(); + public abstract string Description { get; } + public async Task CanExecuteAsync(TContext context, CancellationToken token) + { + try + { + token.ThrowIfCancellationRequested(); + return await CanExecuteProvider(context, token); + } + catch (OperationCanceledException ex) + { + return false; + } + catch (Exception ex) + { + Looger.Error(ex); + return false; + } + } + protected virtual Task CanExecuteProvider(TContext context, CancellationToken token) => Task.FromResult(true); + + public async Task ExecuteAsync(TContext context, CancellationToken token) + { + try + { + token.ThrowIfCancellationRequested(); + await ExecuteProvider(context, token); + } + + catch (Exception ex) + { + Looger.Error(ex); + throw ex; + } + } + + protected abstract Task ExecuteProvider(TContext context, CancellationToken token); + + public void Dispose() + { + throw new NotImplementedException(); + } +} diff --git a/Store.Product/Publish/Steps/CheckProductPropertyStep.cs b/Store.Product/Publish/Steps/CheckProductPropertyStep.cs new file mode 100644 index 0000000..022dd23 --- /dev/null +++ b/Store.Product/Publish/Steps/CheckProductPropertyStep.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Product.Publish.Steps; + +public class CheckProductPropertyStep : PublishStep +{ + public override string Description => "检查商品属性"; + + protected override Task ExecuteProvider(PublishContextBase context, CancellationToken token) + { + throw new NotImplementedException(); + } +} diff --git a/Store.Product/Publish/Steps/DetailStep/CheckPrice.cs b/Store.Product/Publish/Steps/DetailStep/CheckPrice.cs new file mode 100644 index 0000000..d25cddf --- /dev/null +++ b/Store.Product/Publish/Steps/DetailStep/CheckPrice.cs @@ -0,0 +1,35 @@ +using MAX.Core.Commons; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LittleCow.ProductModule.ExecActions +{ + /// + /// 检测价格 + /// + public class CheckPrice : PublishStep + { + public override string Description => "检测价格"; + + protected override Task ExecuteProvider(PublishContextBase context, CancellationToken token) + { + return Task.CompletedTask; + ////过滤上传商品价格 + //if (context.Parameters.IsFilterPrice.IsOpened) + //{ + // decimal PriceStart = context.Parameters.IsFilterPrice.Value.Item1; + // decimal PriceEnd = context.Parameters.IsFilterPrice.Value.Item2; + // if (context.Product.Price <= PriceStart || context.Product.Price >= PriceEnd) + // { + // //result.Msg = "上传商品价格不符合条件,成功过滤"; + // //result.NextStep = Emums.UpResultStatus.Return; + // } + //} + //return result; + } + } +} diff --git a/Store.Product/Publish/Steps/WXPublishParameters.cs b/Store.Product/Publish/Steps/WXPublishParameters.cs new file mode 100644 index 0000000..2f2719e --- /dev/null +++ b/Store.Product/Publish/Steps/WXPublishParameters.cs @@ -0,0 +1,16 @@ +using Store.Product.Models.PublishParameters; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Product.Publish.Steps; + +public class WXPublishParameters: PublishParameters +{ + /// + /// 跳过已存在的商品 + /// + public bool SkipExisting { get; set; } +} diff --git a/Store.Product/Publish/Steps/Wx/AddRandomImageBorderStep.cs b/Store.Product/Publish/Steps/Wx/AddRandomImageBorderStep.cs new file mode 100644 index 0000000..f4e7fea --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/AddRandomImageBorderStep.cs @@ -0,0 +1,169 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“为图片添加随机外框”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class AddRandomImageBorderStep : WxProductPublishStepBase +{ + public override string Description => "为图片添加随机外框"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + if (selectProd.is_fission_title && chkRandomImgBorder.Checked) + { + // selectProd.isFission + int mainCount = 9; + int detailCount = 1; + Invoke(new Action(() => + { + mainCount = txtMainBorderImgCount.Text.Trim().ParseToInt(); + detailCount = txtDetailBorderImgCount.Text.Trim().ParseToInt(); + })); + // api上传图片文件 边框主图 + if (1 == 1) + { + if (chkRandomMainImgOrder.Checked) + upProdParam.product.info.headImg = upProdParam.product.info.headImg.OrderBy(x => Guid.NewGuid()).Take(100).ToList(); + for (int n = 0; n < upProdParam.product.info.headImg.Count && n < mainCount; n++) + { + var imgUrl = upProdParam.product.info.headImg[n]; + try + { + string errMsg = ""; + //var imgBase64 = GetImgBase64ForApi(out errMsg, imgUrl, needAddRandomBlock, wantAddRandomBlock, 800, 800); + var imgBase64 = GetImgBase64ForApi(out errMsg, imgUrl, false, false, 800, 800, true); + if (!string.IsNullOrEmpty(errMsg)) + { + throw new Exception(errMsg); + } + + var urlImg = BaseClass.BuildWXImgObjString("product/wx_api_upload_image"); + var upDate = UserLogin.GetSignaturex(); + upDate += "&token=" + token.accessToken; + //upDate += "&is_file=true"; + //if (needAddRandomBlock || wantAddRandomBlock) + // upDate += "&random_code=true"; + upDate += "&img_url=" + imgUrl; + upDate += "&img_base64=" + imgBase64; + + var sTime = DateTime.Now; + var apiUpImgFileRst = GetHtml.PostUtf8(urlImg, upDate); + if (cell != null) + { + Invoke(new Action(() => + { + cell.Value = "正在优化主图文件 " + (DateTime.Now - sTime).TotalMilliseconds.ParseToInt(); + })); + } + + for (int i = 0; i < 5 && apiUpImgFileRst.Contains("access_token expired"); i++) + { + token.mdyTime = DateTime.Now.AddDays(-100); + token = new WxApiBLL().GetRandomWxToken(); + apiUpImgFileRst = GetHtml.PostUtf8(urlImg, upDate); + } + var upImgUrl = BaseRegex.GetStringJson(ref apiUpImgFileRst, "img_url"); + if (apiUpImgFileRst.Contains("\"errcode\":0") && !string.IsNullOrEmpty(upImgUrl)) + { + upProdParam.product.info.headImg[n] = upImgUrl; + } + else + { + string upImgErrMsg = BaseRegex.GetStringJsonByRegs(ref apiUpImgFileRst, apiUpImgFileRst, "msg", "errMsg", "errorMsg", "err_msg", "error_msg"); + throw new Exception("优化主图失败 " + upImgErrMsg); + } + } + catch (Exception ex) + { + throw new Exception("优化主图失败 " + ex.Message); + } + } + } + // api上传图片文件 边框详情图 + if (1 == 1) + { + if (chkRandomDetailImgOrder.Checked) + upProdParam.product.info.detail.detailImg = upProdParam.product.info.detail.detailImg.OrderBy(x => Guid.NewGuid()).Take(100).ToList(); + for (int n = 0; n < upProdParam.product.info.detail.detailImg.Count && n < detailCount; n++) + { + var imgUrl = upProdParam.product.info.detail.detailImg[n]; + try + { + string errMsg = ""; + //var imgBase64 = GetImgBase64ForApi(out errMsg, imgUrl, needAddRandomBlock, wantAddRandomBlock, 800, 800); + var imgBase64 = GetImgBase64ForApi(out errMsg, imgUrl, false, false, 800, 800, true); + if (!string.IsNullOrEmpty(errMsg)) + { + throw new Exception(errMsg); + } + + var urlImg = BaseClass.BuildWXImgObjString("product/wx_api_upload_image"); + var upDate = UserLogin.GetSignaturex(); + upDate += "&token=" + token.accessToken; + //upDate += "&is_file=true"; + //if (needAddRandomBlock || wantAddRandomBlock) + // upDate += "&random_code=true"; + upDate += "&img_url=" + imgUrl; + upDate += "&img_base64=" + imgBase64; + + var sTime = DateTime.Now; + var apiUpImgFileRst = GetHtml.PostUtf8(urlImg, upDate); + if (cell != null) + { + Invoke(new Action(() => + { + cell.Value = "正在优化详情图文件 " + (DateTime.Now - sTime).TotalMilliseconds.ParseToInt(); + })); + } + + for (int i = 0; i < 5 && apiUpImgFileRst.Contains("access_token expired"); i++) + { + token.mdyTime = DateTime.Now.AddDays(-100); + token = new WxApiBLL().GetRandomWxToken(); + apiUpImgFileRst = GetHtml.PostUtf8(urlImg, upDate); + } + var upImgUrl = BaseRegex.GetStringJson(ref apiUpImgFileRst, "img_url"); + if (apiUpImgFileRst.Contains("\"errcode\":0") && !string.IsNullOrEmpty(upImgUrl)) + { + upProdParam.product.info.detail.detailImg[n] = upImgUrl; + } + else + { + string upImgErrMsg = BaseRegex.GetStringJsonByRegs(ref apiUpImgFileRst, apiUpImgFileRst, "msg", "errMsg", "errorMsg", "err_msg", "error_msg"); + throw new Exception("优化详情图失败 " + upImgErrMsg); + } + } + catch (Exception ex) + { + throw new Exception("优化详情图失败 " + ex.Message); + } + } + } + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/AddTitlePrefixStep.cs b/Store.Product/Publish/Steps/Wx/AddTitlePrefixStep.cs new file mode 100644 index 0000000..bc471ac --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/AddTitlePrefixStep.cs @@ -0,0 +1,40 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“添加标题前缀”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class AddTitlePrefixStep : WxProductPublishStepBase +{ + public override string Description => "添加标题前缀"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + if (!string.IsNullOrWhiteSpace(wxUploadRule.TitleHeadWords)) + upProdParam.product.info.title = wxUploadRule.TitleHeadWords + upProdParam.product.info.title; + } +} diff --git a/Store.Product/Publish/Steps/Wx/BuildSkuInfoStep.cs b/Store.Product/Publish/Steps/Wx/BuildSkuInfoStep.cs new file mode 100644 index 0000000..fb773c0 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/BuildSkuInfoStep.cs @@ -0,0 +1,171 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; +using Store.Framework.Extensions; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“构建SKU信息”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class BuildSkuInfoStep : WxProductPublishStepBase +{ + public override string Description => "构建SKU信息"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + upProdParam.product.productSkus = new List(); + skuIndex = 0; + id = 3; + catId = 200000000003; + fCatId = 200000000003; + valueCatId = (17 + Randomx.GetNumber(11)).ParseToLong(); + dicSpecParentNameId = new Dictionary(); + dicSpecParentNameCatId = new Dictionary(); + dicSpecNamefCatId = new Dictionary(); + dicSpecNameCatId = new Dictionary(); + foreach (var skuInfo in prodInfo.detail.sku) + { + foreach (var spec in skuInfo.spec) + { + if (!dicSpecParentNameId.Keys.Contains(spec.parent_name)) + { + dicSpecParentNameId[spec.parent_name] = id++; + dicSpecParentNameCatId[spec.parent_name] = catId++; + } + if (!dicSpecNamefCatId.Keys.Contains(spec.spec_name)) + { + dicSpecNamefCatId[spec.spec_name] = fCatId++; + dicSpecNameCatId[spec.spec_name] = valueCatId++; + } + } + } + if (dbProd.product_get_type_text == "厂商直采" && !prodInfo.detail.sku.Any(s => s.quantity.ParseToInt() > 0)) + { + throw new Exception("源商品库存为0"); + } + foreach (var skuInfo in prodInfo.detail.sku) + { + productSkuParam skuParam = new productSkuParam(); + upProdParam.product.productSkus.Add(skuParam); + for (int i = skuInfo.spec.Count - 1; i >= 0; i--) + { + var specInfo = skuInfo.spec[i]; + skuParam.__skuKey = specInfo.parent_name + "." + specInfo.spec_name + "_"; + skuParam.__skuKey += specInfo.spec_name; + } + skuParam.__skuKey.TrimEnd('_'); + skuParam.index = skuIndex++; + skuParam.productSkuInfo = new productSkuInfoParam(); + // 加价 + skuParam.productSkuInfo.salePrice = (skuInfo.multi_price * defaultAddPriceRule.AddPriceTimes + defaultAddPriceRule.AddMorePrice * 100).ParseToInt(); + if (defaultAddPriceRule.AddMorePriceFirst) + skuParam.productSkuInfo.salePrice = ((skuInfo.multi_price + defaultAddPriceRule.AddMorePrice * 100) * defaultAddPriceRule.AddPriceTimes).ParseToInt(); + // 阶梯式加价,如果命中则按阶梯式加价进行 + if (defaultAddPriceRule.IsStepAddPrice) + { + foreach (var addPrice in lstAddPriceRule) + { + if (skuInfo.multi_price > addPrice.MoreThanPrice * 100 && skuInfo.multi_price <= addPrice.LessThanPrice * 100) + { + if (defaultAddPriceRule.AddMorePriceFirst) // 先加后乘 + skuParam.productSkuInfo.salePrice = ((skuInfo.multi_price + addPrice.AddMorePrice * 100) * addPrice.AddPriceTimes).ParseToInt(); + else + skuParam.productSkuInfo.salePrice = (skuInfo.multi_price * addPrice.AddPriceTimes + addPrice.AddMorePrice * 100).ParseToInt(); + break; + } + } + } + // 聚水潭上微信小店,加运费(其他平台shipPrice默认为0) + skuParam.productSkuInfo.salePrice += (shipPrice * defaultAddPriceRule.ShippingPriceTimes).ParseToInt(); + // 商品裂变随机增加价格 + if (selectProd.is_fission_title && chkFissionRandomPriceChange.Checked) + { + Invoke(new Action(() => + { + var fissionRandomPrice = Randomx.GetId((txtFissionRandomPriceChange.Text.ParseToDecimal() * 100).ParseToInt() + 1); + skuParam.productSkuInfo.salePrice += fissionRandomPrice; + })); + } + // sku图 + skuParam.productSkuInfo.thumbImg = upProdParam.product.info.productSaleAttrs[0].items.FirstOrDefault(a => a.value == skuInfo.spec[0].spec_name)?.imgUrl; + if (dbProd.special_prod_type == "爆款数据") + { + if (!skuInfo.out_sku_sn.StartsWith("BS")) // Best-seller 爆款 + skuInfo.out_sku_sn = "BS" + skuInfo.out_sku_sn; + } + skuParam.productSkuInfo.skuCode = skuInfo.out_sku_sn; + skuParam.productSkuInfo.outSkuId = skuInfo.out_sku_sn; + skuParam.productSkuInfo.out_sku_id = skuInfo.out_sku_sn; + //skuParam.productSkuInfo.barCode = skuInfo.out_sku_sn; // 条形码 + skuParam.skuKey.outSkuId = skuInfo.out_sku_sn; + skuParam.skuKey.productKey = new productKeyParam(); + skuParam.skuKey.productKey.outProductId = upProdParam.product.info.spuCode; + // 库存 + skuParam.productSkuInfo.stockInfo = new stockInfoParam() { stockNum = defaultAddPriceRule.StockNum }; + // 厂商直采上家库存为0则按0上架 + if (dbProd.product_get_type_text == "厂商直采" && skuInfo.quantity.IsNullOrZero()) + skuParam.productSkuInfo.stockInfo.stockNum = 0; + skuParam.productSkuInfo.saleParam = new List(); + foreach (var specInfo in skuInfo.spec) + { + saleParamParam saleParam = new saleParamParam(); + skuParam.productSkuInfo.saleParam.Add(saleParam); + saleParam.categorys = new List(); + + // specKey + categoryKeyParam skuCateKeyParam = new categoryKeyParam(); + saleParam.categorys.Add(skuCateKeyParam); + skuCateKeyParam.isCustomize = true; + skuCateKeyParam.isMainSale = true; + skuCateKeyParam.name = specInfo.parent_name; + skuCateKeyParam.id = dicSpecParentNameId[specInfo.parent_name].ParseToInt(); // 3; + skuCateKeyParam.catId = dicSpecParentNameCatId[specInfo.parent_name]; + skuCateKeyParam.label = "自定义规格"; + skuCateKeyParam.catInfo = new skuCatInfoParam() { isCustomize = 1 }; + skuCateKeyParam.catType = 8; + + // specValue + categoryValueParam skuCateValueParam = new categoryValueParam(); + saleParam.categorys.Add(skuCateValueParam); + skuCateValueParam.fCatId = dicSpecNamefCatId[specInfo.spec_name]; + skuCateValueParam.catId = dicSpecNameCatId[specInfo.spec_name]; + skuCateValueParam.name = specInfo.spec_name; + skuCateValueParam.skuNameValue = specInfo.spec_name; + skuCateValueParam.desc = ""; + skuCateValueParam.catInfo = new categoryValueCatInfo() { editByUser = 1 }; + skuCateValueParam.thumbImg = ""; + var specIndex = skuInfo.spec.IndexOf(specInfo); + skuCateValueParam.priority = prodInfo.detail.sku.Select(s => s.spec[specIndex].spec_name).Distinct().ToList().IndexOf(specInfo.spec_name); + skuCateValueParam.selected = false; + } + skuParam.productSkuInfo.skuDeliverInfo = new skuDeliverInfoParam(); + skuParam.productSkuInfo.stockDiff = new stockDiffParam() { diffType = 1, num = defaultAddPriceRule.StockNum }; + // 厂商直采上家库存为0则按0上架 + if (dbProd.product_get_type_text == "厂商直采" && skuInfo.quantity.IsNullOrZero()) + skuParam.productSkuInfo.stockDiff.num = 0; + skuParam.status = 5; + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/BuildSkuPropertiesStep.cs b/Store.Product/Publish/Steps/Wx/BuildSkuPropertiesStep.cs new file mode 100644 index 0000000..47d3825 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/BuildSkuPropertiesStep.cs @@ -0,0 +1,342 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“构建SKU规格属性”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class BuildSkuPropertiesStep : WxProductPublishStepBase +{ + public override string Description => "构建SKU规格属性"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + + if (cell != null) + { + Invoke(new Action(() => + { + cell.Value = "正在生成sku"; + })); + } + Thread.Sleep(Randomx.GetId(100, 300)); + + upProdParam.product.info.productSaleAttrs = new List(); + if (prodInfo.detail.sku == null) + prodInfo.detail.sku = new List(); + if (prodInfo.detail.sku.Count == 0) + prodInfo.detail.sku.Add(new sku_item() { spec = new List() }); + if (prodInfo.detail.sku.FirstOrDefault().spec == null) + prodInfo.detail.sku.FirstOrDefault().spec = new List(); + if (prodInfo.detail.sku.FirstOrDefault().spec.Count == 0) + prodInfo.detail.sku.FirstOrDefault().spec.Add(new spec_item() { parent_name = "规格", spec_name = "默认" }); + if (prodInfo.detail.sku.FirstOrDefault().thumb_url.IsNullOrEmpty()) + prodInfo.detail.sku.FirstOrDefault().thumb_url = prodInfo.detail.galleries.FirstOrDefault(g => g.type == 1)?.url; + + maxSkuSpecCnt = prodInfo.detail.sku.Max(x => x.spec?.Count).ParseToInt(); + foreach (var skuTmp in prodInfo.detail.sku.ToList()) + { + if (skuTmp.spec?.Count != maxSkuSpecCnt) + prodInfo.detail.sku.Remove(skuTmp); + } + + // 处理系统指定的规格名称 + //if ((upProdParam.product.info.category.FirstOrDefault()?.name == "食品饮料" || prodInfo.detail.cats.FirstOrDefault() == "食品饮料") && + // (selectProd.cat_name == "坚果炒货" || selectProd.cat_name == "果干")) + //{ + // if (!prodInfo.detail.sku.FirstOrDefault().spec.Any(s => s.parent_name == "规格/净含量")) + // { + // foreach (var sku in prodInfo.detail.sku) + // { + // sku.spec.FirstOrDefault().parent_name = "规格/净含量"; + // } + // } + //} + ////else if ((upProdParam.product.info.category.FirstOrDefault()?.name == "食品饮料" || prodInfo.detail.cats.FirstOrDefault() == "食品饮料")) + ////{ + //// if (!prodInfo.detail.sku.FirstOrDefault().spec.Any(s => s.parent_name == "规格")) + //// { + //// foreach (var sku in prodInfo.detail.sku) + //// { + //// sku.spec.FirstOrDefault().parent_name = "规格"; + //// } + //// } + ////} + // 坚果炒货类,系统返回的试规格/净含量但是要求填规格 + if ((upProdParam.product.info?.category?.FirstOrDefault()?.name == "食品饮料" || prodInfo.detail?.cats?.FirstOrDefault() == "食品饮料") && + (upProdParam.product.info?.category?.LastOrDefault()?.name == "坚果炒货" || upProdParam.product.info?.category?.LastOrDefault()?.name == "果干")) + { + if (!prodInfo.detail.sku.FirstOrDefault().spec.Any(s => s.parent_name == "规格")) + { + foreach (var sku in prodInfo.detail.sku) + { + sku.spec.FirstOrDefault().parent_name = "规格"; + } + } + } + else + { + var leafCat = upProdParam.product.info.category.LastOrDefault(); + if (leafCat?.catInfo?.saleAttr?.attr != null) + { + foreach (var att in leafCat.catInfo.saleAttr.attr) + { + //decimal4_unit //g;kg;斤 + if (att.isRequired == true) + { + // 规格属性名放在第二个规格上 + if (att.normalRelease.ruleType == 2 && prodInfo.detail.sku.FirstOrDefault()?.spec.Count == 2) + { + foreach (var sku in prodInfo.detail.sku) + { + // 如果有指定规格属性名称就不再设置 + if (!sku.spec.Any(sp => sp.parent_name == att.name)) + { + sku.spec[1].parent_name = att.name; + } + // 如果不在指定位置上,交换位置 + else if (sku.spec[0].parent_name == att.name) + { + var specTmp = sku.spec[0]; + sku.spec[0] = sku.spec[1]; + sku.spec[1] = specTmp; + } + if (att.type == "decimal4_unit") + { + if (att.value == "g;kg;斤") + { + var specName = ""; + foreach (var unit in att.value.Split(';').Reverse().ToList()) + { + Match specMatch = Regex.Match(sku.spec[1].spec_name.Replace(" ", "").Replace("-", "负号"), @"\d+(.\d+)?" + unit); + //Match specMatch = Regex.Match(sku.spec[1].spec_name.Replace(" ", ""), @"((0{1}.\d{1,4})|([1-9]\d{1}.\d{1,4})|([1-9]+\d)|0)" + unit); + if (specMatch.Success) + { + specName = specMatch.Value.Replace(unit, "")/*.ParseToDecimal().ParseToInt()*/ + " " + unit; + break; + } + } + if (string.IsNullOrEmpty(specName)) + specName = Randomx.GetId(10, 500) + " g"; + sku.spec[1].spec_name = specName; + } + } + } + } + // 规格属性名放在第一个规格上 + else + { + foreach (var sku in prodInfo.detail.sku) + { + // 如果有指定规格属性名称就不再设置 + if (!sku.spec.Any(sp => sp.parent_name == att.name)) + { + sku.spec[0].parent_name = att.name; + } + // 如果不在指定位置上,交换位置 + else if (sku.spec.Count > 1 && sku.spec[1].parent_name == att.name) + { + var specTmp = sku.spec[0]; + sku.spec[0] = sku.spec[1]; + sku.spec[1] = specTmp; + } + if (att.type == "decimal4_unit") + { + if (att.value == "g;kg;斤") + { + var specName = ""; + foreach (var unit in att.value.Split(';').Reverse().ToList()) + { + Match specMatch = Regex.Match(sku.spec[0].spec_name.Replace(" ", "").Replace("-", "负号"), @"\d+(.\d+)?" + unit); + //Match specMatch = Regex.Match(sku.spec[0].spec_name.Replace(" ", ""), @"((0{1}.\d{1,4})|([1-9]\d{1}.\d{1,4})|([1-9]+\d)|0)" + unit); + if (specMatch.Success) + { + specName = specMatch.Value.Replace(unit, "")/*.ParseToDecimal().ParseToInt()*/ + " " + unit; + break; + } + } + if (string.IsNullOrEmpty(specName)) + specName = Randomx.GetId(10, 500) + " g"; + sku.spec[0].spec_name = specName; + } + } + } + } + } + } + } + } + + if (prodInfo.detail.sku.FirstOrDefault()?.spec?.Count > 0) + { + productSaleAttrParam attrParam = new productSaleAttrParam() + { + key = prodInfo.detail.sku.FirstOrDefault()?.spec[0].parent_name, + hasImg = true, + items = new List() + }; + upProdParam.product.info.productSaleAttrs.Add(attrParam); + foreach (var sku in prodInfo.detail.sku) + { + var spec = sku.spec[0]; + if (!attrParam.items.Any(it => it.value == spec.spec_name) && !string.IsNullOrEmpty(sku.thumb_url)) + { + if (dbProd.product_get_type_text == "微信店采") + { + attrParam.items.Add(new productSaleAttrItemParam() { value = spec.spec_name, imgUrl = sku.thumb_url, }); + continue; + } + + // 获取缓存盗链 sku图 + if (wxImgMapDic.ContainsKey(BaseClass.GetMd5(sku.thumb_url))) + { + attrParam.items.Add(new productSaleAttrItemParam() { value = spec.spec_name, imgUrl = wxImgMapDic[BaseClass.GetMd5(sku.thumb_url)], }); + continue; + } + // 使用接口链接传sku图 + if (1 == 1 && !sku.thumb_url.Contains("wst.wxapp.tc.qq.com")) + { + var urlImg = BaseClass.BuildWXImgObjString("product/wx_api_upload_image"); + var upDate = UserLogin.GetSignaturex(); + upDate += "&token=" + token.accessToken; + upDate += "&post_data=" + GetHtml.UrlEncodeUtf8("{\"img_url\":\"" + sku.thumb_url.Replace("https://", "http://") + "\"}"); + + var sTime = DateTime.Now; + var upDateRst = GetHtml.PostUtf8(urlImg, upDate); + if (cell != null) + { + Invoke(new Action(() => + { + cell.Value = "正在生成sku " + (DateTime.Now - sTime).TotalMilliseconds.ParseToInt(); + })); + } + + for (int i = 0; i < 20 && upDateRst.Contains("access_token expired"); i++) + { + token.mdyTime = DateTime.Now.AddDays(-100); + token = new WxApiBLL().GetRandomWxToken(); + upDateRst = GetHtml.PostUtf8(urlImg, upDate); + } + var upImgUrl = BaseRegex.GetStringJson(ref upDateRst, "img_url"); + if (upDateRst.Contains("\"errcode\":0") && !string.IsNullOrEmpty(upImgUrl)) + { + attrParam.items.Add(new productSaleAttrItemParam() { value = spec.spec_name, imgUrl = upImgUrl, }); + wxImgMapDic[BaseClass.GetMd5(sku.thumb_url)] = upImgUrl; + isChangeImgDic = true; + continue; // 传图成功跳过模拟上传 + } + else + { + } + } + + // api上传图片文件 sku图 + if (1 == 1) + { + try + { + string errMsg = ""; + var imgBase64 = GetImgBase64ForApi(out errMsg, sku.thumb_url, false, false); + if (!string.IsNullOrEmpty(errMsg)) + { + throw new Exception(errMsg); + } + + var urlImg = BaseClass.BuildWXImgObjString("product/wx_api_upload_image"); + var upDate = UserLogin.GetSignaturex(); + upDate += "&token=" + token.accessToken; + //upDate += "&is_file=true"; + //if (needAddRandomBlock || wantAddRandomBlock) + // upDate += "&random_code=true"; + upDate += "&img_url=" + sku.thumb_url; + upDate += "&img_base64=" + imgBase64; + + var sTime = DateTime.Now; + var apiUpImgFileRst = GetHtml.PostUtf8(urlImg, upDate); + if (cell != null) + { + Invoke(new Action(() => + { + cell.Value = "正在上传商品sku图文件 " + (DateTime.Now - sTime).TotalMilliseconds.ParseToInt(); + })); + } + + for (int i = 0; i < 5 && apiUpImgFileRst.Contains("access_token expired"); i++) + { + token.mdyTime = DateTime.Now.AddDays(-100); + token = new WxApiBLL().GetRandomWxToken(); + apiUpImgFileRst = GetHtml.PostUtf8(urlImg, upDate); + } + var upImgUrl = BaseRegex.GetStringJson(ref apiUpImgFileRst, "img_url"); + if (apiUpImgFileRst.Contains("\"errcode\":0") && !string.IsNullOrEmpty(upImgUrl)) + { + attrParam.items.Add(new productSaleAttrItemParam() { value = spec.spec_name, imgUrl = upImgUrl, }); + wxImgMapDic[BaseClass.GetMd5(sku.thumb_url)] = upImgUrl; + isChangeImgDic = true; + continue; + } + else + { + } + } + catch (Exception ex) + { + throw new Exception("修改详情图失败 " + ex.Message); + } + } + + // 上传sku图 + var locationStr = UploadImg(shop, sku.thumb_url, cell); + if (!locationStr.Contains("\"success\":true")) + throw new Exception(locationStr); + var location = BaseRegex.GetStringJson(ref locationStr, "Location"); + attrParam.items.Add(new productSaleAttrItemParam() { value = spec.spec_name, imgUrl = location, }); + Thread.Sleep(Randomx.GetId(10, 50)); + + //wxImgMapDic[BaseClass.GetMd5(sku.thumb_url)] = location; + //isChangeImgDic = true; + } + } + } + if (prodInfo.detail.sku.FirstOrDefault()?.spec?.Count > 1) + { + productSaleAttrParam attrParam = new productSaleAttrParam() + { + key = prodInfo.detail.sku.FirstOrDefault()?.spec[1].parent_name, + hasImg = false, + items = new List() + }; + upProdParam.product.info.productSaleAttrs.Add(attrParam); + foreach (var sku in prodInfo.detail.sku) + { + var spec = sku.spec[1]; + if (!attrParam.items.Any(it => it.value == spec.spec_name)) + attrParam.items.Add(new productSaleAttrItemParam() { value = spec.spec_name }); + } + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/CalculateShippingTemplateStep.cs b/Store.Product/Publish/Steps/Wx/CalculateShippingTemplateStep.cs new file mode 100644 index 0000000..f3b5c71 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/CalculateShippingTemplateStep.cs @@ -0,0 +1,141 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Framework.Extensions; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“获取推单运费模板并计算运费”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class CalculateShippingTemplateStep : WxProductPublishStepBase +{ + public override string Description => "获取推单运费模板并计算运费"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + if (cell != null) + { + Invoke(new Action(() => + { + cell.Value = "正在选取运费模板"; + })); + } + + shippingTemplateJson = ""; + shipPrice = 0; + + if (dbProd.product_get_type_text == "厂商直采") + { + var urlShipping = BaseClass.BuildObjString("product/wx_get_shipping_template"); + var dataShipping = Work.UserLogin.GetSignaturex() + + "&coid=" + dbProd.skc_id; + var htmlShipping = GetHtml.PostUtf8(urlShipping, dataShipping); + if (string.IsNullOrEmpty(htmlShipping) || !htmlShipping.Contains("\"code\":200")) + htmlShipping = GetHtml.PostUtf8(urlShipping, dataShipping); + if (string.IsNullOrEmpty(htmlShipping) || !htmlShipping.Contains("\"code\":200")) + { + } + else + { + if (htmlShipping.Contains("\"data\":\"\"")) + { + } + else + { + try + { + shippingTemplateJson = JsonHelper.SerializeObject(JsonHelper.JObjectParse(htmlShipping)["data"]); + } + catch + { + + } + } + } + + if (string.IsNullOrEmpty(shippingTemplateJson)) + { + // 获取运费模板 + var urlShip = BaseClass.BuildObjString("product/e57_get_freight_template"); + var dataShip = Work.UserLogin.GetSignaturex() + "&coid=" + dbProd.skc_id; + var shipRst = GetHtml.PostUtf8(urlShip, dataShip); + if (string.IsNullOrEmpty(shipRst) || !shipRst.Contains("\"code\":0")) + shipRst = GetHtml.PostUtf8(urlShipping, dataShipping); + if (!shipRst.Contains("\"code\":0")) + { + var errMsg = BaseRegex.GetStringJson(ref shipRst, "msg"); + if (string.IsNullOrEmpty(errMsg)) + errMsg = shipRst; + throw new Exception("运费模板获取失败 " + errMsg); + } + try + { + //[{"id":"204","warehouseNum":"15016108","freightType":0,"provinces":"香港特别行政区,台湾省,澳门特别行政区","freightPrice":9999900,"createBy":"vmslin","createTime":"2026-01-30T16:45:03+08:00","updateBy":"vmslin","updateTime":"2026-01-30T16:45:03+08:00"},{"id":"205","warehouseNum":"15016108","freightType":3,"provinces":"海南省,新疆维吾尔自治区,西藏自治区,内蒙古自治区","freightPrice":1530,"createBy":"vmslin","createTime":"2026-01-30T16:45:51+08:00","updateBy":"vmslin","updateTime":"2026-01-30T16:45:51+08:00"},{"id":"206","warehouseNum":"15016108","freightType":1,"provinces":"北京市,上海市","freightPrice":410,"createBy":"vmslin","createTime":"2026-01-30T16:46:04+08:00","updateBy":"vmslin","updateTime":"2026-01-30T16:46:04+08:00"},{"id":"207","warehouseNum":"15016108","freightType":1,"provinces":"安徽省,重庆市,福建省,广东省,甘肃省,广西壮族自治区,贵州省,河北省,湖北省,黑龙江省,河南省,湖南省,吉林省,江苏省,江西省,辽宁省,宁夏回族自治区,青海省,四川省,山东省,山西省,陕西省,天津市,云南省,浙江省","freightPrice":310,"createBy":"vmslin","createTime":"2026-01-30T16:46:17+08:00","updateBy":"vmslin","updateTime":"2026-01-30T16:46:17+08:00"}] + shippingTemplateJson = JsonHelper.SerializeObject(JsonHelper.JObjectParse(shipRst)["data"]); + if (shipRst.Contains("\"code\":0") && !string.IsNullOrEmpty(shippingTemplateJson)) + { + urlShipping = BaseClass.BuildObjString("product/wx_add_mdy_shipping_template"); + dataShipping = Work.UserLogin.GetSignaturex() + + "&coid=" + dbProd.skc_id + + "&shipping_template_json=" + shippingTemplateJson; + htmlShipping = GetHtml.PostUtf8(urlShipping, dataShipping); + if (string.IsNullOrEmpty(htmlShipping) || !htmlShipping.Contains("\"code\":200")) + htmlShipping = GetHtml.PostUtf8(urlShipping, dataShipping); + } + } + catch (Exception ex) + { + throw new Exception("运费模板解析失败 " + ex.Message); + } + } + + if (!string.IsNullOrEmpty(shippingTemplateJson)) + { + try + { + int count = 0; + var shipObjs = JArray.Parse(shippingTemplateJson); + foreach (var shipObj in shipObjs) + { + var provincesStr = shipObj["provinces"].ParseToString(); + if (provincesStr.Split(',').Length > count) + { + count = provincesStr.Split(',').Length; + shipPrice = shipObj["freightPrice"].ParseToInt(); + } + } + } + catch (Exception ex) + { + throw new Exception("运费模板获取失败 " + ex.Message); + } + } + if (shipPrice <= 0) + { + throw new Exception("运费模板获取失败 未获取到"); + } + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/CommissionPreparationStep.cs b/Store.Product/Publish/Steps/Wx/CommissionPreparationStep.cs new file mode 100644 index 0000000..c9054d1 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/CommissionPreparationStep.cs @@ -0,0 +1,111 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Framework.Extensions; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“处理佣金上传”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class CommissionPreparationStep : WxProductPublishStepBase +{ + public override string Description => "处理佣金上传"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + if (BaseClass.UserAuthority.Contains("佣金上货") && chkCommission.Checked) + { + if (dbProd.special_prod_type != "爆款数据" && dbProd.product_get_type_text != "微信小店") + throw new Exception("拥金上货失败:仅爆款微信小店数据支持佣金上传!"); + + var rst = WXApi.GetAndUpdateWebStatus(context.Shop.Cookie); + if (!rst.Html.Contains("\"isOpenLeague\":true")) + { + throw new Exception("拥金上货失败:需要开通优选联盟!"); + } + + var url = BaseClass.BuildObjString("product/wx_get_commission_rate"); + var date = UserLogin.GetSignaturex(); + date += "&spu_ids=" + dbProd.spu_id; + + var prodCommissionRst = GetHtml.PostUtf8(url, date); + if (!prodCommissionRst.Contains("\"code\":200")) + { + var errMsg = BaseRegex.GetStringJsonByRegs(ref prodCommissionRst, prodCommissionRst, "msg"); + throw new Exception("拥金上货失败:" + errMsg); + } + fromCommissionRate = BaseRegex.GetStringJson(ref prodCommissionRst, "commission_rate").ParseToNullableInt(); + if (fromCommissionRate == null) + fromCommissionRate = -1; + if (prodCommissionRst.Contains("\"data\":[]") || BaseRegex.GetStringJson(ref prodCommissionRst, "mdy_time").ParseToString().ParseToDateTime() < DateTime.Now.AddDays(-30)) + { + var assistantShop = new ShopLoginInfo(); + assistantShop.Cookies = JsonHelper.SerializeObject(lstWxSaleAssistantCookie); + var result = WXApi.wxGetAssistant(context.Shop.Cookie, dbProd.spu_id); + if (result.Html.Contains("mcn magic invalid")) + { + //throw new Exception("拥金上货失败:请先登录上货助手"); + Invoke(new Action(() => + { + //BaseMsgForm.ShowError + btnLoginAssistant_Click(null, null); + })); + assistantShop.Cookies = JsonHelper.SerializeObject(lstWxSaleAssistantCookie); + result = WXApi.wxGetAssistant(assistantShop, dbProd.spu_id); + if (result.Contains("mcn magic invalid")) + throw new Exception("拥金上货失败:请先登录上货助手"); + } + fromCommissionRate = BaseRegex.GetStringJson(ref result, "commissionRate").ParseToNullableInt(); + var prodName = BaseRegex.GetStringJson(ref result, "name"); + if (fromCommissionRate == null) + fromCommissionRate = -1; + if (prodName.IsNullOrEmpty()) + prodName = dbProd.product_name; + //if (fromCommissionRate != null && !string.IsNullOrEmpty(prodName)) + { + url = BaseClass.BuildObjString("product/wx_add_mdy_commission_rate"); + date = UserLogin.GetSignaturex(); + date += "&spu_id=" + dbProd.spu_id; + date += "&commission_rate=" + fromCommissionRate; + date += "&product_name=" + prodName; + var addCommissionRateRst = GetHtml.PostUtf8(url, date); + } + } + Invoke(new Action(() => + { + minCommissionRate = this.txtMinUpCommission.Text.Trim().ParseToInt() * 10000; + maxCommissionRate = this.txtMaxUpCommission.Text.Trim().ParseToInt() * 10000; + commissionRateChange = this.txtCommissionChangeValue.Text.Trim().ParseToInt() * 10000; + if (cmbCommissionAddOrReduce.Text != "增加") + commissionRateChange = -commissionRateChange; + commissionRate = fromCommissionRate.ParseToInt() + commissionRateChange; + })); + if (fromCommissionRate < minCommissionRate || fromCommissionRate > maxCommissionRate) + { + throw new Exception("拥金上货失败:原商品佣金比例 " + fromCommissionRate * 0.0001 + "% 超过设置的佣金范围!"); + } + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/Convert1688ProductStep.cs b/Store.Product/Publish/Steps/Wx/Convert1688ProductStep.cs new file mode 100644 index 0000000..94201d5 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/Convert1688ProductStep.cs @@ -0,0 +1,52 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“1688商品格式转换”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class Convert1688ProductStep : WxProductPublishStepBase +{ + public override string Description => "1688商品格式转换"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + if (dbProd.product_get_type_text == "1688厂家") + { + string convertRst = ""; + var pInfo = new PDDProdFormConverter()._1688ClientToPddConvert(out convertRst, dbProd); + if (!string.IsNullOrEmpty(convertRst)) + throw new Exception("商品查询错误 " + convertRst); + } + if (dbProd.product_get_type_text == "1688外部数据") + { + string convertRst = ""; + var pInfo = new PDDProdFormConverter()._1688OutToPddConvert(out convertRst, dbProd); + if (!string.IsNullOrEmpty(convertRst)) + throw new Exception("商品查询错误 " + convertRst); + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/ConvertCloudProductStep.cs b/Store.Product/Publish/Steps/Wx/ConvertCloudProductStep.cs new file mode 100644 index 0000000..ca6fdca --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/ConvertCloudProductStep.cs @@ -0,0 +1,79 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“云端商品格式转换”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class ConvertCloudProductStep : WxProductPublishStepBase +{ + public override string Description => "云端商品格式转换"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + if (dbProd.db_type == "云端数据" && dbProd.product_get_type_text == "多多") + { + string convertRst = ""; + + var content = dbProd.product_json; + if (content.Contains("\"spec_keys\"") && content.Contains("\"spec_values\"") && content.Contains("\"is_sku_pre_sale\"")) + { + convertRst = new PDDProdFormConverter().PddFrontToPddBackConvert(content, true, null, true, false); + } + else if (content.Contains("\"goods_id\"") && content.Contains("\"goods_name\"") && content.Contains("\"gallery\"") && + content.Contains("\"priority\"") && content.Contains("\"enable_share\"") && content.Contains("\"goods_property\"") && + (content.Contains("\"ref_pid\"") || content.Contains("\"goods_property\":[]")) && content.Contains("\"sku_id\"") && content.Contains("\"quantity\"") && + content.Contains("\"specs\"") && content.Contains("\"spec_key\"") && content.Contains("\"spec_value\"") && + content.Contains("\"group_price\"") && content.Contains("img.pddpic.com")) + { + convertRst = new PDDProdFormConverter().PddFrontToPddBackConvert2(content, true, null, true, false); + } + else if (content.Contains("\"mallId\"") && content.Contains("\"mallName\"") && content.Contains("\"goodsId\"") && content.Contains("\"goodsName\"") && + content.Contains("\"catID\"") && content.Contains("\"catID4\"") && content.Contains("\"topGallery\"") && content.Contains("\"detailGallery\"") && + content.Contains("\"shareDesc\"") && content.Contains("\"maxOnSaleGroupPriceInCent\"") && content.Contains("\"shareDesc\"") && + content.Contains("\"specs\"") && content.Contains("\"spec_key\"") && content.Contains("\"spec_value\"") && + content.Contains("\"groupPrice\"") && content.Contains("img.pddpic.com")) + { + convertRst = new PDDProdFormConverter().PddFrontToPddBackConvert3(content, true, null, true, false); + } + else if (content.Contains("\"商品编号\"") && content.Contains("\"商品链接\"") && content.Contains("\"标题\"")) + { + convertRst = new PDDProdFormConverter().PddFrontToPddBackConvertV4(content, true, null, true, false); + } + else + { + convertRst = new PDDProdFormConverter().PddFrontToPddBackConvertFactory("", true, null, true, false, content); + } + + if (!string.IsNullOrEmpty(convertRst) && !convertRst.StartsWith("success")) + throw new Exception("商品查询错误 " + convertRst); + + if (convertRst.StartsWith("success")) + convertRst = convertRst.Substring("success".Length); + dbProd.product_json = convertRst; + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/ConvertJstPddProductStep.cs b/Store.Product/Publish/Steps/Wx/ConvertJstPddProductStep.cs new file mode 100644 index 0000000..76a0987 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/ConvertJstPddProductStep.cs @@ -0,0 +1,54 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“聚水潭商品转换为PDD统一格式”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class ConvertJstPddProductStep : WxProductPublishStepBase +{ + public override string Description => "聚水潭商品转换为PDD统一格式"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + if (dbProd.product_get_type_text == "厂商直采") + { + if (dbProd.sku_secret_json.IsNullOrEmpty() || !dbProd.sku_secret_json.Contains("goodsId")) + { + throw new Exception("商品查询格式错误"); + } + if (dbProd.wx_cat_leaf.IsNullOrZero() || dbProd.send_to_out_server != 1 || dbProd.distribution_set != 1) + { + throw new Exception("商品查询校验错误"); + } + + string convertRst = ""; + var pInfo = new PDDProdFormConverter().JstPddProdToPddConvert(out convertRst, dbProd); + if (!string.IsNullOrEmpty(convertRst)) + throw new Exception("商品查询错误 " + convertRst); + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/ConvertWxAppProductStep.cs b/Store.Product/Publish/Steps/Wx/ConvertWxAppProductStep.cs new file mode 100644 index 0000000..088136f --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/ConvertWxAppProductStep.cs @@ -0,0 +1,45 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“微信小店App格式转换”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class ConvertWxAppProductStep : WxProductPublishStepBase +{ + public override string Description => "微信小店App格式转换"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + if (dbProd.product_get_type_text == "微信小店") + { + string convertRst = ""; + var pInfo = new PDDProdFormConverter().WXClientToPddConvert(out convertRst, dbProd); + if (!string.IsNullOrEmpty(convertRst)) + throw new Exception("商品查询错误 " + convertRst); + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/ConvertWxRecycleProductStep.cs b/Store.Product/Publish/Steps/Wx/ConvertWxRecycleProductStep.cs new file mode 100644 index 0000000..fe8d48e --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/ConvertWxRecycleProductStep.cs @@ -0,0 +1,52 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“微信回采格式转换”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class ConvertWxRecycleProductStep : WxProductPublishStepBase +{ + public override string Description => "微信回采格式转换"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + if (BaseClass.UserAuthority.ParseToString().Contains("微信回采") && chkUseWXComplateProd.Checked && dbProd.has_wx_format == "1") + { + string errMsg = ""; + wxEditProdInfo = GetProductWXComplate(dbProd.secret_spu_id, out errMsg); + + if (wxEditProdInfo == null) + { + Invoke(new Action(() => + { + cell.Value = errMsg; + })); + throw new Exception("" + errMsg); + } + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/ConvertWxShopProductStep.cs b/Store.Product/Publish/Steps/Wx/ConvertWxShopProductStep.cs new file mode 100644 index 0000000..dfbf6ff --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/ConvertWxShopProductStep.cs @@ -0,0 +1,45 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“微信店采格式转换”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class ConvertWxShopProductStep : WxProductPublishStepBase +{ + public override string Description => "微信店采格式转换"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + if (dbProd.product_get_type_text == "微信店采") + { + string convertRst = ""; + var pInfo = new PDDProdFormConverter().WXProdToPddConvert(out convertRst, dbProd); + if (!string.IsNullOrEmpty(convertRst)) + throw new Exception("商品查询错误 " + convertRst); + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/DetectCategoryStep.cs b/Store.Product/Publish/Steps/Wx/DetectCategoryStep.cs new file mode 100644 index 0000000..ed8fdd0 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/DetectCategoryStep.cs @@ -0,0 +1,165 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“识别商品类目”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class DetectCategoryStep : WxProductPublishStepBase +{ + public override string Description => "识别商品类目"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + if (cell != null) + { + Invoke(new Action(() => + { + cell.Value = "正在生成类目属性"; + })); + } + aiCateIdLst = new List(); + // 聚水潭商品已识别类目使用类目缓存 + if (dbProd.product_get_type_text == "厂商直采" && dbProd.wx_cat_leaf == null) + { + throw new Exception("该商品类目信息不完整"); + } + // 微信商品使用类目缓存 + if (!dbProd.wx_cat_leaf.IsNullOrZero()) + { + if (dbProd.wx_cat_3 == 7370) + dbProd.wx_cat_3 = 547591; + if (dbProd.wx_cat_leaf == 7370) + dbProd.wx_cat_leaf = 547591; + + if (!dbProd.wx_cat_1.IsNullOrZero()) + aiCateIdLst.Add(dbProd.wx_cat_1.ParseToLong()); + if (!dbProd.wx_cat_2.IsNullOrZero()) + aiCateIdLst.Add(dbProd.wx_cat_2.ParseToLong()); + if (!dbProd.wx_cat_3.IsNullOrZero()) + aiCateIdLst.Add(dbProd.wx_cat_3.ParseToLong()); + if (!dbProd.wx_cat_leaf.IsNullOrZero()) + aiCateIdLst.Add(dbProd.wx_cat_leaf.ParseToLong()); + aiCateIdLst = aiCateIdLst.Distinct().ToList(); + } + else + { + //------------------------------------------------------------------------------------------------------------------------------------- + // ai识别类目暂存主图位置 + int imgId = 0; + string tempImgPath = Path.GetTempPath().Trim('\\') + "\\" + Randomx.GetWordWithNumber(8).ToLower(); + List tempImgFullPathLst = new List(); + //------------------------------------------------------------------------------------------------------------------------------------- + // 生成ai识别类目所需图片 + var aiGalleries = prodInfo.detail.galleries.Where(g => g.type == 1).Take(3).ToList(); + foreach (var gallery_item in aiGalleries) // 9张主图 + { + imgId++; + string getImgError; + var imgUrl = gallery_item.url; + { + if (!imgUrl.Contains("wxapp") && imgUrl.Contains("mmecimage.cn")) + imgUrl = imgUrl.Replace("https://", "http://"); + } + var image = GetHtml.GetUrlImg(imgUrl, out getImgError); + var imageBg = new Bitmap(800, 800); + if (!string.IsNullOrEmpty(getImgError) || image == null) + { + throw new Exception("初始化主图上传失败"); + } + using (Graphics g = Graphics.FromImage(imageBg)) + { + g.DrawImage(image, new Rectangle(0, 0, imageBg.Width, imageBg.Height), 0, 0, image.Width, image.Height, GraphicsUnit.Pixel); + } + + // 取前三张图片保存到系统temp路径,配合商品标题获取商品类目 + if (imgId <= 3) + { + if (!Directory.Exists(tempImgPath)) + Directory.CreateDirectory(tempImgPath); + string imgFullPath = tempImgPath + "\\图片" + imgId + ".jpg"; + tempImgFullPathLst.Add(imgFullPath); + if (imgId == 1) + { + try + { + image.Save(imgFullPath, System.Drawing.Imaging.ImageFormat.Jpeg); + } + catch + { + // 真实图像失败传假图也能识别 + int fileSize = Randomx.GetId(1024, 10240); + using (BinaryWriter writer = new BinaryWriter(File.Create(imgFullPath))) + { + for (int i = 0; i < fileSize; i++) + { + writer.Write((byte)Randomx.GetId(256)); // 生成0到255之间的随机字节 + } + } + } + } + else + { + // 只需要传一张真实图像即可 + int fileSize = Randomx.GetId(1024, 10240); + using (BinaryWriter writer = new BinaryWriter(File.Create(imgFullPath))) + { + for (int i = 0; i < fileSize; i++) + { + writer.Write((byte)Randomx.GetId(256)); // 生成0到255之间的随机字节 + } + } + } + } + } + Thread.Sleep(Randomx.GetId(100, 300)); + //------------------------------------------------------------------------------------------------------------------------------------- + // 根据Ai分类,获取类目信息 + //{"code":0,"predInfo":{"firstCatRes":[{"catId":10000111,"catName":"服饰内衣","level":1},{"catId":10000111,"catName":"服饰内衣","level":1},{"catId":10000111,"catName":"服饰内衣","level":1}],"secondCatRes":[{"catId":10000113,"catName":"女装","level":2},{"catId":10000113,"catName":"女装","level":2},{"catId":10000113,"catName":"女装","level":2}],"thirdCatRes":[{"catId":547705,"catName":"牛仔裤","level":3,"modelVersion":""},{"catId":547704,"catName":"加绒裤","level":3,"modelVersion":"商品标题含'加绒加厚、保暖'等关键词,这表示裤子具备加绒保暖功能。主图展示了加绒内衬,也符合【服饰内衣:女装:裤子:加绒裤】类目特征。"},{"catId":547701,"catName":"休闲裤","level":3,"modelVersion":""}],"singleSecondCatRes":[],"singleFirstCatRes":[],"isSupportedCate":1},"aiCategoryList":[{"id":547705,"paths":[{"id":"10000111","name":"服饰内衣","sonIdList":["10000397","10000115","10000112","10000113","10000114"],"fId":"0","level":1,"leaf":false},{"id":"10000113","name":"女装","sonIdList":["546810","546811","530041","547706","6090","6089","6088","6087","6083","6076","6073","6070","6065","6063","6058","10000672","10000671","10000670","10000669","10000668","10000667","10000666","10000665","10000664","10000663","10000662","10000661","10000660","10000659","10000658","10000657","10000656","10000655"],"fId":"10000111","level":2,"leaf":false},{"id":"10000672","name":"裤子","sonIdList":["547699","547700","547701","547702","547703","547704","547705"],"fId":"10000113","level":3,"leaf":false},{"id":"547705","name":"牛仔裤","fId":"10000672","level":4,"leaf":true,"state":0,"hasTrialCategory":false,"isTrialCategory":false,"isLocalLife":false}],"depositAmount":0,"catAuth":true,"limitSubjectTypes":[]},{"id":547704,"paths":[{"id":"10000111","name":"服饰内衣","sonIdList":["10000397","10000115","10000112","10000113","10000114"],"fId":"0","level":1,"leaf":false},{"id":"10000113","name":"女装","sonIdList":["546810","546811","530041","547706","6090","6089","6088","6087","6083","6076","6073","6070","6065","6063","6058","10000672","10000671","10000670","10000669","10000668","10000667","10000666","10000665","10000664","10000663","10000662","10000661","10000660","10000659","10000658","10000657","10000656","10000655"],"fId":"10000111","level":2,"leaf":false},{"id":"10000672","name":"裤子","sonIdList":["547699","547700","547701","547702","547703","547704","547705"],"fId":"10000113","level":3,"leaf":false},{"id":"547704","name":"加绒裤","fId":"10000672","level":4,"leaf":true,"state":0,"hasTrialCategory":false,"isTrialCategory":false,"isLocalLife":false}],"depositAmount":0,"catAuth":true,"limitSubjectTypes":[]},{"id":547701,"paths":[{"id":"10000111","name":"服饰内衣","sonIdList":["10000397","10000115","10000112","10000113","10000114"],"fId":"0","level":1,"leaf":false},{"id":"10000113","name":"女装","sonIdList":["546810","546811","530041","547706","6090","6089","6088","6087","6083","6076","6073","6070","6065","6063","6058","10000672","10000671","10000670","10000669","10000668","10000667","10000666","10000665","10000664","10000663","10000662","10000661","10000660","10000659","10000658","10000657","10000656","10000655"],"fId":"10000111","level":2,"leaf":false},{"id":"10000672","name":"裤子","sonIdList":["547699","547700","547701","547702","547703","547704","547705"],"fId":"10000113","level":3,"leaf":false},{"id":"547701","name":"休闲裤","fId":"10000672","level":4,"leaf":true,"state":0,"hasTrialCategory":false,"isTrialCategory":false,"isLocalLife":false}],"depositAmount":0,"catAuth":true,"limitSubjectTypes":[]}],"version":"ProductClsRAG","noQualificationCatIds":[547705,547704,547701],"depositCatInfos":[{"catId":547705,"amountToPay":0,"error":false},{"catId":547704,"amountToPay":0,"error":false},{"catId":547701,"amountToPay":0,"error":false}],"visionCallMethod":"ProductClsRAG"} + var productClassifyRst = WXUpload.getProductClassify(shop, upProdInitParam.product.info.title, tempImgFullPathLst); + if (!productClassifyRst.Contains("\"code\":0")) + { + var error = BaseRegex.GetStringJson(ref productClassifyRst, "msg"); + if (string.IsNullOrEmpty(error)) + error = productClassifyRst; + throw new Exception("获取类目失败 " + error); + } + WXGetProdCateInfo cateInfo = new WXGetProdCateInfo(); + try + { + cateInfo = JsonHelper.DeserializeJsonToObject(productClassifyRst); + foreach (var catePath in cateInfo.aiCategoryList.FirstOrDefault().paths) + { + aiCateIdLst.Add(catePath.id.ParseToLong()); + } + } + catch (Exception ex) + { + throw new Exception("" + "根据图片获取类目失败 " + ex.Message); + } + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/FillMainImagesStep.cs b/Store.Product/Publish/Steps/Wx/FillMainImagesStep.cs new file mode 100644 index 0000000..90bad87 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/FillMainImagesStep.cs @@ -0,0 +1,77 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“主图不足时使用详情图补充”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class FillMainImagesStep : WxProductPublishStepBase +{ + public override string Description => "主图不足时使用详情图补充"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + mainImgCount = (prodInfo?.detail?.galleries?.Where(g => g.type == 1).Count()).ParseToInt(); + if (mainImgCount > 0 && mainImgCount < 3) + { + var gallery1 = prodInfo?.detail?.galleries?.Where(g => g.type == 2).FirstOrDefault(); + // 先补一张 + if (gallery1 != null) + { + prodInfo.detail.galleries.Add(new gallery_item() + { + type = 1, + url = gallery1.url, + width = gallery1.width, + height = gallery1.height, + }); + } + // 如果补了只有两张再补一张 + if (mainImgCount == 1 && (prodInfo?.detail?.galleries?.Where(g => g.type == 1).Count()).ParseToInt() > 1) + { + var gallery2 = prodInfo?.detail?.galleries?[1]; + if (gallery2 != null) + { + prodInfo.detail.galleries.Add(new gallery_item() + { + type = 1, + url = gallery2.url, + width = gallery2.width, + height = gallery2.height, + }); + } + } + } + if ((prodInfo?.detail?.galleries?.Where(g => g.type == 1).Count()).ParseToInt() < 3) + { + Invoke(new Action(() => + { + cell.Value = "主图数量少于三张"; + })); + throw new Exception("主图数量少于三张"); + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/FilterDuplicateImagesStep.cs b/Store.Product/Publish/Steps/Wx/FilterDuplicateImagesStep.cs new file mode 100644 index 0000000..e38cb83 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/FilterDuplicateImagesStep.cs @@ -0,0 +1,87 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“过滤重复图片”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class FilterDuplicateImagesStep : WxProductPublishStepBase +{ + public override string Description => "过滤重复图片"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + needAddRandomBlock = false; + wantAddRandomBlock = chkImgRandomBlock.Checked && (/*upProdParam.product.info.category.FirstOrDefault()?.name == "图书" ||*/ + prodInfo.detail.cats.FirstOrDefault() == "书籍/杂志/报纸" || dbProd.main_cat_name == "图书" || dbProd.main_cat_name == "书籍/杂志/报纸"); + { + List galls = new List(); + foreach (var gall in prodInfo.detail.galleries.Where(g => g.type == 1).ToList()) + { + if (!galls.Any(g => g.url == gall.url)/* && !prodInfo.detail.sku.Any(s => s.thumb_url == gall.url)*/) + galls.Add(gall); + } + foreach (var gall in prodInfo.detail.galleries.Where(g => g.type == 2).ToList()) + { + if (!galls.Any(g => g.url == gall.url)/* && !prodInfo.detail.sku.Any(s => s.thumb_url == gall.url)*/) + galls.Add(gall); + } + prodInfo.detail.galleries.Clear(); + prodInfo.detail.galleries.AddRange(galls); + int repeatSn = 0; + while (prodInfo.detail.galleries.Where(g => g.type == 1).Count() > 0 && prodInfo.detail.galleries.Where(g => g.type == 1).Count() < 3) + { + repeatSn++; + var gallOld = prodInfo.detail.galleries.LastOrDefault(g => g.type == 1); + var gallNew = JsonHelper.DeserializeJsonToObject(JsonHelper.SerializeObject(gallOld)); + // 如果有 替换掉之前附加的后缀 + for (int i = 0; i < 10; i++) + { + gallNew.url = gallNew.url.Replace("?re_sn1=" + i, ""); + gallNew.url = gallNew.url.Replace("?re_sn1=" + i, ""); + } + if (gallNew.url.Contains("?")) + gallNew.url += "&re_sn1=" + repeatSn; + else + gallNew.url += "?re_sn1=" + repeatSn; + prodInfo.detail.galleries.Add(gallNew); + needAddRandomBlock = true; + } + if (prodInfo.detail.galleries.Where(g => g.type == 1).Count() > 0 && prodInfo.detail.galleries.Where(g => g.type == 2).Count() <= 0) + { + var gallOld = prodInfo.detail.galleries.FirstOrDefault(g => g.type == 1); + var gallNew = JsonHelper.DeserializeJsonToObject(JsonHelper.SerializeObject(gallOld)); + if (gallNew.url.Contains("?")) + gallNew.url += "&re_sn2=" + 1; + else + gallNew.url += "?re_sn2=" + 1; + gallNew.type = 2; + prodInfo.detail.galleries.Add(gallNew); + needAddRandomBlock = true; + } + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/FissionTitleStep.cs b/Store.Product/Publish/Steps/Wx/FissionTitleStep.cs new file mode 100644 index 0000000..6e2f2fe --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/FissionTitleStep.cs @@ -0,0 +1,55 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“裂变标题”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class FissionTitleStep : WxProductPublishStepBase +{ + public override string Description => "裂变标题"; + + protected override Task CanExecuteProvider(WXPublishContext context, CancellationToken token) + { + return CurrentUser.IsAuthorized("裂变调试") && chkFissionGoodsName.Checked; + } + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + var titles = txtFissionTitle.Text.Split(new string[] { "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries).ToList(); + if (titles.Count == 0) + { + cancellationTokenSource.Cancel(); + throw new Exception("裂变标题数量不足,将暂停上传"); + } + selectProd.product_name = titles[0]; + selectProd.is_fission_title = true; + titles.RemoveAt(0); + txtFissionTitle.Text = string.Join("\r\n", titles); + context.Progress.Report("标题裂变成功,更改为:" + selectProd.product_name); + + return Task.CompletedTask; + } +} diff --git a/Store.Product/Publish/Steps/Wx/GenerateAiProductImageStep.cs b/Store.Product/Publish/Steps/Wx/GenerateAiProductImageStep.cs new file mode 100644 index 0000000..69d933f --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/GenerateAiProductImageStep.cs @@ -0,0 +1,145 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“AI生成商品裂变图片”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class GenerateAiProductImageStep : WxProductPublishStepBase +{ + public override string Description => "AI生成商品裂变图片"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + if (chkAiImage.Checked) + { + int imgCount = 1; + Invoke(new Action(() => + { + if (cell != null) + cell.Value = "正在Ai生图"; + imgCount = txtAiImgCount.Text.Trim().ParseToInt(); + if (imgCount == 0) + imgCount = 1; + })); + + List tList = new List(); + List errStrLst = new List(); + for (int n = 0; n < upProdParam.product.info.headImg.Count && n < imgCount; n++) + { + while (tList.Count >= 10) + { + Thread.Sleep(500); + tList.RemoveAll(t => t.IsCanceled || t.IsFaulted || t.IsCompleted); + } + var tt = Task.Run(() => + { + var imgUrl = GetAiImg(upProdParam.product.info.headImg[n], upProdParam.product.info.shortTitle); + if (imgUrl.StartsWith("生成Ai图片错误")) + { + errStrLst.Add(imgUrl); + return; + //throw new Exception("" + imgUrl); + } + + // api上传图片文件 Ai主图 + if (1 == 1) + { + try + { + string errMsg = ""; + //var imgBase64 = GetImgBase64ForApi(out errMsg, imgUrl, needAddRandomBlock, wantAddRandomBlock, 800, 800); + var imgBase64 = GetImgBase64ForApi(out errMsg, imgUrl, false, false, 800, 800); + if (!string.IsNullOrEmpty(errMsg)) + { + errStrLst.Add(errMsg); + return; + //return errMsg; + } + + var urlImg = BaseClass.BuildWXImgObjString("product/wx_api_upload_image"); + var upDate = UserLogin.GetSignaturex(); + upDate += "&token=" + token.accessToken; + //upDate += "&is_file=true"; + //if (needAddRandomBlock || wantAddRandomBlock) + // upDate += "&random_code=true"; + upDate += "&img_url=" + imgUrl; + upDate += "&img_base64=" + imgBase64; + + var sTime = DateTime.Now; + var apiUpImgFileRst = GetHtml.PostUtf8(urlImg, upDate); + if (cell != null) + { + Invoke(new Action(() => + { + cell.Value = "正在上传Ai主图文件 " + (DateTime.Now - sTime).TotalMilliseconds.ParseToInt(); + })); + } + + for (int i = 0; i < 5 && apiUpImgFileRst.Contains("access_token expired"); i++) + { + token.mdyTime = DateTime.Now.AddDays(-100); + token = new WxApiBLL().GetRandomWxToken(); + apiUpImgFileRst = GetHtml.PostUtf8(urlImg, upDate); + } + var upImgUrl = BaseRegex.GetStringJson(ref apiUpImgFileRst, "img_url"); + if (apiUpImgFileRst.Contains("\"errcode\":0") && !string.IsNullOrEmpty(upImgUrl)) + { + upProdParam.product.info.headImg[n] = upImgUrl; + //if (n == 0) + // upProdParam.product.info.detail.detailImg.Insert(0, upImgUrl); + } + else + { + string upImgErrMsg = BaseRegex.GetStringJsonByRegs(ref apiUpImgFileRst, apiUpImgFileRst, "msg", "errMsg", "errorMsg", "err_msg", "error_msg"); + errStrLst.Add("上传Ai图失败 " + upImgErrMsg); + return; + //throw new Exception("上传Ai图失败 " + upImgErrMsg); + } + } + catch (Exception ex) + { + errStrLst.Add("上传Ai图失败 " + ex.Message); + return; + //throw new Exception("上传Ai图失败 " + ex.Message); + } + } + }); + tList.Add(tt); + } + while (tList.Count > 0) + { + Thread.Sleep(500); + tList.RemoveAll(t => t.IsCanceled || t.IsFaulted || t.IsCompleted); + } + // 错误超过一定数量直接报错返回 + if (errStrLst.Count > 0 && errStrLst.Count >= imgCount / 3) + { + throw new Exception(string.Join(",", errStrLst)); + } + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/GenerateAiShortTitleStep.cs b/Store.Product/Publish/Steps/Wx/GenerateAiShortTitleStep.cs new file mode 100644 index 0000000..b0238f2 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/GenerateAiShortTitleStep.cs @@ -0,0 +1,70 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“生成AI短标题”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class GenerateAiShortTitleStep : WxProductPublishStepBase +{ + public override string Description => "生成AI短标题"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + if (wxUploadRule.IsAiShortTitle || chkAiImage.Checked) + { + if (cell != null) + { + Invoke(new Action(() => + { + cell.Value = "正在生成短标题"; + })); + } + + WXGetShortTitleParam titleParam = new WXGetShortTitleParam() + { + categories = new List(), + headImgUrls = upProdParam.product.info.headImg, + productTitle = upProdParam.product.info.title, + }; + foreach (var cate in upProdParam.product.info.category) + { + titleParam.categories.Add(new categorieParam() { catId = cate.catId, catName = cate.name }); + } + var shortTitleRst = WXUpload.genProductShortTitle(shop, titleParam); + if (!shortTitleRst.Contains("\"code\":0") && !shortTitleRst.Contains("\"success\":true")) + { + var error = BaseRegex.GetStringJson(ref shortTitleRst, "msg"); + if (string.IsNullOrEmpty(error)) + error = shortTitleRst; + throw new Exception("短标题获取失败 " + error); + } + upProdParam.product.info.shortTitle = BaseRegex.GetStringJson(ref shortTitleRst, "shortTitle"); + upProdParam.product.info.aiGenShortTitle = upProdParam.product.info.shortTitle; + upProdParam.product.info.title = upProdParam.product.info.shortTitle + upProdParam.product.info.title; + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/GenerateCategoryTemplateStep.cs b/Store.Product/Publish/Steps/Wx/GenerateCategoryTemplateStep.cs new file mode 100644 index 0000000..47aa697 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/GenerateCategoryTemplateStep.cs @@ -0,0 +1,226 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“生成类目模板”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class GenerateCategoryTemplateStep : WxProductPublishStepBase +{ + public override string Description => "生成类目模板"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + if (cell != null) + { + Invoke(new Action(() => + { + cell.Value = "正在生成类目模板"; + })); + } + + if (aiCateIdLst.LastOrDefault() == 7370) + { + aiCateIdLst.RemoveAt(aiCateIdLst.Count - 1); + aiCateIdLst.Add(547591); + } + + getCateRst = WXUpload.getCategoryOnShelf(shop, aiCateIdLst); + if (!getCateRst.Contains("\"code\":0")) + { + var error = BaseRegex.GetStringJson(ref getCateRst, "msg"); + if (string.IsNullOrEmpty(error)) + error = getCateRst; + throw new Exception("获取类目模板失败" + error); + } + + jCateRst = JObject.Parse(getCateRst); + upProdParam.product.info.category = JsonHelper.DeserializeJsonToList(JsonHelper.SerializeObject(jCateRst["category"])); + // 这里用了ai获取类目,如下填写 + upProdParam.product.info.recommendCategory = new recommendCategoryParam() + { + isAccepted = true, + categoryNames = new List(), + recommendSource = "ai", + interfaceName = "ProductClsRAG", + useNewInterface = 1, + }; + foreach (var cateParam in upProdParam.product.info.category) + { + upProdParam.product.info.recommendCategory.categoryNames.Add(cateParam.name); + } + + // 获取叶子类目id + + leafCatId = (upProdParam.product.info.category.LastOrDefault()?.catId).ParseToLong(); //哲学经典著作 + bound = GlobalConfig.WxCatBoundList.FirstOrDefault(b => b.leaf_id == leafCatId); + if (leafCatId == 547591) + bound = GlobalConfig.WxCatBoundList.FirstOrDefault(b => b.leaf_id == 7370); + if (bound == null) + throw new Exception("获取类目保证金失败"); + maxBount = txtFilterBoundMax.Text.Trim().ParseToInt(); + minBount = txtFilterBoundMin.Text.Trim().ParseToInt(); + if (bound.cat_bound < minBount || bound.cat_bound > maxBount) + throw new Exception("类目保证金(" + bound.cat_bound + ")超过过滤限制(" + minBount + "-" + maxBount + ")"); + + // 定向类目需指定品牌Id + spBrandId = ""; + spBrandName = ""; + if (leafCatId == 547591) + { + // 先写死 + //spBrandId = 10914219.ToString(); + + var rst = WXUpload.getMixBrand(shop, leafCatId.ToString()); + Invoke(new Action(() => { spBrandName = cmbSpBrandName.Text.Trim(); })); + + if (!string.IsNullOrEmpty(spBrandName) && rst.Contains("\"code\":0")) + { + var jObj = JsonHelper.JObjectParse(rst); + if (rst.Contains("mixBrandList")) + { + foreach (var jItem in jObj["mixBrandList"]) // bizuinBrandList mixBrandList + { + if (jItem["chName"].ParseToString() == spBrandName) + spBrandId = jItem["brandId"].ParseToString(); + } + } + if (rst.Contains("bizuinBrandList")) + { + foreach (var jItem in jObj["bizuinBrandList"]) // bizuinBrandList mixBrandList + { + if (jItem["chName"].ParseToString() == spBrandName) + spBrandId = jItem["brandId"].ParseToString(); + } + } + } + + if (spBrandId.IsNullOrEmpty()) + { + throw new Exception("定向类目绑定品牌失败"); + } + + // 国表定向类目 + upProdParam.product.info.brandId = spBrandId.ParseToLong(); + } + else + { + + // 检测是否有类目上传权限 + var cateUpCheckRst = WXUpload.batchCheckCategoryRelation(shop, leafCatId); + //"{\"code\":0,\"list\":[{\"category_id\":\"1427\",\"result\":0,\"no_auth_reason_type\":0}]}" + //"{\"code\":0,\"list\":[{\"category_id\":\"7486\",\"result\":0,\"no_auth_reason_type\":0}]}" + if (!cateUpCheckRst.Contains("\"code\":0") && !cateUpCheckRst.Contains("\"success\":true")) + { + var error = BaseRegex.GetStringJson(ref cateUpCheckRst, "msg"); + if (string.IsNullOrEmpty(error)) + error = cateUpCheckRst; + throw new Exception("类目校验错误 " + error); + } + if (!cateUpCheckRst.Contains("\"result\":1")) + { + if (aiCateIdLst.FirstOrDefault() == 10000215) + { + //@"{""categoryInfo"":{""categories"":[{""id"":10000215},{""id"":10000222},{""id"":7483}],""categoryDeposit"":""1000.00"",""isNewApplyCat"":true,""licenseGroupList"":[{""licenseGroupId"":""1"",""license"":{""licenseId"":""4"",""cosIdList"":[""1771897594802-oM5657FQAaKYlf2uP300NBgx9HYY-0581""],""licenseFieldList"":[{""key"":""audit_license_registration_no"",""value"":""YB23509020049441""},{""key"":""search_url"",""value"":""""}],""isReuse"":true}}],""ruleVersion"":5234,""certificate"":[]},""brandIdList"":[],""leafCategoryId"":7483,""reuse"":0,""applyType"":""normalApply""}"; + var getCatDataRst = WXUpload.getSingleCategoryData(shop, leafCatId, aiCateIdLst); + if (!getCatDataRst.Contains("\"code\":0") && !getCatDataRst.Contains("\"success\":true")) + { + throw new Exception("类目申请失败 查询类目资质文件失败"); + } + + WXCateAuthParam wxCateAuthParam = new WXCateAuthParam(); + wxCateAuthParam.leafCategoryId = leafCatId; + wxCateAuthParam.categoryInfo.categoryDeposit = bound.cat_bound.ToString("0.00"); + wxCateAuthParam.categoryInfo.ruleVersion = BaseRegex.GetIntJson(ref getCatDataRst, "ruleVersion"); + foreach (var cateId in aiCateIdLst) + { + wxCateAuthParam.categoryInfo.categories.Add(new WXCateId() { id = cateId }); + } + try + { + if (!JsonHelper.JObjectParse(getCatDataRst)["reuseApplyData"].IsJTokenNullOrEmpty()) + { + var jlicenseGroupList = JsonHelper.JObjectParse(getCatDataRst)["reuseApplyData"]["catInfo"]["licenseGroupList"]; + var licenseGroupListStr = JsonHelper.SerializeObject(jlicenseGroupList); + wxCateAuthParam.categoryInfo.licenseGroupList = JsonHelper.DeserializeJsonToList(licenseGroupListStr); + } + else + { + var licenseGroup = new licenseGroup(); + wxCateAuthParam.categoryInfo.licenseGroupList.Add(licenseGroup); + licenseGroup.license.licenseId = "4"; + var cosIdList = "[" + BaseRegex.GetMatchRegexString(ref getCatDataRst, "\"cosIdList\":\\[([\\s\\S]*?)\\]") + "]"; + licenseGroup.license.cosIdList = JsonHelper.DeserializeJsonToList(cosIdList); + var licenseFieldList = "[" + BaseRegex.GetMatchRegexString(ref getCatDataRst, "\"licenseFieldList\":\\[([\\s\\S]*?)\\]") + "]"; + licenseGroup.license.licenseFieldList = JsonHelper.DeserializeJsonToList(licenseFieldList); + } + var authCateRst = WXUpload.wxCateAuth(shop, aiCateIdLst, wxCateAuthParam); + if (!authCateRst.Contains("\"code\":0") && !authCateRst.Contains("\"success\":true")) + { + var error = BaseRegex.GetStringJson(ref authCateRst, "msg"); + if (string.IsNullOrEmpty(error)) + error = BaseRegex.GetStringJson(ref authCateRst, "errmsg"); + if (string.IsNullOrEmpty(error)) + error = authCateRst; + throw new Exception("类目申请失败 " + error); + } + Thread.Sleep(Randomx.GetId(5000, 7000)); + cateUpCheckRst = WXUpload.batchCheckCategoryRelation(shop, leafCatId); + if (!cateUpCheckRst.Contains("\"code\":0") && !cateUpCheckRst.Contains("\"success\":true")) + { + var error = BaseRegex.GetStringJson(ref cateUpCheckRst, "msg"); + if (string.IsNullOrEmpty(error)) + error = cateUpCheckRst; + throw new Exception("类目申请校验错误 " + error); + } + if (!cateUpCheckRst.Contains("\"result\":1")) + throw new Exception("类目申请审核中 请稍后再试"); + } + catch + { + throw new Exception("类目申请失败 复用资质文件失败,请重新提交资质文件"); + } + //throw new Exception("类目申请失败 请手动申请资质类目"); + } + else + { + Thread.Sleep(Randomx.GetId(1000, 2000)); + var cateAuthorRst = WXUpload.addFinderCategoryV2(shop, leafCatId); + if (!cateAuthorRst.Contains("\"code\":0") && !cateAuthorRst.Contains("\"success\":true")) + { + var error = BaseRegex.GetStringJson(ref cateAuthorRst, "msg"); + if (string.IsNullOrEmpty(error)) + error = BaseRegex.GetStringJson(ref cateAuthorRst, "errmsg"); + if (string.IsNullOrEmpty(error)) + error = cateAuthorRst; + throw new Exception("类目申请错误 " + error); + } + } + } + } + + } +} diff --git a/Store.Product/Publish/Steps/Wx/GetShopApiTokenStep.cs b/Store.Product/Publish/Steps/Wx/GetShopApiTokenStep.cs new file mode 100644 index 0000000..6249207 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/GetShopApiTokenStep.cs @@ -0,0 +1,60 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“获取店铺Api Token”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class GetShopApiTokenStep : WxProductPublishStepBase +{ + public override string Description => "获取店铺Api Token"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + // 获取店铺的accessToken + token = lstWXShopTokenInfo.FirstOrDefault(t => t.shopId == shop.ShopId); + if (token == null) + token = new WXShopTokenInfo(); + if (token.isExpire || string.IsNullOrEmpty(token.accessToken)) + { + AuthInfo auth = GlobalConfig.lstWxShopAuthInfo.FirstOrDefault(sa => sa.ShopId == shop.ShopId); + //if (auth != null && auth.ExpiresTime > DateTime.Now.AddMinutes(5)) + { + token = new WxApiBLL().GetShopWxToken(shop); + + if (!string.IsNullOrEmpty(token.accessToken)) + { + lstWXShopTokenInfo.RemoveAll(t => t.shopId == shop.ShopId); + lstWXShopTokenInfo.Add(token); + NaiBa.Util.Helper.ConfigHelper.SaveConfig("wx_shop_token_list", lstWXShopTokenInfo); + } + } + } + // 如果店铺token不可用,随机获取一个上传图片的accessToken + if (token == null || token.isExpire || string.IsNullOrEmpty(token.accessToken)) + token = new WxApiBLL().GetRandomWxToken(); + } +} diff --git a/Store.Product/Publish/Steps/Wx/GetShopShippingTemplateStep.cs b/Store.Product/Publish/Steps/Wx/GetShopShippingTemplateStep.cs new file mode 100644 index 0000000..f713738 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/GetShopShippingTemplateStep.cs @@ -0,0 +1,71 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; +using Store.Framework.Extensions; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“获取店铺运费模板”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class GetShopShippingTemplateStep : WxProductPublishStepBase +{ + public override string Description => "获取店铺运费模板"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + expressRst = WXUpload.getFreightTemplate(shop); + if (!expressRst.Contains("\"code\":0") && !expressRst.Contains("\"success\":true")) + { + var error = BaseRegex.GetStringJson(ref expressRst, "msg"); + if (string.IsNullOrEmpty(error)) + error = expressRst; + throw new Exception("运费模板获取失败 " + error); + } + try + { + var addressArr = JsonHelper.JObjectParse(expressRst)["freightTemplateList"]; + foreach (var addressObj in addressArr) + { + var addressStr = JsonHelper.SerializeObject(addressObj); + if (addressObj["isDefault"].ParseToInt() == 1) + { + string templateId = addressObj["templateId"].ParseToString(); + upProdParam.product.info.expressInfo = new expressInfoParam(); + upProdParam.product.info.expressInfo.templateId = templateId; // BaseRegex.GetStringJson(ref expressRst, "templateId"); + break; + } + } + if (upProdParam.product.info.expressInfo == null) + { + upProdParam.product.info.expressInfo = new expressInfoParam(); + upProdParam.product.info.expressInfo.templateId = BaseRegex.GetStringJson(ref expressRst, "templateId"); + } + } + catch (Exception ex) + { + throw new Exception("运费模板获取失败 " + ex.Message); + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/InitializeProductDataStep.cs b/Store.Product/Publish/Steps/Wx/InitializeProductDataStep.cs new file mode 100644 index 0000000..1ff7b77 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/InitializeProductDataStep.cs @@ -0,0 +1,363 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using MAX.Core.Helpers; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; +using Store.Framework.Extensions; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“初始化微信小店商品参数”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class InitializeProductDataStep : WxProductPublishStepBase +{ + public override string Description => "初始化微信小店商品参数"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + prodJObj = new JObject(); + try + { + prodJObj = JObject.Parse(dbProd.product_json); + + var lstTitleReplaceText = new List(); + if (!txtReplaceText.Text.Trim().IsNullOrEmpty()) + lstTitleReplaceText.AddRange(txtReplaceText.Text.Trim().Split(new string[] { ",", ",", " ", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries)); + // 过滤标题敏感词 + if (lstTitleReplaceText.Count > 0) + { + if (chkSkipReplaceText.Checked) + { + if (lstTitleReplaceText.Any(t => selectProd.product_name.Contains(t))) + throw new Exception("命中敏感词"); + if (lstTitleReplaceText.Any(t => dbProd.product_name.Contains(t))) + throw new Exception("命中敏感词"); + var goods_name = prodJObj["detail"]["goods_name"].ParseToString(); + if (lstTitleReplaceText.Any(t => goods_name.Contains(t))) + throw new Exception("命中敏感词"); + var tiny_name = prodJObj["detail"]["tiny_name"].ParseToString(); + if (lstTitleReplaceText.Any(t => tiny_name.Contains(t))) + throw new Exception("命中敏感词"); + var goods_desc = prodJObj["detail"]["goods_desc"].ParseToString(); + if (lstTitleReplaceText.Any(t => goods_desc.Contains(t))) + throw new Exception("命中敏感词"); + var share_desc = prodJObj["detail"]["share_desc"].ParseToString(); + if (lstTitleReplaceText.Any(t => share_desc.Contains(t))) + throw new Exception("命中敏感词"); + } + else + { + foreach (var txt in lstTitleReplaceText) + { + dbProd.product_name = dbProd.product_name.Replace(txt, ""); + selectProd.product_name = selectProd.product_name.Replace(txt, ""); + } + + var goods_name = prodJObj["detail"]["goods_name"].ParseToString(); + foreach (var txt in lstTitleReplaceText) + { + goods_name = goods_name.Replace(txt, ""); + } + prodJObj["detail"]["goods_name"] = goods_name; + + var tiny_name = prodJObj["detail"]["tiny_name"].ParseToString(); + foreach (var txt in lstTitleReplaceText) + { + tiny_name = tiny_name.Replace(txt, ""); + } + prodJObj["detail"]["tiny_name"] = tiny_name; + + var goods_desc = prodJObj["detail"]["goods_desc"].ParseToString(); + foreach (var txt in lstTitleReplaceText) + { + goods_desc = goods_desc.Replace(txt, ""); + } + prodJObj["detail"]["goods_desc"] = goods_desc; + + var share_desc = prodJObj["detail"]["share_desc"].ParseToString(); + foreach (var txt in lstTitleReplaceText) + { + share_desc = share_desc.Replace(txt, ""); + } + prodJObj["detail"]["share_desc"] = share_desc; + } + } + // 过滤标题特殊字符 + if (chkRemoveSpecialChar.Checked) + { + var chLst = new List { ']', '】', ')', ')', '}', }; + foreach (var ch in chLst) + { + if (dbProd.product_name.Contains(ch) && dbProd.product_name.IndexOf(ch) < dbProd.product_name.Length / 2 && dbProd.product_name.Length > 10) + dbProd.product_name = dbProd.product_name.Substring(dbProd.product_name.IndexOf(ch)).Trim(ch); + + if (selectProd.product_name.Contains(ch) && selectProd.product_name.IndexOf(ch) < selectProd.product_name.Length / 2 && selectProd.product_name.Length > 10) + selectProd.product_name = selectProd.product_name.Substring(selectProd.product_name.IndexOf(ch)).Trim(ch); + + var goods_name = prodJObj["detail"]["goods_name"].ParseToString(); + if (goods_name.Contains(ch) && goods_name.IndexOf(ch) < goods_name.Length / 2 && goods_name.Length > 10) + prodJObj["detail"]["goods_name"] = goods_name.Substring(goods_name.IndexOf(ch)).Trim(ch); + } + + } + // 替换货号 + if (!string.IsNullOrEmpty(dbProd.ext_code)) + prodJObj["detail"]["out_goods_sn"] = dbProd.ext_code; + // 替换编辑过的sku + if (!string.IsNullOrEmpty(dbProd.sku_edit_json)) + prodJObj["detail"]["sku"] = JArray.Parse(JsonHelper.SerializeObject(JsonHelper.DeserializeJsonToList(dbProd.sku_edit_json))); + // 过滤sku敏感词 + if (lstTitleReplaceText.Count > 0) + { + var skuJArr = prodJObj["detail"]["sku"] as JArray; + try + { + foreach (var skuJObj in skuJArr) + { + var specJArr = skuJObj["spec"] as JArray; + for (int i = 0; i < specJArr.Count; i++) + { + foreach (var txt in lstTitleReplaceText) + { + specJArr[i]["spec_name"] = specJArr[i]["spec_name"].ParseToString().Replace(txt, ""); + } + } + } + } + catch (Exception ex) + { + throw new Exception("商品数据初始化失败" + ex.Message); + } + } + // 同颜色规格向上浮动同价格 + if (chkSameSpecPriceUp.Checked) + { + var priceTextLst = dbProd.price_text.Split('-'); + if (priceTextLst.Length != 2 || priceTextLst[0] != priceTextLst[1]) + { + Dictionary specPriceDic = new Dictionary(); + Dictionary specMultiPriceDic = new Dictionary(); + var skuJArr = prodJObj["detail"]["sku"] as JArray; + try + { + // 计算主spec的最高价 + foreach (var skuJObj in skuJArr) + { + var specJArr = skuJObj["spec"] as JArray; + if (!specPriceDic.ContainsKey(specJArr[0]["spec_name"].ParseToString()) || skuJObj["price"].ParseToInt() > specPriceDic[specJArr[0]["spec_name"].ParseToString()]) + { + specPriceDic[specJArr[0]["spec_name"].ParseToString()] = skuJObj["price"].ParseToInt(); + } + if (!specMultiPriceDic.ContainsKey(specJArr[0]["spec_name"].ParseToString()) || skuJObj["multi_price"].ParseToInt() > specMultiPriceDic[specJArr[0]["spec_name"].ParseToString()]) + { + specMultiPriceDic[specJArr[0]["spec_name"].ParseToString()] = skuJObj["multi_price"].ParseToInt(); + } + } + // 统一按最高价设置 + foreach (var skuJObj in skuJArr) + { + var specJArr = skuJObj["spec"] as JArray; + skuJObj["price"] = specPriceDic[specJArr[0]["spec_name"].ParseToString()]; + skuJObj["multi_price"] = specMultiPriceDic[specJArr[0]["spec_name"].ParseToString()]; + } + } + catch (Exception ex) + { + throw new Exception("商品数据初始化失败" + ex.Message); + } + } + } + // 同颜色规格向下浮动同价格 + if (chkSameSpecPriceDown.Checked) + { + var priceTextLst = dbProd.price_text.Split('-'); + if (priceTextLst.Length != 2 || priceTextLst[0] != priceTextLst[1]) + { + Dictionary specPriceDic = new Dictionary(); + Dictionary specMultiPriceDic = new Dictionary(); + var skuJArr = prodJObj["detail"]["sku"] as JArray; + try + { + // 计算主spec的最低价 + foreach (var skuJObj in skuJArr) + { + var specJArr = skuJObj["spec"] as JArray; + if (!specPriceDic.ContainsKey(specJArr[0]["spec_name"].ParseToString()) || skuJObj["price"].ParseToInt() < specPriceDic[specJArr[0]["spec_name"].ParseToString()]) + { + specPriceDic[specJArr[0]["spec_name"].ParseToString()] = skuJObj["price"].ParseToInt(); + } + if (!specMultiPriceDic.ContainsKey(specJArr[0]["spec_name"].ParseToString()) || skuJObj["multi_price"].ParseToInt() < specMultiPriceDic[specJArr[0]["spec_name"].ParseToString()]) + { + specMultiPriceDic[specJArr[0]["spec_name"].ParseToString()] = skuJObj["multi_price"].ParseToInt(); + } + } + // 统一按最低价设置 + foreach (var skuJObj in skuJArr) + { + var specJArr = skuJObj["spec"] as JArray; + skuJObj["price"] = specPriceDic[specJArr[0]["spec_name"].ParseToString()]; + skuJObj["multi_price"] = specMultiPriceDic[specJArr[0]["spec_name"].ParseToString()]; + } + } + catch (Exception ex) + { + throw new Exception("商品数据初始化失败" + ex.Message); + } + } + } + // 只保留最高价 + if (chkHighestPriceSku.Checked) + { + int price = 0; + var skuJArr = prodJObj["detail"]["sku"] as JArray; + try + { + // 计算主spec的最高价 + foreach (var skuJObj in skuJArr) + { + if (price < skuJObj["multi_price"].ParseToInt()) + price = skuJObj["multi_price"].ParseToInt(); + } + + var product_json_tmp = JsonHelper.SerializeObject(prodJObj); + + // 移除第一组所有sku + skuJArr.RemoveAll(); + + // 序列化第二组,从组中挑选符合价格条件的sku往第一组内填写 + var prodJObj2 = JObject.Parse(product_json_tmp); + var skuJArr2 = prodJObj2["detail"]["sku"] as JArray; + foreach (var skuJObj2 in skuJArr2) + { + if (skuJObj2["multi_price"].ParseToInt() >= price) + skuJArr.Add(skuJObj2); + } + if (skuJArr.Count == 0) + { + throw new Exception("商品sku差价不满足上货条件,跳过"); + } + } + catch (Exception ex) + { + throw new Exception("商品数据初始化失败" + ex.Message); + } + } + // 只保留最低价 + if (chkLowestPriceSku.Checked) + { + int price = 99999999; + var skuJArr = prodJObj["detail"]["sku"] as JArray; + try + { + // 计算主spec的最低价 + foreach (var skuJObj in skuJArr) + { + if (price > skuJObj["multi_price"].ParseToInt()) + price = skuJObj["multi_price"].ParseToInt(); + } + + var product_json_tmp = JsonHelper.SerializeObject(prodJObj); + + // 移除第一组所有sku + skuJArr.RemoveAll(); + + // 序列化第二组,从组中挑选符合价格条件的sku往第一组内填写 + var prodJObj2 = JObject.Parse(product_json_tmp); + var skuJArr2 = prodJObj2["detail"]["sku"] as JArray; + foreach (var skuJObj2 in skuJArr2) + { + if (skuJObj2["multi_price"].ParseToInt() <= price) + skuJArr.Add(skuJObj2); + } + if (skuJArr.Count == 0) + { + throw new Exception("商品sku差价不满足上货条件,跳过"); + } + } + catch (Exception ex) + { + throw new Exception("商品数据初始化失败" + ex.Message); + } + } + // 过滤低价sku,如果所有sku都不达标,跳过商品 + if (!string.IsNullOrEmpty(txtMinPrice.Text)) + { + var product_json_tmp = JsonHelper.SerializeObject(prodJObj); + + // 移除第一组所有sku + var skuJArr = prodJObj["detail"]["sku"] as JArray; + skuJArr.RemoveAll(); + + // 序列化第二组,从组中挑选符合价格条件的sku往第一组内填写 + var prodJObj2 = JObject.Parse(product_json_tmp); + var skuJArr2 = prodJObj2["detail"]["sku"] as JArray; + foreach (var skuJObj2 in skuJArr2) + { + var limitMinPrice = this.txtMinPrice.Text.Trim().ParseToDecimal() * 100; + // 供应商价加价10%(最低1元)赚取差价 先加价后过滤,过滤条件也要相应增加10% + //if (dbProd.product_get_type_text == "厂商直采") + // limitPrice += Math.Max(100m, limitPrice * 0.1m); + if (skuJObj2["multi_price"].ParseToInt() >= limitMinPrice) + skuJArr.Add(skuJObj2); + } + if (skuJArr.Count == 0) + { + throw new Exception("商品sku价格不满足上货条件,跳过"); + } + } + // 过滤高价sku,如果所有sku都不达标,跳过商品 + if (!string.IsNullOrEmpty(txtMaxPrice.Text)) + { + var product_json_tmp = JsonHelper.SerializeObject(prodJObj); + + // 移除第一组所有sku + var skuJArr = prodJObj["detail"]["sku"] as JArray; + skuJArr.RemoveAll(); + + // 序列化第二组,从组中挑选符合价格条件的sku往第一组内填写 + var prodJObj2 = JObject.Parse(product_json_tmp); + var skuJArr2 = prodJObj2["detail"]["sku"] as JArray; + foreach (var skuJObj2 in skuJArr2) + { + var limitMaxPrice = this.txtMaxPrice.Text.Trim().ParseToDecimal() * 100; + // 供应商价加价10%(最低1元)赚取差价 先加价后过滤,过滤条件也要相应增加10% + //if (dbProd.product_get_type_text == "厂商直采") + // limitPrice += Math.Max(100m, limitPrice * 0.1m); + if (skuJObj2["multi_price"].ParseToInt() <= limitMaxPrice) + skuJArr.Add(skuJObj2); + } + if (skuJArr.Count == 0) + { + throw new Exception("商品sku价格不满足上货条件,跳过"); + } + } + + dbProd.product_json = JsonHelper.SerializeObject(prodJObj); + } + catch (Exception ex) + { + throw new Exception("商品数据初始化失败" + ex.Message); + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/InitializeUploadProductStep.cs b/Store.Product/Publish/Steps/Wx/InitializeUploadProductStep.cs new file mode 100644 index 0000000..b5bb18c --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/InitializeUploadProductStep.cs @@ -0,0 +1,47 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“初始化上传参数与店铺登录校验”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class InitializeUploadProductStep : WxProductPublishStepBase +{ + public override string Description => "初始化上传参数与店铺登录校验"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + if (shop == null || string.IsNullOrEmpty(shop.ShopId) || shop.LoginStatus != "登录成功") + { + throw new Exception("店铺为空或未登录!"); + } + if (cell != null) + { + context.Progress.Report("正在初始化上传"); + } + return Task.CompletedTask; + } +} diff --git a/Store.Product/Publish/Steps/Wx/MIGRATION_NOTES.md b/Store.Product/Publish/Steps/Wx/MIGRATION_NOTES.md new file mode 100644 index 0000000..724f851 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/MIGRATION_NOTES.md @@ -0,0 +1,26 @@ +# 微信 UploadProduct 步骤迁移说明 + +本次把 `TEMUKJD/WXForms/ProductForms/WXDBProdUploadForm.cs` 中 `UploadProduct` 的原有 `#region` 按顺序迁入 `Store.Product/Publish/Steps/Wx`。 + +## 新入口参数 + +- `WxProductUploadParam` 是新的承载实体,入口位于 `WXDBProdUploadForm.UploadProduct`。 +- 原上传参数实体 `WXUploadProductParam` / `WXUploadProductInitParam` 未修改。 +- 上传时由 `CaptureWxProductUploadFormState()` 和 `WxProductPublishState` 把旧对象、窗体控件值和运行时回调赋给新实体。 +- 后续可逐步把 `ProductParameter` / `InitParameter` 替换为 `Store.Product` 自有 DTO。 + +## 需要手工核对的重点 + +1. `WxProductPublishState` 中标记为 `dynamic` 的跨步骤字段,应替换为最终 DTO 的明确类型。 +2. `WxProductUploadFormState` / `WxControlState` 是窗体控件快照代理。若控件类型、名称或属性不一致,请对照 `CaptureWxProductUploadFormState()` 修改。 +3. 原代码依赖但仍在 `TEMUKJD` 中的服务包括:`WXUpload`、`PDDProdFormConverter`、`WxApiBLL`、`WXVideoUploader`、`WxGetAssistant`、`WXProdNoCheckEditHelper`、`AdminForms.WXAdminQualificationImageForm`、`BaseMsgForm`。 + - 为避免让 `Store.Product` 反向引用 WinForms 主程序,本次没有加 `TEMUKJD` 项目引用。 + - 后续应把这些能力迁到 `Store.Product` 服务或通过运行时适配接口注入。 +4. `WxProductUploadRuntime.Cell` / `CancellationTokenSource` 已用状态代理替代 WinForms 对象。 +5. `GetProductWXComplate`、`btnLoginAssistant_Click` 目前在 `WxProductPublishStepBase` 中是迁移占位实现。 +6. `WxShopTokenInfoList` 当前在入口处做了列表快照;若步骤内需要直接回写窗体静态列表,需要改为共享集合或增加回调。 +7. `SubmitProductStep` 中的 `DataGridViewRow` 统计逻辑仍直接依赖表格控件,后续建议迁到运行时回调。 + +## 步骤顺序 + +步骤顺序由 `WxProductPublishPipelineFactory.CreateDefault()` 统一维护,和原 `UploadProduct` 从上到下的执行顺序一致。 \ No newline at end of file diff --git a/Store.Product/Publish/Steps/Wx/MatchProductPropertiesStep.cs b/Store.Product/Publish/Steps/Wx/MatchProductPropertiesStep.cs new file mode 100644 index 0000000..c0499e5 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/MatchProductPropertiesStep.cs @@ -0,0 +1,759 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“匹配产品属性”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class MatchProductPropertiesStep : WxProductPublishStepBase +{ + public override string Description => "匹配产品属性"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + //真丝、蚕丝、香云纱、羊绒、羊毛、羊羔毛、羽绒、皮草 + upProdParam.product.info.param = new List(); // 商品属性值 + productAttr = upProdParam.product.info.category.LastOrDefault().catInfo.productAttr; + + // 直接转换微信回采 + if (BaseClass.UserAuthority.ParseToString().Contains("微信回采") && chkUseWXComplateProd.Checked && dbProd.has_wx_format == "1" && wxEditProdInfo != null) + { + foreach (var wxEditPropParam in wxEditProdInfo.product.info.param) + { + var wxPropParam = new wxPropParam(); + upProdParam.product.info.param.Add(wxPropParam); + foreach (var wxEditCategoryParam in wxEditPropParam.categorys) + { + var wxCategoryParam = new categoryPropParam() + { + catType = wxEditCategoryParam.catType, + name = wxEditCategoryParam.name, + catInfo = new catInfoParam() + { + inputType = wxEditCategoryParam.catInfo.inputType, + isRequired = wxEditCategoryParam.catInfo.isRequired, + isCustomize = wxEditCategoryParam.catInfo.isCustomize, + } + }; + wxPropParam.categorys.Add(wxCategoryParam); + } + } + } + // 直接转换微信店采 + else if (dbProd.product_get_type_text == "微信店采") + { + foreach (var wxEditPropParam in wxEditProdInfo.product.info.param) + { + var wxPropParam = new wxPropParam(); + upProdParam.product.info.param.Add(wxPropParam); + foreach (var wxEditCategoryParam in wxEditPropParam.categorys) + { + // 修改书名 + if (wxEditPropParam.categorys.FirstOrDefault().name == "书名" && wxEditCategoryParam == wxEditPropParam.categorys.LastOrDefault()) + { + wxEditCategoryParam.name = prodTitle; + } + var wxCategoryParam = new categoryPropParam() + { + catType = wxEditCategoryParam.catType, + name = wxEditCategoryParam.name, + catInfo = new catInfoParam() + { + inputType = wxEditCategoryParam?.catInfo?.inputType, + isRequired = wxEditCategoryParam?.catInfo?.isRequired, + isCustomize = wxEditCategoryParam?.catInfo?.isCustomize, + } + }; + wxPropParam.categorys.Add(wxCategoryParam); + } + } + } + else + { + foreach (var propInfo in prodInfo.property.goods_properties) + { + if (propInfo.name == "容量") + { + + } + if (propInfo.name == "格数") + { + + } + if (propInfo.values.Count == 0) + continue; + + + var attr = productAttr.attr.FirstOrDefault(r => r.name == propInfo.name); + if (attr == null) + attr = productAttr.attr.FirstOrDefault(r => r.name.Replace("填充材料", "填充材质") == propInfo.name || + r.name.Replace("材质", "材质种类") == propInfo.name); + // 材质 产品名称 适用年龄 面料 生产企业名称 CCC证书编号 尺寸(长) 尺寸(宽) 尺寸(高) 物品(含配件)清单 型号 产品名称 + if (propInfo.name == "生产日期" && propInfo.values.Count > 0) + { + propInfo.values.FirstOrDefault().value = DateTime.Now.Date.AddDays(-15).ToString("yyyy-MM-dd"); + propInfo.values.FirstOrDefault().raw_value = propInfo.values.FirstOrDefault().value; + } + if (attr == null) + { + if (propInfo.name.Contains("面料俗称")) + { + attr = productAttr.attr.FirstOrDefault(r => r.name == "面料材质"); + } + if (propInfo.name.Contains("适用年龄")) + { + attr = productAttr.attr.FirstOrDefault(r => r.name == "适用年龄" || r.name == "适用年龄段"); + } + if (propInfo.name.Contains("填充物")) + { + attr = productAttr.attr.FirstOrDefault(r => r.name == "填充物种类" || r.name == "填充物"); + } + + // 书籍类目属性匹配 + if (upProdParam.product.info.category.FirstOrDefault()?.name == "图书" || prodInfo.detail.cats.FirstOrDefault() == "书籍/杂志/报纸") + { + if (propInfo.name == "出版社名称") + attr = productAttr.attr.FirstOrDefault(r => r.name == "出版社名称"); + else if ((propInfo.name == "ISBN编号" || propInfo.name == "书号") && !prodInfo.property.goods_properties.Any(p => p.name == "ISBN版号/CN刊号")) + attr = productAttr.attr.FirstOrDefault(r => r.name == "ISBN版号/CN刊号"); + else if (propInfo.name == "书名") + attr = productAttr.attr.FirstOrDefault(r => r.name == "书名"); + else if (propInfo.name == "定价") + attr = productAttr.attr.FirstOrDefault(r => r.name == "定价"); + else if (propInfo.name == "作者") + attr = productAttr.attr.FirstOrDefault(r => r.name == "作者"); + else if (propInfo.name == "出版时间") + attr = productAttr.attr.FirstOrDefault(r => r.name == "出版时间"); + else if (propInfo.name == "版次") + attr = productAttr.attr.FirstOrDefault(r => r.name == "版次"); + else if (propInfo.name == "装帧类型") + attr = productAttr.attr.FirstOrDefault(r => r.name == "包装"); + else if (propInfo.name == "包册数") + attr = productAttr.attr.FirstOrDefault(r => r.name == "图书册数"); + } + if (upProdParam.product.info.category.FirstOrDefault()?.name == "食品饮料" || prodInfo.detail.cats.FirstOrDefault() == "食品饮料") + { + if (propInfo.name == "厂名") + attr = productAttr.attr.FirstOrDefault(r => r.name == "生产企业名称"); + else if (propInfo.name == "食品生产许可证编号") + attr = productAttr.attr.FirstOrDefault(r => r.name == "食品生产许可证编号"); + else if (propInfo.name == "产品标准号") + attr = productAttr.attr.FirstOrDefault(r => r.name == "产品标准号"); + else if (propInfo.name == "保质期") + attr = productAttr.attr.FirstOrDefault(r => r.name == "保质期"); + else if (propInfo.name == "生产日期") + { + attr = productAttr.attr.FirstOrDefault(r => r.name == "生产日期"); + } + else if (propInfo.name == "产品重量") + { + attr = productAttr.attr.FirstOrDefault(r => r.name == "单件净含量"); + if (attr == null) + attr = productAttr.attr.FirstOrDefault(r => r.name == "净含量"); + } + else if (propInfo.name == "包装方式") + attr = productAttr.attr.FirstOrDefault(r => r.name == "包装方式"); + //else if (propInfo.name == "配料表") + // attr = productAttr.attr.FirstOrDefault(r => r.name == "配料表"); + //else if (propInfo.name == "联系方式") + // attr = productAttr.attr.FirstOrDefault(r => r.name == "联系方式"); + else if (propInfo.name == "厂址") + attr = productAttr.attr.FirstOrDefault(r => r.name == "地址"); + else if (propInfo.name == "储藏方法") + attr = productAttr.attr.FirstOrDefault(r => r.name == "贮存条件"); + } + } + if (attr == null) + { + continue; + } + var attrValueLst = attr.value.Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries).ToList(); + var attrValue = attrValueLst.FirstOrDefault(v => v == propInfo.values.FirstOrDefault()?.value); + if (attrValue.IsNullOrEmpty()) + attrValue = attrValueLst.FirstOrDefault(v => v.Replace("高硼硅玻璃", "耐热玻璃(高硼硅)") == propInfo.values.FirstOrDefault()?.value); + if (propInfo.name == "保质期") + { + attrValue = "30 天"; + if (propInfo.values.Count > 0) + { + if (propInfo.values.FirstOrDefault().value.Contains("年") || propInfo.values.FirstOrDefault().value.Contains("月") + || propInfo.values.FirstOrDefault().value.Contains("日") || propInfo.values.FirstOrDefault().value.Contains("天") + || propInfo.values.FirstOrDefault().value.Contains("见包装")) + { + attrValue = propInfo.values.FirstOrDefault().value; + } + if (attrValue.Contains("天") && attrValue.Replace("天", "").ParseToInt() < 30) + { + throw new Exception("食品保质期小于30天,跳过上传"); + } + } + } + + if (propInfo.name == "产品重量") + { + attrValue = "10g"; + if (propInfo.values.FirstOrDefault().value.Contains("g") || propInfo.values.FirstOrDefault().value.Contains("kg")) + attrValue = propInfo.values.FirstOrDefault().value; + } + if (propInfo.name == "风格" && attrValue == null) + { + attrValue = attrValueLst.FirstOrDefault(v => v == propInfo.values.FirstOrDefault().value + "风"); + } + if (attr.type == "string") + { + attrValue = propInfo.values.FirstOrDefault()?.value; + } + //书籍属性处理 + if (upProdParam.product.info.category.FirstOrDefault()?.name == "图书" || prodInfo.detail.cats.FirstOrDefault() == "书籍/杂志/报纸") + { + // 修改书名 作者 + if (attr.name == "书名" /*&& dbProd.product_get_type_text == "微信小店"*/) + { + attrValue = prodTitle; + } + if (attr.name == "作者" && attrValue == "无") + { + attrValue = new List() { "赵老师", "钱老师", "孙老师", "李老师", "周老师", "吴老师", "郑老师", "王老师", "张老师", "孙老师", "刘老师" }.OrderBy(x => Guid.NewGuid()).FirstOrDefault(); + attrValue = "无"; + } + + if (attrValue.IsNullOrEmpty() && attr.type == "integer_unit" && (propInfo.values.FirstOrDefault()?.value).IsNumber()) + { + attrValue = propInfo.values.FirstOrDefault()?.value + " " + attr.value.Split(';').FirstOrDefault(); + } + if (attrValue.IsNullOrEmpty() && attr.name == "包装") + { + if (propInfo.values.FirstOrDefault()?.value == "平装-胶订") + attrValue = "平装"; + } + } + // 强制改为国产 + { + if (attrValueLst.Contains("国产")) + attrValue = "国产"; + else if (attr.name == "是否进口") + { + attrValue = "非进口"; + if (attr.value == "是;否") + attrValue = "否"; + } + else if (attr.name == "商品产地") + { + attrValue = "中国大陆"; + } + else if (attr.name == "产地") + { + if (attr.type == "select_one") + { + //北京;天津;上海;重庆;河北石家庄;河北唐山;河北秦皇岛;河北邯郸;河北邢台;河北保定;河北张家口;河北承德;河北沧州;河北廊坊;河北衡水;山西太原;山西大同;山西阳泉;山西长治;山西晋城;山西朔州;山西晋中;山西运城;山西忻州;山西临汾;山西吕梁;内蒙古呼和浩特;内蒙古包头;内蒙古乌海;内蒙古赤峰;内蒙古通辽;内蒙古鄂尔多斯;内蒙古呼伦贝尔;内蒙古巴彦淖尔;内蒙古乌兰察布;内蒙古兴安;内蒙古锡林郭勒;内蒙古阿拉善;辽宁沈阳;辽宁大连;辽宁鞍山;辽宁抚顺;辽宁本溪;辽宁丹东;辽宁锦州;辽宁营口;辽宁阜新;辽宁辽阳;辽宁盘锦;辽宁铁岭;辽宁朝阳;辽宁葫芦岛;吉林长春;吉林吉林;吉林四平;吉林辽源;吉林通化;吉林白山;吉林松原;吉林白城;吉林延边;黑龙江哈尔滨;黑龙江齐齐哈尔;黑龙江鸡西;黑龙江鹤岗;黑龙江双鸭山;黑龙江大庆;黑龙江伊春;黑龙江佳木斯;黑龙江七台河;黑龙江牡丹江;黑龙江黑河;黑龙江绥化;黑龙江大兴安岭;江苏南京;江苏无锡;江苏徐州;江苏常州;江苏苏州;江苏南通;江苏连云港;江苏淮安;江苏盐城;江苏扬州;江苏镇江;江苏泰州;江苏宿迁;浙江杭州;浙江宁波;浙江温州;浙江嘉兴;浙江湖州;浙江绍兴;浙江金华;浙江衢州;浙江舟山;浙江台州;浙江丽水;安徽合肥;安徽芜湖;安徽蚌埠;安徽淮南;安徽马鞍山;安徽淮北;安徽铜陵;安徽安庆;安徽黄山;安徽滁州;安徽阜阳;安徽宿州;安徽六安;安徽亳州;安徽池州;安徽宣城;福建福州;福建厦门;福建莆田;福建三明;福建泉州;福建漳州;福建南平;福建龙岩;福建宁德;江西南昌;江西景德镇;江西萍乡;江西九江;江西新余;江西鹰潭;江西赣州;江西吉安;江西宜春;江西抚州;江西上饶;山东济南;山东青岛;山东淄博;山东枣庄;山东东营;山东烟台;山东潍坊;山东济宁;山东泰安;山东威海;山东日照;山东临沂;山东德州;山东聊城;山东滨州;山东菏泽;河南郑州;河南开封;河南洛阳;河南平顶山;河南安阳;河南鹤壁;河南新乡;河南焦作;河南濮阳;河南许昌;河南漯河;河南三门峡;河南南阳;河南商丘;河南信阳;河南周口;河南驻马店;河南济源;湖北武汉;湖北黄石;湖北十堰;湖北宜昌;湖北襄阳;湖北鄂州;湖北荆门;湖北孝感;湖北荆州;湖北黄冈;湖北咸宁;湖北随州;湖北恩施;湖北仙桃;湖北潜江;湖北天门;湖北神农架;湖南长沙;湖南株洲;湖南湘潭;湖南衡阳;湖南邵阳;湖南岳阳;湖南常德;湖南张家界;湖南益阳;湖南郴州;湖南永州;湖南怀化;湖南娄底;湖南湘西;广东广州;广东韶关;广东深圳;广东珠海;广东汕头;广东佛山;广东江门;广东湛江;广东茂名;广东肇庆;广东惠州;广东梅州;广东汕尾;广东河源;广东阳江;广东清远;广东东莞;广东中山;广东潮州;广东揭阳;广东云浮;广西南宁;广西柳州;广西桂林;广西梧州;广西北海;广西防城港;广西钦州;广西贵港;广西玉林;广西百色;广西贺州;广西河池;广西来宾;广西崇左;海南海口;海南三亚;海南三沙;海南儋州;海南五指山;海南琼海;海南文昌;海南万宁;海南东方;海南定安;海南屯昌;海南澄迈;海南临高;海南白沙;海南昌江;海南乐东;海南陵水;海南保亭;海南琼中;四川成都;四川自贡;四川攀枝花;四川泸州;四川德阳;四川绵阳;四川广元;四川遂宁;四川内江;四川乐山;四川南充;四川眉山;四川宜宾;四川广安;四川达州;四川雅安;四川巴中;四川资阳;四川阿坝;四川甘孜;四川凉山;贵州贵阳;贵州六盘水;贵州遵义;贵州安顺;贵州毕节;贵州铜仁;贵州黔西南;贵州黔东南;贵州黔南;云南昆明;云南曲靖;云南玉溪;云南保山;云南昭通;云南丽江;云南普洱;云南临沧;云南楚雄;云南红河;云南文山;云南西双版纳;云南大理;云南德宏;云南怒江;云南迪庆;西藏拉萨;西藏日喀则;西藏昌都;西藏林芝;西藏山南;西藏那曲;西藏阿里;陕西西安;陕西铜川;陕西宝鸡;陕西咸阳;陕西渭南;陕西延安;陕西汉中;陕西榆林;陕西安康;陕西商洛;甘肃兰州;甘肃嘉峪关;甘肃金昌;甘肃白银;甘肃天水;甘肃武威;甘肃张掖;甘肃平凉;甘肃酒泉;甘肃庆阳;甘肃定西;甘肃陇南;甘肃临夏;甘肃甘南;青海西宁;青海海东;青海海北;青海黄南;青海海南;青海果洛;青海玉树;青海海西;宁夏银川;宁夏石嘴山;宁夏吴忠;宁夏固原;宁夏中卫;新疆乌鲁木齐;新疆克拉玛依;新疆吐鲁番;新疆哈密;新疆昌吉;新疆博尔塔拉;新疆巴音郭楞;新疆阿克苏;新疆克孜勒苏;新疆喀什;新疆和田;新疆伊犁;新疆塔城;新疆阿勒泰;新疆石河子;新疆阿拉尔;新疆图木舒克;新疆五家渠;新疆北屯;新疆铁门关;新疆双河;新疆可克达拉;新疆昆玉;新疆胡杨河;新疆新星;新疆白杨 + //北京;天津;上海;重庆;河北;山西;内蒙古;辽宁;吉林;黑龙江;江苏;浙江;安徽;福建;江西;山东;河南;湖北;湖南;广东;广西;海南;四川;贵州;云南;西藏;陕西;甘肃;青海;宁夏;新疆 + var values = attr.value.Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries).OrderBy(x => Guid.NewGuid()).ToList(); + if (values.Contains("中国大陆")) + attrValue = "中国大陆"; + else + { + var value = values.FirstOrDefault(v => prodTitle.ParseToString().Contains(v)); + if (value == null) + value = values.FirstOrDefault(v => prodTitle.ParseToString().Contains(string.Join("", v.Take(2)))); + if (value == null) + value = values.FirstOrDefault(v => "北京;天津;上海;重庆;河北;山西;内蒙古;辽宁;吉林;黑龙江;江苏;浙江;安徽;福建;江西;山东;河南;湖北;湖南;广东;广西;海南;四川;贵州;云南;西藏;陕西;甘肃;青海;宁夏;新疆".Contains(string.Join("", v.Take(2)))); + attrValue = value; + } + } + else + { + attrValue = "中国大陆"; + } + } + } + // 强制非收藏品 + { + if (attrValueLst.Contains("是否具有拍卖/收藏价值")) + { + attrValue = "不具有"; + } + if (attrValueLst.Contains("是否批量生产(孤品)")) + { + attrValue = "批量生产"; + } + } + if (attrValue.IsNullOrEmpty() && attr.isRequired == true) + { + if (attrValueLst.Contains("其他")) + attrValue = "其他"; + else if (attrValueLst.Contains("常规")) + attrValue = "常规"; + else if (attrValueLst.Contains("通用")) + attrValue = "通用"; + else if (attr.value.Contains("绒丝;羽毛;白鸭绒;白鹅绒;灰鸭绒;灰鹅绒;鸭毛片;鹅毛片")) + { + attrValue = "绒丝"; + } + //else if (attrValueLst.Any(v => v.Contains("%"))) + //{ + // attrValue = attrValueLst.FirstOrDefault(); + //} + else + attrValue = attrValueLst.Where(v => !"真丝、蚕丝、香云纱、羊绒、羊毛、羊羔绒、羊羔毛、羽绒、皮草".Contains(v)).OrderBy(x => Guid.NewGuid()).FirstOrDefault(); + } + if (attrValue.IsNullOrEmpty()) + { + if (attrValueLst.Contains("其他")) + attrValue = "其他"; + else if (attrValueLst.Contains("通用")) + attrValue = "通用"; + } + if (attrValue.IsNullOrEmpty() && attr.type == "string") + { + attrValue = propInfo.values.FirstOrDefault()?.value; + } + if (attr.type == "decimal4_unit" || attr.type == "integer_unit") + { + if (attr.value == "%") + attrValue = "1 %"; + else if (attr.value == "g;kg") + attrValue = "50 g"; + else if (attr.value == "g") + attrValue = "10 g"; + else if (attr.value == "kg") + attrValue = "1 kg"; + //else if (attr.value == "g;kg;ml;L") + // attrValue = "100 g"; + else if (attr.value == "cm") + { + var propValue = propInfo.values.FirstOrDefault()?.value; + var match = BaseRegex.GetMatchRegexString(ref propValue, @"([\d]{1,4})cm"); + if (!string.IsNullOrEmpty(match)) + { + attrValue = match + " cm"; + } + } + else if (attr.value == "mm") + { + var propValue = propInfo.values.FirstOrDefault()?.value; + var match = BaseRegex.GetMatchRegexString(ref propValue, @"([\d]{1,4})mm"); + if (!string.IsNullOrEmpty(match)) + { + attrValue = match + " mm"; + } + } + else if (attr.value.Contains("岁以上") && attr.value.Contains("个月以上")) + { + attrValue = "7 岁以上"; + if (propInfo.values.FirstOrDefault().value.Contains("岁以上") || propInfo.values.FirstOrDefault().value.Contains("个月以上")) + attrValue = propInfo.values.FirstOrDefault().value; + if (propInfo.values.FirstOrDefault().value.Contains("岁")) + { + var propValue = propInfo.values.FirstOrDefault().value; + var match = BaseRegex.GetMatchRegexString(ref propValue, @"([\d]{1,3})"); + if (!string.IsNullOrEmpty(match)) + { + attrValue = match + " 岁以上"; + } + } + else if (propInfo.values.FirstOrDefault().value.Contains("月")) + { + var propValue = propInfo.values.FirstOrDefault().value; + var match = BaseRegex.GetMatchRegexString(ref propValue, @"([\d]{1,3})"); + if (!string.IsNullOrEmpty(match)) + { + attrValue = match + " 个月以上"; + } + } + } + else if (attr.value == "个") + { + if ((propInfo.values.FirstOrDefault()?.value).ParseToString().EndsWith("个")) + { + attrValue = propInfo.values.FirstOrDefault()?.value; + if (attrValue.TrimEnd('个').ParseToInt() == 0) + attrValue = BaseRegex.GetMatchMaxRegexString(ref attrValue, "(\\d{1,6})") + (attrValue.Contains("只") ? " 只" : " 个"); + if (attrValue.TrimEnd('个').ParseToInt() == 0) + attrValue = "1 个"; + } + else + attrValue = "1 个"; + } + else if (attr.value == "只;个") + { + if ((propInfo.values.FirstOrDefault()?.value).ParseToString().EndsWith("只") || (propInfo.values.FirstOrDefault()?.value).ParseToString().EndsWith("个")) + { + attrValue = propInfo.values.FirstOrDefault()?.value; + if (attrValue.TrimEnd('只', '个').ParseToInt() == 0) + attrValue = BaseRegex.GetMatchMaxRegexString(ref attrValue, "(\\d{1,6})") + (attrValue.Contains("只") ? " 只" : " 个"); + if (attrValue.TrimEnd('只', '个').ParseToInt() == 0) + attrValue = "1 个"; + } + else + attrValue = "1 个"; + } + else if (attr.value == "ml") + { + if ((propInfo.values.FirstOrDefault()?.value).ParseToString().EndsWith(attr.value)) + attrValue = propInfo.values.FirstOrDefault()?.value.TrimStart('-', ' '); + else + attrValue = "100 ml"; + } + else if (attr.value == "L") + { + if ((propInfo.values.FirstOrDefault()?.value).ParseToString().EndsWith(attr.value)) + { + attrValue = propInfo.values.FirstOrDefault()?.value; + if (attrValue.TrimEnd('L').ParseToInt() == 0) + attrValue = BaseRegex.GetMatchMaxRegexString(ref attrValue, "(\\d{1,6})") + " L"; + if (attrValue.TrimEnd('L').ParseToInt() == 0) + attrValue = "1 L"; + } + else + attrValue = "1 L"; + } + else if (attr.value == "g;kg;ml;L") + { + var propInfoValue = (propInfo.values.FirstOrDefault()?.value).ParseToString(); + if (propInfoValue.EndsWith("g") || propInfoValue.EndsWith("kg") || propInfoValue.EndsWith("ml") || propInfoValue.EndsWith("L")) + attrValue = propInfo.values.FirstOrDefault()?.value; + else + attrValue = "100 g"; + } + else if (attr.value == "日;月;年") + { + var propInfoValue = (propInfo.values.FirstOrDefault()?.value).ParseToString(); + if (propInfoValue.EndsWith("日") || propInfoValue.EndsWith("月") || propInfoValue.EndsWith("年")) + attrValue = propInfo.values.FirstOrDefault()?.value + propInfoValue.LastOrDefault(); + else + attrValue = "30 日"; + } + else if (attr.value.ParseToString().Length > 0 && !attr.value.ParseToString().Contains(";")) + { + if ((propInfo.values.FirstOrDefault()?.value).ParseToString().EndsWith(attr.value)) + { + attrValue = propInfo.values.FirstOrDefault()?.value; + if (attrValue.Substring(0, attrValue.Length - attr.value.Length - 1).ParseToInt() == 0) + attrValue = BaseRegex.GetMatchMaxRegexString(ref attrValue, "(\\d{1,6})") + " " + attr.value; + if (attrValue.Substring(0, attrValue.Length - attr.value.Length - 1).ParseToInt() == 0) + attrValue = "1 " + attr.value; + } + else + attrValue = "1 " + attr.value; + } + else + { + var param = recommendParamRst.productInfo.param.FirstOrDefault(p => p.categorys?.FirstOrDefault()?.name == attr.name); + if (param != null && param.categorys.Count == 2) + { + attrValue = param.categorys[1].name; + if (attrValue.Contains("磅") && attrValue.Replace("磅", "").ParseToDecimal() > 0) + { + if ((";" + attr.value + ";").Contains(";g;")) + attrValue = (attrValue.Replace("磅", "").ParseToDecimal() * 454).ParseToInt() + "g"; + else if ((";" + attr.value + ";").Contains(";kg;")) + attrValue = (attrValue.Replace("磅", "").ParseToDecimal() * 0.454m).ParseToDecimal() + "kg"; + } + } + else + { + attrValue = propInfo.values.FirstOrDefault()?.value.ParseToString(); + var units = attr.value.Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries); + foreach (var unit in units) + { + var val = attrValue.Replace(unit, "").Trim(); + if (attr.type == "decimal4_unit" && val.ParseToDecimal() != 0m) + { + attrValue = val + " " + unit; + break; + } + if (attr.type == "integer_unit" && val.ParseToInt() != 0) + { + attrValue = val + " " + unit; + break; + } + } + } + } + } + + if (string.IsNullOrEmpty(attrValue)) + continue; + + var wxPropParam = new wxPropParam(); + upProdParam.product.info.param.Add(wxPropParam); + var wxCategoryParam1 = new categoryPropParam() + { + catType = 7, + name = attr.name, + catInfo = new catInfoParam() { inputType = attr.normalRelease.ruleType, isRequired = attr.isRequired == true ? 1 : 0, isCustomize = 0 } + }; + var wxCategoryParam2 = new categoryPropParam() + { + catType = 7, + name = attrValue, + catInfo = new catInfoParam() { inputType = attr.normalRelease.ruleType, isRequired = attr.isRequired == true ? 1 : 0, isCustomize = 0 } + }; + wxPropParam.categorys.Add(wxCategoryParam1); + wxPropParam.categorys.Add(wxCategoryParam2); + } + } + foreach (var wxPropParam in upProdParam.product.info.param) + { + if (wxPropParam.categorys.Count > 0 && + wxPropParam.categorys.FirstOrDefault().name == "作者" && (wxPropParam.categorys.LastOrDefault().name == "无" || + wxPropParam.categorys.LastOrDefault().name == "-" || wxPropParam.categorys.LastOrDefault().name == "/")) + { + wxPropParam.categorys.LastOrDefault().name = upProdParam.product.info.param.FirstOrDefault(p => p.categorys.FirstOrDefault()?.name == "出版社名称").categorys.LastOrDefault()?.name; + } + } + + foreach (var attr in productAttr.attr) + { + if (attr.isRequired != true) + continue; + // 已填写内没有但是必填属性 + if (upProdParam.product.info.param.Any(p => p.categorys.FirstOrDefault()?.name == attr.name)) + continue; + + var attrValueLst = attr.value.Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries).ToList(); + string attrValue = ""; + if (attrValue.IsNullOrEmpty() && attr.isRequired == true) + { + if (attrValueLst.Contains("其他")) + attrValue = "其他"; + else if (attrValueLst.Contains("常规")) + attrValue = "常规"; + else if (attrValueLst.Contains("通用")) + attrValue = "通用"; + else if (attrValueLst.Contains("国产")) + attrValue = "国产"; + else if (attr.value.Contains("绒丝;羽毛;白鸭绒;白鹅绒;灰鸭绒;灰鹅绒;鸭毛片;鹅毛片")) + { + attrValue = "绒丝"; + } + else if (attr.name == "是否进口") + { + attrValue = "非进口"; + if (attr.value == "是;否") + attrValue = "否"; + } + else if (attr.name == "商品产地") + { + attrValue = "中国大陆"; + } + else if (attr.name == "产地") + { + if (attr.type == "select_one") + { + //北京;天津;上海;重庆;河北石家庄;河北唐山;河北秦皇岛;河北邯郸;河北邢台;河北保定;河北张家口;河北承德;河北沧州;河北廊坊;河北衡水;山西太原;山西大同;山西阳泉;山西长治;山西晋城;山西朔州;山西晋中;山西运城;山西忻州;山西临汾;山西吕梁;内蒙古呼和浩特;内蒙古包头;内蒙古乌海;内蒙古赤峰;内蒙古通辽;内蒙古鄂尔多斯;内蒙古呼伦贝尔;内蒙古巴彦淖尔;内蒙古乌兰察布;内蒙古兴安;内蒙古锡林郭勒;内蒙古阿拉善;辽宁沈阳;辽宁大连;辽宁鞍山;辽宁抚顺;辽宁本溪;辽宁丹东;辽宁锦州;辽宁营口;辽宁阜新;辽宁辽阳;辽宁盘锦;辽宁铁岭;辽宁朝阳;辽宁葫芦岛;吉林长春;吉林吉林;吉林四平;吉林辽源;吉林通化;吉林白山;吉林松原;吉林白城;吉林延边;黑龙江哈尔滨;黑龙江齐齐哈尔;黑龙江鸡西;黑龙江鹤岗;黑龙江双鸭山;黑龙江大庆;黑龙江伊春;黑龙江佳木斯;黑龙江七台河;黑龙江牡丹江;黑龙江黑河;黑龙江绥化;黑龙江大兴安岭;江苏南京;江苏无锡;江苏徐州;江苏常州;江苏苏州;江苏南通;江苏连云港;江苏淮安;江苏盐城;江苏扬州;江苏镇江;江苏泰州;江苏宿迁;浙江杭州;浙江宁波;浙江温州;浙江嘉兴;浙江湖州;浙江绍兴;浙江金华;浙江衢州;浙江舟山;浙江台州;浙江丽水;安徽合肥;安徽芜湖;安徽蚌埠;安徽淮南;安徽马鞍山;安徽淮北;安徽铜陵;安徽安庆;安徽黄山;安徽滁州;安徽阜阳;安徽宿州;安徽六安;安徽亳州;安徽池州;安徽宣城;福建福州;福建厦门;福建莆田;福建三明;福建泉州;福建漳州;福建南平;福建龙岩;福建宁德;江西南昌;江西景德镇;江西萍乡;江西九江;江西新余;江西鹰潭;江西赣州;江西吉安;江西宜春;江西抚州;江西上饶;山东济南;山东青岛;山东淄博;山东枣庄;山东东营;山东烟台;山东潍坊;山东济宁;山东泰安;山东威海;山东日照;山东临沂;山东德州;山东聊城;山东滨州;山东菏泽;河南郑州;河南开封;河南洛阳;河南平顶山;河南安阳;河南鹤壁;河南新乡;河南焦作;河南濮阳;河南许昌;河南漯河;河南三门峡;河南南阳;河南商丘;河南信阳;河南周口;河南驻马店;河南济源;湖北武汉;湖北黄石;湖北十堰;湖北宜昌;湖北襄阳;湖北鄂州;湖北荆门;湖北孝感;湖北荆州;湖北黄冈;湖北咸宁;湖北随州;湖北恩施;湖北仙桃;湖北潜江;湖北天门;湖北神农架;湖南长沙;湖南株洲;湖南湘潭;湖南衡阳;湖南邵阳;湖南岳阳;湖南常德;湖南张家界;湖南益阳;湖南郴州;湖南永州;湖南怀化;湖南娄底;湖南湘西;广东广州;广东韶关;广东深圳;广东珠海;广东汕头;广东佛山;广东江门;广东湛江;广东茂名;广东肇庆;广东惠州;广东梅州;广东汕尾;广东河源;广东阳江;广东清远;广东东莞;广东中山;广东潮州;广东揭阳;广东云浮;广西南宁;广西柳州;广西桂林;广西梧州;广西北海;广西防城港;广西钦州;广西贵港;广西玉林;广西百色;广西贺州;广西河池;广西来宾;广西崇左;海南海口;海南三亚;海南三沙;海南儋州;海南五指山;海南琼海;海南文昌;海南万宁;海南东方;海南定安;海南屯昌;海南澄迈;海南临高;海南白沙;海南昌江;海南乐东;海南陵水;海南保亭;海南琼中;四川成都;四川自贡;四川攀枝花;四川泸州;四川德阳;四川绵阳;四川广元;四川遂宁;四川内江;四川乐山;四川南充;四川眉山;四川宜宾;四川广安;四川达州;四川雅安;四川巴中;四川资阳;四川阿坝;四川甘孜;四川凉山;贵州贵阳;贵州六盘水;贵州遵义;贵州安顺;贵州毕节;贵州铜仁;贵州黔西南;贵州黔东南;贵州黔南;云南昆明;云南曲靖;云南玉溪;云南保山;云南昭通;云南丽江;云南普洱;云南临沧;云南楚雄;云南红河;云南文山;云南西双版纳;云南大理;云南德宏;云南怒江;云南迪庆;西藏拉萨;西藏日喀则;西藏昌都;西藏林芝;西藏山南;西藏那曲;西藏阿里;陕西西安;陕西铜川;陕西宝鸡;陕西咸阳;陕西渭南;陕西延安;陕西汉中;陕西榆林;陕西安康;陕西商洛;甘肃兰州;甘肃嘉峪关;甘肃金昌;甘肃白银;甘肃天水;甘肃武威;甘肃张掖;甘肃平凉;甘肃酒泉;甘肃庆阳;甘肃定西;甘肃陇南;甘肃临夏;甘肃甘南;青海西宁;青海海东;青海海北;青海黄南;青海海南;青海果洛;青海玉树;青海海西;宁夏银川;宁夏石嘴山;宁夏吴忠;宁夏固原;宁夏中卫;新疆乌鲁木齐;新疆克拉玛依;新疆吐鲁番;新疆哈密;新疆昌吉;新疆博尔塔拉;新疆巴音郭楞;新疆阿克苏;新疆克孜勒苏;新疆喀什;新疆和田;新疆伊犁;新疆塔城;新疆阿勒泰;新疆石河子;新疆阿拉尔;新疆图木舒克;新疆五家渠;新疆北屯;新疆铁门关;新疆双河;新疆可克达拉;新疆昆玉;新疆胡杨河;新疆新星;新疆白杨 + //北京;天津;上海;重庆;河北;山西;内蒙古;辽宁;吉林;黑龙江;江苏;浙江;安徽;福建;江西;山东;河南;湖北;湖南;广东;广西;海南;四川;贵州;云南;西藏;陕西;甘肃;青海;宁夏;新疆 + var values = attr.value.Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries).OrderBy(x => Guid.NewGuid()).ToList(); + if (values.Contains("中国大陆")) + attrValue = "中国大陆"; + else + { + var value = values.FirstOrDefault(v => prodTitle.ParseToString().Contains(v)); + if (value == null) + value = values.FirstOrDefault(v => prodTitle.ParseToString().Contains(string.Join("", v.Take(2)))); + if (value == null) + value = values.FirstOrDefault(v => "北京;天津;上海;重庆;河北;山西;内蒙古;辽宁;吉林;黑龙江;江苏;浙江;安徽;福建;江西;山东;河南;湖北;湖南;广东;广西;海南;四川;贵州;云南;西藏;陕西;甘肃;青海;宁夏;新疆".Contains(string.Join("", v.Take(2)))); + attrValue = value; + } + } + else + { + attrValue = "中国大陆"; + } + } + else if (attr.name == "是否具有拍卖/收藏价值") + { + attrValue = "不具有"; + } + else if (attr.name == "是否批量生产(孤品)") + { + attrValue = "批量生产"; + } + //else if (attrValueLst.Any(v => v.Contains("%"))) + //{ + // attrValue = attrValueLst.FirstOrDefault(); + //} + else if (!(attr.type == "decimal4_unit" || attr.type == "integer_unit")) + attrValue = attrValueLst.Where(v => !"真丝、蚕丝、香云纱、羊绒、羊毛、羊羔绒、羊羔毛、羽绒、皮草".Contains(v)).OrderBy(x => Guid.NewGuid()).FirstOrDefault(); + + //书籍属性处理 + if (upProdParam.product.info.category.FirstOrDefault()?.name == "图书" || prodInfo.detail.cats.FirstOrDefault() == "书籍/杂志/报纸") + { + //if (attr.name == "ISBN版号/CN刊号") + // attrValue = "9787922575016"; + if (attr.name == "书名") + attrValue = prodTitle; + if (attr.name == "是否为单一书号丛书") + attrValue = "是"; + if (attr.name == "图书册数") + attrValue = "1 册"; + if (attr.name == "出版社名称") + attrValue = new List() { "技术出版社", "文化出版社", "美术出版社" }[Randomx.GetId(3)]; + if (attr.name == "作者") + { + attrValue = new List() { "赵老师", "钱老师", "孙老师", "李老师", "周老师", "吴老师", "郑老师", "王老师", "张老师", "孙老师", "刘老师" }.OrderBy(x => Guid.NewGuid()).FirstOrDefault(); + attrValue = "无"; + } + if (attr.name == "包装") + attrValue = "平装"; + if (attr.name == "出版时间") + attrValue = DateTime.Now.AddMonths(0 - Randomx.GetId(3, 7)).ToString("yyyy-MM"); + } + if (upProdParam.product.info.category.FirstOrDefault()?.name == "食品饮料" || prodInfo.detail.cats.FirstOrDefault() == "食品饮料") + { + if (attr.name == "食品名称") + attrValue = prodTitle; + if (attr.name == "生产企业名称") + attrValue = "见包装"; + if (attr.name == "食品生产许可证编号") + { + attrValue = "SC10241162100670"; + attrValue = "见包装"; + } + if (attr.name == "产品标准号") + { + attrValue = "GB 2726"; + attrValue = "见包装"; + } + if (attr.name == "生产日期") + attrValue = DateTime.Now.Date.AddDays(-15).ToString("yyyy-MM-dd"); + if (attr.name == "净含量") + attrValue = "见包装"; + if (attr.name == "单件净含量") + { + attrValue = "见包装"; + } + if (attr.name == "包装方式") + attrValue = "袋装"; + if (attr.name == "配料表") + attrValue = "水"; + if (attr.name == "联系方式") + attrValue = "无"; + if (attr.name == "地址") + attrValue = "见包装"; + if (attr.name == "贮存条件") + attrValue = "常温"; + + //if (attr.name == "保质期") + //{ + // if (chkDefaultBestBefore.Checked == false) + //throw new Exception("食品属性不含保质期,跳过上传"); + // if (txtDefaultBestBefore.Text.ParseToInt() < 15) + //throw new Exception("食品默认保质期设置小于15天,跳过上传"); + // attrValue = txtDefaultBestBefore.Text.ParseToInt() + " 天"; + //} + } + + if (attr.name == "保质期") + { + if (chkDefaultBestBefore.Checked == false) + throw new Exception("属性不含保质期,跳过上传"); + if (txtDefaultBestBefore.Text.ParseToInt() < 3) + throw new Exception("默认保质期设置小于3天,跳过上传"); + attrValue = txtDefaultBestBefore.Text.ParseToInt() + " 天"; + } + } + if (attrValue.IsNullOrEmpty() && attr.type == "string") + { + attrValue = "其他"; + } + if ((attrValue.IsNullOrEmpty() || attrValue == "见包装" || attrValue == "%") && (attr.type == "decimal4_unit" || attr.type == "integer_unit")) + { + // 走系统推荐 + var param = recommendParamRst.productInfo.param.FirstOrDefault(p => p.categorys?.FirstOrDefault()?.name == attr.name); + if (param != null && param.categorys.Count == 2) + { + attrValue = param.categorys[1].name; + if (attrValue.Contains("磅") && attrValue.Replace("磅", "").ParseToDecimal() > 0) + { + if ((";" + attr.value + ";").Contains(";g;")) + attrValue = (attrValue.Replace("磅", "").ParseToDecimal() * 454).ParseToInt() + "g"; + else if ((";" + attr.value + ";").Contains(";kg;")) + attrValue = (attrValue.Replace("磅", "").ParseToDecimal() * 0.454m).ParseToDecimal() + "kg"; + } + } + else if (attr.value == "%") + attrValue = "1 %"; + else if (attr.value == "g;kg") + attrValue = "50 g"; + else if (attr.value == "g") + attrValue = "10 g"; + else if (attr.value == "kg") + attrValue = "1 kg"; + else if (attr.value == "g;kg;ml;L") + attrValue = "100 g"; + else if (attr.value == "cm") + attrValue = "20 cm"; + else if (attr.value == "mm") + attrValue = "20 mm"; + else if (attr.value == "毫米(mm);厘米(cm);米(m)") + attrValue = "20 毫米(mm)"; + else if (attr.value.Contains("岁以上") && attr.value.Contains("个月以上")) + attrValue = "7 岁以上"; + else if (attr.value == "只;个") + attrValue = "1 个"; + else if (attr.value == "个") + attrValue = "1 个"; + else if (attr.value == "ml") + attrValue = "100 ml"; + else if (attr.value == "L") + attrValue = "1 L"; + else if (attr.value == "日;月;年") + attrValue = "30 日"; + } + + var wxPropParam = new wxPropParam(); + upProdParam.product.info.param.Add(wxPropParam); + var wxCategoryParam1 = new categoryPropParam() + { + catType = 7, + name = attr.name, + catInfo = new catInfoParam() { inputType = attr.normalRelease.ruleType, isRequired = attr.isRequired == true ? 1 : 0, isCustomize = 0 } + }; + var wxCategoryParam2 = new categoryPropParam() + { + catType = 7, + name = attrValue, + catInfo = new catInfoParam() { inputType = attr.normalRelease.ruleType, isRequired = attr.isRequired == true ? 1 : 0, isCustomize = 0 } + }; + wxPropParam.categorys.Add(wxCategoryParam1); + wxPropParam.categorys.Add(wxCategoryParam2); + } + if (upProdParam.product.info.category.FirstOrDefault()?.name == "图书" || prodInfo.detail.cats.FirstOrDefault() == "书籍/杂志/报纸") + { + foreach (var param in upProdParam.product.info.param.ToList()) + { + if (param.categorys.FirstOrDefault()?.name == "作者" && param.categorys.LastOrDefault()?.name == "无") + { + param.categorys.LastOrDefault().name = upProdParam.product.info.param.FirstOrDefault(p => p.categorys.FirstOrDefault()?.name == "出版社名称")?.categorys?.LastOrDefault()?.name; + if (param.categorys.LastOrDefault().name.IsNullOrEmpty()) + param.categorys.LastOrDefault().name = "无"; + break; + } + } + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/PrepareHotlinkImageMapStep.cs b/Store.Product/Publish/Steps/Wx/PrepareHotlinkImageMapStep.cs new file mode 100644 index 0000000..598ccf9 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/PrepareHotlinkImageMapStep.cs @@ -0,0 +1,52 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“生成图片盗链映射”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class PrepareHotlinkImageMapStep : WxProductPublishStepBase +{ + public override string Description => "生成图片盗链映射"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + isChangeImgDic = false; + wxImgMapDic = new Dictionary(); + if (!string.IsNullOrEmpty(dbProd.wx_img_map_json)) + { + try + { + wxImgMapDic = JsonHelper.DeserializeJsonToObject>(dbProd.wx_img_map_json); + } + catch + { + isChangeImgDic = true; + wxImgMapDic = new Dictionary(); + } + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/PrepareSourceProductStep.cs b/Store.Product/Publish/Steps/Wx/PrepareSourceProductStep.cs new file mode 100644 index 0000000..bc0b7c7 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/PrepareSourceProductStep.cs @@ -0,0 +1,63 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“提取源商品详情并生成SKU货号”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class PrepareSourceProductStep : WxProductPublishStepBase +{ + public override string Description => "提取源商品详情并生成SKU货号"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + prodInfo = JsonHelper.DeserializeJsonToObject(dbProd.product_json); + + // 如果没有货号或有相同货号无法区分sku,自定义货号 + if (dbProd.db_type == "云端数据") + { + foreach (var sku in prodInfo.detail.sku) + { + var skuId = sku.sku_id; + if (skuId.IsNullOrZero()) + skuId = BaseClass.GetLong(string.Join(";", sku.spec.Select(s => s.spec_name))); + sku.out_sku_sn = GetNewExtCode(skuId.ParseToString()); + } + } + else if (dbProd.product_get_type_text != "厂商直采") + { + if (prodInfo.detail.sku.Any(s => s.out_sku_sn.IsNullOrEmpty()) || + prodInfo.detail.sku.Select(s => s.out_sku_sn).ToList().Count > prodInfo.detail.sku.Select(s => s.out_sku_sn).Distinct().ToList().Count) + { + foreach (var sku in prodInfo.detail.sku) + { + sku.out_sku_sn = string.Join(";", sku.spec.Select(s => s.spec_name)); + sku.out_sku_sn = "EC1" + Math.Abs(BaseClass.GetLong(sku.out_sku_sn)) + "A"; + } + } + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/RestoreWxShopSourceProductStep.cs b/Store.Product/Publish/Steps/Wx/RestoreWxShopSourceProductStep.cs new file mode 100644 index 0000000..a35b30b --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/RestoreWxShopSourceProductStep.cs @@ -0,0 +1,50 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“微信店采原始格式还原”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class RestoreWxShopSourceProductStep : WxProductPublishStepBase +{ + public override string Description => "微信店采原始格式还原"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + wxEditProdInfo = null; + if (dbProd.product_get_type_text == "微信店采") + { + try + { + wxEditProdInfo = JsonHelper.DeserializeJsonToObject(dbProd.product_json); + } + catch (Exception ex) + { + throw new Exception("微信商品格式获取失败" + ex.Message); + } + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/SaveDetailImageHotlinkMapStep.cs b/Store.Product/Publish/Steps/Wx/SaveDetailImageHotlinkMapStep.cs new file mode 100644 index 0000000..c208bd4 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/SaveDetailImageHotlinkMapStep.cs @@ -0,0 +1,55 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“保存详情图盗链映射”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class SaveDetailImageHotlinkMapStep : WxProductPublishStepBase +{ + public override string Description => "保存详情图盗链映射"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + if (isChangeImgDic && dbProd.db_type != "云端数据") + { + var url = BaseClass.BuildObjString("product/mdy_pdd_product"); + var data = ""; + Invoke(new Action(() => + { + data = UserLogin.GetSignaturex(); + data += "&spu_id=" + dbProd.spu_id; + data += "&wx_img_map_json=" + JsonHelper.SerializeObject(wxImgMapDic); + })); + var html = GetHtml.PostUtf8(url, data); + if (string.IsNullOrEmpty(html) || !html.Contains("\"code\":200")) + html = GetHtml.PostUtf8(url, data); + if (string.IsNullOrEmpty(html) || !html.Contains("\"code\":200")) + html = GetHtml.PostUtf8(url, data); + isChangeImgDic = false; + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/SaveDraftHotlinkMapStep.cs b/Store.Product/Publish/Steps/Wx/SaveDraftHotlinkMapStep.cs new file mode 100644 index 0000000..ac4b487 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/SaveDraftHotlinkMapStep.cs @@ -0,0 +1,55 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“保存草稿商品盗链映射”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class SaveDraftHotlinkMapStep : WxProductPublishStepBase +{ + public override string Description => "保存草稿商品盗链映射"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + if (isChangeImgDic && dbProd.db_type != "云端数据") + { + var url = BaseClass.BuildObjString("product/mdy_pdd_product"); + var data = ""; + Invoke(new Action(() => + { + data = UserLogin.GetSignaturex(); + data += "&spu_id=" + dbProd.spu_id; + data += "&wx_img_map_json=" + JsonHelper.SerializeObject(wxImgMapDic); + })); + var html = GetHtml.PostUtf8(url, data); + if (string.IsNullOrEmpty(html) || !html.Contains("\"code\":200")) + html = GetHtml.PostUtf8(url, data); + if (string.IsNullOrEmpty(html) || !html.Contains("\"code\":200")) + html = GetHtml.PostUtf8(url, data); + isChangeImgDic = false; + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/SaveMainImageHotlinkMapStep.cs b/Store.Product/Publish/Steps/Wx/SaveMainImageHotlinkMapStep.cs new file mode 100644 index 0000000..0dc739a --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/SaveMainImageHotlinkMapStep.cs @@ -0,0 +1,55 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“保存主图盗链映射”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class SaveMainImageHotlinkMapStep : WxProductPublishStepBase +{ + public override string Description => "保存主图盗链映射"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + if (isChangeImgDic && dbProd.db_type != "云端数据") + { + var url = BaseClass.BuildObjString("product/mdy_pdd_product"); + var data = ""; + Invoke(new Action(() => + { + data = UserLogin.GetSignaturex(); + data += "&spu_id=" + dbProd.spu_id; + data += "&wx_img_map_json=" + JsonHelper.SerializeObject(wxImgMapDic); + })); + var html = GetHtml.PostUtf8(url, data); + if (string.IsNullOrEmpty(html) || !html.Contains("\"code\":200")) + html = GetHtml.PostUtf8(url, data); + if (string.IsNullOrEmpty(html) || !html.Contains("\"code\":200")) + html = GetHtml.PostUtf8(url, data); + isChangeImgDic = false; + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/SaveSkuHotlinkMapStep.cs b/Store.Product/Publish/Steps/Wx/SaveSkuHotlinkMapStep.cs new file mode 100644 index 0000000..193401d --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/SaveSkuHotlinkMapStep.cs @@ -0,0 +1,55 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“保存SKU图片盗链映射”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class SaveSkuHotlinkMapStep : WxProductPublishStepBase +{ + public override string Description => "保存SKU图片盗链映射"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + if (isChangeImgDic && dbProd.db_type != "云端数据") + { + var url = BaseClass.BuildObjString("product/mdy_pdd_product"); + var data = ""; + Invoke(new Action(() => + { + data = UserLogin.GetSignaturex(); + data += "&spu_id=" + dbProd.spu_id; + data += "&wx_img_map_json=" + JsonHelper.SerializeObject(wxImgMapDic); + })); + var html = GetHtml.PostUtf8(url, data); + if (string.IsNullOrEmpty(html) || !html.Contains("\"code\":200")) + html = GetHtml.PostUtf8(url, data); + if (string.IsNullOrEmpty(html) || !html.Contains("\"code\":200")) + html = GetHtml.PostUtf8(url, data); + isChangeImgDic = false; + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/SetAfterSaleInfoStep.cs b/Store.Product/Publish/Steps/Wx/SetAfterSaleInfoStep.cs new file mode 100644 index 0000000..8ae2935 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/SetAfterSaleInfoStep.cs @@ -0,0 +1,244 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“设置售后信息”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class SetAfterSaleInfoStep : WxProductPublishStepBase +{ + public override string Description => "设置售后信息"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + + if (cell != null) + { + Invoke(new Action(() => + { + cell.Value = "正在选取售后属性"; + })); + } + + // 非上海仓退货 + if (!chkUseShanghaiReturnOrderAddress.Checked) + { + var afterSaleAddress = ""; + var afterSaleDetail = ""; + // 厂商直采获取售后地址用于处理售后单 + if (dbProd.product_get_type_text == "厂商直采") + { + + var urlAfterSale = BaseClass.BuildObjString("product/wx_get_after_sale"); + var dataAfterSale = Work.UserLogin.GetSignaturex(); + if (!string.IsNullOrEmpty(dbProd.real_coid)) + dataAfterSale += "&coid=" + dbProd.skc_id + "->" + dbProd.real_coid; + else + dataAfterSale += "&coid=" + dbProd.skc_id; + var htmlAfterSale = GetHtml.PostUtf8(urlAfterSale, dataAfterSale); + if (string.IsNullOrEmpty(htmlAfterSale) || !htmlAfterSale.Contains("\"code\":200")) + htmlAfterSale = GetHtml.PostUtf8(urlAfterSale, dataAfterSale); + if (string.IsNullOrEmpty(htmlAfterSale) || !htmlAfterSale.Contains("\"code\":200")) + { + + } + else + { + if (htmlAfterSale.Contains("\"data\":\"\"")) + { + } + else + { + try + { + afterSaleAddress = JsonHelper.SerializeObject(JsonHelper.JObjectParse(htmlAfterSale)["data"]); + } + catch + { + + } + } + } + + //{"address_id":null,"user_name":"退货","postal_code":null,"province_name":"广东省","city_name":"揭阳市","county_name":"榕城区","street_name":"东兴街道","detail_info":"建阳路万泰华庭二期对面眼镜","tel_number":"13024622251","number":"10431013","name":"麦多多供应链"} + //{"address_id":null,"user_name":"退货","postal_code":null,"province_name":"广东省","city_name":"揭阳市","county_name":"榕城区","street_name":"东兴街道","detail_info":"建阳路万泰华庭二期对面眼镜","tel_number":"13024622251","number":"10431013","name":"麦多多供应链"} + if (string.IsNullOrEmpty(afterSaleAddress)) + { + // 获取推单售后地址 + var urlAfter = BaseClass.BuildObjString("product/e57_after_sale"); + var dataAfter = Work.UserLogin.GetSignaturex() + "&coid=" + dbProd.skc_id; + var afterRst = GetHtml.PostUtf8(urlAfter, dataAfter); + try + { + afterSaleAddress = JsonHelper.SerializeObject(JsonHelper.JObjectParse(afterRst)["data"]); + if (afterRst.Contains("\"code\":0") && !string.IsNullOrEmpty(afterSaleAddress)) + { + urlAfterSale = BaseClass.BuildObjString("product/wx_add_mdy_after_sale"); + dataAfterSale = Work.UserLogin.GetSignaturex() + + "&coid=" + dbProd.skc_id + + "&after_sale_json=" + afterSaleAddress; + htmlAfterSale = GetHtml.PostUtf8(urlAfterSale, dataAfterSale); + if (string.IsNullOrEmpty(htmlAfterSale) || !htmlAfterSale.Contains("\"code\":200")) + htmlAfterSale = GetHtml.PostUtf8(urlAfterSale, dataAfterSale); + } + } + catch (Exception ex) + { + } + } + + if (string.IsNullOrEmpty(afterSaleAddress)) + { + throw new Exception("售后地址信息获取失败"); + } + } + + // 官方库1688厂家数据必须有退货地址才能上传,用户自采数据使用店铺或默认退货地址 26.5.29 + // 官方库暂时也放开限制可以随意传 26.5.31 + else if (dbProd.product_get_type_text == "1688厂家" && dbProd.add_user_name == "admin" && 1 == 2) + { + var urlAfterSale = BaseClass.BuildObjString("product/wx_get_after_sale"); + var dataAfterSale = Work.UserLogin.GetSignaturex(); + dataAfterSale += "&coid=" + dbProd.from_shop_id; + var htmlAfterSale = GetHtml.PostUtf8(urlAfterSale, dataAfterSale); + if (string.IsNullOrEmpty(htmlAfterSale) || !htmlAfterSale.Contains("\"code\":200")) + htmlAfterSale = GetHtml.PostUtf8(urlAfterSale, dataAfterSale); + if (string.IsNullOrEmpty(htmlAfterSale) || !htmlAfterSale.Contains("\"code\":200")) + { + + } + else + { + if (htmlAfterSale.Contains("\"data\":\"\"")) + { + } + else + { + try + { + afterSaleAddress = JsonHelper.SerializeObject(JsonHelper.JObjectParse(htmlAfterSale)["data"]); + } + catch + { + + } + } + } + + if (string.IsNullOrEmpty(afterSaleAddress)) + { + throw new Exception("售后地址信息获取失败"); + } + } + + else if (GlobalConfig.GlobalSettings.IsWxUseSoftwareDefaultAddress == true && !IsWxNotUseSoftwareDefaultAddressOnce) + { + if (GlobalConfig.WXShopDefaultAddressList.Any(a => a.shop_id == shop.ShopId)) + WXShopDefaultAddressListForm.GetShopDefaultAddress(new List() { shop }); + var address = GlobalConfig.WXShopDefaultAddressList.First(a => a.shop_id == shop.ShopId); + if (!address.isFullAddress) + throw new Exception("店铺地址信息获取失败"); + afterSaleAddress = JsonHelper.SerializeObject(address); + } + + if (!string.IsNullOrEmpty(afterSaleAddress)) + { + afterSaleDetail = BaseRegex.GetStringJson(ref afterSaleAddress, "detail_info"); + if (string.IsNullOrEmpty(afterSaleDetail)) + { + throw new Exception("售后地址详细信息获取失败"); + } + } + + var addressRst = WXUpload.getAddress(shop, afterSaleDetail); + if (!addressRst.Contains("\"code\":0") && !addressRst.Contains("\"success\":true")) + { + var error = BaseRegex.GetStringJson(ref addressRst, "msg"); + if (string.IsNullOrEmpty(error)) + error = addressRst; + throw new Exception("售后地址获取失败 " + error); + } + if (addressRst.Contains("\"totalNum\":0") && !string.IsNullOrEmpty(afterSaleDetail)) + { + string provinceName = BaseRegex.GetStringJson(ref afterSaleAddress, "province_name"); + string cityName = BaseRegex.GetStringJson(ref afterSaleAddress, "city_name"); + string countyName = BaseRegex.GetStringJson(ref afterSaleAddress, "county_name"); + string detailInfo = afterSaleDetail; + string userName = BaseRegex.GetStringJson(ref afterSaleAddress, "user_name"); + string telNumber = BaseRegex.GetStringJson(ref afterSaleAddress, "tel_number"); + var addAddRst = WXUpload.addAddress(shop, provinceName, cityName, countyName, detailInfo, userName, telNumber); + + Thread.Sleep(Randomx.GetId(500, 1000)); + + addressRst = WXUpload.getAddress(shop, afterSaleDetail); + if (!addressRst.Contains("\"code\":0") && !addressRst.Contains("\"success\":true")) + { + var error = BaseRegex.GetStringJson(ref addressRst, "msg"); + if (string.IsNullOrEmpty(error)) + error = addressRst; + throw new Exception("售后地址获取失败 " + error); + } + } + + if (addressRst.Contains("\"totalNum\":0")) + { + throw new Exception("售后地址获取失败"); + } + + upProdParam.product.info.afterSaleInfo = new afterSaleInfoParam(); + upProdParam.product.info.afterSaleInfo.afterSaleAddressId = BaseRegex.GetStringJson(ref addressRst, "addressId"); + } + // 上海仓退货 + else + { + try + { + string addressId = ""; + string setAddressRst = WXProdNoCheckEditHelper.SetShanghaiReturnOrderAddress(shop); + if (setAddressRst.Contains("\"success\":true")) + { + addressId = WXProdNoCheckEditHelper.dicShopAddressId[shop.ShopId]; + } + else + { + var errmsg = BaseRegex.GetStringJson(ref setAddressRst, "msg"); + if (string.IsNullOrWhiteSpace(errmsg)) + errmsg = setAddressRst; + WXProdNoCheckEditHelper.dicShopAddressId.Remove(shop.ShopId); + throw new Exception("退货地址指定失败:" + errmsg); + } + + upProdParam.product.info.afterSaleInfo = new afterSaleInfoParam(); + upProdParam.product.info.afterSaleInfo.afterSaleAddressId = addressId; + } + catch (Exception ex) + { + WXProdNoCheckEditHelper.dicShopAddressId.Remove(shop.ShopId); + throw new Exception("退货地址指定失败:" + ex.Message); + } + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/SetCommissionInfoStep.cs b/Store.Product/Publish/Steps/Wx/SetCommissionInfoStep.cs new file mode 100644 index 0000000..9a02af9 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/SetCommissionInfoStep.cs @@ -0,0 +1,46 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“设置佣金信息”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class SetCommissionInfoStep : WxProductPublishStepBase +{ + public override string Description => "设置佣金信息"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + if (BaseClass.UserAuthority.Contains("佣金上货") && chkCommission.Checked) + { + upProdParam.product.info.leagueItemInfo = new leagueItemInfoParam(); + upProdParam.product.info.leagueItemInfo.itemSwitch = 1; + upProdParam.product.info.leagueItemInfo.itemRatio = commissionRate; + upProdParam.product.info.leagueItemInfo.headSupplierPublicItemSwitch = 1; + upProdParam.product.info.leagueItemInfo.headSupplierPublicItemServiceRatio = commissionRate; + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/SetPreSaleInfoStep.cs b/Store.Product/Publish/Steps/Wx/SetPreSaleInfoStep.cs new file mode 100644 index 0000000..3ffed64 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/SetPreSaleInfoStep.cs @@ -0,0 +1,50 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“设置预售信息”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class SetPreSaleInfoStep : WxProductPublishStepBase +{ + public override string Description => "设置预售信息"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + if (chkPreSale.Checked) + { + upProdParam.product.deliverInfo = new productDeliverInfoParam(); + upProdParam.product.deliverInfo.deliverType = 1; + int preSaleDays = Math.Max(3, wxUploadRule.PreSaleDays); + preSaleDays = Math.Min(45, preSaleDays); + upProdParam.product.deliverInfo.skuDeliverInfo = new skuDeliverInfoParam() + { + stockType = 1, + fullPaymentPresaleDeliveryTime = preSaleDays.ParseToString() + }; + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/SetProductExtCodeStep.cs b/Store.Product/Publish/Steps/Wx/SetProductExtCodeStep.cs new file mode 100644 index 0000000..69d9d46 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/SetProductExtCodeStep.cs @@ -0,0 +1,48 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“设置商品货号”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class SetProductExtCodeStep : WxProductPublishStepBase +{ + public override string Description => "设置商品货号"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + extCode = dbProd.ext_code.IsNullOrEmpty() ? prodInfo.detail.out_goods_sn : dbProd.ext_code; + if (extCode.IsNullOrEmpty() || extCode.Length <= 10) + { + //extCode = "EC" + Randomx.GetId(1000).ToString().PadLeft(3, '0') + dbProd.spu_id + "C"; + extCode = GetEC1ExtCode(dbProd.spu_id); + } + upProdParam.product.info.spuCode = extCode; + upProdParam.product.info.outProductId = extCode; + upProdParam.product.info.out_product_id = extCode; + upProdParam.product.productKey.outProductId = extCode; + } +} diff --git a/Store.Product/Publish/Steps/Wx/SetProductTitleStep.cs b/Store.Product/Publish/Steps/Wx/SetProductTitleStep.cs new file mode 100644 index 0000000..8d1f879 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/SetProductTitleStep.cs @@ -0,0 +1,45 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“设置商品标题”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class SetProductTitleStep : WxProductPublishStepBase +{ + public override string Description => "设置商品标题"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + prodTitle = selectProd.product_name; + if (prodTitle.IsNullOrEmpty()) + prodTitle = dbProd.product_name.IsNullOrEmpty() ? prodInfo.detail.goods_name : dbProd.product_name; + + upProdInitParam.product.info.title = prodTitle; + upProdParam.product.info.title = prodTitle; + upProdParam.sessionInfo.sessionId = upProdInitParam.product.sessionId; + } +} diff --git a/Store.Product/Publish/Steps/Wx/SetQualificationsAndTagImagesStep.cs b/Store.Product/Publish/Steps/Wx/SetQualificationsAndTagImagesStep.cs new file mode 100644 index 0000000..eb704fb --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/SetQualificationsAndTagImagesStep.cs @@ -0,0 +1,673 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“资质信息(含书籍、食品、成人用品标签图)”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class SetQualificationsAndTagImagesStep : WxProductPublishStepBase +{ + public override string Description => "设置资质信息并生成标签图"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + // 微信回采直接复制 + if (BaseClass.UserAuthority.ParseToString().Contains("微信回采") && chkUseWXComplateProd.Checked && dbProd.has_wx_format == "1" && wxEditProdInfo != null) + { + upProdParam.product.info.qualificationList.Clear(); + if (wxEditProdInfo.product?.info?.qualificationList?.Count > 0) + { + foreach (var editQualification in wxEditProdInfo.product.info.qualificationList) + { + upProdParam.product.info.qualificationList.Add(new qualificationParam() + { + qualificationId = editQualification.qualificationId, + qualificationUrl = editQualification.qualificationUrl, + }); + } + } + + if (upProdParam.product.info.category.FirstOrDefault()?.name == "成人用品" && upProdParam.product.info.category[1].name != "情趣内衣") + { + if (wxEditProdInfo.product.info.brandId.ParseToLong() > 0) + DefaultAdultBrandId = wxEditProdInfo.product.info.brandId; + if (wxEditProdInfo.product.info.brandId.ParseToLong() > 0) + upProdParam.product.info.brandId = wxEditProdInfo.product.info.brandId.ParseToLong(); + } + } + // 微信店采直接复制 + //else if (dbProd.product_get_type_text == "微信店采" && upProdParam.product.info.category.FirstOrDefault()?.name != "图书" && + // upProdParam.product.info.category.FirstOrDefault()?.name != "食品饮料") + else if (dbProd.product_get_type_text == "微信店采" && + upProdParam.product.info.category.FirstOrDefault()?.name == "成人用品" && upProdParam.product.info.category[1].name != "情趣内衣") + { + upProdParam.product.info.qualificationList.Clear(); + if (wxEditProdInfo.product?.info?.qualificationList?.Count > 0) + { + foreach (var editQualification in wxEditProdInfo.product.info.qualificationList) + { + upProdParam.product.info.qualificationList.Add(new qualificationParam() + { + qualificationId = editQualification.qualificationId, + qualificationUrl = editQualification.qualificationUrl, + }); + } + } + + if (upProdParam.product.info.category.FirstOrDefault()?.name == "成人用品" && upProdParam.product.info.category[1].name != "情趣内衣") + { + if (wxEditProdInfo.product.info.brandId.ParseToLong() > 0) + DefaultAdultBrandId = wxEditProdInfo.product.info.brandId; + if (wxEditProdInfo.product.info.brandId.ParseToLong() > 0) + upProdParam.product.info.brandId = wxEditProdInfo.product.info.brandId.ParseToLong(); + } + } + else + { + //qualificationList + if (upProdParam.product.info.category.FirstOrDefault()?.name == "图书" || prodInfo.detail.cats.FirstOrDefault() == "书籍/杂志/报纸") + { + if (cell != null) + { + Invoke(new Action(() => + { + cell.Value = "正在生成图书专有信息"; + })); + } + + if (lstQulificationImage.Count == 0) + { + var pageCount = 1; + var seconds = 0m; + lstQulificationImage = AdminForms.WXAdminQualificationImageForm.AdminGetServerQualificationImageLst(out pageCount, out seconds, "图书"); + } + var qulificationImage = lstQulificationImage.OrderBy(x => Guid.NewGuid()).FirstOrDefault(); + if (qulificationImage != null) + { + if (qulificationImage.img_base64.IsNullOrEmpty()) + { + var pageCount = 1; + var seconds = 0m; + qulificationImage.img_base64 = AdminForms.WXAdminQualificationImageForm.AdminGetServerQualificationImageLst(out pageCount, out seconds, "图书", qulificationImage.img_md5) + .FirstOrDefault()?.img_base64; + } + } + + //string publisher = "出版社名称:" + new List() { "技术出版社", "文化出版社", "美术出版社" }[Randomx.GetId(3)]; + ////string isbn = "ISBN:"; + //string isbn = "ISBN/CN刊号:"; + ////string bookName = "书名:"; + //string bookName = "图书名称:"; + //string bookPrice = "定价:" + upProdParam.product.productSkus.Max(s => s.productSkuInfo.salePrice) * 0.01; + //string author = "作者:王老师"; + //string publishTime = "出版时间:" + DateTime.Now.AddMonths(-3).ToString("yyyy年MM月dd日"); + //string edition = "版次:1"; + //string binding = "装帧:平装"; + + //foreach (var propParam in upProdParam.product.info.param) + //{ + // if (propParam.categorys[0].name.Contains("出版社名称")) + // publisher = "出版社名称:" + propParam.categorys[1].name; + // else if (propParam.categorys[0].name.Contains("ISBN版号/CN刊号")) + // { + // //isbn = "ISBN:" + propParam.categorys[1].name; + // isbn = "ISBN/CN刊号:" + propParam.categorys[1].name; + // if (propParam.categorys[1].name.Length == 13) + // { + // var isbnStr = propParam.categorys[1].name; + // isbn = "ISBN/CN刊号:" + isbnStr[0] + isbnStr[1] + isbnStr[2] + "-" + isbnStr[3] + "-" + isbnStr[4] + isbnStr[5] + isbnStr[6] + isbnStr[7] + "-" + isbnStr[8] + isbnStr[9] + isbnStr[10] + isbnStr[11] + "-" + isbnStr[12]; + // } + // } + // else if (propParam.categorys[0].name.Contains("书名")) + // { + // //bookName = "书名:" + propParam.categorys[1].name; + // bookName = "图书名称:" + propParam.categorys[1].name; + // } + // else if (propParam.categorys[0].name.Contains("定价")) + // { + // //bookPrice = "定价:" + propParam.categorys[1].name; + // propParam.categorys[1].name = bookPrice; + // } + // else if (propParam.categorys[0].name.Contains("作者")) + // author = "作者:" + propParam.categorys[1].name; + // else if (propParam.categorys[0].name.Contains("出版时间")) + // publishTime = "出版时间:" + propParam.categorys[1].name; + // else if (propParam.categorys[0].name.Contains("版次")) + // edition = "版次:" + propParam.categorys[1].name; + // else if (propParam.categorys[0].name.Contains("包装")) + // binding = "装帧:" + propParam.categorys[1].name; + //} + + //var bookStr = publisher + "\r\n" + "\r\n" + + // isbn + "\r\n" + "\r\n" + + // bookName + "\r\n" + "\r\n" + + // bookPrice + "\r\n" + "\r\n" + + // author + "\r\n" + "\r\n" + + // publishTime + "\r\n" + "\r\n" + + // edition + "\r\n" + "\r\n" + + // binding; + + //int imgW = 800; int imgH = 800; int textImgH = 0; + //string getImgError; + //var img_url = upProdParam.product.info.headImg[0]; + //if (!img_url.Contains("wst.wxapp.tc.qq.com") && !img_url.Contains("mmecimage.cn")) + // img_url = img_url.Replace("https://", "http://"); + //var imageMain = GetHtml.GetUrlImg(img_url, out getImgError); + //if (imageMain == null) + // imageMain = new Bitmap(800, 800); + //using (Bitmap bitmap = new Bitmap(1, 1)) // 尺寸不重要,只是为了使用Graphics对象 + //using (Graphics g = Graphics.FromImage(bitmap)) // 获取Graphics对象 + //{ + // // 定义字体 + // Font font = new Font("微软雅黑", 16, FontStyle.Regular); + // g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; + // // 定义要测量的字符串 + // string text = bookStr; + // // 测量字符串大小 + // SizeF size = g.MeasureString(text, font, imgW - 120); + // // 输出高度信息 + // //Console.WriteLine("Text Height: " + size.Height); + // textImgH = size.Height.ParseToInt() + 180; + //} + + ////var bookImg = new Bitmap(imgW, imgH); + ////using (Graphics g = Graphics.FromImage(bookImg)) + ////{ + //// g.Clear(Color.White); + //// //Font font = new Font("宋体", 15, FontStyle.Bold); + //// //Font font = new Font("微软雅黑", 15, FontStyle.Regular); + //// Font font = new Font("微软雅黑", 16, FontStyle.Regular); + //// //Font font = new Font("宋体", 17, FontStyle.Bold); + //// Brush textBrush = Brushes.Black; + //// g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; + //// RectangleF rectF = new RectangleF(60, 80, imgW - 120, imgH - 180); + //// g.DrawString(bookStr, font, textBrush, rectF); + ////} + + //// 绘制一张带主图的商品详情页 + //var bookImg = new Bitmap(imgW, imgH + textImgH); + //using (Graphics g = Graphics.FromImage(bookImg)) + //{ + // g.Clear(Color.FromArgb(248, 239, 244)); + + // g.DrawImage(imageMain, new Rectangle(0, 0, imgW, imgH), 0, 0, imageMain.Width, imageMain.Height, GraphicsUnit.Pixel); + + // //Font font = new Font("宋体", 15, FontStyle.Bold); + // //Font font = new Font("微软雅黑", 15, FontStyle.Regular); + // Font font = new Font("微软雅黑", 18, FontStyle.Regular); + // //Font font = new Font("宋体", 17, FontStyle.Bold); + // Brush textBrush = Brushes.Black; + // g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; + // RectangleF rectF = new RectangleF(60, imgH + 80, imgW - 120, textImgH - 180); + // g.DrawString(bookStr, font, textBrush, rectF); + //} + + + string publisher = "出版发行:" + new List() { "技术出版社", "文化出版社", "美术出版社" }[Randomx.GetId(3)]; + //string isbn = "ISBN:"; + string isbn = "ISBN/CN刊号:"; + //string bookName = "书名:"; + string bookName = "图书名称:"; + string bookPrice = "定  价:" + upProdParam.product.productSkus.Max(s => s.productSkuInfo.salePrice) * 0.01; + string author = "作  者:王老师"; + string publishTime = "出版时间:" + DateTime.Now.AddMonths(-3).ToString("yyyy年MM月dd日"); + string edition = "版  次:1"; + string binding = "装  帧:平装"; + + foreach (var propParam in upProdParam.product.info.param) + { + if (propParam.categorys[0].name.Contains("出版社名称")) + publisher = "出版发行:" + propParam.categorys[1].name; + else if (propParam.categorys[0].name.Contains("ISBN版号/CN刊号")) + { + //isbn = "ISBN:" + propParam.categorys[1].name; + isbn = "ISBN/CN刊号:" + propParam.categorys[1].name; + if (propParam.categorys[1].name.Length == 13) + { + var isbnStr = propParam.categorys[1].name; + isbn = "ISBN/CN刊号:" + isbnStr[0] + isbnStr[1] + isbnStr[2] + "-" + isbnStr[3] + "-" + isbnStr[4] + isbnStr[5] + isbnStr[6] + isbnStr[7] + "-" + isbnStr[8] + isbnStr[9] + isbnStr[10] + isbnStr[11] + "-" + isbnStr[12]; + } + } + else if (propParam.categorys[0].name.Contains("书名")) + { + //bookName = "书名:" + propParam.categorys[1].name; + bookName = "图书名称:" + propParam.categorys[1].name; + } + else if (propParam.categorys[0].name.Contains("定价")) + { + //bookPrice = "定价:" + propParam.categorys[1].name; + propParam.categorys[1].name = bookPrice; + } + else if (propParam.categorys[0].name.Contains("作者")) + { + if (propParam.categorys[1].name != "/") + author = "作  者:" + propParam.categorys[1].name; + else + author = ""; + } + else if (propParam.categorys[0].name.Contains("出版时间")) + publishTime = "出版时间:" + propParam.categorys[1].name; + else if (propParam.categorys[0].name.Contains("版次")) + edition = "版  次:" + propParam.categorys[1].name; + else if (propParam.categorys[0].name.Contains("包装")) + binding = "装  帧:" + propParam.categorys[1].name; + } + if (author.IsNullOrEmpty()) + author = "作  者:" + publisher.Replace("出版发行:", ""); + + var bookStr = publisher + "\r\n" + "\r\n" + + isbn + "\r\n" + "\r\n" + + bookName + "\r\n" + "\r\n" + + bookPrice + "\r\n" + "\r\n" + + author + "\r\n" + "\r\n" + + publishTime + "\r\n" + "\r\n" + + edition + "\r\n" + "\r\n" + + binding; + + int imgW = 800; int imgH = 800; int textImgH = 0; + string getImgError; + var img_url = upProdParam.product.info.headImg[0]; + if (!img_url.Contains("wst.wxapp.tc.qq.com") && !img_url.Contains("mmecimage.cn")) + img_url = img_url.Replace("https://", "http://"); + var imageMain = GetHtml.GetUrlImg(img_url, out getImgError); + if (imageMain == null) + imageMain = new Bitmap(800, 800); + using (Bitmap bitmap = new Bitmap(1, 1)) // 尺寸不重要,只是为了使用Graphics对象 + using (Graphics g = Graphics.FromImage(bitmap)) // 获取Graphics对象 + { + // 定义字体 + Font font = new Font("微软雅黑", 16, FontStyle.Regular); + g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; + // 定义要测量的字符串 + string text = bookStr; + // 测量字符串大小 + SizeF size = g.MeasureString(text, font, imgW - 120); + // 输出高度信息 + //Console.WriteLine("Text Height: " + size.Height); + textImgH = size.Height.ParseToInt() + 180; + } + + + var bookImg = TemuKJD.Properties.Resources.empty_book_900_1200; + if (!string.IsNullOrEmpty(qulificationImage?.img_base64)) + { + // 使用服务端随即图 + try + { + bookImg = Base64ChangeImgUrl.Base64ToImage(qulificationImage.img_base64); + } + catch (Exception ex) + { + } + } + imgH = bookImg.Height; + imgW = bookImg.Width; + using (Graphics g = Graphics.FromImage(bookImg)) + { + Brush textBrush = Brushes.Black; + g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; + + float txtTop = 240, txtBottom = 240, txtLeft = 280, txtRight = 270; + if (qulificationImage.txt_margin.ParseToString().Split(',').Select(m => m.ParseToInt()).ToList() is List marginValueList && marginValueList.Count == 4) + { + txtLeft = marginValueList[0] + 10 - Randomx.GetId(21); + txtTop = marginValueList[1] + 10 - Randomx.GetId(21); + txtRight = marginValueList[2] + 10 - Randomx.GetId(21); + txtBottom = marginValueList[3] + 10 - Randomx.GetId(21); + } + int txtWidth = (int)imgW - (int)txtLeft - (int)txtRight; // 370; + + // 书名 + Font font = new Font("宋体", 22, FontStyle.Bold); + RectangleF rectF = new RectangleF(txtLeft, txtTop, txtWidth, imgH - txtTop - 80); + g.DrawString(bookName.Replace("图书名称:", ""), font, textBrush, rectF); + // 测量文本的高度 决定接下来文字绘制位置 + var textSize = g.MeasureString(bookName.Replace("图书名称:", ""), font, txtWidth); + txtTop += 20 + textSize.Height; + + font = new Font("宋体", 15, FontStyle.Bold); + rectF = new RectangleF(txtLeft, txtTop, txtWidth, imgH - txtTop - 80); + g.DrawString(author.Replace("作  者:", "").TrimEnd('著', ' ') + " 著", font, textBrush, rectF); + // 测量文本的高度 + textSize = g.MeasureString(author.Replace("作  者:", "").TrimEnd('著', ' ') + " 著", font, txtWidth); + txtTop += 20 + textSize.Height; + + rectF = new RectangleF(txtLeft, txtTop, txtWidth, imgH - txtTop - 80); + g.DrawString(publisher.Replace("出版发行:", ""), font, textBrush, rectF); + // 测量文本的高度 + textSize = g.MeasureString(publisher.Replace("出版发行:", ""), font, txtWidth); + txtTop += 20 + textSize.Height; + + rectF = new RectangleF(txtLeft, txtTop, txtWidth, imgH - txtTop - 80); + g.DrawString(isbn.Replace("ISBN/CN刊号:", "ISBN "), font, textBrush, rectF); + // 测量文本的高度 + textSize = g.MeasureString(isbn.Replace("ISBN/CN刊号:", "ISBN "), font, txtWidth); + txtTop += 20 + textSize.Height; + + // 以下从下往上写 + txtTop = imgH - txtBottom; + // 测量文本的高度 + textSize = g.MeasureString(binding, font, txtWidth); + txtTop -= (10 + textSize.Height); + rectF = new RectangleF(txtLeft, txtTop, txtWidth, imgH - txtTop); + g.DrawString(binding, font, textBrush, rectF); + + // 测量文本的高度 + textSize = g.MeasureString(edition, font, txtWidth); + txtTop -= (10 + textSize.Height); + rectF = new RectangleF(txtLeft, txtTop, txtWidth, imgH - txtTop); + g.DrawString(edition, font, textBrush, rectF); + + // 测量文本的高度 + textSize = g.MeasureString(publishTime, font, txtWidth); + txtTop -= (10 + textSize.Height); + rectF = new RectangleF(txtLeft, txtTop, txtWidth, imgH - txtTop); + g.DrawString(publishTime, font, textBrush, rectF); + + // 测量文本的高度 + textSize = g.MeasureString(author, font, txtWidth); + txtTop -= (10 + textSize.Height); + rectF = new RectangleF(txtLeft, txtTop, txtWidth, imgH - txtTop); + g.DrawString(author, font, textBrush, rectF); + + // 测量文本的高度 + textSize = g.MeasureString(bookPrice + "元", font, txtWidth); + txtTop -= (10 + textSize.Height); + rectF = new RectangleF(txtLeft, txtTop, txtWidth, imgH - txtTop); + g.DrawString(bookPrice + "元", font, textBrush, rectF); + + // 测量文本的高度 + textSize = g.MeasureString(bookName, font, txtWidth); + txtTop -= (10 + textSize.Height); + rectF = new RectangleF(txtLeft, txtTop, txtWidth, imgH - txtTop); + g.DrawString(bookName, font, textBrush, rectF); + + // 测量文本的高度 + textSize = g.MeasureString(isbn.Replace("ISBN/CN刊号:", "书  号:ISBN "), font, txtWidth); + textSize = g.MeasureString(isbn.Replace("ISBN/CN刊号:", "书  号:"), font, txtWidth); + txtTop -= (10 + textSize.Height); + rectF = new RectangleF(txtLeft, txtTop, txtWidth, imgH - txtTop); + g.DrawString(isbn.Replace("ISBN/CN刊号:", "书  号: "), font, textBrush, rectF); + + // 测量文本的高度 + textSize = g.MeasureString(publisher, font, txtWidth); + txtTop -= (10 + textSize.Height); + rectF = new RectangleF(txtLeft, txtTop, txtWidth, imgH - txtTop); + g.DrawString(publisher, font, textBrush, rectF); + + } + + + Thread.Sleep(Randomx.GetId(500, 800)); + var locationStr = UploadImg(shop, bookImg, cell); + if (!locationStr.Contains("\"success\":true")) + throw new Exception(locationStr); + var location = BaseRegex.GetStringJson(ref locationStr, "Location"); + upProdParam.product.info.qualificationList.Add(new qualificationParam() { qualificationId = "1111485", qualificationUrl = new List() { location }, }); + + using (MemoryStream memoryStream = new MemoryStream()) + { + bookImg.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Jpeg); + byte[] imageBytes = memoryStream.ToArray(); + var imgBase64 = Convert.ToBase64String(imageBytes); + + var urlImg = BaseClass.BuildWXImgObjString("product/wx_api_upload_image"); + var upDate = UserLogin.GetSignaturex(); + upDate += "&token=" + token.accessToken; + //upDate += "&is_file=true"; + //if (needAddRandomBlock || wantAddRandomBlock) + // upDate += "&random_code=true"; + upDate += "&img_url=" + location; + upDate += "&img_base64=" + imgBase64; + + var sTime = DateTime.Now; + var apiUpImgFileRst = GetHtml.PostUtf8(urlImg, upDate); + if (cell != null) + { + Invoke(new Action(() => + { + cell.Value = "正在上传资质图文件 " + (DateTime.Now - sTime).TotalMilliseconds.ParseToInt(); + })); + } + + for (int i = 0; i < 5 && apiUpImgFileRst.Contains("access_token expired"); i++) + { + token.mdyTime = DateTime.Now.AddDays(-100); + token = new WxApiBLL().GetRandomWxToken(); + apiUpImgFileRst = GetHtml.PostUtf8(urlImg, upDate); + } + var upImgUrl = BaseRegex.GetStringJson(ref apiUpImgFileRst, "img_url"); + upProdParam.product.info.headImg = upProdParam.product.info.headImg.Take(8).ToList(); + upProdParam.product.info.headImg.Add(upImgUrl); + } + } + + //qualificationList + if (upProdParam.product.info.category.FirstOrDefault()?.name == "食品饮料" || prodInfo.detail.cats.FirstOrDefault() == "食品饮料") + { + if (cell != null) + { + Invoke(new Action(() => + { + cell.Value = "正在生成食品饮料专有信息"; + })); + } + + + string foodName = "食品名称:" + prodTitle; + string companyName = "生产商名称:中国地区食品厂"; + string SCID = "食品生产许可证编号:SC10241162100670"; + string GBID = "产品标准号:GB 2726"; + string ExpDate = "保质期:30天"; + string MakeDate = "生产日期:" + DateTime.Now.AddDays(-15).ToString("yyyy-MM-dd"); + string NetContent = "净含量:见包装"; + string Pack = "包装方式:袋装"; + string inList = "配料表:水"; + string contact = "联系方式:无"; + string address = "地址:见包装"; + string Storage = "贮存条件:常温"; + + foreach (var propParam in upProdParam.product.info.param) + { + if (propParam.categorys[0].name.Contains("食品名称") && !propParam.categorys[1].name.Contains("见包装") && !propParam.categorys[1].name.Contains("见外包装") && + !propParam.categorys[1].name.Contains("1") && propParam.categorys[1].name.Length > 5) + foodName = "食品名称:" + propParam.categorys[1].name; + else if (propParam.categorys[0].name.Contains("生产企业名称") && !propParam.categorys[1].name.Contains("见包装") && !propParam.categorys[1].name.Contains("见外包装")) + companyName = "生产商名称:" + propParam.categorys[1].name; + else if (propParam.categorys[0].name.Contains("食品生产许可证编号") && !propParam.categorys[1].name.Contains("见包装") && !propParam.categorys[1].name.Contains("见外包装")) + SCID = "食品生产许可证编号:" + propParam.categorys[1].name; + else if (propParam.categorys[0].name.Contains("产品标准号") && !propParam.categorys[1].name.Contains("见包装") && !propParam.categorys[1].name.Contains("见外包装")) + GBID = "产品标准号:" + propParam.categorys[1].name; + else if (propParam.categorys[0].name.Contains("保质期")) + ExpDate = "保质期:" + propParam.categorys[1].name; + else if (propParam.categorys[0].name.Contains("生产日期")) + MakeDate = "生产日期:" + propParam.categorys[1].name; + else if (propParam.categorys[0].name.Contains("净含量")) + NetContent = "净含量:" + propParam.categorys[1].name; + else if (propParam.categorys[0].name.Contains("包装方式")) + Pack = "包装方式:" + propParam.categorys[1].name; + else if (propParam.categorys[0].name.Contains("配料表")) + inList = "配料表:" + propParam.categorys[1].name; + else if (propParam.categorys[0].name.Contains("联系方式")) + contact = "联系方式:" + propParam.categorys[1].name; + else if (propParam.categorys[0].name.Contains("地址")) + address = "地址:" + propParam.categorys[1].name; + else if (propParam.categorys[0].name.Contains("贮存条件")) + Storage = "贮存条件:" + propParam.categorys[1].name; + } + + var bookStr = foodName + "\r\n" + "\r\n"; + bookStr += companyName + "\r\n" + "\r\n"; + bookStr += SCID + "\r\n" + "\r\n"; + bookStr += GBID + "\r\n" + "\r\n"; + bookStr += ExpDate + "\r\n" + "\r\n"; + bookStr += MakeDate + "\r\n" + "\r\n"; + bookStr += NetContent + "\r\n" + "\r\n"; + bookStr += Pack + "\r\n" + "\r\n"; + bookStr += inList + "\r\n" + "\r\n"; + bookStr += contact + "\r\n" + "\r\n"; + bookStr += address + "\r\n" + "\r\n"; + bookStr += Storage + "\r\n" + "\r\n"; + + int width = 800; + int height = 1000; + var bookImg = new Bitmap(width, height); + using (Graphics g = Graphics.FromImage(bookImg)) + { + g.Clear(Color.Gray); + + Brush borderBrush = Brushes.Gray; + Brush contentBrush = Brushes.White; + g.FillRectangle(contentBrush, new RectangleF(8, 8, width - 16, height - 16)); + g.FillRectangle(borderBrush, new RectangleF(18, 18, width - 36, height - 36)); + g.FillRectangle(contentBrush, new RectangleF(22, 22, width - 44, height - 44)); + g.FillRectangle(borderBrush, new RectangleF(40, 140, width - 80, 4)); + + SizeF textSize; + { + Font font = new Font("微软雅黑", 30, FontStyle.Regular); + Brush textBrush = Brushes.DarkRed; + g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; + // 测量文本的宽度 + textSize = g.MeasureString("商 品 标 签", font); + float x = (width - textSize.Width) / 2; + + RectangleF rectF = new RectangleF(x, 50, width - x, height - 200); + g.DrawString("商 品 标 签", font, textBrush, rectF); + } + + { + //Font font = new Font("宋体", 15, FontStyle.Bold); + Font font = new Font("微软雅黑", 15, FontStyle.Regular); + Brush textBrush = Brushes.Black; + g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; + RectangleF rectF = new RectangleF(80, 120 + textSize.Height, width - 160, height - 80 - 120 - textSize.Height); + g.DrawString(bookStr, font, textBrush, rectF); + } + } + + Thread.Sleep(Randomx.GetId(500, 800)); + var locationStr = UploadImg(shop, bookImg, cell); + if (!locationStr.Contains("\"success\":true")) + throw new Exception(locationStr); + var location = BaseRegex.GetStringJson(ref locationStr, "Location"); + upProdParam.product.info.qualificationList.Add(new qualificationParam() { qualificationId = "50000057", qualificationUrl = new List() { location }, }); + } + + //qualificationList + if (upProdParam.product.info.category.FirstOrDefault()?.name == "成人用品" && upProdParam.product.info.category[1].name != "情趣内衣") + { + if (cell != null) + { + Invoke(new Action(() => + { + cell.Value = "正在生成成人用品专有信息"; + })); + } + + string prodName = "产品名称:" + + (upProdParam.product.info.category.Count > 2 ? upProdParam.product.info.category[2].name : upProdParam.product.info.category.LastOrDefault()?.name); + string company = "生产企业:浙江邦臣医药有限公司"; + string address = "制造商地址:浙江省金华市婺城区神丽路1399号6号厂房"; + string model = "产品型号:见包装"; + string material = "材质:其他"; + string user = "适用人群:18岁以上成年人"; + + foreach (var propParam in upProdParam.product.info.param) + { + //if (propParam.categorys[0].name.Contains("产品名称")) + // prodName = "产品名称:" + propParam.categorys[1].name; + } + + var bookStr = prodName + "\r\n" + "\r\n" + + company + "\r\n" + "\r\n" + + address + "\r\n" + "\r\n" + + model + "\r\n" + "\r\n" + + material + "\r\n" + "\r\n" + + user; + + var bookImg = new Bitmap(800, 800); + using (Graphics g = Graphics.FromImage(bookImg)) + { + g.Clear(Color.White); + //Font font = new Font("宋体", 15, FontStyle.Bold); + Font font = new Font("微软雅黑", 15, FontStyle.Regular); + Brush textBrush = Brushes.Black; + g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; + RectangleF rectF = new RectangleF(80, 80, 800 - 80, 800 - 80); + g.DrawString(bookStr, font, textBrush, rectF); + } + + Thread.Sleep(Randomx.GetId(500, 800)); + var locationStr = UploadImg(shop, bookImg, cell); + if (!locationStr.Contains("\"success\":true")) + throw new Exception(locationStr); + var location = BaseRegex.GetStringJson(ref locationStr, "Location"); + upProdParam.product.info.qualificationList.Add(new qualificationParam() { qualificationId = "50000089", qualificationUrl = new List() { location }, }); + } + + if (upProdParam.product.info.category.FirstOrDefault()?.name == "成人用品" && upProdParam.product.info.category[1].name != "情趣内衣") + { + if (cell != null) + { + Invoke(new Action(() => + { + cell.Value = "正在生成成人用品专有信息"; + })); + } + + string prodName = (upProdParam.product.info.category.Count > 2 ? upProdParam.product.info.category[2].name : upProdParam.product.info.category.LastOrDefault()?.name); + + var baseImg = TemuKJD.Properties.Resources.qingqu; + var newImg = new Bitmap(baseImg.Width, baseImg.Height); + using (Graphics g = Graphics.FromImage(newImg)) + { + //g.Clear(Color.White); + g.DrawImage(baseImg, new Rectangle(0, 0, newImg.Width, newImg.Height), 0, 0, baseImg.Width, baseImg.Height, GraphicsUnit.Pixel); + + g.FillRectangle(Brushes.White, 860.ParseToInt(), (1024 - 4).ParseToInt(), 500, 120); + + //Font font = new Font("宋体", txtSize.Text.Trim().ParseToInt(), FontStyle.Bold); + Font font = new Font("宋体", 44.ParseToInt(), FontStyle.Regular); + //Font font = new Font("微软雅黑", 15, FontStyle.Regular); + Brush textBrush = Brushes.Black; + textBrush = new SolidBrush(Color.FromArgb(0, 0, 0)); + g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit; + RectangleF rectF = new RectangleF(860, 1024, baseImg.Width - 80, baseImg.Height - 80); + if (!string.IsNullOrWhiteSpace(prodName.Trim())) + g.DrawString(prodName.Trim(), font, textBrush, rectF); + } + + Thread.Sleep(Randomx.GetId(500, 800)); + var locationStr = UploadImg(shop, newImg, cell); + if (!locationStr.Contains("\"success\":true")) + throw new Exception(locationStr); + var location = BaseRegex.GetStringJson(ref locationStr, "Location"); + upProdParam.product.info.qualificationList.Add(new qualificationParam() { qualificationId = "50000115", qualificationUrl = new List() { location }, }); + } + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/SkipUploadedProductStep.cs b/Store.Product/Publish/Steps/Wx/SkipUploadedProductStep.cs new file mode 100644 index 0000000..8608e7b --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/SkipUploadedProductStep.cs @@ -0,0 +1,117 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using MAX.Core.DependencyInjection; +using MAX.Core.Helpers; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Framework.Extensions; +using Store.Management.Framework.Apis; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“跳过已上传商品”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class SkipUploadedProductStep : WxProductPublishStepBase +{ + + public SkipUploadedProductStep() + { + } + public override string Description => "跳过已上传商品"; + + protected override Task CanExecuteProvider(WXPublishContext context, CancellationToken token) + { + return Task.FromResult(context.Parameters.SkipExisting); + } + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + // 裂变按标题名称 + if (selectProd.is_fission_title) + { + HttpResult result = WXApi.searchProductWithTitle(context.Shop.Cookie, GetHtml.UrlEncodeUtf8(context.Product.Title)); + string searchRst = result.Html; + if (!searchRst.Contains("\"code\":0") && !searchRst.Contains("\"success\":true")) + { + var error = BaseRegex.GetStringJson(ref searchRst, "msg"); + if (string.IsNullOrEmpty(error)) + error = searchRst; + throw new Exception("已存在商品查询错误 " + error); + } + try + { + var num = JObject.Parse(searchRst)["totalNum"].ParseToInt(); + if (num > 0) + { + throw new Exception("商品已存在,选择跳过"); + } + } + catch (Exception ex) + { + throw new Exception("已存在商品查询错误 " + ex.Message); + } + } + // 非裂变商品按商品Id + else + { + var searchExtCode = dbProd.ext_code; + if (dbProd.db_type == "云端数据") + { + dbProd.ext_code = searchExtCode = GetNewExtCode(dbProd.spu_id); + } + else + { + if (searchExtCode.ParseToString().Length < 10) + searchExtCode = GetEC1ExtCode(dbProd.spu_id); + } + if (dbProd.sku_secret_json != null && dbProd.sku_secret_json.Contains("goodsId")) + { + var sku_secret_json = dbProd.sku_secret_json; + searchExtCode = BaseRegex.GetStringJson(ref sku_secret_json, "goodsId"); + } + var result = WXApi.searchProduct(context.Shop.Cookie, searchExtCode); + string searchRst = result.Html; + if (!searchRst.Contains("\"code\":0") && !searchRst.Contains("\"success\":true")) + { + var error = BaseRegex.GetStringJson(ref searchRst, "msg"); + if (string.IsNullOrEmpty(error)) + error = searchRst; + throw new Exception("已存在商品查询错误 " + error); + } + try + { + var num = JObject.Parse(searchRst)["totalNum"].ParseToInt(); + if (num > 0) + { + throw new Exception("商品已存在,选择跳过"); + } + } + catch (Exception ex) + { + throw new Exception("已存在商品查询错误 " + ex.Message); + } + } + + return Task.CompletedTask; + } +} diff --git a/Store.Product/Publish/Steps/Wx/SubmitProductDraftStep.cs b/Store.Product/Publish/Steps/Wx/SubmitProductDraftStep.cs new file mode 100644 index 0000000..fcf2ff7 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/SubmitProductDraftStep.cs @@ -0,0 +1,107 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“保存商品草稿”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class SubmitProductDraftStep : WxProductPublishStepBase +{ + public override string Description => "保存商品草稿"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + // 直接使用微信回采情趣商品品牌 + if (BaseClass.UserAuthority.ParseToString().Contains("微信回采") && chkUseWXComplateProd.Checked && + upProdParam.product.info.category.FirstOrDefault()?.name == "成人用品" && upProdParam.product.info.category[1].name != "情趣内衣") + { + if (DefaultAdultBrandId.ParseToLong() > 0) + upProdParam.product.info.brandId = DefaultAdultBrandId.ParseToLong(); + } + // 情趣内衣等类目禁止设置运费险 + //if (upProdParam.product.info.category[1].name == "情趣内衣" || + // upProdParam.product.info.category[1].name == "水果") + if (chkFreightInsurance.Checked) + { + upProdParam.product.info.extraService.freightInsurance = 1; + } + else + { + upProdParam.product.info.extraService.freightInsurance = 0; + } + // 假1赔3 + if (upProdParam.product.info.category[0].name == "珠宝首饰" || upProdParam.product.info.category[1].name == "时尚饰品") + { + upProdParam.product.info.extraService.fakeOnePayFour = 1; + } + // 坏损包退 damageGuarantee + if (upProdParam.product.info.category[0].name == "生鲜" || upProdParam.product.info.category[0].name == "酒类" || + upProdParam.product.info.category[0].name == "食品饮料" || upProdParam.product.info.category[0].name == "保健食品/膳食营养补充食品" || + (upProdParam.product.info.category[0].name == "农资园艺" && upProdParam.product.info.category[1].name == "花卉绿植")) + { + upProdParam.product.info.extraService.damageGuarantee = 1; + } + // 生鲜不支持换货,关闭 + if (upProdParam.product.info.category[0].name == "生鲜") + { + upProdParam.product.info.extraService.exchangeSupport = 0; + } + + if (cell != null) + { + Invoke(new Action(() => + { + cell.Value = "正在提交商品"; + })); + } + Thread.Sleep(Randomx.GetId(1000, 2000)); + + uploadDraftRst = WXUpload.uploadProductDraft(shop, upProdParam); + if (!uploadDraftRst.Contains("\"code\":0") && !uploadDraftRst.Contains("\"success\":true")) + { + var error = BaseRegex.GetStringJson(ref uploadDraftRst, "msg"); + if (string.IsNullOrEmpty(error)) + { + if (uploadDraftRst.Contains("\"errcode\":9401043")) + error = "sku价格异常,请检查加价比例"; + else + error = uploadDraftRst; + } + var draftId = BaseRegex.GetStringJson(ref uploadDraftRst, "productId").ParseToLong(); + throw new Exception("草稿保存失败 " + GetHtml.UrlEncodeUtf8(error)); + } + if (cell != null) + { + Invoke(new Action(() => + { + var timeSpan = DateTime.Now - startTime; + var duration = timeSpan.TotalSeconds; + cell.Value = "商品已保存草稿,正在提交上架" + " " + duration.ParseToInt() + "秒"; + })); + } + + } +} diff --git a/Store.Product/Publish/Steps/Wx/SubmitProductStep.cs b/Store.Product/Publish/Steps/Wx/SubmitProductStep.cs new file mode 100644 index 0000000..3764aac --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/SubmitProductStep.cs @@ -0,0 +1,273 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“提交商品上架”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class SubmitProductStep : WxProductPublishStepBase +{ + public override string Description => "提交商品上架"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + upProdParam.product.productKey.productId = BaseRegex.GetLongJson(ref uploadDraftRst, "productId").ParseToString(); + + commissionRstStr = ""; + if (chkUpDraft.Checked && BaseClass.UserAuthority.Contains("佣金上货") && chkCommission.Checked) + { + var rst = WXUpload.addLeagueItemForUpdatingProduct(shop, 1, commissionRate, 1, commissionRate, upProdParam.product.productKey.productId.ParseToLong()); + if (!rst.Contains("\"code\":0")) + { + var error = BaseRegex.GetStringJsonByRegs(ref rst, rst, "msg", "errMsg", "errorMsg", "err_msg", "error_msg"); + commissionRstStr = "设置佣金比例失败 " + rst; + } + } + + if (chkUpDraft.Checked) + { + selectProd.wxDraftProduct = upProdParam; + selectProd.draft_id = upProdParam.product.productKey.productId; + selectProd.status = "草稿箱"; + selectProd.add_time = DateTime.Now; + upload_draft_product_lst.Add(selectProd); + NaiBa.Util.Helper.ConfigHelper.SaveConfig("wx_select_upload_draft_products", upload_draft_product_lst); + + StopWithResult("{\"success\":true,\"draftId\":" + upProdParam.product.productKey.productId + ",\"status\":\"草稿箱\",\"errMsg\":\"指定上传到草稿箱" + commissionRstStr + "\"}"); + return; + } + + // 提交商品上架 + if (!chkTestNoAuditUpload.Checked) + { + var uploadProdRst = WXUpload.upProductV2(shop, upProdParam); + //{"success":true,"retcode":209006,"errcode":209006,"productId":10001226244322,"productKey":{"productId":"10001226244322"}} // 提审成功 + //{"success":true,"retcode":0,"errcode":0,"productId":10001226095876,"productKey":{"productId":"10001226095876"}} // 未能提审 + if (!uploadProdRst.Contains("\"code\":0") && !uploadProdRst.Contains("\"success\":true")) + { + var error = BaseRegex.GetStringJson(ref uploadProdRst, "msg"); + if (string.IsNullOrEmpty(error)) + error = BaseRegex.GetStringJson(ref uploadProdRst, "errmsg"); + if (string.IsNullOrEmpty(error)) + error = BaseRegex.GetStringJson(ref uploadProdRst, "errcode"); + if (string.IsNullOrEmpty(error)) + error = uploadProdRst; + throw new Exception("上架失败 " + error); + } + if (upProdParam.product.productKey.productId.IsNullOrEmpty()) + upProdParam.product.productKey.productId = BaseRegex.GetLongJson(ref uploadProdRst, "productId").ParseToString(); + + if (BaseClass.UserAuthority.Contains("佣金上货") && chkCommission.Checked) + { + var rst = WXUpload.addLeagueItemForUpdatingProduct(shop, 1, commissionRate, 1, commissionRate, upProdParam.product.productKey.productId.ParseToLong(), 1); + if (!rst.Contains("\"code\":0")) + { + var error = BaseRegex.GetStringJsonByRegs(ref rst, rst, "msg", "errMsg", "errorMsg", "err_msg", "error_msg"); + commissionRstStr = "设置佣金比例失败 " + rst; + } + } + if (chkIsSubmitSuccess.Checked) + { + Thread.Sleep(Randomx.GetId(2000, 3001)); + var uploadCheckRst = WXUpload.SearchProductUpload(shop, upProdParam.product.productKey.productId); + if (uploadCheckRst.Contains("\"productList\":[]") || uploadCheckRst.Contains("\"totalNum\":0")) + { + throw new Exception("上架提审失败 请检查是否在架商品数已达到上限或店铺受到其他限制"); + } + } + } + else + { + var getEditProd = WXUpload.getEditProductV2(shop, upProdParam.product.productKey.productId); + //getEditProd = getEditProd.Replace("8388275713", "33333333"); + //getEditProd = getEditProd.Replace("33333333", "8387633147"); + + string productJson = ""; + try + { + // 获取免审编辑回传数据 + JsonSerializerSettings jsonSetting = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }; + var prodStr = JsonConvert.SerializeObject(upProdParam, jsonSetting); + JObject jObj = JsonHelper.JObjectParse(prodStr); + + JObject jObjTmp = JsonHelper.JObjectParse(getEditProd); + jObj["product"] = jObjTmp["product"]; + + //productJson = productJson.Replace("999", "1005"); + jObj["product"]["status"] = 1; //默认为5 + foreach (var sku in jObj["product"]["productSkus"]) + { + sku["productSkuInfo"]["stockInfo"]["stockNum"] = "1000"; + sku["productSkuInfo"]["stockInfo"]["totalNum"] = "1000"; + //sku["productSkuInfo"]["realStockInfo"]["stockNum"] = "1000"; + //sku["productSkuInfo"]["realStockInfo"]["totalNum"] = "1000"; + + var realStockInfo = new realStockInfoParam() { stockNum = 999, totalNum = 999 }; + sku["productSkuInfo"]["realStockInfo"] = JsonHelper.JObjectParse(JsonHelper.SerializeObject(realStockInfo)); + } + productJson = JsonHelper.SerializeObject(jObj); + + // 提交上架 + //var uploadProdRst = WXUpload.upProductJObjectV2(shop, productJson); + } + catch (Exception ex) + { + } + + string submitJson = "{\"name\":\"goods/" + upProdParam.product.productKey.productId + "/" + DateTime.Now.ToString("yyyy-MM-dd") + "/" + + BaseClass.DateTimeToTimestampMilliseconds(DateTime.Now) + "/submit.json\"}"; + var initFileName = BaseRegex.GetStringJson(ref submitJson, "name"); + DateTime tickTime = DateTime.Now.AddSeconds(0 - Randomx.GetId(10, 30)); + int time1 = BaseClass.DateTimeToTimestampSeconds(tickTime) - Randomx.GetId(80, 150); + int time2 = BaseClass.DateTimeToTimestampSeconds(tickTime); + + var uploadPartRst = WXUpload.initUploadPart(shop, submitJson, upProdParam.product.productKey.productId, time1, time2); + string uploadPartId = BaseRegex.GetStringJson(ref uploadPartRst, "uploadId"); + + //upProdParam.product.status = 5; + //upProdParam.product.editStatus = 0; + //upProdParam.product.auditReason = ""; + //upProdParam.product.info.skuIdList = new skuIdListParam(); + //upProdParam.product.info.skuList = new skuListParam(); + //upProdParam.product.info.skuList.skuInfo = new List(); + //var listingAuditFreeRst = WXUpload.listingAuditFree(shop, upProdParam, upProdParam.product.productKey.productId, time1, time2); + // 提交免审修改(实际生效接口) + var listingAuditFreeRst = WXUpload.listingAuditFree(shop, productJson, upProdParam.product.productKey.productId, time1, time2); + + string tempBlobPath = Path.GetTempPath().Trim('\\') + "\\" + Randomx.GetWordWithNumber(8).ToLower(); + if (!Directory.Exists(tempBlobPath)) + Directory.CreateDirectory(tempBlobPath); + string blobFullPath = tempBlobPath + "\\blob"; // C:\Users\Administrator\AppData\Local\Temp\3mer3k21\blob + using (StreamWriter sw = new StreamWriter(blobFullPath)) + { + //skuParam.productSkuInfo.realStockInfo = new realStockInfoParam() { stockNum = 1005, totalNum = 1005 }; + + + var jObj = JsonHelper.JObjectParse(productJson); + foreach (JObject skuObj in jObj["product"]["productSkus"]) + { + skuObj["productSkuInfo"]["stockInfo"]["stockNum"] = 1005; + + var realStockInfo = new realStockInfoParam() { stockNum = 999, totalNum = 999 }; + skuObj["productSkuInfo"]["realStockInfo"] = JsonHelper.JObjectParse(JsonHelper.SerializeObject(realStockInfo)); + } + + JsonSerializerSettings jsonSetting = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }; + foreach (var sku in upProdParam.product.productSkus) + { + sku.productSkuInfo.stockInfo.stockNum = 1005; + } + var prodStr = JsonConvert.SerializeObject(upProdParam, jsonSetting); + prodStr = JsonHelper.SerializeObject(jObj["product"]); + sw.Write(prodStr); + } + uploadPartRst = WXUpload.uploadPartBinary(shop, initFileName, blobFullPath, uploadPartId, upProdParam.product.productKey.productId, time1, time2); + //{"code":0,"etag":"18e3f73046378472219641ef181d3232"} + //C:\Users\Administrator\AppData\Local\Temp\9uaeiplr\blob + + var eTag = BaseRegex.GetStringJson(ref uploadPartRst, "etag"); + string rstParmStr = @"{""name"":""" + initFileName + @""",""uploadId"":""" + uploadPartId + @""",""etagList"":[""" + eTag + @"""],""partSeqList"":[1]}"; + var complateRst = WXUpload.completeUploadPart(shop, rstParmStr, upProdParam.product.productKey.productId, time1, time2); + + } + + if (cell != null) + { + Invoke(new Action(() => + { + var timeSpan = DateTime.Now - startTime; + var duration = timeSpan.TotalSeconds; + cell.Value = "商品已上传" + " " + duration.ParseToInt() + "秒"; + })); + } + + // 已传的商品保存为安全商品 + if (dbProd.wx_safe_status == "未校验") + { + var url = BaseClass.BuildObjString("product/mdy_pdd_product"); + var data = ""; + Invoke(new Action(() => + { + data = UserLogin.GetSignaturex(); + data += "&spu_id=" + dbProd.spu_id; + data += "&wx_safe_status=安全商品"; + })); + var html = GetHtml.PostUtf8(url, data); + if (string.IsNullOrEmpty(html) || !html.Contains("\"code\":200")) + html = GetHtml.PostUtf8(url, data); + if (string.IsNullOrEmpty(html) || !html.Contains("\"code\":200")) + html = GetHtml.PostUtf8(url, data); + } + if (!string.IsNullOrEmpty(dbProd.secret_spu_id)) + { + //add_secret_spu_id_shop_id_map //测试id "2017522348787531778" FC4C8B7F3C6E4B029AD2A12D4D323B41 // 老函数已作废 + var url = BaseClass.BuildObjString("product/wx_add_secret_id_up_map"); + var data = ""; + Invoke(new Action(() => + { + data = UserLogin.GetSignaturex(); + data += "&up_secret_spu_id=" + dbProd.secret_spu_id; + data += "&up_spu_id=" + dbProd.spu_id; + data += "&shop_id=" + shop.ShopId; + data += "&index=" + Math.Abs(dbProd.secret_spu_id.GetHashCode()) % 1024; + data += "&price_max=" + upProdParam.product.productSkus.Max(s => s.productSkuInfo.salePrice); + data += "&price_min=" + upProdParam.product.productSkus.Min(s => s.productSkuInfo.salePrice); + })); + var html = GetHtml.PostUtf8(url, data); + if (string.IsNullOrEmpty(html) || !html.Contains("\"code\":200")) + html = GetHtml.PostUtf8(url, data); + if (string.IsNullOrEmpty(html) || !html.Contains("\"code\":200")) + html = GetHtml.PostUtf8(url, data); + } + + //colUploadCount + var dgvShopRow = dgvShop.Rows.Cast().FirstOrDefault(row => row.Cells["colShopId"]?.Value.ParseToString() == shop.ShopId); + if (dgvShopRow != null) + { + var day = DateTime.Now.ToString("yyyyMMdd").ParseToInt(); + if (!GlobalConfig.dicWxShopUploadInfo.ContainsKey(shop.ShopId)) + GlobalConfig.dicWxShopUploadInfo[shop.ShopId] = new WxShopUploadInfo(); + if (!GlobalConfig.dicWxShopUploadInfo[shop.ShopId].DicDayUploadCount.ContainsKey(day)) + GlobalConfig.dicWxShopUploadInfo[shop.ShopId].DicDayUploadCount[day] = 0; + GlobalConfig.dicWxShopUploadInfo[shop.ShopId].DicDayUploadCount[day] += 1; + // 删除45天前的记录 + var removeKeys = GlobalConfig.dicWxShopUploadInfo[shop.ShopId].DicDayUploadCount.Keys.Where(s => s < DateTime.Now.AddDays(-45).ToString("yyyyMMdd").ParseToInt()).ToList(); + foreach (var key in removeKeys) + { + GlobalConfig.dicWxShopUploadInfo[shop.ShopId].DicDayUploadCount.Remove(key); + } + NaiBa.Util.Helper.ConfigHelper.SaveConfig("wx_shop_upload_info", GlobalConfig.dicWxShopUploadInfo); + + Invoke(new Action(() => + { + dgvShopRow.Cells["colUploadCount"].Value = GlobalConfig.dicWxShopUploadInfo[shop.ShopId].DicDayUploadCount[day]; + })); + } + + StopWithResult("{\"success\":true,\"draftId\":" + upProdParam.product.productKey.productId + ",\"status\":\"已上传\",\"msg\":\"上传成功" + commissionRstStr + "\"}"); + return; + } +} diff --git a/Store.Product/Publish/Steps/Wx/UploadDetailImagesStep.cs b/Store.Product/Publish/Steps/Wx/UploadDetailImagesStep.cs new file mode 100644 index 0000000..81936d6 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/UploadDetailImagesStep.cs @@ -0,0 +1,181 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“上传详情图”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class UploadDetailImagesStep : WxProductPublishStepBase +{ + public override string Description => "上传详情图"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + if (cell != null) + { + Invoke(new Action(() => + { + cell.Value = "正在生成商品详情"; + })); + } + Thread.Sleep(Randomx.GetId(100, 300)); + upProdParam.product.info.detail = new productDetailParam(); // 详情图 + detailGalleries = prodInfo.detail.galleries.Where(g => g.type == 2).ToList(); + if (detailGalleries.Count == 0) + detailGalleries.Add(new gallery_item() { type = 2, url = prodInfo.detail.galleries.FirstOrDefault(g => g.type == 1)?.url }); + + foreach (var gallery_item in detailGalleries) + { + try + { + if (dbProd.product_get_type_text == "微信店采") + { + upProdParam.product.info.detail.detailImg.Add(gallery_item.url); + continue; + } + + // 获取缓存盗链 详情图 + if (wxImgMapDic.ContainsKey(BaseClass.GetMd5(gallery_item.url))) + { + upProdParam.product.info.detail.detailImg.Add(wxImgMapDic[BaseClass.GetMd5(gallery_item.url)]); + continue; + } + + if (needAddRandomBlock || wantAddRandomBlock || gallery_item.url.Contains("wst.wxapp.tc.qq.com")) + { + // 如果想打码或者需要打码,就走差异化详情图,不走api直传 + } + else + { + // 使用接口链接传详情图 + var urlImg = BaseClass.BuildWXImgObjString("product/wx_api_upload_image"); + var upDate = UserLogin.GetSignaturex(); + upDate += "&token=" + token.accessToken; + upDate += "&post_data=" + GetHtml.UrlEncodeUtf8("{\"img_url\":\"" + gallery_item.url.Replace("https://", "http://") + "\"}"); + + var sTime = DateTime.Now; + var upDateRst = GetHtml.PostUtf8(urlImg, upDate); + if (cell != null) + { + Invoke(new Action(() => + { + cell.Value = "正在生成商品详情图 " + (DateTime.Now - sTime).TotalMilliseconds.ParseToInt(); + })); + } + + for (int i = 0; i < 20 && upDateRst.Contains("access_token expired"); i++) + { + token.mdyTime = DateTime.Now.AddDays(-100); + token = new WxApiBLL().GetRandomWxToken(); + upDateRst = GetHtml.PostUtf8(urlImg, upDate); + } + var upImgUrl = BaseRegex.GetStringJson(ref upDateRst, "img_url"); + if (upDateRst.Contains("\"errcode\":0") && !string.IsNullOrEmpty(upImgUrl)) + { + upProdParam.product.info.detail.detailImg.Add(upImgUrl); + wxImgMapDic[BaseClass.GetMd5(gallery_item.url)] = upImgUrl; + isChangeImgDic = true; + continue; + } + else + { + } + } + } + catch (Exception ex) + { + } + + // api上传图片文件 详情图 + if (1 == 1) + { + try + { + string errMsg = ""; + var imgBase64 = GetImgBase64ForApi(out errMsg, gallery_item.url, needAddRandomBlock, wantAddRandomBlock); + if (!string.IsNullOrEmpty(errMsg)) + { + throw new Exception(errMsg); + } + + var urlImg = BaseClass.BuildWXImgObjString("product/wx_api_upload_image"); + var upDate = UserLogin.GetSignaturex(); + upDate += "&token=" + token.accessToken; + //upDate += "&is_file=true"; + //if (needAddRandomBlock || wantAddRandomBlock) + // upDate += "&random_code=true"; + upDate += "&img_url=" + gallery_item.url; + upDate += "&img_base64=" + imgBase64; + + var sTime = DateTime.Now; + var apiUpImgFileRst = GetHtml.PostUtf8(urlImg, upDate); + if (cell != null) + { + Invoke(new Action(() => + { + cell.Value = "正在上传商品详情图文件 " + (DateTime.Now - sTime).TotalMilliseconds.ParseToInt(); + })); + } + + for (int i = 0; i < 5 && apiUpImgFileRst.Contains("access_token expired"); i++) + { + token.mdyTime = DateTime.Now.AddDays(-100); + token = new WxApiBLL().GetRandomWxToken(); + apiUpImgFileRst = GetHtml.PostUtf8(urlImg, upDate); + } + var upImgUrl = BaseRegex.GetStringJson(ref apiUpImgFileRst, "img_url"); + if (apiUpImgFileRst.Contains("\"errcode\":0") && !string.IsNullOrEmpty(upImgUrl)) + { + upProdParam.product.info.detail.detailImg.Add(upImgUrl); + wxImgMapDic[BaseClass.GetMd5(gallery_item.url)] = upImgUrl; + isChangeImgDic = true; + continue; + } + else + { + } + } + catch (Exception ex) + { + throw new Exception("修改详情图失败 " + ex.Message); + } + } + + // 模拟上传图片 + bool randomDrawBlock = false; + if (needAddRandomBlock || wantAddRandomBlock) + randomDrawBlock = true; + var locationStr = UploadImg(shop, gallery_item.url, cell, randomDrawBlock); + if (!locationStr.Contains("\"success\":true")) + throw new Exception(locationStr); + var location = BaseRegex.GetStringJson(ref locationStr, "Location"); + upProdParam.product.info.detail.detailImg.Add(location); + //wxImgMapDic[BaseClass.GetMd5(gallery_item.url)] = location; + //isChangeImgDic = true; + Thread.Sleep(Randomx.GetId(10, 50)); + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/UploadMainImagesStep.cs b/Store.Product/Publish/Steps/Wx/UploadMainImagesStep.cs new file mode 100644 index 0000000..22fff97 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/UploadMainImagesStep.cs @@ -0,0 +1,243 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“上传主图”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class UploadMainImagesStep : WxProductPublishStepBase +{ + public override string Description => "上传主图"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + if (cell != null) + { + Invoke(new Action(() => + { + cell.Value = "正在生成主图并识别商品"; + })); + } + Thread.Sleep(Randomx.GetId(100, 300)); + mainGalleries = prodInfo.detail.galleries.Where(g => g.type == 1).Take(9).ToList(); + foreach (var gallery_item in mainGalleries) // 9张主图 + { + try + { + if (dbProd.product_get_type_text == "微信店采") + { + upProdInitParam.product.info.headImg.Add(gallery_item.url); + upProdParam.product.info.headImg.Add(gallery_item.url); + continue; + } + + // 获取缓存盗链 主图 + if (wxImgMapDic.ContainsKey(BaseClass.GetMd5(gallery_item.url))) + { + upProdInitParam.product.info.headImg.Add(wxImgMapDic[BaseClass.GetMd5(gallery_item.url)]); + upProdParam.product.info.headImg.Add(wxImgMapDic[BaseClass.GetMd5(gallery_item.url)]); + continue; + } + + if (needAddRandomBlock || wantAddRandomBlock || gallery_item.url.Contains("wst.wxapp.tc.qq.com")) + { + // 如果想打码或者需要打码,就走差异化详情图,不走api直传 + } + else + { + // 使用接口链接传主图 + var urlImg = BaseClass.BuildWXImgObjString("product/wx_api_upload_image"); + var upDate = UserLogin.GetSignaturex(); + upDate += "&token=" + token.accessToken; + upDate += "&post_data=" + GetHtml.UrlEncodeUtf8("{\"img_url\":\"" + gallery_item.url.Replace("https://", "http://") + "\"}"); + + var sTime = DateTime.Now; + var upDateRst = GetHtml.PostUtf8(urlImg, upDate); + if (cell != null) + { + Invoke(new Action(() => + { + cell.Value = "正在生成主图并识别商品 " + (DateTime.Now - sTime).TotalMilliseconds.ParseToInt(); + })); + } + + for (int i = 0; i < 20 && upDateRst.Contains("access_token expired"); i++) + { + token.mdyTime = DateTime.Now.AddDays(-100); + token = new WxApiBLL().GetRandomWxToken(); + upDateRst = GetHtml.PostUtf8(urlImg, upDate); + } + var upImgUrl = BaseRegex.GetStringJson(ref upDateRst, "img_url"); + if (upDateRst.Contains("\"errcode\":0") && !string.IsNullOrEmpty(upImgUrl)) + { + upProdInitParam.product.info.headImg.Add(upImgUrl); + upProdParam.product.info.headImg.Add(upImgUrl); + wxImgMapDic[BaseClass.GetMd5(gallery_item.url)] = upImgUrl; + isChangeImgDic = true; + continue; + } + else + { + } + } + } + catch + { + } + + // api上传图片文件 主图 + if (1 == 1) + { + try + { + string errMsg = ""; + var imgBase64 = GetImgBase64ForApi(out errMsg, gallery_item.url, needAddRandomBlock, wantAddRandomBlock, 800, 800); + if (!string.IsNullOrEmpty(errMsg)) + { + throw new Exception(errMsg); + } + + var urlImg = BaseClass.BuildWXImgObjString("product/wx_api_upload_image"); + var upDate = UserLogin.GetSignaturex(); + upDate += "&token=" + token.accessToken; + //upDate += "&is_file=true"; + //if (needAddRandomBlock || wantAddRandomBlock) + // upDate += "&random_code=true"; + upDate += "&img_url=" + gallery_item.url; + upDate += "&img_base64=" + imgBase64; + + var sTime = DateTime.Now; + var apiUpImgFileRst = GetHtml.PostUtf8(urlImg, upDate); + if (cell != null) + { + Invoke(new Action(() => + { + cell.Value = "正在上传主图文件并识别商品 " + (DateTime.Now - sTime).TotalMilliseconds.ParseToInt(); + })); + } + + for (int i = 0; i < 5 && apiUpImgFileRst.Contains("access_token expired"); i++) + { + token.mdyTime = DateTime.Now.AddDays(-100); + token = new WxApiBLL().GetRandomWxToken(); + apiUpImgFileRst = GetHtml.PostUtf8(urlImg, upDate); + } + var upImgUrl = BaseRegex.GetStringJson(ref apiUpImgFileRst, "img_url"); + if (apiUpImgFileRst.Contains("\"errcode\":0") && !string.IsNullOrEmpty(upImgUrl)) + { + upProdInitParam.product.info.headImg.Add(upImgUrl); + upProdParam.product.info.headImg.Add(upImgUrl); + wxImgMapDic[BaseClass.GetMd5(gallery_item.url)] = upImgUrl; + isChangeImgDic = true; + continue; + } + else + { + } + } + catch (Exception ex) + { + throw new Exception("修改主图失败 " + ex.Message); + } + } + + // 模拟上传图片 + if (1 == 1) + { + var upImgAuthRst = WXUpload.getUploadTempImgAuth(shop); + for (int i = 0; i < 10 && upImgAuthRst.Contains("LogicError"); i++) + { + Invoke(new Action(() => + { + cell.Value = "图片提报失败,正在重试。" + (i + 1); + })); + Thread.Sleep(Randomx.GetId(3000, 6000)); + upImgAuthRst = WXUpload.getUploadTempImgAuth(shop); + } + if (!upImgAuthRst.Contains("\"code\":0")) + { + var error = BaseRegex.GetStringJson(ref upImgAuthRst, "msg"); + if (string.IsNullOrEmpty(error)) + error = upImgAuthRst; + throw new Exception("申请上传主图失败 " + GetHtml.UrlEncodeUtf8(error)); + } + string disK = BaseRegex.GetStringJson(ref upImgAuthRst, "disK"); + long disT = BaseRegex.GetLongJson(ref upImgAuthRst, "disT"); + string ext = BaseRegex.GetStringJson(ref upImgAuthRst, "ext"); + string imgBase64 = ""; + + //------------------------------------------------------------------------------------------------------------------------------------- + //生成主图base64字符串 + string getImgError; + var image = GetHtml.GetUrlImg(gallery_item.url.Replace("https://", "http://"), out getImgError); + var imageBg = new Bitmap(800, 800); + if (!string.IsNullOrEmpty(getImgError) || image == null) + { + throw new Exception("初始化主图上传失败"); + } + try + { + using (Graphics g = Graphics.FromImage(imageBg)) + { + g.DrawImage(image, new Rectangle(0, 0, imageBg.Width, imageBg.Height), 0, 0, image.Width, image.Height, GraphicsUnit.Pixel); + if (needAddRandomBlock || wantAddRandomBlock) + { + g.FillRectangle(new SolidBrush(Color.FromArgb(Randomx.GetId(256), Randomx.GetId(256), Randomx.GetId(256))), + new Rectangle(Randomx.GetId(imageBg.Width - 30), Randomx.GetId(imageBg.Height - 30), 30, 30)); + } + } + using (MemoryStream memoryStream = new MemoryStream()) + { + imageBg.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Jpeg); + byte[] imageBytes = memoryStream.ToArray(); + imgBase64 = Convert.ToBase64String(imageBytes); + } + } + catch (Exception ex) + { + throw new Exception("修改主图失败 " + ex.Message); + } + + //------------------------------------------------------------------------------------------------------------------------------------- + //上传主图获取路径 + var upImgRst = WXUpload.adtimageupload(shop, imgBase64, disK, disT, ext).ParseToString(); + var location = BaseRegex.GetStringJson(ref upImgRst, "Location"); + if (string.IsNullOrEmpty(location)) + { + var error = BaseRegex.GetStringJson(ref upImgAuthRst, "msg"); + if (string.IsNullOrEmpty(error)) + error = upImgAuthRst; + throw new Exception("主图上传失败 " + error); + } + upProdInitParam.product.info.headImg.Add(location); + upProdParam.product.info.headImg.Add(location); + //wxImgMapDic[BaseClass.GetMd5(gallery_item.url)] = location; + //isChangeImgDic = true; + Thread.Sleep(Randomx.GetId(10, 50)); + } + } + } +} diff --git a/Store.Product/Publish/Steps/Wx/UploadVideoStep.cs b/Store.Product/Publish/Steps/Wx/UploadVideoStep.cs new file mode 100644 index 0000000..68eb8e3 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/UploadVideoStep.cs @@ -0,0 +1,77 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.Config; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Work; + +namespace Store.Product.Publish.Steps.Wx; +/// +/// 由 WXDBProdUploadForm.UploadProduct 的“上传商品视频”区段迁移。 +/// TODO(迁移): 原区段中的 TEMUKJD/WXUpload/窗体服务引用需按 Store.Product 运行时适配逐项核对。 +/// +public sealed class UploadVideoStep : WxProductPublishStepBase +{ + public override string Description => "上传商品视频"; + + protected override Task ExecuteProvider(WXPublishContext context, CancellationToken cancellationToken) + { + if (prodInfo.detail.galleries.Any(a => a.type == 6)) + { + List videos = prodInfo.detail.galleries.Where(a => a.type == 6).ToList(); + //下载视频保存 + foreach (var item in videos) + { + Invoke(new Action(() => + { + cell.Value = "开始下载视频"; + })); + byte[] files; + bool d_ret = GetHtml.DownloadFile(item.url, out files); + if (d_ret == false) + { + Invoke(new Action(() => + { + cell.Value = "视频下载失败"; + })); + throw new Exception("视频下载失败"); + } + Invoke(new Action(() => + { + cell.Value = "开始上传视频"; + })); + var uploader = new WXVideoUploader(); + var video = uploader.Upload(shop, selectProd.id.ToString(), files); + upProdParam.product.info.headVideoInfo.Add(new + { + videoUrl = video.DownloadUrl, + videoHeadImg = video.VideoImgUrl, + videoLen = video.VideoDuration, + videoSize = 0, + videoSource = 0, + videoFormat = 0, + playableVideoUrl = video.DownloadUrl + }); + } + } + //prodInfo.detail.galleries.RemoveAll(g => g.type == 6); // 视频暂时移除,以后再视情况加上 + } +} diff --git a/Store.Product/Publish/Steps/Wx/WXPublishContext.cs b/Store.Product/Publish/Steps/Wx/WXPublishContext.cs new file mode 100644 index 0000000..12e9ca0 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/WXPublishContext.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Product.Publish.Steps.Wx; + +public class WXPublishContext : PublishContextBase +{ + public IProgress Progress { get; set; } +} diff --git a/Store.Product/Publish/Steps/Wx/WxProductPublishPipelineFactory.cs b/Store.Product/Publish/Steps/Wx/WxProductPublishPipelineFactory.cs new file mode 100644 index 0000000..a16993d --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/WxProductPublishPipelineFactory.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; + +namespace Store.Product.Publish.Steps.Wx; + +public class WXPublishPipeline : PublishPipeline +{ + public WXPublishPipeline(IEnumerable> steps) : base(steps) + { + + } +} +/// +/// 微信商品上传步骤顺序,来源于 WXDBProdUploadForm.UploadProduct 的原有执行顺序。 +/// +public static class WxProductPublishPipelineFactory +{ + public static WXPublishPipeline CreateDefault() + { + return new WXPublishPipeline( + [ + new InitializeUploadProductStep(),//初始化上传参数与店铺登录校验 + new FissionTitleStep(),//裂变标题 + new SkipUploadedProductStep(),//跳过已上传商品 + new CommissionPreparationStep(),//佣金上传准备 + new ConvertJstPddProductStep(),//聚水潭商品转换为PDD统一格式 + new RestoreWxShopSourceProductStep(),//微信店采原始格式还原 + new ConvertWxShopProductStep(),//微信店采格式转换 + new ConvertWxRecycleProductStep(),//微信回采格式转换 + new ConvertWxAppProductStep(),//微信小店App格式转换 + new Convert1688ProductStep(),//1688商品格式转换 + new ConvertCloudProductStep(),//云端商品格式转换 + new InitializeProductDataStep(),//初始化微信小店商品参数 + new PrepareHotlinkImageMapStep(),//生成图片盗链映射 + new PrepareSourceProductStep(),//提取源商品详情并生成SKU货号 + new UploadVideoStep(),//上传商品视频 + new FillMainImagesStep(),//主图不足时使用详情图补充 + new FilterDuplicateImagesStep(),//过滤重复图片 + new GetShopApiTokenStep(),//获取店铺ApiToken + new SetProductTitleStep(),//设置商品标题 + new SetProductExtCodeStep(),//设置商品货号 + new UploadMainImagesStep(),//上传主图 + new SaveMainImageHotlinkMapStep(),//保存主图盗链映射 + new DetectCategoryStep(),//识别商品类目 + new GenerateCategoryTemplateStep(),//生成类目模板 + new GenerateAiShortTitleStep(),//生成AI短标题 + new AddTitlePrefixStep(),//添加标题前缀 + new SetAfterSaleInfoStep(),//设置售后信息 + new CalculateShippingTemplateStep(),//获取推单运费模板并计算运费 + new GetShopShippingTemplateStep(),//获取店铺运费模板 + new UploadDetailImagesStep(),//上传详情图 + new SaveDetailImageHotlinkMapStep(),//保存详情图盗链映射 + new MatchProductPropertiesStep(),//匹配产品属性 + new BuildSkuPropertiesStep(),//构建SKU规格属性 + new SaveSkuHotlinkMapStep(),//保存SKU图片盗链映射 + new BuildSkuInfoStep(),//构建SKU信息 + new SetPreSaleInfoStep(),//设置预售信息 + new SetCommissionInfoStep(),//设置佣金信息 + new SetQualificationsAndTagImagesStep(),//资质信息(含书籍、食品、成人用品标签图) + new GenerateAiProductImageStep(),//AI生成商品裂变图片 + new AddRandomImageBorderStep(),//为图片添加随机外框 + new SubmitProductDraftStep(),//保存商品草稿 + new SaveDraftHotlinkMapStep(),//保存草稿商品盗链映射 + new SubmitProductStep(),//提交商品上架 + ] + ); + } +} diff --git a/Store.Product/Publish/Steps/Wx/WxProductPublishStepBase.cs b/Store.Product/Publish/Steps/Wx/WxProductPublishStepBase.cs new file mode 100644 index 0000000..89643b6 --- /dev/null +++ b/Store.Product/Publish/Steps/Wx/WxProductPublishStepBase.cs @@ -0,0 +1,222 @@ +using AllBase; +using AllBase.Extension; +using AllBase.Helper; +using AllBase.Model.DBModel; +using AllBase.Model.Param.WXParam; +using AllBase.Model.Result; +using AllBase.Model.Result.PDDResult; +using AllBase.Model.Result.WXResult; +using MAX.Core.DependencyInjection; +using Newtonsoft.Json.Linq; +using Store.Management.Framework.Apis; +using Store.Product.Models.PublishParameters.Wx; +using Store.Product.Publish; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Store.Product.Publish.Steps.Wx; + +/// +/// 微信商品发布步骤基类。 +/// 该基类把原窗体方法的可变状态、控件快照和窗体能力适配集中到一个入口, +/// 让各 Step 尽量保持与原 UploadProduct 区段一致的代码,便于逐段核对。 +/// +public abstract class WxProductPublishStepBase : PublishStep +{ + protected WXApi WXApi => LazyServiceProvider.Instance.LazyGetService(); + + + private PublishContextBase _context; + + protected WxProductUploadParam Upload => _context.WxUpload; + protected WxProductPublishState State => Upload.State; + protected WxProductUploadOptions Options => Upload.Options; + protected WxProductUploadFormState Form => Options.Form; + protected dynamic RuntimeHost => Upload.Runtime.Host; + + protected ShopLoginInfo shop => Upload.Source.Shop; + protected KJDDBProduct dbProd => Upload.Source.DbProduct; + protected TemuDBProductSelect selectProd => Upload.Source.SelectProduct; + protected DateTime startTime => Upload.Source.StartTime; + protected WXUploadProductInitParam upProdInitParam => Upload.InitParameter; + protected WXUploadProductParam upProdParam => Upload.ProductParameter; + + // TODO(迁移): cell/cancellationTokenSource 原为 WinForms 对象,这里通过代理回调保持步骤代码形状。 + protected dynamic cell => Upload.Runtime.Cell; + protected dynamic cancellationTokenSource => Upload.Runtime.CancellationTokenSource; + + protected dynamic chkFissionGoodsName => Form.chkFissionGoodsName; + protected dynamic txtFissionTitle => Form.txtFissionTitle; + protected dynamic chkSkipUploaded => Form.chkSkipUploaded; + protected dynamic chkCommission => Form.chkCommission; + protected dynamic cmbCommissionAddOrReduce => Form.cmbCommissionAddOrReduce; + protected dynamic txtCommissionChangeValue => Form.txtCommissionChangeValue; + protected dynamic txtMinUpCommission => Form.txtMinUpCommission; + protected dynamic txtMaxUpCommission => Form.txtMaxUpCommission; + protected dynamic chkImgRandomBlock => Form.chkImgRandomBlock; + protected dynamic chkRandomMainImgOrder => Form.chkRandomMainImgOrder; + protected dynamic chkRandomDetailImgOrder => Form.chkRandomDetailImgOrder; + protected dynamic chkFissionRandomPriceChange => Form.chkFissionRandomPriceChange; + protected dynamic txtFissionRandomPriceChange => Form.txtFissionRandomPriceChange; + protected dynamic chkHighestPriceSku => Form.chkHighestPriceSku; + protected dynamic chkLowestPriceSku => Form.chkLowestPriceSku; + protected dynamic chkSameSpecPriceUp => Form.chkSameSpecPriceUp; + protected dynamic chkSameSpecPriceDown => Form.chkSameSpecPriceDown; + protected dynamic txtMinPrice => Form.txtMinPrice; + protected dynamic txtMaxPrice => Form.txtMaxPrice; + protected dynamic txtReplaceText => Form.txtReplaceText; + protected dynamic chkSkipReplaceText => Form.chkSkipReplaceText; + protected dynamic chkRemoveSpecialChar => Form.chkRemoveSpecialChar; + protected dynamic chkUseWXComplateProd => Form.chkUseWXComplateProd; + protected dynamic chkUseRecommand => Form.chkUseRecommand; + protected dynamic cmbSpBrandName => Form.cmbSpBrandName; + protected dynamic txtFilterBoundMax => Form.txtFilterBoundMax; + protected dynamic txtFilterBoundMin => Form.txtFilterBoundMin; + protected dynamic txtMainBorderImgCount => Form.txtMainBorderImgCount; + protected dynamic txtDetailBorderImgCount => Form.txtDetailBorderImgCount; + protected dynamic chkRandomImgBorder => Form.chkRandomImgBorder; + protected dynamic chkAiImage => Form.chkAiImage; + protected dynamic txtAiImgCount => Form.txtAiImgCount; + protected dynamic chkFreightInsurance => Form.chkFreightInsurance; + protected dynamic chkUseShanghaiReturnOrderAddress => Form.chkUseShanghaiReturnOrderAddress; + protected dynamic chkPreSale => Form.chkPreSale; + protected dynamic chkDefaultBestBefore => Form.chkDefaultBestBefore; + protected dynamic txtDefaultBestBefore => Form.txtDefaultBestBefore; + protected dynamic chkTestNoAuditUpload => Form.chkTestNoAuditUpload; + protected dynamic chkUpDraft => Form.chkUpDraft; + protected dynamic chkIsSubmitSuccess => Form.chkIsSubmitSuccess; + protected dynamic dgvShop => Form.dgvShop; + + protected int commissionRate { get => State.CommissionRate; set => State.CommissionRate = value; } + protected int minCommissionRate { get => State.MinCommissionRate; set => State.MinCommissionRate = value; } + protected int maxCommissionRate { get => State.MaxCommissionRate; set => State.MaxCommissionRate = value; } + protected int commissionRateChange { get => State.CommissionRateChange; set => State.CommissionRateChange = value; } + protected int? fromCommissionRate { get => State.FromCommissionRate; set => State.FromCommissionRate = value; } + protected WXGetEditProductInfoLite wxEditProdInfo { get => State.WxEditProdInfo; set => State.WxEditProdInfo = value; } + protected JObject prodJObj { get => State.ProdJObject; set => State.ProdJObject = value; } + protected bool isChangeImgDic { get => State.IsChangeImgDic; set => State.IsChangeImgDic = value; } + protected Dictionary wxImgMapDic { get => State.WxImgMapDic; set => State.WxImgMapDic = value; } + protected PDDProductInfo prodInfo { get => State.ProdInfo; set => State.ProdInfo = value; } + protected int mainImgCount { get => State.MainImgCount; set => State.MainImgCount = value; } + protected bool needAddRandomBlock { get => State.NeedAddRandomBlock; set => State.NeedAddRandomBlock = value; } + protected bool wantAddRandomBlock { get => State.WantAddRandomBlock; set => State.WantAddRandomBlock = value; } + protected WXShopTokenInfo token { get => State.Token; set => State.Token = value; } + protected string prodTitle { get => State.ProdTitle; set => State.ProdTitle = value; } + protected string extCode { get => State.ExtCode; set => State.ExtCode = value; } + protected List mainGalleries { get => State.MainGalleries; set => State.MainGalleries = value; } + protected List aiCateIdLst { get => State.AiCateIdLst; set => State.AiCateIdLst = value; } + protected string getCateRst { get => State.GetCateRst; set => State.GetCateRst = value; } + protected JObject jCateRst { get => State.JCateRst; set => State.JCateRst = value; } + protected long leafCatId { get => State.LeafCatId; set => State.LeafCatId = value; } + protected WxCatBoundInfo bound { get => State.Bound; set => State.Bound = value; } + protected int maxBount { get => State.MaxBound; set => State.MaxBound = value; } + protected int minBount { get => State.MinBound; set => State.MinBound = value; } + protected string spBrandId { get => State.SpBrandId; set => State.SpBrandId = value; } + protected string spBrandName { get => State.SpBrandName; set => State.SpBrandName = value; } + protected string shippingTemplateJson { get => State.ShippingTemplateJson; set => State.ShippingTemplateJson = value; } + protected int shipPrice { get => State.ShipPrice; set => State.ShipPrice = value; } + protected string expressRst { get => State.ExpressRst; set => State.ExpressRst = value; } + protected List detailGalleries { get => State.DetailGalleries; set => State.DetailGalleries = value; } + protected WXUploadProductInfoRecommendParam recommendParamRst { get => State.RecommendParamRst; set => State.RecommendParamRst = value; } + protected allAttrParam productAttr { get => State.ProductAttr; set => State.ProductAttr = value; } + protected int maxSkuSpecCnt { get => State.MaxSkuSpecCnt; set => State.MaxSkuSpecCnt = value; } + protected int skuIndex { get => State.SkuIndex; set => State.SkuIndex = value; } + protected int id { get => State.Id; set => State.Id = value; } + protected long catId { get => State.CatId; set => State.CatId = value; } + protected long fCatId { get => State.FCatId; set => State.FCatId = value; } + protected long valueCatId { get => State.ValueCatId; set => State.ValueCatId = value; } + protected Dictionary dicSpecParentNameId { get => State.DicSpecParentNameId; set => State.DicSpecParentNameId = value; } + protected Dictionary dicSpecParentNameCatId { get => State.DicSpecParentNameCatId; set => State.DicSpecParentNameCatId = value; } + protected Dictionary dicSpecNamefCatId { get => State.DicSpecNameFCatId; set => State.DicSpecNameFCatId = value; } + protected Dictionary dicSpecNameCatId { get => State.DicSpecNameCatId; set => State.DicSpecNameCatId = value; } + protected string uploadDraftRst { get => State.UploadDraftRst; set => State.UploadDraftRst = value; } + protected string commissionRstStr { get => State.CommissionRstStr; set => State.CommissionRstStr = value; } + protected string DefaultAdultBrandId { get => State.DefaultAdultBrandId; set => State.DefaultAdultBrandId = value; } + protected bool IsWxNotUseSoftwareDefaultAddressOnce { get => State.IsWxNotUseSoftwareDefaultAddressOnce; set => State.IsWxNotUseSoftwareDefaultAddressOnce = value; } + protected List lstWXShopTokenInfo { get => State.WxShopTokenInfoList; set => State.WxShopTokenInfoList = value; } + protected List lstQulificationImage { get => State.QualificationImages; set => State.QualificationImages = value; } + protected List forceFilteKeyWords { get => State.ForceFilterKeywords; set => State.ForceFilterKeywords = value; } + protected dynamic lstAddPriceRule => State.AddPriceRules; + protected dynamic wxUploadRule => State.WxUploadRule; + protected string pdd_product_cates { get => State.PddProductCates; set => State.PddProductCates = value; } + protected dynamic upload_draft_product_lst => State.UploadDraftProductList; + + + + /// + /// 正常结束发布流程(例如提交成功、指定上传到草稿箱)时写入最终结果并停止后续步骤。 + /// 失败场景请直接抛出异常。 + /// + protected void StopWithResult(string result) + { + Upload.Result = result; + Upload.IsCompleted = true; + } + + protected void Invoke(Action action) + { + action?.Invoke(); + } + + protected string UploadImg(ShopLoginInfo shopInfo, string imgUrl, object cellState = null, bool randomBlock = false) + { + return Upload.Runtime.UploadImgByUrl?.Invoke(shopInfo, imgUrl, cellState ?? cell, randomBlock) ?? ""; + } + + protected string UploadImg(ShopLoginInfo shopInfo, Bitmap image, object cellState = null) + { + return Upload.Runtime.UploadImgByBitmap?.Invoke(shopInfo, image, cellState ?? cell) ?? ""; + } + + protected string GetImgBase64ForApi(out string errorMsg, string imgUrl, bool needAddRandomBlock, bool wantAddRandomBlock, + int imgWidth = 0, int imgHeight = 0, bool wantAddRandomBorder = false) + { + if (Upload.Runtime.GetImgBase64ForApi == null) + { + errorMsg = "GetImgBase64ForApi 尚未从窗体映射到新上下文"; + return ""; + } + + return Upload.Runtime.GetImgBase64ForApi(out errorMsg, imgUrl, needAddRandomBlock, wantAddRandomBlock, imgWidth, imgHeight, wantAddRandomBorder); + } + + protected string GetAiImg(string imgUrl, string imgWord) + { + return Upload.Runtime.GetAiImg?.Invoke(imgUrl, imgWord) ?? ""; + } + + protected static string GetNewExtCode(string spuId) + { + if (spuId.IsNullOrEmpty()) + throw new Exception("货号指定失败,值不能为空!"); + if (!spuId.StartsWith("P1-")) + return "P1-" + spuId; + return spuId; + } + + protected static string GetEC1ExtCode(string spuId) + { + if (spuId.IsNullOrEmpty()) + throw new Exception("货号指定失败,值不能为空!"); + if (!spuId.StartsWith("EC1") && !spuId.EndsWith("A")) + return "EC1" + spuId + "A"; + return spuId; + } + + // TODO(迁移): 以下两个窗体私有方法的真实实现仍留在 WXDBProdUploadForm,后续应迁到对应服务。 + protected virtual void btnLoginAssistant_Click(object sender, EventArgs e) + { + throw new NotImplementedException("请把 WXDBProdUploadForm.btnLoginAssistant_Click 的实现迁入微信发布运行时适配。"); + } + + protected virtual WXGetEditProductInfoLite GetProductWXComplate(string secretSpuId, out string errMsg) + { + errMsg = "GetProductWXComplate 尚未迁移"; + return null; + } +} \ No newline at end of file diff --git a/Store.Product/Store.Product.csproj b/Store.Product/Store.Product.csproj new file mode 100644 index 0000000..0f00cdd --- /dev/null +++ b/Store.Product/Store.Product.csproj @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/TEMUKJD CEFv138.sln b/TEMUKJD CEFv138.sln index 9aab8c2..051c583 100644 --- a/TEMUKJD CEFv138.sln +++ b/TEMUKJD CEFv138.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 -VisualStudioVersion = 18.9.12120.119 stable +VisualStudioVersion = 18.9.12120.119 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AllBase", "AllBase\AllBase.csproj", "{6461D807-B1C8-45F0-B388-E0CA1A418DE0}" EndProject @@ -17,8 +17,21 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig .gitignore = .gitignore + Directory.Build.props = Directory.Build.props EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Store.Product", "Store.Product\Store.Product.csproj", "{56233CFD-6072-435A-B8B7-BD2D1892A3B1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Store.Management.Framework", "Store.Management.Framework\Store.Management.Framework.csproj", "{86BE8F99-4A94-4051-92B0-D28D5620EB42}" +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.Framework", "Store.Framework\Store.Framework.csproj", "{EF44F866-5893-79EA-1BCF-1AF6E9693D3E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MAX.Framework", "MAX.Framework\MAX.Framework.csproj", "{1F0DE7F8-6D2A-6E73-EE8F-DA74894C6128}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Store.Test", "test\Store.Test\Store.Test.csproj", "{F99AD942-24F3-48C3-8ACE-B4340C6A135D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -77,12 +90,85 @@ Global {9BCF10B0-85A2-4F0A-9D44-2C0555D28FBD}.Release|x64.Build.0 = Release|x64 {9BCF10B0-85A2-4F0A-9D44-2C0555D28FBD}.Release|x86.ActiveCfg = Release|Any CPU {9BCF10B0-85A2-4F0A-9D44-2C0555D28FBD}.Release|x86.Build.0 = Release|Any CPU + {56233CFD-6072-435A-B8B7-BD2D1892A3B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {56233CFD-6072-435A-B8B7-BD2D1892A3B1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {56233CFD-6072-435A-B8B7-BD2D1892A3B1}.Debug|x64.ActiveCfg = Debug|Any CPU + {56233CFD-6072-435A-B8B7-BD2D1892A3B1}.Debug|x64.Build.0 = Debug|Any CPU + {56233CFD-6072-435A-B8B7-BD2D1892A3B1}.Debug|x86.ActiveCfg = Debug|Any CPU + {56233CFD-6072-435A-B8B7-BD2D1892A3B1}.Debug|x86.Build.0 = Debug|Any CPU + {56233CFD-6072-435A-B8B7-BD2D1892A3B1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {56233CFD-6072-435A-B8B7-BD2D1892A3B1}.Release|Any CPU.Build.0 = Release|Any CPU + {56233CFD-6072-435A-B8B7-BD2D1892A3B1}.Release|x64.ActiveCfg = Release|Any CPU + {56233CFD-6072-435A-B8B7-BD2D1892A3B1}.Release|x64.Build.0 = Release|Any CPU + {56233CFD-6072-435A-B8B7-BD2D1892A3B1}.Release|x86.ActiveCfg = Release|Any CPU + {56233CFD-6072-435A-B8B7-BD2D1892A3B1}.Release|x86.Build.0 = Release|Any CPU + {86BE8F99-4A94-4051-92B0-D28D5620EB42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {86BE8F99-4A94-4051-92B0-D28D5620EB42}.Debug|Any CPU.Build.0 = Debug|Any CPU + {86BE8F99-4A94-4051-92B0-D28D5620EB42}.Debug|x64.ActiveCfg = Debug|Any CPU + {86BE8F99-4A94-4051-92B0-D28D5620EB42}.Debug|x64.Build.0 = Debug|Any CPU + {86BE8F99-4A94-4051-92B0-D28D5620EB42}.Debug|x86.ActiveCfg = Debug|Any CPU + {86BE8F99-4A94-4051-92B0-D28D5620EB42}.Debug|x86.Build.0 = Debug|Any CPU + {86BE8F99-4A94-4051-92B0-D28D5620EB42}.Release|Any CPU.ActiveCfg = Release|Any CPU + {86BE8F99-4A94-4051-92B0-D28D5620EB42}.Release|Any CPU.Build.0 = Release|Any CPU + {86BE8F99-4A94-4051-92B0-D28D5620EB42}.Release|x64.ActiveCfg = Release|Any CPU + {86BE8F99-4A94-4051-92B0-D28D5620EB42}.Release|x64.Build.0 = Release|Any CPU + {86BE8F99-4A94-4051-92B0-D28D5620EB42}.Release|x86.ActiveCfg = Release|Any CPU + {86BE8F99-4A94-4051-92B0-D28D5620EB42}.Release|x86.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}.Debug|x64.ActiveCfg = Debug|Any CPU + {281F6609-E5BC-8A5C-8382-CD7C4670ACBF}.Debug|x64.Build.0 = Debug|Any CPU + {281F6609-E5BC-8A5C-8382-CD7C4670ACBF}.Debug|x86.ActiveCfg = Debug|Any CPU + {281F6609-E5BC-8A5C-8382-CD7C4670ACBF}.Debug|x86.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 + {281F6609-E5BC-8A5C-8382-CD7C4670ACBF}.Release|x64.ActiveCfg = Release|Any CPU + {281F6609-E5BC-8A5C-8382-CD7C4670ACBF}.Release|x64.Build.0 = Release|Any CPU + {281F6609-E5BC-8A5C-8382-CD7C4670ACBF}.Release|x86.ActiveCfg = Release|Any CPU + {281F6609-E5BC-8A5C-8382-CD7C4670ACBF}.Release|x86.Build.0 = Release|Any CPU + {EF44F866-5893-79EA-1BCF-1AF6E9693D3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EF44F866-5893-79EA-1BCF-1AF6E9693D3E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EF44F866-5893-79EA-1BCF-1AF6E9693D3E}.Debug|x64.ActiveCfg = Debug|Any CPU + {EF44F866-5893-79EA-1BCF-1AF6E9693D3E}.Debug|x64.Build.0 = Debug|Any CPU + {EF44F866-5893-79EA-1BCF-1AF6E9693D3E}.Debug|x86.ActiveCfg = Debug|Any CPU + {EF44F866-5893-79EA-1BCF-1AF6E9693D3E}.Debug|x86.Build.0 = Debug|Any CPU + {EF44F866-5893-79EA-1BCF-1AF6E9693D3E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EF44F866-5893-79EA-1BCF-1AF6E9693D3E}.Release|Any CPU.Build.0 = Release|Any CPU + {EF44F866-5893-79EA-1BCF-1AF6E9693D3E}.Release|x64.ActiveCfg = Release|Any CPU + {EF44F866-5893-79EA-1BCF-1AF6E9693D3E}.Release|x64.Build.0 = Release|Any CPU + {EF44F866-5893-79EA-1BCF-1AF6E9693D3E}.Release|x86.ActiveCfg = Release|Any CPU + {EF44F866-5893-79EA-1BCF-1AF6E9693D3E}.Release|x86.Build.0 = Release|Any CPU + {1F0DE7F8-6D2A-6E73-EE8F-DA74894C6128}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F0DE7F8-6D2A-6E73-EE8F-DA74894C6128}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F0DE7F8-6D2A-6E73-EE8F-DA74894C6128}.Debug|x64.ActiveCfg = Debug|Any CPU + {1F0DE7F8-6D2A-6E73-EE8F-DA74894C6128}.Debug|x64.Build.0 = Debug|Any CPU + {1F0DE7F8-6D2A-6E73-EE8F-DA74894C6128}.Debug|x86.ActiveCfg = Debug|Any CPU + {1F0DE7F8-6D2A-6E73-EE8F-DA74894C6128}.Debug|x86.Build.0 = Debug|Any CPU + {1F0DE7F8-6D2A-6E73-EE8F-DA74894C6128}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F0DE7F8-6D2A-6E73-EE8F-DA74894C6128}.Release|Any CPU.Build.0 = Release|Any CPU + {1F0DE7F8-6D2A-6E73-EE8F-DA74894C6128}.Release|x64.ActiveCfg = Release|Any CPU + {1F0DE7F8-6D2A-6E73-EE8F-DA74894C6128}.Release|x64.Build.0 = Release|Any CPU + {1F0DE7F8-6D2A-6E73-EE8F-DA74894C6128}.Release|x86.ActiveCfg = Release|Any CPU + {1F0DE7F8-6D2A-6E73-EE8F-DA74894C6128}.Release|x86.Build.0 = Release|Any CPU + {F99AD942-24F3-48C3-8ACE-B4340C6A135D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F99AD942-24F3-48C3-8ACE-B4340C6A135D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F99AD942-24F3-48C3-8ACE-B4340C6A135D}.Debug|x64.ActiveCfg = Debug|Any CPU + {F99AD942-24F3-48C3-8ACE-B4340C6A135D}.Debug|x64.Build.0 = Debug|Any CPU + {F99AD942-24F3-48C3-8ACE-B4340C6A135D}.Debug|x86.ActiveCfg = Debug|Any CPU + {F99AD942-24F3-48C3-8ACE-B4340C6A135D}.Debug|x86.Build.0 = Debug|Any CPU + {F99AD942-24F3-48C3-8ACE-B4340C6A135D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F99AD942-24F3-48C3-8ACE-B4340C6A135D}.Release|Any CPU.Build.0 = Release|Any CPU + {F99AD942-24F3-48C3-8ACE-B4340C6A135D}.Release|x64.ActiveCfg = Release|Any CPU + {F99AD942-24F3-48C3-8ACE-B4340C6A135D}.Release|x64.Build.0 = Release|Any CPU + {F99AD942-24F3-48C3-8ACE-B4340C6A135D}.Release|x86.ActiveCfg = Release|Any CPU + {F99AD942-24F3-48C3-8ACE-B4340C6A135D}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {9BCF10B0-85A2-4F0A-9D44-2C0555D28FBD} = {CE81E85E-0369-45A6-98B9-B5A07130A2E5} + {F99AD942-24F3-48C3-8ACE-B4340C6A135D} = {CE81E85E-0369-45A6-98B9-B5A07130A2E5} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A8028C02-4AC4-4504-9626-20698CAE7FD7} diff --git a/test/Store.Test/Store.Test.csproj b/test/Store.Test/Store.Test.csproj new file mode 100644 index 0000000..1abc697 --- /dev/null +++ b/test/Store.Test/Store.Test.csproj @@ -0,0 +1,27 @@ + + + + net462 + latest + enable + enable + false + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Store.Test/UnitTest1.cs b/test/Store.Test/UnitTest1.cs new file mode 100644 index 0000000..08c1a36 --- /dev/null +++ b/test/Store.Test/UnitTest1.cs @@ -0,0 +1,32 @@ +using Autofac; +using LittleCow.Core.Infrastructure; +using MAX.Core.Autofac; +using MAX.Core.Cryptography; +using MAX.Core.DependencyInjection; +using Store.Client; +using Store.Client.Apis; +using Store.Client.HttpExtension; +using Store.Client.Services; + +namespace Store.Test; + +public class Tests +{ + [SetUp] + public void Setup() + { + Bootstrap.Builder(); + } + + [Test] + public void TestUserLogin() + { + IocManager.Resolve().Login(new Framework.Models.UserInfo + { + UserName = "18990991234", + Password = "18990991234" + }); + //new UserApi().Login("18990991234", MD5Helper.Md5_Lower32("18990991234")); + } + +} \ No newline at end of file