From f2caec608378bd3f49f8a9554b4ad88829f3c73b Mon Sep 17 00:00:00 2001 From: chfei Date: Fri, 18 Sep 2026 18:40:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=A1=86=E6=9E=B6=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=91=E5=BA=93=E6=90=9C=E7=B4=A2=E6=A8=A1?= =?UTF-8?q?=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/MAX.Core/Caches/MemoryCache.cs | 2 +- .../IDependencyRegistrar.cs | 6 +- Core/MAX.Core/Interfaces/IPriority.cs | 33 + .../IStartup.cs | 2 +- .../SingleInstance.cs | 2 +- Core/MAX.Core/Logger/ICustomLogger.cs | 3 +- Core/MAX.Core/Startup.cs | 2 +- Core/MAX.Framework/Commons/AppException.cs | 18 + .../Commons/GlobalExceptionFilter.cs | 42 + Core/MAX.Framework/Commons/PageData.cs | 26 + .../Commons/UnifyResultFilter.cs | 52 ++ .../UnsupportedMediaTypeHandlingMiddleware.cs | 53 ++ .../DependencyInjection/FreeSqlDB.cs | 2 +- .../JwtServiceCollectionExtensions.cs | 6 +- .../DependencyInjection/LoggerExtensions.cs | 22 + .../ServiceCollectionExtensions.cs | 47 +- .../ServiceRegistrarProxy.cs | 2 +- .../StoreHubAppTypeFinder.cs | 4 +- Core/MAX.Framework/Dtos/ApiResult.cs | 69 ++ .../MAX.Framework}/Helpers/ObjectHelper.cs | 2 +- .../MAX.Framework}/Helpers/WinCmdHelper.cs | 2 +- Core/MAX.Framework/Interfaces/IModule.cs | 12 + Core/MAX.Framework/Logger/NLogger.cs | 138 +++ .../MAX.Framework/MAX.Framework.csproj | 14 +- .../MAX.Framework}/Security/CurrentUser.cs | 2 +- .../MAX.Framework}/Security/ICurrentUser.cs | 2 +- .../Security/IOnlineUserStore.cs | 2 +- .../MAX.Framework}/Security/ITokenService.cs | 2 +- .../Security/JwtBearerResponseHandler.cs | 8 +- .../MAX.Framework}/Security/JwtClaimTypes.cs | 2 +- .../MAX.Framework}/Security/JwtOptions.cs | 2 +- .../MAX.Framework}/Security/OnlineUserInfo.cs | 2 +- .../Security/OnlineUserStore.cs | 2 +- .../MAX.Framework}/Security/TokenService.cs | 2 +- Core/MAX.Framework/Startup.cs | 24 + .../Controllers/ProductController.cs | 328 +++++++ Models/CloudProduct/Dtos/GoodsImportItem.cs | 14 + Models/CloudProduct/Dtos/GoodsImportResult.cs | 32 + .../CloudProduct/Dtos/OpenSearchStatusDto.cs | 42 + Models/CloudProduct/Dtos/ProductSearchInfo.cs | 60 ++ .../CloudProduct/Dtos/ProductSearchRequest.cs | 83 ++ Models/CloudProduct/Entities/ProductEntity.cs | 124 +++ Models/CloudProduct/Enums/PlatformType.cs | 9 + .../ConverterToGoodsFactory.cs | 58 ++ .../ImportConverters/ConverterV1.cs | 180 ++++ .../ImportConverters/IGoodsConverter.cs | 99 +++ .../CloudProduct/Services/OpenSearchServer.cs | 498 +++++++++++ .../Services/ProductRepository.cs | 13 + .../CloudProduct/Services/ProductService.cs | 409 +++++++++ .../CloudProduct/Services/RawDataStorage.cs | 77 ++ Models/CloudProduct/Startup.cs | 28 + .../Store.Modules.CloudProduct.csproj | 21 + .../Controllers/AuthController.cs | 46 +- .../Controllers/UserController.cs | 9 +- .../Controllers/WeatherForecastController.cs | 9 +- .../v2/WeatherForecastController.cs | 7 +- {StoreHub => Store.API}/Program.cs | 8 +- .../Properties/launchSettings.json | 4 +- .../Store.Api.csproj | 12 +- {StoreHub => Store.API}/StoreHub.http | 0 {StoreHub => Store.API}/WeatherForecast.cs | 2 +- {StoreHub => Store.API}/WeatherForecastV2.cs | 2 +- .../appsettings.Development.json | 0 {StoreHub => Store.API}/appsettings.json | 13 +- Store.Api.slnx | 16 + Store.Framework/Base/AppControllerBase.cs | 12 + .../Models/Users.cs | 2 +- .../Repositorys/SqlRepository.cs | 4 +- .../Startup.cs | 35 +- Store.Framework/Store.Framework.csproj | 20 + .../Commons/BaseClass.cs | 0 .../Commons/EncrypUtil.cs | 0 .../Commons/Extension/Extension.Convert.cs | 0 .../Commons/Extension/Extension.Enum.cs | 0 .../Commons/Extension/Extension.Exception.cs | 0 .../Commons/Extension/Extension.Linq.cs | 0 .../Commons/Extension/Extension.Validate.cs | 0 .../Commons/Helper/HtmlHelper.cs | 0 .../Commons/Helper/JsonHelper.cs | 0 .../Commons/Helper/ReflectionHelper.cs | 0 .../Commons/Helper/RegexHelper.cs | 0 .../Commons/HtmlUtils.cs | 0 .../Commons/HttpUtils.cs | 0 .../Commons/JsonUtils.cs | 0 .../Commons/KJDConfig.cs | 11 +- Store.KJDWebService/Commons/MysqlUtils.cs | 835 ++++++++++++++++++ .../Commons/RandomUtils.cs | 0 .../Commons/RegexUtils.cs | 0 .../Commons/TimeUtils.cs | 0 .../Controllers/BXXPriceController.cs | 0 .../Controllers/BXXProductController.cs | 0 .../Controllers/BXXProductThirdController.cs | 0 .../Controllers/BXXShopController.cs | 0 .../Controllers/BXXUserController.cs | 0 .../Controllers/BaseApiController.cs | 0 .../Controllers/DYShopController.cs | 0 .../Controllers/DYUserController.cs | 0 .../Controllers/Login.cs | 2 +- .../Controllers/OrderController.cs | 0 .../Controllers/PriceController.cs | 0 .../Controllers/ProductController.cs | 0 .../Controllers/ProductThirdController.cs | 0 .../Controllers/ShopController.cs | 0 .../Controllers/ToolController.cs | 0 .../Controllers/UpdateController.cs | 0 .../Controllers/UserController.cs | 0 .../Controllers/WXOrderController.cs | 0 .../Controllers/WXProductController.cs | 0 .../Controllers/WXShopController.cs | 0 .../WXShopRevenueStatisticsController.cs | 0 .../Controllers/WXUserController.cs | 0 .../Models/CommentParam.cs | 0 .../Models/DBPriceResultParam.cs | 0 .../DBProdDbIdMapProductIdResultParam.cs | 0 .../Models/DBProductResultParam.cs | 0 .../Models/ErpModel/ErpMerchantInfo.cs | 0 .../Models/JSTDownProdSkuInfo.cs | 0 .../Models/OrderParam.cs | 0 .../Models/PddModel/BuyerLoginInfo.cs | 0 .../Models/PddModel/PDDMaterialInfo.cs | 0 .../PddModel/PDDProductDecorationInfo.cs | 0 .../Models/PddModel/PDDProductDetailInfo.cs | 0 .../Models/PddModel/PDDProductInfo.cs | 0 .../Models/PddModel/PDDProductPropertyInfo.cs | 0 .../Models/PddModel/PDDSizeSpecInfo.cs | 0 .../Models/ProductPurchasePriceResultParam.cs | 0 .../Models/ProductSelectResultParam.cs | 0 .../Models/Result.cs | 0 .../Models/ShopModel/JSTListProductSkuInfo.cs | 0 .../Models/ShopModel/JitCardCodeParam.cs | 0 .../Models/ShopModel/ShopParam.cs | 0 .../Models/ShopModel/sku_img_replace.cs | 0 .../Models/SkuImgResult.cs | 0 .../Models/TemuDBOrderInfo.cs | 0 .../Models/UserLoginResultParam.cs | 0 .../Models/WXAddressInfo.cs | 0 .../Models/WXShopDefaultAddressInfo.cs | 0 .../Models/WxModel/WXBuyUrlNotesInfo.cs | 0 .../Models/WxModel/WXClientProductInfoLite.cs | 0 .../WxModel/WXGetEditProductInfoLite.cs | 0 .../Models/WxModel/WXOrderAmountInfo.cs | 0 .../WxModel/WXOrderDecryptAddressInfo.cs | 0 .../Models/WxModel/WxOptimizeExpressInfo.cs | 0 .../Models/WxModel/oem.cs | 0 ...ei_external_procurement_after_sale_info.cs | 0 .../oms_wei_external_procurement_log.cs | 0 .../Models/WxModel/oms_wei_order.cs | 0 .../Models/WxModel/oms_wei_order_item.cs | 0 .../Models/WxModel/oms_wei_refund.cs | 0 .../Models/WxModel/order_item_ext.cs | 0 .../WxModel/order_pdd_buyer_after_sale.cs | 0 .../Models/WxModel/order_pdd_buyer_order.cs | 0 .../WxModel/user_payment_record_info.cs | 0 .../Models/WxModel/user_points_record_info.cs | 0 .../WxModel/wx_qualification_image_info.cs | 0 .../Models/WxProdSecretSpuIdShopIdMapParam.cs | 0 .../Properties/launchSettings.json | 0 .../Repositorys/SqlRepository.cs | 0 .../Services/BXXPriceService.cs | 0 .../Services/BXXProductService.cs | 0 .../Services/BXXProductThirdService.cs | 0 .../Services/BXXShopService.cs | 0 .../Services/BXXUserService.cs | 0 .../Services/DYShopService.cs | 0 .../Services/DYUserService.cs | 0 .../Services/DemoService.cs | 0 .../Services/OrderService.cs | 0 .../Services/PriceService.cs | 0 .../Services/ProductService.cs | 0 .../Services/ProductThirdService.cs | 0 .../Services/ServiceBase.cs | 2 +- .../Services/ShopService.cs | 0 .../Services/ToolService.cs | 0 .../Services/UpdateService.cs | 0 .../Services/UserService.cs | 0 .../Services/WXExpressOptimizeService.cs | 0 .../Services/WXOemService.cs | 0 .../Services/WXOrderService.cs | 0 .../Services/WXProductService.cs | 0 .../WXShopRevenueStatisticsService.cs | 0 .../Services/WXShopService.cs | 0 .../Services/WXUserService.cs | 0 .../Startup.cs | 0 .../StoreHub.KJDWebService.csproj | 2 +- .../Dtos/UserDto.cs | 0 .../Mappers/UserMapper.cs | 2 +- .../Store.Services.csproj | 2 +- .../UserService.cs | 4 +- StoreHub.Framework/Commons/ApiResult.cs | 30 - StoreHub.Framework/Logger/NLogger.cs | 138 --- StoreHub.KJDWebService/Commons/MysqlUtils.cs | 503 ----------- StoreHub.slnx | 12 - 192 files changed, 3626 insertions(+), 804 deletions(-) create mode 100644 Core/MAX.Core/Interfaces/IPriority.cs rename Core/MAX.Core/{Infrastructure => Interfaces}/IStartup.cs (92%) rename Core/MAX.Core/{Infrastructure => Interfaces}/SingleInstance.cs (95%) create mode 100644 Core/MAX.Framework/Commons/AppException.cs create mode 100644 Core/MAX.Framework/Commons/GlobalExceptionFilter.cs create mode 100644 Core/MAX.Framework/Commons/PageData.cs create mode 100644 Core/MAX.Framework/Commons/UnifyResultFilter.cs create mode 100644 Core/MAX.Framework/Commons/UnsupportedMediaTypeHandlingMiddleware.cs rename {StoreHub.Framework => Core/MAX.Framework}/DependencyInjection/FreeSqlDB.cs (93%) rename {StoreHub.Framework => Core/MAX.Framework}/DependencyInjection/JwtServiceCollectionExtensions.cs (96%) create mode 100644 Core/MAX.Framework/DependencyInjection/LoggerExtensions.cs rename {StoreHub.Framework => Core/MAX.Framework}/DependencyInjection/ServiceCollectionExtensions.cs (72%) rename {StoreHub.Framework => Core/MAX.Framework}/DependencyInjection/ServiceRegistrarProxy.cs (98%) rename {StoreHub.Framework => Core/MAX.Framework}/DependencyInjection/StoreHubAppTypeFinder.cs (85%) create mode 100644 Core/MAX.Framework/Dtos/ApiResult.cs rename {StoreHub.Framework => Core/MAX.Framework}/Helpers/ObjectHelper.cs (97%) rename {StoreHub.Framework => Core/MAX.Framework}/Helpers/WinCmdHelper.cs (99%) create mode 100644 Core/MAX.Framework/Interfaces/IModule.cs create mode 100644 Core/MAX.Framework/Logger/NLogger.cs rename StoreHub.Framework/StoreHub.Framework.csproj => Core/MAX.Framework/MAX.Framework.csproj (80%) rename {StoreHub.Framework => Core/MAX.Framework}/Security/CurrentUser.cs (97%) rename {StoreHub.Framework => Core/MAX.Framework}/Security/ICurrentUser.cs (95%) rename {StoreHub.Framework => Core/MAX.Framework}/Security/IOnlineUserStore.cs (96%) rename {StoreHub.Framework => Core/MAX.Framework}/Security/ITokenService.cs (95%) rename {StoreHub.Framework => Core/MAX.Framework}/Security/JwtBearerResponseHandler.cs (85%) rename {StoreHub.Framework => Core/MAX.Framework}/Security/JwtClaimTypes.cs (91%) rename {StoreHub.Framework => Core/MAX.Framework}/Security/JwtOptions.cs (97%) rename {StoreHub.Framework => Core/MAX.Framework}/Security/OnlineUserInfo.cs (94%) rename {StoreHub.Framework => Core/MAX.Framework}/Security/OnlineUserStore.cs (98%) rename {StoreHub.Framework => Core/MAX.Framework}/Security/TokenService.cs (98%) create mode 100644 Core/MAX.Framework/Startup.cs create mode 100644 Models/CloudProduct/Controllers/ProductController.cs create mode 100644 Models/CloudProduct/Dtos/GoodsImportItem.cs create mode 100644 Models/CloudProduct/Dtos/GoodsImportResult.cs create mode 100644 Models/CloudProduct/Dtos/OpenSearchStatusDto.cs create mode 100644 Models/CloudProduct/Dtos/ProductSearchInfo.cs create mode 100644 Models/CloudProduct/Dtos/ProductSearchRequest.cs create mode 100644 Models/CloudProduct/Entities/ProductEntity.cs create mode 100644 Models/CloudProduct/Enums/PlatformType.cs create mode 100644 Models/CloudProduct/ImportConverters/ConverterToGoodsFactory.cs create mode 100644 Models/CloudProduct/ImportConverters/ConverterV1.cs create mode 100644 Models/CloudProduct/ImportConverters/IGoodsConverter.cs create mode 100644 Models/CloudProduct/Services/OpenSearchServer.cs create mode 100644 Models/CloudProduct/Services/ProductRepository.cs create mode 100644 Models/CloudProduct/Services/ProductService.cs create mode 100644 Models/CloudProduct/Services/RawDataStorage.cs create mode 100644 Models/CloudProduct/Startup.cs create mode 100644 Models/CloudProduct/Store.Modules.CloudProduct.csproj rename {StoreHub => Store.API}/Controllers/AuthController.cs (66%) rename {StoreHub => Store.API}/Controllers/UserController.cs (74%) rename {StoreHub => Store.API}/Controllers/WeatherForecastController.cs (83%) rename {StoreHub => Store.API}/Controllers/v2/WeatherForecastController.cs (85%) rename {StoreHub => Store.API}/Program.cs (90%) rename {StoreHub => Store.API}/Properties/launchSettings.json (91%) rename StoreHub/StoreHub.csproj => Store.API/Store.Api.csproj (53%) rename {StoreHub => Store.API}/StoreHub.http (100%) rename {StoreHub => Store.API}/WeatherForecast.cs (91%) rename {StoreHub => Store.API}/WeatherForecastV2.cs (92%) rename {StoreHub => Store.API}/appsettings.Development.json (100%) rename {StoreHub => Store.API}/appsettings.json (55%) create mode 100644 Store.Api.slnx create mode 100644 Store.Framework/Base/AppControllerBase.cs rename {StoreHub.Framework => Store.Framework}/Models/Users.cs (99%) rename {StoreHub.Framework => Store.Framework}/Repositorys/SqlRepository.cs (90%) rename {StoreHub.Framework => Store.Framework}/Startup.cs (58%) create mode 100644 Store.Framework/Store.Framework.csproj rename {StoreHub.KJDWebService => Store.KJDWebService}/Commons/BaseClass.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Commons/EncrypUtil.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Commons/Extension/Extension.Convert.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Commons/Extension/Extension.Enum.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Commons/Extension/Extension.Exception.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Commons/Extension/Extension.Linq.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Commons/Extension/Extension.Validate.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Commons/Helper/HtmlHelper.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Commons/Helper/JsonHelper.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Commons/Helper/ReflectionHelper.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Commons/Helper/RegexHelper.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Commons/HtmlUtils.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Commons/HttpUtils.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Commons/JsonUtils.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Commons/KJDConfig.cs (77%) create mode 100644 Store.KJDWebService/Commons/MysqlUtils.cs rename {StoreHub.KJDWebService => Store.KJDWebService}/Commons/RandomUtils.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Commons/RegexUtils.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Commons/TimeUtils.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Controllers/BXXPriceController.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Controllers/BXXProductController.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Controllers/BXXProductThirdController.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Controllers/BXXShopController.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Controllers/BXXUserController.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Controllers/BaseApiController.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Controllers/DYShopController.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Controllers/DYUserController.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Controllers/Login.cs (93%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Controllers/OrderController.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Controllers/PriceController.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Controllers/ProductController.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Controllers/ProductThirdController.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Controllers/ShopController.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Controllers/ToolController.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Controllers/UpdateController.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Controllers/UserController.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Controllers/WXOrderController.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Controllers/WXProductController.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Controllers/WXShopController.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Controllers/WXShopRevenueStatisticsController.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Controllers/WXUserController.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/CommentParam.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/DBPriceResultParam.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/DBProdDbIdMapProductIdResultParam.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/DBProductResultParam.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/ErpModel/ErpMerchantInfo.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/JSTDownProdSkuInfo.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/OrderParam.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/PddModel/BuyerLoginInfo.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/PddModel/PDDMaterialInfo.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/PddModel/PDDProductDecorationInfo.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/PddModel/PDDProductDetailInfo.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/PddModel/PDDProductInfo.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/PddModel/PDDProductPropertyInfo.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/PddModel/PDDSizeSpecInfo.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/ProductPurchasePriceResultParam.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/ProductSelectResultParam.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/Result.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/ShopModel/JSTListProductSkuInfo.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/ShopModel/JitCardCodeParam.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/ShopModel/ShopParam.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/ShopModel/sku_img_replace.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/SkuImgResult.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/TemuDBOrderInfo.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/UserLoginResultParam.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/WXAddressInfo.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/WXShopDefaultAddressInfo.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/WxModel/WXBuyUrlNotesInfo.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/WxModel/WXClientProductInfoLite.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/WxModel/WXGetEditProductInfoLite.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/WxModel/WXOrderAmountInfo.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/WxModel/WXOrderDecryptAddressInfo.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/WxModel/WxOptimizeExpressInfo.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/WxModel/oem.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/WxModel/oms_wei_external_procurement_after_sale_info.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/WxModel/oms_wei_external_procurement_log.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/WxModel/oms_wei_order.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/WxModel/oms_wei_order_item.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/WxModel/oms_wei_refund.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/WxModel/order_item_ext.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/WxModel/order_pdd_buyer_after_sale.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/WxModel/order_pdd_buyer_order.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/WxModel/user_payment_record_info.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/WxModel/user_points_record_info.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/WxModel/wx_qualification_image_info.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Models/WxProdSecretSpuIdShopIdMapParam.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Properties/launchSettings.json (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Repositorys/SqlRepository.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Services/BXXPriceService.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Services/BXXProductService.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Services/BXXProductThirdService.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Services/BXXShopService.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Services/BXXUserService.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Services/DYShopService.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Services/DYUserService.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Services/DemoService.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Services/OrderService.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Services/PriceService.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Services/ProductService.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Services/ProductThirdService.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Services/ServiceBase.cs (94%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Services/ShopService.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Services/ToolService.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Services/UpdateService.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Services/UserService.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Services/WXExpressOptimizeService.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Services/WXOemService.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Services/WXOrderService.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Services/WXProductService.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Services/WXShopRevenueStatisticsService.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Services/WXShopService.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Services/WXUserService.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/Startup.cs (100%) rename {StoreHub.KJDWebService => Store.KJDWebService}/StoreHub.KJDWebService.csproj (88%) rename {StoreHub.Services => Store.Services}/Dtos/UserDto.cs (100%) rename {StoreHub.Services => Store.Services}/Mappers/UserMapper.cs (94%) rename StoreHub.Services/StoreHub.Services.csproj => Store.Services/Store.Services.csproj (75%) rename {StoreHub.Services => Store.Services}/UserService.cs (96%) delete mode 100644 StoreHub.Framework/Commons/ApiResult.cs delete mode 100644 StoreHub.Framework/Logger/NLogger.cs delete mode 100644 StoreHub.KJDWebService/Commons/MysqlUtils.cs delete mode 100644 StoreHub.slnx diff --git a/Core/MAX.Core/Caches/MemoryCache.cs b/Core/MAX.Core/Caches/MemoryCache.cs index b2bc36a..4631bc3 100644 --- a/Core/MAX.Core/Caches/MemoryCache.cs +++ b/Core/MAX.Core/Caches/MemoryCache.cs @@ -1,6 +1,6 @@  using MAX.Core.DependencyInjection; -using MAX.Core.Infrastructure; +using MAX.Core.Interfaces; using System; using System.Collections.Concurrent; using System.Linq; diff --git a/Core/MAX.Core/DependencyInjection/IDependencyRegistrar.cs b/Core/MAX.Core/DependencyInjection/IDependencyRegistrar.cs index 92cb66b..4abc09e 100644 --- a/Core/MAX.Core/DependencyInjection/IDependencyRegistrar.cs +++ b/Core/MAX.Core/DependencyInjection/IDependencyRegistrar.cs @@ -1,10 +1,12 @@ -namespace MAX.Core.DependencyInjection; +using MAX.Core.Interfaces; + +namespace MAX.Core.DependencyInjection; /// /// 依赖注册器:把当前模块的服务注册到 。 /// 底层使用哪个容器由宿主项目决定,MAX.Core 不引用任何容器。 /// -public interface IDependencyRegistrar +public interface IDependencyRegistrar: IPriority { /// /// 注册服务 diff --git a/Core/MAX.Core/Interfaces/IPriority.cs b/Core/MAX.Core/Interfaces/IPriority.cs new file mode 100644 index 0000000..3b61afb --- /dev/null +++ b/Core/MAX.Core/Interfaces/IPriority.cs @@ -0,0 +1,33 @@ +namespace MAX.Core.Interfaces; + +public interface IPriority +{ + /// 优先级(优先级越高越先执行) + int Priority { get; } +} + +public static class IntPriority +{ + public const int Highest = 5000; + + public const int VeryHigh = 4000; + + public const int Higher = 3000; + + public const int High = 2000; + + public const int AboveNormal = 1000; + + public const int Normal = 0; + + public const int BelowNormal = -1000; + + public const int Low = -2000; + + public const int Lower = -3000; + + public const int VeryLow = -4000; + + public const int Lowest = -5000; + +} diff --git a/Core/MAX.Core/Infrastructure/IStartup.cs b/Core/MAX.Core/Interfaces/IStartup.cs similarity index 92% rename from Core/MAX.Core/Infrastructure/IStartup.cs rename to Core/MAX.Core/Interfaces/IStartup.cs index edbe252..0767a3f 100644 --- a/Core/MAX.Core/Infrastructure/IStartup.cs +++ b/Core/MAX.Core/Interfaces/IStartup.cs @@ -1,6 +1,6 @@ using MAX.Core.DependencyInjection; -namespace MAX.Core.Infrastructure; +namespace MAX.Core.Interfaces; /// /// 启动器:实现 ,由宿主项目统一发现并按 排序执行。 diff --git a/Core/MAX.Core/Infrastructure/SingleInstance.cs b/Core/MAX.Core/Interfaces/SingleInstance.cs similarity index 95% rename from Core/MAX.Core/Infrastructure/SingleInstance.cs rename to Core/MAX.Core/Interfaces/SingleInstance.cs index b6eb50a..7a2df56 100644 --- a/Core/MAX.Core/Infrastructure/SingleInstance.cs +++ b/Core/MAX.Core/Interfaces/SingleInstance.cs @@ -1,7 +1,7 @@ using System; using System.Reflection; -namespace MAX.Core.Infrastructure; +namespace MAX.Core.Interfaces; /// /// 单例,需要在当前类中定义 diff --git a/Core/MAX.Core/Logger/ICustomLogger.cs b/Core/MAX.Core/Logger/ICustomLogger.cs index 456a96b..2014a9c 100644 --- a/Core/MAX.Core/Logger/ICustomLogger.cs +++ b/Core/MAX.Core/Logger/ICustomLogger.cs @@ -1,5 +1,4 @@ -using MAX.Core.Infrastructure; - + namespace MAX.Core.Logger; /// 自定义日志记录器接口 diff --git a/Core/MAX.Core/Startup.cs b/Core/MAX.Core/Startup.cs index 724d8fd..2f5cb1d 100644 --- a/Core/MAX.Core/Startup.cs +++ b/Core/MAX.Core/Startup.cs @@ -24,7 +24,7 @@ */ using MAX.Core.DependencyInjection; -using MAX.Core.Infrastructure; +using MAX.Core.Interfaces; namespace MAX.Core; diff --git a/Core/MAX.Framework/Commons/AppException.cs b/Core/MAX.Framework/Commons/AppException.cs new file mode 100644 index 0000000..84cd2af --- /dev/null +++ b/Core/MAX.Framework/Commons/AppException.cs @@ -0,0 +1,18 @@ +using MAX.Framework.Dtos; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MAX.Framework.Commons +{ + public class AppException : Exception + { + public int Code { get; } + + public AppException(string message, int code = ResultCode.BusinessError) + : base(message) + { + Code = code; + } + } +} diff --git a/Core/MAX.Framework/Commons/GlobalExceptionFilter.cs b/Core/MAX.Framework/Commons/GlobalExceptionFilter.cs new file mode 100644 index 0000000..465a872 --- /dev/null +++ b/Core/MAX.Framework/Commons/GlobalExceptionFilter.cs @@ -0,0 +1,42 @@ +using MAX.Framework.Dtos; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Text; + +namespace MAX.Framework.Commons +{ + public class GlobalExceptionFilter : IExceptionFilter + { + private readonly ILogger _logger; + + public GlobalExceptionFilter(ILogger logger) + { + _logger = logger; + } + + public void OnException(ExceptionContext context) + { + var traceId = context.HttpContext.TraceIdentifier; + + var result = context.Exception switch + { + AppException be => ApiResult.Fail(be.Message, be.Code), + ValidationException ve => ApiResult.Fail(ve.Message, ResultCode.ValidationError), + UnauthorizedAccessException => ApiResult.Fail("未授权访问", ResultCode.Unauthorized), + _ => ApiResult.Fail("服务器内部错误", ResultCode.ServerError) + }; + + _logger.LogError(context.Exception, "请求异常 TraceId: {TraceId}", traceId); + + context.Result = new ObjectResult(result) + { + StatusCode = 200 // 关键:HTTP状态码统一200,业务状态用Code表达 + }; + context.ExceptionHandled = true; + } + } +} diff --git a/Core/MAX.Framework/Commons/PageData.cs b/Core/MAX.Framework/Commons/PageData.cs new file mode 100644 index 0000000..8e83396 --- /dev/null +++ b/Core/MAX.Framework/Commons/PageData.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MAX.Framework.Commons +{ + public class PageData + { + public long Total { get; set; } + public int PageIndex { get; set; } + public int PageSize { get; set; } + public int TotalPages => PageSize > 0 ? (int)Math.Ceiling((double)Total / PageSize) : 0; + public List List { get; set; } = new(); + + public PageData() { } + + public PageData(List list, long total, int pageIndex, int pageSize) + { + List = list; + Total = total; + PageIndex = pageIndex; + PageSize = pageSize; + } + } + +} diff --git a/Core/MAX.Framework/Commons/UnifyResultFilter.cs b/Core/MAX.Framework/Commons/UnifyResultFilter.cs new file mode 100644 index 0000000..f4aa54f --- /dev/null +++ b/Core/MAX.Framework/Commons/UnifyResultFilter.cs @@ -0,0 +1,52 @@ +using MAX.Framework.Dtos; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MAX.Framework.Commons +{ + /// + /// 返回过滤 + /// + public class UnifyResultFilter : IActionFilter + { + public void OnActionExecuted(ActionExecutedContext context) + { + if (context.Result is ObjectResult objResult) + { + if (!IsAlreadyWrapped(objResult.Value)) + { + var apiResult = ApiResult.Success(objResult.Value); + + objResult.Value = apiResult; + objResult.DeclaredType = null; + } + } + else if (context.Result is EmptyResult || context.Result is NoContentResult) + { + context.Result = new ObjectResult(ApiResult.Success()); + } + } + + public void OnActionExecuting(ActionExecutingContext context) { } + private static bool IsAlreadyWrapped(object? value) + { + if (value is null) return false; + + var type = value.GetType(); + + // 递归判断:只要类型链上有任何一层是 ApiResult<> 的开放泛型定义,或直接是 ApiResult,就算已包装 + while (type is not null) + { + if (type == typeof(ApiResult)) return true; + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ApiResult<>)) return true; + type = type.BaseType; + } + + return false; + } + } + +} diff --git a/Core/MAX.Framework/Commons/UnsupportedMediaTypeHandlingMiddleware.cs b/Core/MAX.Framework/Commons/UnsupportedMediaTypeHandlingMiddleware.cs new file mode 100644 index 0000000..3e67365 --- /dev/null +++ b/Core/MAX.Framework/Commons/UnsupportedMediaTypeHandlingMiddleware.cs @@ -0,0 +1,53 @@ +using MAX.Framework.Dtos; +using Microsoft.AspNetCore.Http; +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.Json; + +namespace MAX.Framework.Commons +{ + public class UnsupportedMediaTypeHandlingMiddleware + { + private readonly RequestDelegate _next; + + public UnsupportedMediaTypeHandlingMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task InvokeAsync(HttpContext context) + { + // 用一个内存流替换原始的Response.Body,方便判断状态码后决定是否要改写 + var originalBodyStream = context.Response.Body; + using var memoryStream = new MemoryStream(); + context.Response.Body = memoryStream; + + await _next(context); + + if (context.Response.StatusCode == StatusCodes.Status415UnsupportedMediaType) + { + var result = ApiResult.Fail("请求参数格式不正确", ResultCode.ValidationError); + + var json = JsonSerializer.Serialize(result); + var bytes = Encoding.UTF8.GetBytes(json); + + context.Response.Clear(); + context.Response.StatusCode = StatusCodes.Status200OK; // 统一成200,和其他接口一致 + context.Response.ContentType = "application/json; charset=utf-8"; + context.Response.ContentLength = bytes.Length; + + await context.Response.Body.WriteAsync(bytes); + await context.Response.Body.FlushAsync(originalBodyStream is null ? default : default); + } + else + { + // 非415的情况,把内存流内容原样写回真正的Response.Body + memoryStream.Seek(0, SeekOrigin.Begin); + await memoryStream.CopyToAsync(originalBodyStream); + } + + context.Response.Body = originalBodyStream; + } + } +} diff --git a/StoreHub.Framework/DependencyInjection/FreeSqlDB.cs b/Core/MAX.Framework/DependencyInjection/FreeSqlDB.cs similarity index 93% rename from StoreHub.Framework/DependencyInjection/FreeSqlDB.cs rename to Core/MAX.Framework/DependencyInjection/FreeSqlDB.cs index a5483c4..6a6f1e0 100644 --- a/StoreHub.Framework/DependencyInjection/FreeSqlDB.cs +++ b/Core/MAX.Framework/DependencyInjection/FreeSqlDB.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using System.Text; -namespace StoreHub.Framework.DependencyInjection +namespace MAX.Framework.DependencyInjection { public static class FreeSqlDB { diff --git a/StoreHub.Framework/DependencyInjection/JwtServiceCollectionExtensions.cs b/Core/MAX.Framework/DependencyInjection/JwtServiceCollectionExtensions.cs similarity index 96% rename from StoreHub.Framework/DependencyInjection/JwtServiceCollectionExtensions.cs rename to Core/MAX.Framework/DependencyInjection/JwtServiceCollectionExtensions.cs index 803657a..8126967 100644 --- a/StoreHub.Framework/DependencyInjection/JwtServiceCollectionExtensions.cs +++ b/Core/MAX.Framework/DependencyInjection/JwtServiceCollectionExtensions.cs @@ -1,15 +1,15 @@ using Asp.Versioning.OpenApi; +using MAX.Framework.Security; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi; -using StoreHub.Framework.Security; using System.Security.Claims; using System.Text; -namespace StoreHub.Framework.DependencyInjection; +namespace MAX.Framework.DependencyInjection; /// /// JWT 认证服务注册入口(由宿主 Program.cs 调用)。 @@ -21,7 +21,7 @@ public static class JwtServiceCollectionExtensions /// /// 绑定 Jwt 配置节到 ,并在启动时校验签名密钥长度; /// 注册 (令牌生成/校验)与 (当前用户访问器); - /// 注册 认证,认证失败/授权失败统一返回 格式; + /// 注册 认证,认证失败/授权失败统一返回 格式; /// 注册 (基于 IMemoryCache 的在线会话存储):单点登录、在线人员查询、退出销毁; /// 为 OpenAPI 文档声明 Bearer(JWT) 安全方案,并对标记了 [Authorize] 的接口声明需要携带令牌。 /// diff --git a/Core/MAX.Framework/DependencyInjection/LoggerExtensions.cs b/Core/MAX.Framework/DependencyInjection/LoggerExtensions.cs new file mode 100644 index 0000000..500f7dc --- /dev/null +++ b/Core/MAX.Framework/DependencyInjection/LoggerExtensions.cs @@ -0,0 +1,22 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using NLog.Web; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MAX.Framework.DependencyInjection +{ + public static class LoggerExtensions + { + public static WebApplicationBuilder AddLogger(this WebApplicationBuilder builder) + { + builder.Logging.ClearProviders(); + //builder.Logging.SetMinimumLevel(LogLevel.Information); + builder.Host.UseNLog(); + return builder; + } + } +} diff --git a/StoreHub.Framework/DependencyInjection/ServiceCollectionExtensions.cs b/Core/MAX.Framework/DependencyInjection/ServiceCollectionExtensions.cs similarity index 72% rename from StoreHub.Framework/DependencyInjection/ServiceCollectionExtensions.cs rename to Core/MAX.Framework/DependencyInjection/ServiceCollectionExtensions.cs index f4e90a7..a4095d5 100644 --- a/StoreHub.Framework/DependencyInjection/ServiceCollectionExtensions.cs +++ b/Core/MAX.Framework/DependencyInjection/ServiceCollectionExtensions.cs @@ -1,10 +1,14 @@ using MAX.Core.DependencyInjection; -using MAX.Core.Infrastructure; +using MAX.Core.Interfaces; +using MAX.Framework.Commons; +using MAX.Framework.Dtos; +using MAX.Framework.Interfaces; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using System.Diagnostics; using System.Reflection; -namespace StoreHub.Framework.DependencyInjection +namespace MAX.Framework.DependencyInjection { /// /// IOC 注册入口(由宿主 Program.cs 调用) @@ -28,10 +32,36 @@ namespace StoreHub.Framework.DependencyInjection public static IServiceCollection AddStoreHub(this IServiceCollection services) { IEnumerable assemblies = GetApplicationAssemblies(); - RegisterDependencyMarkers(services, assemblies); RunStartups(services, assemblies); + RunModules(services, assemblies); + + services.AddControllers(options => + { + options.Filters.Add(); + options.Filters.Add(); + }).ConfigureApiBehaviorOptions(options => + { + options.InvalidModelStateResponseFactory = context => + { + // 取出所有校验错误信息,拼成一个字符串(或者只取第一个) + var errors = context.ModelState + .Where(e => e.Value?.Errors.Count > 0) + .SelectMany(e => e.Value!.Errors.Select(err => err.ErrorMessage)) + .ToList(); + + var msg = string.Join("; ", errors); + + var result = ApiResult.Fail(msg, ResultCode.ValidationError); + + return new ObjectResult(result) + { + StatusCode = 200 // 和其他接口保持一致,用Code表达失败,而不是HTTP 400 + }; + }; + + }); return services; } @@ -85,6 +115,17 @@ namespace StoreHub.Framework.DependencyInjection startup.Register(registrar); } } + private static void RunModules(IServiceCollection services, IEnumerable assemblies) + { + IServiceRegistrar registrar = new ServiceRegistrarProxy(services); + + foreach (IModule startup in GetConcreteTypes(assemblies) + .Where(t => typeof(IModule).IsAssignableFrom(t)) + .Select(t => (IModule)Activator.CreateInstance(t, true)!)) + { + startup.AddModule(services); + } + } private static IEnumerable GetConcreteTypes(IEnumerable assemblies) { diff --git a/StoreHub.Framework/DependencyInjection/ServiceRegistrarProxy.cs b/Core/MAX.Framework/DependencyInjection/ServiceRegistrarProxy.cs similarity index 98% rename from StoreHub.Framework/DependencyInjection/ServiceRegistrarProxy.cs rename to Core/MAX.Framework/DependencyInjection/ServiceRegistrarProxy.cs index e32b629..52761e3 100644 --- a/StoreHub.Framework/DependencyInjection/ServiceRegistrarProxy.cs +++ b/Core/MAX.Framework/DependencyInjection/ServiceRegistrarProxy.cs @@ -1,7 +1,7 @@ using MAX.Core.DependencyInjection; using Microsoft.Extensions.DependencyInjection; -namespace StoreHub.Framework.DependencyInjection +namespace MAX.Framework.DependencyInjection { /// /// 将 适配到 Microsoft.Extensions.DependencyInjection diff --git a/StoreHub.Framework/DependencyInjection/StoreHubAppTypeFinder.cs b/Core/MAX.Framework/DependencyInjection/StoreHubAppTypeFinder.cs similarity index 85% rename from StoreHub.Framework/DependencyInjection/StoreHubAppTypeFinder.cs rename to Core/MAX.Framework/DependencyInjection/StoreHubAppTypeFinder.cs index b24cf32..f4f5085 100644 --- a/StoreHub.Framework/DependencyInjection/StoreHubAppTypeFinder.cs +++ b/Core/MAX.Framework/DependencyInjection/StoreHubAppTypeFinder.cs @@ -1,6 +1,6 @@ using MAX.Core.Reflections; -namespace StoreHub.Framework.DependencyInjection +namespace MAX.Framework.DependencyInjection { /// /// 本应用的类型查找器:在 AppTypeFinder 默认前缀(MAX. / Nexus.)之外, @@ -12,7 +12,7 @@ namespace StoreHub.Framework.DependencyInjection protected override List AvailablePrefix() { - return ["StoreHub"]; + return ["Store"]; } } } diff --git a/Core/MAX.Framework/Dtos/ApiResult.cs b/Core/MAX.Framework/Dtos/ApiResult.cs new file mode 100644 index 0000000..54987de --- /dev/null +++ b/Core/MAX.Framework/Dtos/ApiResult.cs @@ -0,0 +1,69 @@ +using MAX.Framework.Commons; +using static System.Runtime.InteropServices.JavaScript.JSType; + +namespace MAX.Framework.Dtos +{ + public static class ResultCode + { + public const int Success = 200; + public const int Fail = 500; + + public const int Unauthorized = 401; + public const int Forbidden = 403; + public const int NotFound = 404; + /// + /// 参数校验失败 + /// + public const int ValidationError = 1001; + /// + /// 业务逻辑异常 + /// + public const int BusinessError = 2001; + /// + /// 服务器内部错误 + /// + public const int ServerError = 5000; + } + /// + /// 非泛型基类,用于统一处理 + /// + public abstract class ApiResultBase + { + /// 业务状态码 + public int Code { get; set; } = ResultCode.Success; + + /// 提示信息 + public string? Msg { get; set; } + + /// 请求是否成功 + public bool Success => Code == ResultCode.Success; + + } + + /// + /// 泛型响应 + /// + public class ApiResult : ApiResultBase + { + public T? Data { get; set; } + + public static ApiResult Success(T data, string msg = "操作成功") + => new ApiResult { Code = ResultCode.Success, Data = data, Msg = msg }; + + public static ApiResult Fail(string msg = "操作失败", int code = ResultCode.Fail, T? data = default) + => new ApiResult { Code = code, Msg = msg, Data = data }; + } + + /// + /// 响应 + /// + public class ApiResult : ApiResult + { + public static ApiResult Success(string msg = "操作成功") + => new ApiResult { Code = ResultCode.Success, Msg = msg }; + + public static ApiResult Fail(string msg = "操作失败", int code = ResultCode.Fail) + => new ApiResult { Code = code, Msg = msg }; + } + +} diff --git a/StoreHub.Framework/Helpers/ObjectHelper.cs b/Core/MAX.Framework/Helpers/ObjectHelper.cs similarity index 97% rename from StoreHub.Framework/Helpers/ObjectHelper.cs rename to Core/MAX.Framework/Helpers/ObjectHelper.cs index 0fdbcf3..0ef0830 100644 --- a/StoreHub.Framework/Helpers/ObjectHelper.cs +++ b/Core/MAX.Framework/Helpers/ObjectHelper.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; -namespace StoreHub.Framework.Helpers; +namespace MAX.Framework.Helpers; public static class ObjectHelper { diff --git a/StoreHub.Framework/Helpers/WinCmdHelper.cs b/Core/MAX.Framework/Helpers/WinCmdHelper.cs similarity index 99% rename from StoreHub.Framework/Helpers/WinCmdHelper.cs rename to Core/MAX.Framework/Helpers/WinCmdHelper.cs index ac7215b..2d31c9b 100644 --- a/StoreHub.Framework/Helpers/WinCmdHelper.cs +++ b/Core/MAX.Framework/Helpers/WinCmdHelper.cs @@ -3,7 +3,7 @@ using System; using System.Diagnostics; using System.Text; -namespace StoreHub.Framework.Helpers; +namespace MAX.Framework.Helpers; public class WinCmdHelper { diff --git a/Core/MAX.Framework/Interfaces/IModule.cs b/Core/MAX.Framework/Interfaces/IModule.cs new file mode 100644 index 0000000..2a14d72 --- /dev/null +++ b/Core/MAX.Framework/Interfaces/IModule.cs @@ -0,0 +1,12 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MAX.Framework.Interfaces +{ + public interface IModule + { + void AddModule(IServiceCollection services); + } +} diff --git a/Core/MAX.Framework/Logger/NLogger.cs b/Core/MAX.Framework/Logger/NLogger.cs new file mode 100644 index 0000000..f1d7c6f --- /dev/null +++ b/Core/MAX.Framework/Logger/NLogger.cs @@ -0,0 +1,138 @@ +//using MAX.Core.Infrastructure; +//using MAX.Core.Logger; +//using NLog; +//using System; +//using LLogLevel = MAX.Core.Logger.LogLevel; +//using NLogLevel = NLog.LogLevel; + +//namespace MAX.Framework.Logger; + +//public class NLogger : SingleInstance, ILoggerFacade +//{ +// private readonly ILogger _log; + +// private NLogger() +// { +// _log = LogManager.GetLogger("GlobalLogger"); +// } + +// public bool IsDebugEnabled { get; private set; } = true; + +// 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/StoreHub.Framework/StoreHub.Framework.csproj b/Core/MAX.Framework/MAX.Framework.csproj similarity index 80% rename from StoreHub.Framework/StoreHub.Framework.csproj rename to Core/MAX.Framework/MAX.Framework.csproj index 4a6063e..7d9b264 100644 --- a/StoreHub.Framework/StoreHub.Framework.csproj +++ b/Core/MAX.Framework/MAX.Framework.csproj @@ -8,10 +8,6 @@ - - - - @@ -19,8 +15,16 @@ - + + + + + + + + + diff --git a/StoreHub.Framework/Security/CurrentUser.cs b/Core/MAX.Framework/Security/CurrentUser.cs similarity index 97% rename from StoreHub.Framework/Security/CurrentUser.cs rename to Core/MAX.Framework/Security/CurrentUser.cs index b4c279a..00df5f3 100644 --- a/StoreHub.Framework/Security/CurrentUser.cs +++ b/Core/MAX.Framework/Security/CurrentUser.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Http; using System.Globalization; using System.Security.Claims; -namespace StoreHub.Framework.Security; +namespace MAX.Framework.Security; /// /// 从 读取当前请求认证票据的默认实现。 diff --git a/StoreHub.Framework/Security/ICurrentUser.cs b/Core/MAX.Framework/Security/ICurrentUser.cs similarity index 95% rename from StoreHub.Framework/Security/ICurrentUser.cs rename to Core/MAX.Framework/Security/ICurrentUser.cs index 26cef69..47bec72 100644 --- a/StoreHub.Framework/Security/ICurrentUser.cs +++ b/Core/MAX.Framework/Security/ICurrentUser.cs @@ -1,6 +1,6 @@ using System.Security.Claims; -namespace StoreHub.Framework.Security; +namespace MAX.Framework.Security; /// /// 当前登录用户访问器:从当前请求的认证票据中读取用户信息。 diff --git a/StoreHub.Framework/Security/IOnlineUserStore.cs b/Core/MAX.Framework/Security/IOnlineUserStore.cs similarity index 96% rename from StoreHub.Framework/Security/IOnlineUserStore.cs rename to Core/MAX.Framework/Security/IOnlineUserStore.cs index 5702077..9334c59 100644 --- a/StoreHub.Framework/Security/IOnlineUserStore.cs +++ b/Core/MAX.Framework/Security/IOnlineUserStore.cs @@ -1,4 +1,4 @@ -namespace StoreHub.Framework.Security; +namespace MAX.Framework.Security; /// /// 在线用户会话存储:基于 的进程内实现, diff --git a/StoreHub.Framework/Security/ITokenService.cs b/Core/MAX.Framework/Security/ITokenService.cs similarity index 95% rename from StoreHub.Framework/Security/ITokenService.cs rename to Core/MAX.Framework/Security/ITokenService.cs index 032bd78..311b76c 100644 --- a/StoreHub.Framework/Security/ITokenService.cs +++ b/Core/MAX.Framework/Security/ITokenService.cs @@ -1,6 +1,6 @@ using System.Security.Claims; -namespace StoreHub.Framework.Security; +namespace MAX.Framework.Security; /// /// 令牌生成结果 diff --git a/StoreHub.Framework/Security/JwtBearerResponseHandler.cs b/Core/MAX.Framework/Security/JwtBearerResponseHandler.cs similarity index 85% rename from StoreHub.Framework/Security/JwtBearerResponseHandler.cs rename to Core/MAX.Framework/Security/JwtBearerResponseHandler.cs index b9db50a..d15b671 100644 --- a/StoreHub.Framework/Security/JwtBearerResponseHandler.cs +++ b/Core/MAX.Framework/Security/JwtBearerResponseHandler.cs @@ -1,10 +1,10 @@ -using Microsoft.AspNetCore.Authentication.JwtBearer; +using MAX.Framework.Dtos; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Http; -using StoreHub.Framework.Commons; using System.Text; using System.Text.Json; -namespace StoreHub.Framework.Security; +namespace MAX.Framework.Security; /// /// JwtBearer 认证失败/授权失败时,统一返回 格式的 JSON 响应。 @@ -38,7 +38,7 @@ internal static class JwtBearerResponseHandler httpContext.Response.StatusCode = statusCode; httpContext.Response.ContentType = "application/json; charset=utf-8"; - string body = JsonSerializer.Serialize(ApiResult.Error(message, statusCode), JsonOptions); + string body = JsonSerializer.Serialize(ApiResult.Fail(message, statusCode), JsonOptions); await httpContext.Response.WriteAsync(body, Encoding.UTF8); } } diff --git a/StoreHub.Framework/Security/JwtClaimTypes.cs b/Core/MAX.Framework/Security/JwtClaimTypes.cs similarity index 91% rename from StoreHub.Framework/Security/JwtClaimTypes.cs rename to Core/MAX.Framework/Security/JwtClaimTypes.cs index 8301fe3..a0660fd 100644 --- a/StoreHub.Framework/Security/JwtClaimTypes.cs +++ b/Core/MAX.Framework/Security/JwtClaimTypes.cs @@ -1,4 +1,4 @@ -namespace StoreHub.Framework.Security; +namespace MAX.Framework.Security; /// /// JWT 自定义声明类型常量。 diff --git a/StoreHub.Framework/Security/JwtOptions.cs b/Core/MAX.Framework/Security/JwtOptions.cs similarity index 97% rename from StoreHub.Framework/Security/JwtOptions.cs rename to Core/MAX.Framework/Security/JwtOptions.cs index 4fbc39a..a261de9 100644 --- a/StoreHub.Framework/Security/JwtOptions.cs +++ b/Core/MAX.Framework/Security/JwtOptions.cs @@ -1,7 +1,7 @@ using Microsoft.IdentityModel.Tokens; using System.Text; -namespace StoreHub.Framework.Security; +namespace MAX.Framework.Security; /// /// JWT 配置选项,默认绑定配置节 Jwt。 diff --git a/StoreHub.Framework/Security/OnlineUserInfo.cs b/Core/MAX.Framework/Security/OnlineUserInfo.cs similarity index 94% rename from StoreHub.Framework/Security/OnlineUserInfo.cs rename to Core/MAX.Framework/Security/OnlineUserInfo.cs index e0d3b3a..2ce9c39 100644 --- a/StoreHub.Framework/Security/OnlineUserInfo.cs +++ b/Core/MAX.Framework/Security/OnlineUserInfo.cs @@ -1,4 +1,4 @@ -namespace StoreHub.Framework.Security; +namespace MAX.Framework.Security; /// /// 在线用户会话信息(单点登录:同一用户仅保留一个有效会话)。 diff --git a/StoreHub.Framework/Security/OnlineUserStore.cs b/Core/MAX.Framework/Security/OnlineUserStore.cs similarity index 98% rename from StoreHub.Framework/Security/OnlineUserStore.cs rename to Core/MAX.Framework/Security/OnlineUserStore.cs index da0b49a..bd06432 100644 --- a/StoreHub.Framework/Security/OnlineUserStore.cs +++ b/Core/MAX.Framework/Security/OnlineUserStore.cs @@ -1,7 +1,7 @@ using Microsoft.Extensions.Caching.Memory; using System.Collections.Concurrent; -namespace StoreHub.Framework.Security; +namespace MAX.Framework.Security; /// /// 基于 的在线用户会话存储。 diff --git a/StoreHub.Framework/Security/TokenService.cs b/Core/MAX.Framework/Security/TokenService.cs similarity index 98% rename from StoreHub.Framework/Security/TokenService.cs rename to Core/MAX.Framework/Security/TokenService.cs index 280f2af..4640976 100644 --- a/StoreHub.Framework/Security/TokenService.cs +++ b/Core/MAX.Framework/Security/TokenService.cs @@ -4,7 +4,7 @@ using Microsoft.IdentityModel.Tokens; using System.Security.Claims; using System.Text; -namespace StoreHub.Framework.Security; +namespace MAX.Framework.Security; /// /// 基于 的 JWT 令牌服务。 diff --git a/Core/MAX.Framework/Startup.cs b/Core/MAX.Framework/Startup.cs new file mode 100644 index 0000000..86f2677 --- /dev/null +++ b/Core/MAX.Framework/Startup.cs @@ -0,0 +1,24 @@ +using FreeSql; +using MAX.Core.DependencyInjection; +using MAX.Core.Interfaces; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace MAX.Framework +{ + public class Startup : IStartup + { + public int Priority => IntPriority.Highest; + + public void Initialize() + { + + } + + public void Register(IServiceRegistrar registrar) + { + + } + } +} + diff --git a/Models/CloudProduct/Controllers/ProductController.cs b/Models/CloudProduct/Controllers/ProductController.cs new file mode 100644 index 0000000..2182bc1 --- /dev/null +++ b/Models/CloudProduct/Controllers/ProductController.cs @@ -0,0 +1,328 @@ +using Asp.Versioning; +using MAX.Framework.Commons; +using MAX.Framework.Dtos; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Store.Framework.Base; +using Store.Modules.CloudProduct.Dtos; +using Store.Modules.CloudProduct.Entities; +using Store.Modules.CloudProduct.ImportConverters; +using Store.Modules.CloudProduct.Services; +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.Json.Nodes; + +namespace Store.Modules.CloudProduct.Controllers +{ + [ApiVersionNeutral] + [Route("/CloudProduct/[action]")] + public class ProductController : AppControllerBase + { + private readonly IProductService _productService; + private readonly IOpenSearchServer _searchServer; + private readonly RawDataStorage _rawDataStorage; + + public ProductController(IProductService productService, IOpenSearchServer searchServer, RawDataStorage rawDataStorage) + { + _productService = productService; + _searchServer = searchServer; + _rawDataStorage = rawDataStorage; + } + [HttpGet] + public async Task Time() + { + return DateTime.Now.ToLongDateString(); + } + /// + /// 搜索 + /// + /// + /// + [HttpPost] + public async Task> SearchGoods(ProductSearchRequest request) + { + // 1. 参数基础校验 + if (request == null) + { + throw new AppException("请求参数不能为空", ResultCode.ValidationError); + } + + // 2. 分页参数校验 + if (request.PageIndex < 1) + { + throw new AppException("页码必须大于0", ResultCode.ValidationError); + } + + if (request.PageSize < 1 || request.PageSize > 2000) + { + throw new AppException("每页数量必须在1-2000之间", ResultCode.ValidationError); + } + + // 3. 价格区间校验 + if (request.MinPrice.HasValue && request.MinPrice < 0) + { + throw new AppException("最低价格不能小于0", ResultCode.ValidationError); + } + + if (request.MaxPrice.HasValue && request.MaxPrice < 0) + { + throw new AppException("最高价格不能小于0", ResultCode.ValidationError); + } + + if (request.MinPrice.HasValue && request.MaxPrice.HasValue && request.MinPrice > request.MaxPrice) + { + throw new AppException("最低价格不能大于最高价格", ResultCode.ValidationError); + } + + // 4. 销量区间校验 + if (request.MinSoldQuantity.HasValue && request.MinSoldQuantity < 0) + { + throw new AppException("最低销量不能小于0", ResultCode.ValidationError); + } + + if (request.MaxSoldQuantity.HasValue && request.MaxSoldQuantity < 0) + { + throw new AppException("最高销量不能小于0", ResultCode.ValidationError); + } + + if (request.MinSoldQuantity.HasValue && request.MaxSoldQuantity.HasValue && + request.MinSoldQuantity > request.MaxSoldQuantity) + { + throw new AppException("最低销量不能大于最高销量", ResultCode.ValidationError); + } + + // 5. 类目层级校验 + if (request.CatId2.HasValue && !request.CatId1.HasValue) + { + throw new AppException("选择二级类目时必须先选择一级类目", ResultCode.ValidationError); + } + + if (request.CatId3.HasValue && !request.CatId2.HasValue) + { + throw new AppException("选择三级类目时必须先选择二级类目", ResultCode.ValidationError); + } + + if (request.CatId4.HasValue && !request.CatId3.HasValue) + { + throw new AppException("选择四级类目时必须先选择三级类目", ResultCode.ValidationError); + } + + // 6. 平台校验 + if (request.Platform.HasValue && request.Platform < 0) + { + throw new AppException("平台类型无效", ResultCode.ValidationError); + } + + // 10. 至少有一个搜索条件 + if (string.IsNullOrWhiteSpace(request.Title) && + !request.MinPrice.HasValue && !request.MaxPrice.HasValue && + !request.CatId1.HasValue && !request.CatId2.HasValue && + !request.CatId3.HasValue && !request.CatId4.HasValue && + !request.MinSoldQuantity.HasValue && !request.MaxSoldQuantity.HasValue && + !request.Platform.HasValue) + { + throw new AppException("请至少提供一个搜索条件", ResultCode.ValidationError); + } + + var result = await _productService.SearchGoods(request); + return new PageData + { + List = result.List, + Total = result.Total, + PageSize = request.PageSize, + PageIndex = request.PageIndex, + }; + } + + + [HttpGet] + public async Task> SearchGoodsToIds(string keyword, int pageindex = 1, int pagesize = 20) + { + + if (string.IsNullOrWhiteSpace(keyword)) + { + throw new AppException("填写关键字", ResultCode.ValidationError); + } + var ret = await _searchServer.SearchByTitleAsync(new ProductSearchRequest + { + Title = keyword, + PageIndex = pageindex, + PageSize = pagesize, + }); + return new PageData + { + PageIndex = pageindex, + PageSize = pagesize, + List = ret.list, + Total = ret.Total, + }; + + } + + /// + /// 导入测试数据:读取 ImportConverters/data.txt 并走完整解析 + 入库 + 分词流程。 + /// + [HttpGet] + public async Task ImportTestData() + { + var filePath = GetDataFilePath(); + + if (!System.IO.File.Exists(filePath)) + { + throw new AppException($"找不到测试数据文件: {filePath}", ResultCode.NotFound); + } + + var content = await System.IO.File.ReadAllTextAsync(filePath); + var goods = ConverterToGoodsFactory.ConvertContent(content); + if (goods == null) + { + throw new AppException("未找到匹配的转换器,无法解析测试数据"); + } + + return await _productService.ImportGoodsAsync(new[] + { + new GoodsImportItem { ProductEntity = goods, RawData = content } + }); + } + + /// + /// 导入接口:接收客户端批量上传的 JSON 文件,解析后入库并写入分词索引。 + /// + [HttpPost] + public async Task ImportFiles([FromForm] List files) + { + if (files == null || files.Count == 0) + { + throw new AppException("请选择要导入的 JSON 文件", ResultCode.ValidationError); + } + + var items = new List(); + var parseErrors = new List(); + + foreach (var file in files) + { + if (file == null || file.Length == 0) + { + continue; + } + + try + { + using var reader = new StreamReader(file.OpenReadStream()); + var content = await reader.ReadToEndAsync(); + + var goods = ConverterToGoodsFactory.ConvertContent(content); + if (goods == null) + { + parseErrors.Add($"{file.FileName}: 未找到匹配的转换器"); + } + else + { + items.Add(new GoodsImportItem { ProductEntity = goods, RawData = content }); + } + } + catch (Exception ex) + { + parseErrors.Add($"{file.FileName}: {ex.Message}"); + } + } + + var result = await _productService.ImportGoodsAsync(items); + result.Failed += parseErrors.Count; + result.Errors.InsertRange(0, parseErrors); + + return result; + } + + /// + /// 查询 OpenSearch 状态。 + /// + [HttpGet] + public async Task GetOpenSearchStatus() + { + return await _searchServer.GetStatusAsync(); + } + + /// + /// 读取指定商品详情:解压缩原始数据并返回 JSON。 + /// + [HttpGet] + public async Task GetGoodsDetail([FromQuery] long goodsId) + { + var raw = await GetRawDataOrThrowAsync(goodsId); + var node = JsonNode.Parse(raw); + return node ?? throw new AppException("商品详情解析失败", ResultCode.BusinessError); + } + + /// + /// 读取指定商品的原始数据(解压缩后原样返回)。 + /// + [HttpGet] + public async Task GetGoodsRawData([FromQuery] long goodsId) + { + return await GetRawDataOrThrowAsync(goodsId); + } + + /// + /// 重新从数据库读取指定商品,覆盖 OpenSearch 已有分词并重新分词。 + /// ids 为空时重建全部商品。 + /// + [HttpPost] + public async Task RebuildIndex([FromBody] long[]? ids) + { + return await _productService.RebuildSearchIndexAsync(ids); + } + + /// + /// 删除商品(按数据库主键 ID)。是否软删除由配置 Delete:SoftDelete 决定, + /// 软删除将 Status 置 1,硬删除物理删除记录;两种方式都会删除 OpenSearch 中对应文档。 + /// + [HttpPost] + public async Task DeleteGoods([FromBody] long[]? ids) + { + if (ids == null || ids.Length == 0) + { + throw new AppException("请提供要删除的商品ID", ResultCode.ValidationError); + } + + return await _productService.DeleteGoodsAsync(ids); + } + [HttpGet] + public async Task ClearSearch() + { + return await _searchServer.ClearIndexAsync(); + } + [HttpGet] + public async Task GetSearchCount() + { + return await _searchServer.CountDocumentsAsync(); + } + + + private async Task GetRawDataOrThrowAsync(long goodsId) + { + if (goodsId <= 0) + throw new AppException("商品ID无效", ResultCode.ValidationError); + + var raw = await _rawDataStorage.ReadAsync(goodsId); + if (raw == null) + throw new AppException($"商品原始数据不存在: {goodsId}", ResultCode.NotFound); + + return raw; + } + + private static string GetDataFilePath() + { + var outputPath = Path.Combine(AppContext.BaseDirectory, "ImportConverters", "data.txt"); + if (System.IO.File.Exists(outputPath)) + return outputPath; + + var workPath = Path.Combine(Directory.GetCurrentDirectory(), "ImportConverters", "data.txt"); + if (System.IO.File.Exists(workPath)) + return workPath; + + return outputPath; + } + } +} diff --git a/Models/CloudProduct/Dtos/GoodsImportItem.cs b/Models/CloudProduct/Dtos/GoodsImportItem.cs new file mode 100644 index 0000000..c0837ca --- /dev/null +++ b/Models/CloudProduct/Dtos/GoodsImportItem.cs @@ -0,0 +1,14 @@ +using Store.Modules.CloudProduct.Entities; + +namespace Store.Modules.CloudProduct.Dtos +{ + /// + /// 导入项:商品模型 + 原始数据 + /// + public class GoodsImportItem + { + public ProductEntity ProductEntity { get; set; } = null!; + + public string RawData { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/Models/CloudProduct/Dtos/GoodsImportResult.cs b/Models/CloudProduct/Dtos/GoodsImportResult.cs new file mode 100644 index 0000000..1003ac6 --- /dev/null +++ b/Models/CloudProduct/Dtos/GoodsImportResult.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Store.Modules.CloudProduct.Dtos +{ + /// + /// 商品导入结果 + /// + public class GoodsImportResult + { + /// + /// 成功条数 + /// + public int Success { get; set; } + + /// + /// 失败条数 + /// + public int Failed { get; set; } + + /// + /// 总条数 + /// + public int Total => Success + Failed; + + /// + /// 错误明细 + /// + public List Errors { get; set; } = new(); + } +} diff --git a/Models/CloudProduct/Dtos/OpenSearchStatusDto.cs b/Models/CloudProduct/Dtos/OpenSearchStatusDto.cs new file mode 100644 index 0000000..ea61c2c --- /dev/null +++ b/Models/CloudProduct/Dtos/OpenSearchStatusDto.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Store.Modules.CloudProduct.Dtos +{ + /// + /// OpenSearch 运行状态 + /// + public class OpenSearchStatusDto + { + /// + /// 节点地址 + /// + public string NodeUrl { get; set; } = string.Empty; + + /// + /// 索引名称 + /// + public string IndexName { get; set; } = string.Empty; + + /// + /// 是否可连接 + /// + public bool Connected { get; set; } + + /// + /// 索引是否存在 + /// + public bool IndexExists { get; set; } + + /// + /// 文档总数(索引不存在时为 -1) + /// + public long DocumentCount { get; set; } = -1; + + /// + /// 说明信息 + /// + public string? Message { get; set; } + } +} diff --git a/Models/CloudProduct/Dtos/ProductSearchInfo.cs b/Models/CloudProduct/Dtos/ProductSearchInfo.cs new file mode 100644 index 0000000..08cf448 --- /dev/null +++ b/Models/CloudProduct/Dtos/ProductSearchInfo.cs @@ -0,0 +1,60 @@ +using OpenSearch.Client; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Store.Modules.CloudProduct.Dtos +{ + /// + /// 商品搜索模型:Id 为主键,Title 用于分词搜索 + /// + public class ProductSearchInfo + { + [Number(NumberType.Byte, Name = "platform")] + public byte Platform { get; set; } + + [Number(NumberType.Long, Name = "id")] + public long Id { get; set; } + + [Text(Name = "title", Analyzer = "ik_max_word", SearchAnalyzer = "ik_smart")] + public string Title { get; set; } = string.Empty; + [Text(Name = "thumbUrl")] + public string? ThumbUrl { get; set; } = string.Empty; + + /// 一级类目ID + [Number(NumberType.Integer, Name = "cat_id_1")] + public int? CatId1 { get; set; } + + /// 二级类目ID + [Number(NumberType.Integer, Name = "cat_id_2")] + public int? CatId2 { get; set; } + + /// 三级类目ID + [Number(NumberType.Integer, Name = "cat_id_3")] + public int? CatId3 { get; set; } + + /// 四级类目ID + [Number(NumberType.Integer, Name = "cat_id_4")] + public int? CatId4 { get; set; } + + /// 最低价快照,用于价格区间过滤和按价格排序 + [Number(NumberType.Long, Name = "min_price")] + public long? MinPrice { get; set; } + + /// 销量,用于按销量排序 + [Number(NumberType.Integer, Name = "sold_quantity")] + public int SoldQuantity { get; set; } + + public override string ToString() => $"[Id={Id}] Title={Title} Cat=({CatId1},{CatId2},{CatId3},{CatId4}) Price={MinPrice} Sold={SoldQuantity}"; + } + /// + /// 搜索排序方式 + /// + public enum GoodsSearchSortBy + { + Relevance = 0, // 相关性(默认) + PriceAsc = 1, // 价格从低到高 + PriceDesc = 2, // 价格从高到低 + SoldDesc = 3, // 销量从高到低 + } +} diff --git a/Models/CloudProduct/Dtos/ProductSearchRequest.cs b/Models/CloudProduct/Dtos/ProductSearchRequest.cs new file mode 100644 index 0000000..bb94f27 --- /dev/null +++ b/Models/CloudProduct/Dtos/ProductSearchRequest.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Store.Modules.CloudProduct.Dtos +{ + /// + /// 商品搜索请求 + /// + public class ProductSearchRequest + { + /// + /// 标题(模糊搜索) + /// + public string Title { get; set; } = string.Empty; + + /// + /// 最低价格(分) + /// + public long? MinPrice { get; set; } + + /// + /// 最高价格(分) + /// + public long? MaxPrice { get; set; } + + /// + /// 一级类目ID + /// + public int? CatId1 { get; set; } + + /// + /// 二级类目ID + /// + public int? CatId2 { get; set; } + + /// + /// 三级类目ID + /// + public int? CatId3 { get; set; } + + /// + /// 四级类目ID + /// + public int? CatId4 { get; set; } + + /// + /// 最低销量 + /// + public int? MinSoldQuantity { get; set; } + + /// + /// 最高销量 + /// + public int? MaxSoldQuantity { get; set; } + + /// + /// 平台类型 + /// + public byte? Platform { get; set; } + + + /// + /// 页码(默认1) + /// + public int PageIndex { get; set; } = 1; + + /// + /// 每页数量(默认20) + /// + public int PageSize { get; set; } = 20; + + /// + /// 排序字段 + /// + public string SortBy { get; set; } = "CreatedTime"; + + /// + /// 排序方式:asc/desc(默认desc) + /// + public string SortOrder { get; set; } = "desc"; + } +} diff --git a/Models/CloudProduct/Entities/ProductEntity.cs b/Models/CloudProduct/Entities/ProductEntity.cs new file mode 100644 index 0000000..2ea4a4c --- /dev/null +++ b/Models/CloudProduct/Entities/ProductEntity.cs @@ -0,0 +1,124 @@ +using FreeSql.DataAnnotations; +using System.Text.Json.Serialization; + +namespace Store.Modules.CloudProduct.Entities +{ + /// + /// 商品表实体 + /// + [Table(Name = "goods")] // 请根据实际表名调整 + public class ProductEntity + { + /// + /// 自增ID + /// + [Column(IsPrimary = true, IsIdentity = true)] + [JsonIgnore] + public long Id { get; set; } + + /// + /// 平台类型 + /// 1 PDD + /// + [Column(IsNullable = false)] + public byte Platform { get; set; } + + /// + /// 商品编号 + /// + [Column(Name = "goods_id", IsNullable = false)] + public long GoodsId { get; set; } + + /// + /// 标题 + /// + [Column(Name = "title", IsNullable = false, StringLength = 500)] + public string Title { get; set; } = string.Empty; + + /// + /// 描述 + /// + [Column(Name = "description", IsNullable = true)] + public string? Description { get; set; } + + /// + /// 缩略图 + /// + [Column(Name = "thumb_url", IsNullable = true, StringLength = 512)] + public string? ThumbUrl { get; set; } + + /// + /// 一级类目ID + /// + [Column(Name = "cat_id_1", IsNullable = true)] + public int? CatId1 { get; set; } + + /// + /// 二级类目ID + /// + [Column(Name = "cat_id_2", IsNullable = true)] + public int? CatId2 { get; set; } + + /// + /// 三级类目ID + /// + [Column(Name = "cat_id_3", IsNullable = true)] + public int? CatId3 { get; set; } + + /// + /// 四级类目ID + /// + [Column(Name = "cat_id_4", IsNullable = true)] + public int? CatId4 { get; set; } + + /// + /// 最低价快照(元),导入时计算 + /// + [Column(Name = "min_price")] + public long? MinPrice { get; set; } + + /// + /// 最高价快照(元),导入时计算 + /// + [Column(Name = "max_price")] + public long? MaxPrice { get; set; } + + /// + /// 销量 + /// + [Column(Name = "sold_quantity", IsNullable = false)] + public int SoldQuantity { get; set; } + + /// + /// 状态:0正常 1已删除 + /// + [Column(IsNullable = false)] + [JsonIgnore] + public byte Status { get; set; } + + /// + /// 创建时间 + /// + [Column(Name = "createdTime", IsNullable = false, ServerTime = DateTimeKind.Utc)] + [JsonIgnore] + + public DateTime CreatedTime { get; set; } + + /// + /// 更新时间(自动更新) + /// + [Column(Name = "updatedTime", IsNullable = false, ServerTime = DateTimeKind.Utc)] + [JsonIgnore] + + public DateTime UpdatedTime { get; set; } + + + /// + /// 是否已分词:0未 1已 + /// + [Column(Name = "is_segmented", IsNullable = false)] + [JsonIgnore] + public byte IsSegmented { get; set; } + + } +} \ No newline at end of file diff --git a/Models/CloudProduct/Enums/PlatformType.cs b/Models/CloudProduct/Enums/PlatformType.cs new file mode 100644 index 0000000..0a11b30 --- /dev/null +++ b/Models/CloudProduct/Enums/PlatformType.cs @@ -0,0 +1,9 @@ +namespace Store.Modules.CloudProduct.Enums +{ + public enum PlatformType + { + None = 0, + PDD = 1, + WX = 2, + } +} diff --git a/Models/CloudProduct/ImportConverters/ConverterToGoodsFactory.cs b/Models/CloudProduct/ImportConverters/ConverterToGoodsFactory.cs new file mode 100644 index 0000000..3d47e04 --- /dev/null +++ b/Models/CloudProduct/ImportConverters/ConverterToGoodsFactory.cs @@ -0,0 +1,58 @@ +using Store.Modules.CloudProduct.Entities; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Store.Modules.CloudProduct.ImportConverters +{ + /// + /// 导入商品转换工厂:按顺序尝试各转换器,返回标准商品模型。 + /// + public class ConverterToGoodsFactory + { + private static readonly List _converters = new List + { + new ConverterV1(), + }; + + /// + /// 根据本地 JSON 文件路径转换。 + /// + public static ProductEntity? Convert(string filePath) + { + if (string.IsNullOrWhiteSpace(filePath)) + throw new ArgumentNullException(nameof(filePath)); + + if (!File.Exists(filePath)) + throw new FileNotFoundException("找不到导入文件", filePath); + + string content = File.ReadAllText(filePath); + return ConvertContent(content); + } + + /// + /// 根据 JSON 内容字符串转换。 + /// + public static ProductEntity? ConvertContent(string content) + { + if (string.IsNullOrWhiteSpace(content)) + throw new ArgumentNullException(nameof(content)); + + foreach (IGoodsConverter item in _converters) + { + item.ReadContent(content); + if (item.IsMatch()) + { + var goods = item.Convert(); + if (goods != null) + return goods; + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/Models/CloudProduct/ImportConverters/ConverterV1.cs b/Models/CloudProduct/ImportConverters/ConverterV1.cs new file mode 100644 index 0000000..465c3fd --- /dev/null +++ b/Models/CloudProduct/ImportConverters/ConverterV1.cs @@ -0,0 +1,180 @@ + +using Store.Modules.CloudProduct.Entities; +using Store.Modules.CloudProduct.Enums; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Nodes; + +namespace Store.Modules.CloudProduct.ImportConverters +{ + /// + /// PDD 前端商品详情数据转换器(数据源格式见 data.txt) + /// + internal class ConverterV1 : GoodsConverterBase + { + public override string ConverterName => "ConverterV1"; + + public override PlatformType Platform => PlatformType.PDD; + + protected override bool IsMatchProvider() + { + try + { + if (JsonNode.Parse(RawData) is not JsonObject root) + return false; + + // data.txt:顶层包含 goods.goods_id + if (root["goods"] is JsonObject goodsObj && goodsObj["goods_id"] != null) + return true; + + // 也兼容顶层直接是商品对象的情况 + if (root["goods_id"] != null && (root["goods_name"] != null || root["title"] != null)) + return true; + + return false; + } + catch + { + return false; + } + } + + protected override ProductEntity ConverterProvider() + { + if (JsonNode.Parse(RawData) is not JsonObject root) + throw new Exception("无法解析 PDD 商品数据"); + + var goodsNode = root["goods"] as JsonObject ?? root; + var priceNode = root["price"] as JsonObject; + + var goods = new ProductEntity + { + Platform = (byte)PlatformType.PDD, + GoodsId = GetLong(goodsNode, "goods_id"), + Title = GetString(goodsNode, "goods_name") + ?? GetString(goodsNode, "short_name") + ?? string.Empty, + Description = GetString(goodsNode, "share_desc") ?? GetString(goodsNode, "short_name"), + ThumbUrl = GetString(goodsNode, "hd_url") ?? GetString(goodsNode, "thumb_url") ?? GetString(goodsNode, "hd_thumb_url") ?? GetString(goodsNode, "image_url"), + CatId1 = GetIntOrNull(goodsNode, "cat_id_1"), + CatId2 = GetIntOrNull(goodsNode, "cat_id_2"), + CatId3 = GetIntOrNull(goodsNode, "cat_id_3"), + CatId4 = GetIntOrNull(goodsNode, "cat_id_4"), + SoldQuantity = GetInt(goodsNode, "sold_quantity"), + Status = 0, + }; + + var price = GetPriceRange(priceNode, root); + goods.MinPrice = price.Min; + goods.MaxPrice = price.Max; + + return goods; + } + + private static string? GetString(JsonObject? obj, string key) + { + if (obj == null || !obj.TryGetPropertyValue(key, out var node) || node == null) + return null; + + if (node is JsonValue value && value.TryGetValue(out var str)) + return str; + + return node.ToJsonString(); + } + + private static long GetLong(JsonObject? obj, string key) + { + if (obj == null || !obj.TryGetPropertyValue(key, out var node) || node == null) + return 0; + + if (node is JsonValue value) + { + if (value.TryGetValue(out var l)) return l; + if (value.TryGetValue(out var d)) return (long)d; + if (long.TryParse(value.ToString(), out var l2)) return l2; + } + + return 0; + } + + private static int GetInt(JsonObject? obj, string key) + { + if (obj == null || !obj.TryGetPropertyValue(key, out var node) || node == null) + return 0; + + if (node is JsonValue value) + { + if (value.TryGetValue(out var i)) return i; + if (value.TryGetValue(out var l)) return (int)l; + if (int.TryParse(value.ToString(), out var i2)) return i2; + } + + return 0; + } + + private static int? GetIntOrNull(JsonObject? obj, string key) + { + if (obj == null || !obj.TryGetPropertyValue(key, out var node) || node == null) + return null; + + if (node is JsonValue value) + { + if (value.TryGetValue(out var i) && i > 0) return i; + if (value.TryGetValue(out var l) && l > 0) return (int)l; + if (int.TryParse(value.ToString(), out var i2) && i2 > 0) return i2; + } + + return null; + } + + private static long? GetLongOrNull(JsonObject? obj, string key) + { + if (obj == null || !obj.TryGetPropertyValue(key, out var node) || node == null) + return null; + + if (node is JsonValue value) + { + if (value.TryGetValue(out var d)) return d; + if (value.TryGetValue(out var l)) return l; + if (long.TryParse(value.ToString(), out var d2)) return d2; + } + + return null; + } + + /// + /// 计算价格区间。优先使用顶层 price 节点,其次遍历 sku。 + /// + private static (long? Min, long? Max) GetPriceRange(JsonObject? priceNode, JsonObject root) + { + long? min = null; + long? max = null; + + if (priceNode != null) + { + min = GetLongOrNull(priceNode, "min_group_price"); + max = GetLongOrNull(priceNode, "max_group_price"); + if (min.HasValue || max.HasValue) + return (min, max); + } + + if (root["sku"] is JsonArray skuArray && skuArray.Count > 0) + { + foreach (var item in skuArray.OfType()) + { + var price = GetLongOrNull(item, "group_price") ?? GetLongOrNull(item, "normal_price"); + if (price == null) continue; + + min = min.HasValue ? Math.Min(min.Value, price.Value) : price; + max = max.HasValue ? Math.Max(max.Value, price.Value) : price; + } + + if (min.HasValue || max.HasValue) + return (min, max); + } + + return (null, null); + } + } +} \ No newline at end of file diff --git a/Models/CloudProduct/ImportConverters/IGoodsConverter.cs b/Models/CloudProduct/ImportConverters/IGoodsConverter.cs new file mode 100644 index 0000000..762a591 --- /dev/null +++ b/Models/CloudProduct/ImportConverters/IGoodsConverter.cs @@ -0,0 +1,99 @@ +using Store.Modules.CloudProduct.Entities; +using Store.Modules.CloudProduct.Enums; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.Json.Nodes; +using System.Threading.Tasks; + +namespace Store.Modules.CloudProduct.ImportConverters +{ + /// + /// 导入商品数据转换器 + /// + public interface IGoodsConverter + { + /// + /// 原始数据 + /// + string RawData { get; } + + /// + /// 获取转换器名称 + /// + string ConverterName { get; } + + /// + /// 支持的平台类型 + /// + PlatformType Platform { get; } + /// + /// 读取内容 + /// + /// + void ReadContent(string content); + /// + /// 判断是否匹配当前转换器 + /// + /// 是否匹配 + bool IsMatch(); + + /// + /// 转换为标准商品模型 + /// + ProductEntity Convert(); + } + + public abstract class GoodsConverterBase : IGoodsConverter + { + private bool _isMatch = false; + public string RawData { get; private set; } + public abstract string ConverterName { get; } + public abstract PlatformType Platform { get; } + + void IGoodsConverter.ReadContent(string filePath) + { + ReadContent(filePath); + } + protected virtual void ReadContent(string filePath) + { + RawData = filePath; + } + + + public virtual bool IsMatch() + { + _isMatch = true; + return IsMatchProvider(); + } + protected abstract bool IsMatchProvider(); + + /// + /// 带校验的转换(基类统一实现) + /// + public virtual ProductEntity Convert() + { + if (_isMatch == false) throw new Exception("未校验数据"); + try + { + JsonNode.Parse(RawData); + } + catch { throw new Exception("数据解析失败,不是有效的JSON格式"); } + + + var product = ConverterProvider(); + if (product == null) + throw new Exception("转换失败"); + // 基础校验 + if (product.GoodsId == 0) + throw new Exception("商品ID不能为空"); + + return product; + } + + protected abstract ProductEntity ConverterProvider(); + + } +} diff --git a/Models/CloudProduct/Services/OpenSearchServer.cs b/Models/CloudProduct/Services/OpenSearchServer.cs new file mode 100644 index 0000000..236b02f --- /dev/null +++ b/Models/CloudProduct/Services/OpenSearchServer.cs @@ -0,0 +1,498 @@ +using MAX.Core.DependencyInjection; +using MAX.Framework.Commons; +using Microsoft.Extensions.Configuration; +using MySqlX.XDevAPI; +using OpenSearch.Client; +using OpenSearch.Net; +using Store.Modules.CloudProduct.Dtos; +using Store.Modules.CloudProduct.Entities; + +namespace Store.Modules.CloudProduct.Services +{ + public interface IOpenSearchServer: ISingletonDependency + { + Task AddOneRecordAsync(ProductSearchInfo goods); + Task BulkIndexGoodsAsync(IEnumerable docs); + Task ClearIndexAsync(); + Task CountDocumentsAsync(); + Task CreateIndexAsync(); + Task DeleteDocumentsAsync(IEnumerable ids); + Task DeleteIndexAsync(); + Task GetStatusAsync(); + Task RebuildIndexAsync(IEnumerable goodsList); + Task<(long Total, List list)> SearchByTitleAsync(ProductSearchRequest request, GoodsSearchSortBy sortBy = GoodsSearchSortBy.Relevance); + } + + internal class OpenSearchServer : IOpenSearchServer + { + private readonly string nodeUrl; + private IOpenSearchClient _client = null!; + private readonly string IndexName = ""; + + // 查询类操作(search/count/status/ping)统一超时 + private static readonly TimeSpan QueryTimeout = TimeSpan.FromSeconds(5); + // 批量写入/建索引/删除索引等管理类操作超时(数据量大时需要更长,可挪到配置文件) + private static readonly TimeSpan AdminTimeout = TimeSpan.FromSeconds(30); + + public OpenSearchServer(IConfiguration configuration) + { + var config = configuration.GetSection("Search"); + nodeUrl = config.GetValue("Url") ?? ""; + IndexName = config.GetValue("IndexName") ?? ""; + _client = CreateClient(); + } + + private IOpenSearchClient CreateClient() + { + var pool = new SingleNodeConnectionPool(new Uri(nodeUrl)); + var settings = new ConnectionSettings(pool) + .DefaultIndex(IndexName) + .DefaultMappingFor(m => m.IndexName(IndexName)) + .RequestTimeout(QueryTimeout) + .MaxRetryTimeout(QueryTimeout) + .PrettyJson() + .DisableDirectStreaming(); // 便于调试打印请求/响应报文 + + return new OpenSearchClient(settings); + } + + /// + /// ============ 1. 创建索引 ============ + /// + public async Task CreateIndexAsync() + { + var existsResponse = await _client.Indices.ExistsAsync(IndexName, d => d + .RequestConfiguration(r => r.RequestTimeout(AdminTimeout))); + if (existsResponse.Exists) + { + if (await IsIndexMappingHealthyAsync()) + { + Console.WriteLine($"索引 [{IndexName}] 已存在且映射正确,无需重复创建。"); + return true; + } + + Console.WriteLine($"索引 [{IndexName}] 的映射不完整(可能由清空索引等操作造成),正在重建..."); + var deleteResponse = await _client.Indices.DeleteAsync(IndexName, d => d + .RequestConfiguration(r => r.RequestTimeout(AdminTimeout))); + if (!deleteResponse.IsValid) + { + throw new AppException("删除损坏索引失败: " + deleteResponse.DebugInformation); + } + } + + // 优先尝试使用 IK 中文分词(需集群安装 analysis-ik 插件) + var createResponse = await TryCreateIndexAsync(useIk: true); + + if (!createResponse.IsValid) + { + Console.WriteLine("IK 分词插件不可用,已回退到 standard 分析器,中文分词将失效,请检查集群是否安装 analysis-ik 插件。"); + createResponse = await TryCreateIndexAsync(useIk: false); + } + + if (createResponse.IsValid) + { + Console.WriteLine($"索引 [{IndexName}] 创建成功。"); + return true; + } + else + { + throw new AppException("索引创建失败: " + createResponse.DebugInformation); + } + } + + /// + /// 校验索引映射是否健康:title 字段必须使用 IK 分析器 + /// + private async Task IsIndexMappingHealthyAsync() + { + try + { + var getResponse = await _client.Indices.GetAsync(IndexName, d => d + .RequestConfiguration(r => r.RequestTimeout(QueryTimeout))); + if (!getResponse.IsValid || !getResponse.Indices.TryGetValue(IndexName, out var indexState)) + { + return false; + } + + var mapping = indexState.Mappings; + if (mapping?.Properties == null + || !mapping.Properties.TryGetValue(new PropertyName("title"), out var property) + || property is not ITextProperty textProperty) + { + return false; + } + + return textProperty.Analyzer == "ik_max_word" && textProperty.SearchAnalyzer == "ik_smart"; + } + catch + { + return false; + } + } + + private async Task TryCreateIndexAsync(bool useIk) + { + var indexAnalyzer = useIk ? "ik_max_word" : "standard"; + var searchAnalyzer = useIk ? "ik_smart" : "standard"; + + return await _client.Indices.CreateAsync(IndexName, c => c + .Settings(s => s + .NumberOfShards(1) + .NumberOfReplicas(0) + ) + .Map(m => m + .Properties(p => p + .Number(n => n.Name(x => x.Id).Type(NumberType.Long)) + .Text(t => t + .Name(x => x.Title) + .Analyzer(indexAnalyzer) + .SearchAnalyzer(searchAnalyzer) + ) + .Number(n => n.Name(x => x.CatId1).Type(NumberType.Integer)) + .Number(n => n.Name(x => x.CatId2).Type(NumberType.Integer)) + .Number(n => n.Name(x => x.CatId3).Type(NumberType.Integer)) + .Number(n => n.Name(x => x.CatId4).Type(NumberType.Integer)) + .Number(n => n.Name(x => x.MinPrice).Type(NumberType.Long)) + .Number(n => n.Name(x => x.SoldQuantity).Type(NumberType.Integer)) + ) + ) + .RequestConfiguration(r => r.RequestTimeout(AdminTimeout)) + ); + } + + /// + /// ============ 2. 删除索引 ============ + /// + public async Task DeleteIndexAsync() + { + var response = await _client.Indices.DeleteAsync(IndexName, d => d + .RequestConfiguration(r => r.RequestTimeout(AdminTimeout))); + if (!response.IsValid) + { + throw new AppException("删除失败: " + response.DebugInformation); + } + } + + /// + /// ============ 3.1 批量写入商品搜索文档(存在则覆盖) ============ + /// + public async Task BulkIndexGoodsAsync(IEnumerable docs) + { + if (docs == null) throw new ArgumentNullException(nameof(docs)); + + var list = docs.ToList(); + if (list.Count == 0) return; + + // 确保索引已按 IK 分词映射创建,否则中文分词无效 + await CreateIndexAsync(); + + var bulkResponse = await _client.BulkAsync(b => b + .Index(IndexName) + .IndexMany(list, (descriptor, doc) => descriptor.Id(doc.Id)) + .RequestConfiguration(r => r.RequestTimeout(AdminTimeout)) + ); + + if (!bulkResponse.IsValid) + { + throw new AppException("写入 OpenSearch 失败: " + bulkResponse.DebugInformation); + } + + // 刷新索引,保证数据立即可搜索(仅用于演示,生产环境不建议频繁 Refresh) + await _client.Indices.RefreshAsync(IndexName, d => d + .RequestConfiguration(r => r.RequestTimeout(AdminTimeout))); + } + + /// + /// ============ 3.2 根据数据库商品重建搜索索引 ============ + /// + public async Task RebuildIndexAsync(IEnumerable goodsList) + { + if (goodsList == null) throw new ArgumentNullException(nameof(goodsList)); + + var docs = goodsList + .Where(x => x != null && x.GoodsId > 0) + .Select(x => new ProductSearchInfo + { + Id = x.GoodsId, + Title = x.Title ?? string.Empty, + CatId1 = x.CatId1, + CatId2 = x.CatId2, + CatId3 = x.CatId3, + CatId4 = x.CatId4, + MinPrice = x.MinPrice, + SoldQuantity = x.SoldQuantity + }) + .ToList(); + + if (docs.Count == 0) return 0; + + await BulkIndexGoodsAsync(docs); + return docs.Count; + } + + /// + /// ============ 3.3 按主键批量删除 OpenSearch 文档(软/硬删除共用) ============ + /// + public async Task DeleteDocumentsAsync(IEnumerable ids) + { + var list = ids?.Distinct().ToList() ?? new List(); + if (list.Count == 0) return; + + var exists = await _client.Indices.ExistsAsync(IndexName, d => d + .RequestConfiguration(r => r.RequestTimeout(QueryTimeout))); + if (!exists.Exists) + { + return; + } + + var response = await _client.BulkAsync(b => + { + foreach (var id in list) + { + b.Delete(d => d.Index(IndexName).Id(id)); + } + b.RequestConfiguration(r => r.RequestTimeout(AdminTimeout)); + return b; + }); + + if (!response.IsValid) + { + throw new AppException("删除 OpenSearch 文档失败: " + response.DebugInformation); + } + + await _client.Indices.RefreshAsync(IndexName, d => d + .RequestConfiguration(r => r.RequestTimeout(AdminTimeout))); + } + + /// + /// ============ 4. 手动新增一条 ============ + /// + public async Task AddOneRecordAsync(ProductSearchInfo goods) + { + if (goods == null) throw new ArgumentNullException(nameof(goods)); + var response = await _client.IndexAsync(goods, i => i + .Index(IndexName) + .Id(goods.Id) + .RequestConfiguration(r => r.RequestTimeout(QueryTimeout)) + ); + + if (response.IsValid) + { + await _client.Indices.RefreshAsync(IndexName, d => d + .RequestConfiguration(r => r.RequestTimeout(QueryTimeout))); + } + else + { + throw new AppException("新增失败: " + response.DebugInformation); + } + } + + /// + /// ============ 5. 分词搜索(分页 + 类目/价格过滤 + 排序) ============ + /// + /// 搜索关键词 + /// 一级类目,不传则不限制 + /// 二级类目,不传则不限制 + /// 三级类目,不传则不限制 + /// 四级类目,不传则不限制 + /// 最低价(含),不传则不限制 + /// 最高价(含),不传则不限制 + /// 排序方式 + /// 页码,从 1 开始 + /// 每页条数 + public async Task<(long Total, List list)> SearchByTitleAsync(ProductSearchRequest request, + GoodsSearchSortBy sortBy = GoodsSearchSortBy.Relevance) + { + string keyword = request.Title; + int? catId1 = request.CatId1; + int? catId2 = request.CatId2; + int? catId3 = request.CatId3; + int? catId4 = request.CatId4; + long? minPrice = request.MinPrice; + long? maxPrice = request.MaxPrice; + int? minsold = request.MinSoldQuantity; + int? maxsold = request.MaxSoldQuantity; + int? platform = request.Platform; + int pageIndex = request.PageIndex; + int pageSize = request.PageSize; + + + //if (string.IsNullOrWhiteSpace(keyword)) + //{ + // throw new AppException("关键词不能为空。"); + //} + + if (pageIndex < 1) pageIndex = 1; + if (pageSize < 1) pageSize = 20; + if (pageSize > 2000) pageSize = 2000; // 避免深分页/单页过大拖垮性能,按需调整上限 + + var from = (pageIndex - 1) * pageSize; + + ISearchResponse searchResponse; + try + { + searchResponse = await _client.SearchAsync(s => s + .Index(IndexName) + .Query(q => q + .Bool(b => + { + if (string.IsNullOrWhiteSpace(keyword) == false) + { + b.Must(m => m.Match(mm => mm.Field(f => f.Title).Query(keyword))); + } + + var filters = new List, QueryContainer>>(); + if (platform.HasValue && platform!.Value > 0) filters.Add(f => f.Term(t => t.Field(x => x.Platform).Value(platform.Value))); + + if (catId1.HasValue) filters.Add(f => f.Term(t => t.Field(x => x.CatId1).Value(catId1.Value))); + if (catId2.HasValue) filters.Add(f => f.Term(t => t.Field(x => x.CatId2).Value(catId2.Value))); + if (catId3.HasValue) filters.Add(f => f.Term(t => t.Field(x => x.CatId3).Value(catId3.Value))); + if (catId4.HasValue) filters.Add(f => f.Term(t => t.Field(x => x.CatId4).Value(catId4.Value))); + + if (minPrice.HasValue || maxPrice.HasValue) + { + filters.Add(f => f.Range(r => + { + var range = r.Field(x => x.MinPrice); + if (minPrice.HasValue) range = range.GreaterThanOrEquals(minPrice.Value); + if (maxPrice.HasValue) range = range.LessThanOrEquals(maxPrice.Value); + return range; + })); + } + + if (minsold.HasValue || maxsold.HasValue) + { + filters.Add(f => f.Range(r => + { + var range = r.Field(x => x.SoldQuantity); + if (minsold.HasValue) range = range.GreaterThanOrEquals(minsold.Value); + if (maxsold.HasValue) range = range.LessThanOrEquals(maxsold.Value); + return range; + })); + } + + + + if (filters.Count > 0) b.Filter(filters.ToArray()); + return b; + }) + ) + .Sort(sort => sortBy switch + { + GoodsSearchSortBy.PriceAsc => sort.Ascending(f => f.MinPrice), + GoodsSearchSortBy.PriceDesc => sort.Descending(f => f.MinPrice), + GoodsSearchSortBy.SoldDesc => sort.Descending(f => f.SoldQuantity), + _ => sort.Descending(SortSpecialField.Score) + }) + .From(from) + .Size(pageSize) + .TrackTotalHits(true) // 命中数超过 10000 时默认不精确统计,需要显式开启 + .RequestConfiguration(r => r.RequestTimeout(QueryTimeout)) + ); + } + catch (OpenSearchClientException ex) when (ex.Response?.Success == false) + { + throw new AppException($"搜索超时或失败({QueryTimeout.TotalSeconds}秒): " + ex.Message); + } + + if (!searchResponse.IsValid) + { + throw new AppException("搜索失败: " + searchResponse.DebugInformation); + } + + //var ids = searchResponse.Documents.Select(d => d.Id).ToList(); + return (searchResponse.Total, searchResponse.Documents.ToList()); + } + + /// + /// ============ 6. 统计文档数 ============ + /// + public async Task CountDocumentsAsync() + { + var countResponse = await _client.CountAsync(c => c + .Index(IndexName) + .RequestConfiguration(r => r.RequestTimeout(QueryTimeout)) + ); + if (countResponse.IsValid) + { + Console.WriteLine($"索引 [{IndexName}] 当前文档总数: {countResponse.Count}"); + return countResponse.Count; + } + else + { + Console.WriteLine("统计失败: " + countResponse.DebugInformation); + throw new AppException(countResponse.DebugInformation); + } + } + + /// + /// ============ 7. 查询 OpenSearch 状态 ============ + /// + public async Task GetStatusAsync() + { + var status = new OpenSearchStatusDto + { + NodeUrl = nodeUrl, + IndexName = IndexName, + }; + + try + { + var ping = await _client.PingAsync(p => p + .RequestConfiguration(r => r.RequestTimeout(QueryTimeout))); + status.Connected = ping.IsValid; + if (!ping.IsValid) + { + status.Message = ping.DebugInformation; + return status; + } + + var exists = await _client.Indices.ExistsAsync(IndexName, d => d + .RequestConfiguration(r => r.RequestTimeout(QueryTimeout))); + status.IndexExists = exists.Exists; + + if (exists.Exists) + { + var count = await _client.CountAsync(c => c + .Index(IndexName) + .RequestConfiguration(r => r.RequestTimeout(QueryTimeout))); + status.DocumentCount = count.IsValid ? count.Count : -1; + if (!count.IsValid) + status.Message = count.DebugInformation; + } + } + catch (Exception ex) + { + status.Connected = false; + status.Message = ex.Message; + } + + return status; + } + + /// + /// 清空索引:删除后按统一映射重建,重建期间该索引不可查(由后台定时任务负责回填数据,接受短暂空窗) + /// + public async Task ClearIndexAsync() + { + var existsResponse = await _client.Indices.ExistsAsync(IndexName, d => d + .RequestConfiguration(r => r.RequestTimeout(QueryTimeout))); + if (!existsResponse.Exists) + { + Console.WriteLine($"索引 {IndexName} 不存在"); + return false; + } + + var deleteResponse = await _client.Indices.DeleteAsync(IndexName, d => d + .RequestConfiguration(r => r.RequestTimeout(AdminTimeout))); + if (!deleteResponse.IsValid) + { + Console.WriteLine($"删除索引 {IndexName} 失败: {deleteResponse.DebugInformation}"); + return false; + } + Console.WriteLine($"索引 {IndexName} 已删除"); + + return await CreateIndexAsync(); + } + } +} \ No newline at end of file diff --git a/Models/CloudProduct/Services/ProductRepository.cs b/Models/CloudProduct/Services/ProductRepository.cs new file mode 100644 index 0000000..2011e23 --- /dev/null +++ b/Models/CloudProduct/Services/ProductRepository.cs @@ -0,0 +1,13 @@ +using FreeSql; +using MAX.Core.DependencyInjection; +using Store.Modules.CloudProduct.Entities; + +namespace Store.Modules.CloudProduct.Services +{ + public class ProductRepository : BaseRepository, IScopedDependency + { + public ProductRepository(IFreeSql fsql) : base(fsql) + { + } + } +} diff --git a/Models/CloudProduct/Services/ProductService.cs b/Models/CloudProduct/Services/ProductService.cs new file mode 100644 index 0000000..d314c83 --- /dev/null +++ b/Models/CloudProduct/Services/ProductService.cs @@ -0,0 +1,409 @@ +using MAX.Core.DependencyInjection; +using Microsoft.Extensions.Configuration; +using Store.Modules.CloudProduct.Dtos; +using Store.Modules.CloudProduct.Entities; + +namespace Store.Modules.CloudProduct.Services +{ + public interface IProductService : IScopedDependency + { + Task DeleteGoodsAsync(IEnumerable ids); + Task> GetGoodsByIdsAsync(IEnumerable ids); + Task ImportGoodsAsync(IEnumerable items); + Task IndexPendingGoodsAsync(int batchSize); + Task RebuildSearchIndexAsync(IEnumerable? ids = null); + Task<(List List, long Total)> SearchGoods(ProductSearchRequest request); + Task UpdateImage(IEnumerable goods); + } + + internal class ProductService : IProductService + { + private readonly ProductRepository _repository; + private readonly OpenSearchServer _search; + private readonly RawDataStorage _rawDataStorage; + private readonly IConfiguration _configuration; + + public ProductService(ProductRepository repository, OpenSearchServer search, RawDataStorage rawDataStorage, IConfiguration configuration) + { + _repository = repository; + _search = search; + _rawDataStorage = rawDataStorage; + _configuration = configuration; + } + + public async Task<(List List, long Total)> SearchGoods(ProductSearchRequest request) + { + // 1. 从搜索引擎获取标题匹配的商品ID列表 + List goodsList = new(); + long total = 0; + + // 从搜索引擎查询标题匹配的ID + (long Total, List list) searchRet = (await _search.SearchByTitleAsync(request)); + goodsList = searchRet.list.Select(a => new ProductEntity + { + Platform = a.Platform, + GoodsId = a.Id, + Title = a.Title, + CatId1 = a.CatId1, + CatId2 = a.CatId2, + CatId3 = a.CatId3, + CatId4 = a.CatId4, + MinPrice = a.MinPrice, + MaxPrice = 0, + SoldQuantity = a.SoldQuantity, + ThumbUrl = a.ThumbUrl, + + }).ToList(); + //goodsIds = searchRet.list.Select(d => d.Id).ToList(); + total = searchRet.Total; + //// 如果没有匹配的结果,直接返回空 + //if (!goodsIds.Any()) + //{ + // return (new List(), 0); + //} + + return (goodsList, total); + + //// 2. 构建数据库查询(只查正常状态,排除软删除) + //var query = _repository.Select.Where(x => x.Status == 0); + + //// 如果有标题搜索,先用ID过滤 + //if (goodsIds.Any()) + //{ + // query = query.Where(x => goodsIds.Contains(x.GoodsId)); + //} + + //// 3. 价格区间查询 + //if (request.MinPrice.HasValue) + //{ + // query = query.Where(x => x.MinPrice >= request.MinPrice.Value); + //} + //if (request.MaxPrice.HasValue) + //{ + // query = query.Where(x => x.MaxPrice <= request.MaxPrice.Value); + //} + + //// 4. 类目查询 + //if (request.CatId1.HasValue) + //{ + // query = query.Where(x => x.CatId1 == request.CatId1); + //} + //if (request.CatId2.HasValue) + //{ + // query = query.Where(x => x.CatId2 == request.CatId2); + //} + //if (request.CatId3.HasValue) + //{ + // query = query.Where(x => x.CatId3 == request.CatId3); + //} + //if (request.CatId4.HasValue) + //{ + // query = query.Where(x => x.CatId4 == request.CatId4); + //} + + //// 5. 销量区间查询 + //if (request.MinSoldQuantity.HasValue) + //{ + // query = query.Where(x => x.SoldQuantity >= request.MinSoldQuantity.Value); + //} + //if (request.MaxSoldQuantity.HasValue) + //{ + // query = query.Where(x => x.SoldQuantity <= request.MaxSoldQuantity.Value); + //} + + //// 6. 平台过滤 + //if (request.Platform.HasValue) + //{ + // query = query.Where(x => x.Platform == request.Platform); + //} + + //// 8. 获取总数 + //total = await query.CountAsync(); + + //// 9. 排序 + //if (!string.IsNullOrEmpty(request.SortBy)) + //{ + // var isAsc = request.SortOrder?.ToLower() == "asc"; + // query = query.OrderByPropertyName(request.SortBy, isAsc); + //} + //else + //{ + // query = query.OrderByDescending(x => x.CreatedTime); + //} + + //// 10. 分页查询 + //var list = await query + // //.Skip((request.PageIndex - 1) * request.PageSize) + // //.Limit(request.PageSize) + // .ToListAsync(); + ////内存排序 + //var orderMap = goodsIds.Select((id, idx) => new { id, idx }) + // .ToDictionary(x => x.id, x => x.idx); + //list = list.OrderBy(x => orderMap.TryGetValue(x.GoodsId, out var idx) ? idx : int.MaxValue) + // .ToList(); + //return (list, total); + } + + /// + /// 批量导入商品:先落库(按平台 + 商品编号去重)+ 保存原始数据, + /// 然后根据配置决定“立即分词”还是“标记未同步交给后台任务分词”。 + /// + public async Task ImportGoodsAsync(IEnumerable items) + { + var result = new GoodsImportResult(); + if (items == null) + return result; + + var indexImmediately = IsImmediateIndexing(); + var docs = new List(); + var indexedIds = new List(); + + foreach (var item in items) + { + var goods = item?.ProductEntity; + if (goods == null) + { + result.Failed++; + result.Errors.Add("商品数据为空"); + continue; + } + + var goodsId = goods.GoodsId; + if (goodsId == 0) + { + result.Failed++; + result.Errors.Add("商品编号(GoodsId)不能为空"); + continue; + } + + try + { + // 导入时统一先标记“未同步”;立即分词成功后再置为“已同步”, + // 后台模式下保持“未同步”,由 Hangfire 定时任务分词。 + goods.IsSegmented = 0; + var saved = await UpsertGoodsAsync(goods); + + var rawData = item?.RawData ?? string.Empty; + if (!string.IsNullOrWhiteSpace(rawData)) + { + await _rawDataStorage.SaveAsync(saved.GoodsId, rawData); + } + + if (indexImmediately) + { + docs.Add(new ProductSearchInfo + { + Platform = saved.Platform, + Id = saved.GoodsId, + Title = saved.Title ?? string.Empty, + ThumbUrl = saved.ThumbUrl, + CatId1 = saved.CatId1, + CatId2 = saved.CatId2, + CatId3 = saved.CatId3, + CatId4 = saved.CatId4, + MinPrice = saved.MinPrice, + SoldQuantity = saved.SoldQuantity, + }); + indexedIds.Add(saved.GoodsId); + } + + result.Success++; + } + catch (Exception ex) + { + result.Failed++; + result.Errors.Add($"GoodsId={goodsId}: {ex.Message}"); + } + } + + if (docs.Count > 0) + { + try + { + await _search.BulkIndexGoodsAsync(docs); + await MarkIndexedAsync(indexedIds); + } + catch (Exception ex) + { + // 写入失败时保持“未同步”,由后台定时任务重试 + result.Failed += docs.Count; + result.Success -= docs.Count; + result.Errors.Add("写入 OpenSearch 失败: " + ex.Message); + } + } + + return result; + } + + /// + /// 后台任务:批量处理未同步(IndexStatus=0)的商品,写入 OpenSearch 后标记为已同步。 + /// + public async Task IndexPendingGoodsAsync(int batchSize) + { + if (batchSize <= 0) + batchSize = 200; + + var pending = await _repository.Select + .Where(x => x.IsSegmented == 0 && x.Status == 0) + .OrderBy(x => x.GoodsId) + .Limit(batchSize) + .ToListAsync(); + + if (pending.Count == 0) + return 0; + + var docs = pending + .Select(x => new ProductSearchInfo + { + Platform = x.Platform, + Id = x.GoodsId, + Title = x.Title ?? string.Empty, + ThumbUrl = x.ThumbUrl, + CatId1 = x.CatId1, + CatId2 = x.CatId2, + CatId3 = x.CatId3, + CatId4 = x.CatId4, + MinPrice = x.MinPrice, + SoldQuantity = x.SoldQuantity, + }) + .ToList(); + + await _search.BulkIndexGoodsAsync(docs); + + await MarkIndexedAsync(pending.Select(x => x.GoodsId)); + return pending.Count; + } + + /// + /// 按平台 + 商品编号查找已存在记录,存在则更新,不存在则新增。 + /// + private async Task UpsertGoodsAsync(ProductEntity goods) + { + var existing = await _repository.Select + .Where(x => x.Platform == goods.Platform && x.GoodsId == goods.GoodsId) + .FirstAsync(); + + if (existing != null) + { + goods.Id = existing.Id; + goods.GoodsId = existing.GoodsId; + goods.CreatedTime = existing.CreatedTime; + goods.UpdatedTime = DateTime.Now; + await _repository.UpdateAsync(goods); + return goods; + } + + goods.CreatedTime = DateTime.Now; + goods.UpdatedTime = DateTime.Now; + return await _repository.InsertAsync(goods); + } + + /// + /// 根据数据库主键 ID 列表读取商品。 + /// + public async Task> GetGoodsByIdsAsync(IEnumerable ids) + { + var idList = ids?.ToList(); + if (idList == null || idList.Count == 0) + return new List(); + + return await _repository.Select + .Where(x => idList.Contains(x.GoodsId)) + .ToListAsync(); + } + + /// + /// 重新从数据库读取指定商品,覆盖 OpenSearch 中已有的分词并重新分词。 + /// ids 为空时重建全部商品。重建成功的记录标记为已同步。 + /// + public async Task RebuildSearchIndexAsync(IEnumerable? ids = null) + { + List goodsList; + + var idList = ids?.ToList(); + if (idList == null || idList.Count == 0) + { + goodsList = await _repository.Select.ToListAsync(); + } + else + { + goodsList = await _repository.Select + .Where(x => idList.Contains(x.GoodsId)) + .ToListAsync(); + } + + var count = await _search.RebuildIndexAsync(goodsList); + await MarkIndexedAsync(goodsList.Where(x => x.GoodsId > 0).Select(x => x.GoodsId)); + return count; + } + + /// + /// 删除商品:按配置决定软删除(Status=1)还是硬删除(物理删除), + /// 两种方式都会删除 OpenSearch 中对应的文档。 + /// + public async Task DeleteGoodsAsync(IEnumerable ids) + { + var idList = ids?.Distinct().ToList(); + if (idList == null || idList.Count == 0) + return 0; + + if (IsSoftDelete()) + { + // 软删除:仅用 Status 标记(0正常 1已删除) + await _repository.Orm.Update() + .Set(x => x.Status, (byte)1) + .Where(x => idList.Contains(x.GoodsId)) + .ExecuteAffrowsAsync(); + } + else + { + // 硬删除:直接删除数据库记录 + await _repository.Orm.Delete() + .Where(x => idList.Contains(x.GoodsId)) + .ExecuteAffrowsAsync(); + } + + // 无论软删除还是硬删除,都要删除 OpenSearch 中的对应文档 + await _search.DeleteDocumentsAsync(idList); + return idList.Count; + } + + public async Task UpdateImage(IEnumerable goods) + { + //string sql = _repository.Orm.Update().SetSource(goods, a => a.GoodsId).UpdateColumns(a => new { a.ThumbUrl }).ToSql(); + return await _repository.Orm.Update() + .SetSource(goods, a => a.GoodsId) + .UpdateColumns(a => new { a.ThumbUrl }) + .ExecuteAffrowsAsync(); + + } + /// + /// 是否导入时立即分词(Indexing:Mode = Immediate)。 + /// + private bool IsImmediateIndexing() + { + var mode = _configuration.GetValue("Indexing:Mode"); + return string.Equals(mode, "Immediate", StringComparison.OrdinalIgnoreCase); + } + + /// + /// 是否软删除(Delete:SoftDelete,默认 true)。 + /// + private bool IsSoftDelete() + { + return _configuration.GetValue("Delete:SoftDelete") ?? true; + } + + private async Task MarkIndexedAsync(IEnumerable ids) + { + var idList = ids?.ToList(); + if (idList == null || idList.Count == 0) + return; + + await _repository.Orm.Update() + .Set(x => x.IsSegmented, (byte)1) + .Where(x => idList.Contains(x.GoodsId)) + .ExecuteAffrowsAsync(); + } + } +} \ No newline at end of file diff --git a/Models/CloudProduct/Services/RawDataStorage.cs b/Models/CloudProduct/Services/RawDataStorage.cs new file mode 100644 index 0000000..ddce2b6 --- /dev/null +++ b/Models/CloudProduct/Services/RawDataStorage.cs @@ -0,0 +1,77 @@ +using MAX.Core.DependencyInjection; +using Microsoft.Extensions.Configuration; +using System.IO.Compression; +using System.Text; + +namespace Store.Modules.CloudProduct.Services +{ + /// + /// 商品原始数据文件存储:按 goodsId % 100 分目录,文件名即商品ID,GZip 压缩保存。 + /// + public class RawDataStorage : IScopedDependency + { + private readonly string _rootPath; + + public RawDataStorage(IConfiguration configuration) + { + var configured = configuration.GetValue("RawData:Path"); + if (string.IsNullOrWhiteSpace(configured)) + configured = "RawData"; + + _rootPath = Path.GetFullPath(configured); + } + + /// + /// 保存原始数据,返回保存后的完整路径。 + /// + public async Task SaveAsync(long goodsId, string rawData) + { + var filePath = GetFilePath(goodsId); + var dir = Path.GetDirectoryName(filePath)!; + Directory.CreateDirectory(dir); + + await using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None); + await using var gzip = new GZipStream(fileStream, CompressionLevel.Fastest); + await using var writer = new StreamWriter(gzip, new UTF8Encoding(false)); + await writer.WriteAsync(rawData); + + return filePath; + } + + /// + /// 读取并解压原始数据;文件不存在时返回 null。 + /// + public async Task ReadAsync(long goodsId) + { + var filePath = GetFilePath(goodsId); + if (!File.Exists(filePath)) + return null; + + await using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); + await using var gzip = new GZipStream(fileStream, CompressionMode.Decompress); + using var reader = new StreamReader(gzip, new UTF8Encoding(false)); + return await reader.ReadToEndAsync(); + } + + /// + /// 判断指定商品的原始数据文件是否存在。 + /// + public bool Exists(long goodsId) + { + return File.Exists(GetFilePath(goodsId)); + } + + private string GetFilePath(long goodsId) + { + var subDir = (goodsId % 100).ToString(); + return Path.Combine(_rootPath, subDir, goodsId.ToString()); + } + public async Task ReadAsync(string filePath) + { + await using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); + await using var gzip = new GZipStream(fileStream, CompressionMode.Decompress); + using var reader = new StreamReader(gzip, new UTF8Encoding(false)); + return await reader.ReadToEndAsync(); + } + } +} \ No newline at end of file diff --git a/Models/CloudProduct/Startup.cs b/Models/CloudProduct/Startup.cs new file mode 100644 index 0000000..35aa28c --- /dev/null +++ b/Models/CloudProduct/Startup.cs @@ -0,0 +1,28 @@ +using MAX.Core.DependencyInjection; +using MAX.Core.Interfaces; +using MAX.Framework.Interfaces; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Store.Modules.CloudProduct +{ + public class Startup : IStartup, IModule + { + public int Priority => IntPriority.Normal; + + public void AddModule(IServiceCollection services) + { + services.AddControllers(); + } + + public void Initialize() + { + + } + + public void Register(IServiceRegistrar registrar) + { + + } + } +} diff --git a/Models/CloudProduct/Store.Modules.CloudProduct.csproj b/Models/CloudProduct/Store.Modules.CloudProduct.csproj new file mode 100644 index 0000000..e19e8eb --- /dev/null +++ b/Models/CloudProduct/Store.Modules.CloudProduct.csproj @@ -0,0 +1,21 @@ + + + + net10.0 + enable + enable + + + + + + + + + + + + + + + diff --git a/StoreHub/Controllers/AuthController.cs b/Store.API/Controllers/AuthController.cs similarity index 66% rename from StoreHub/Controllers/AuthController.cs rename to Store.API/Controllers/AuthController.cs index fc2db5d..73a8bfc 100644 --- a/StoreHub/Controllers/AuthController.cs +++ b/Store.API/Controllers/AuthController.cs @@ -1,27 +1,25 @@ using Asp.Versioning; +using MAX.Framework.Dtos; +using MAX.Framework.Security; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using StoreHub.Framework.Commons; -using StoreHub.Framework.Security; -using StoreHub.Services; -using StoreHub.Services.Dtos; using System.Security.Claims; -namespace StoreHub.Controllers +namespace Store.Api.Controllers { [ApiController] [ApiVersion("1.0")] [Route("api/v{version:apiVersion}/[controller]/[action]")] public class AuthController : ControllerBase { - private readonly IUserService _userService; + //private readonly IUserService _userService; private readonly ITokenService _tokenService; private readonly ICurrentUser _currentUser; private readonly IOnlineUserStore _onlineUserStore; - public AuthController(IUserService userService, ITokenService tokenService, ICurrentUser currentUser, IOnlineUserStore onlineUserStore) + public AuthController( ITokenService tokenService, ICurrentUser currentUser, IOnlineUserStore onlineUserStore) { - _userService = userService; + //_userService = userService; _tokenService = tokenService; _currentUser = currentUser; _onlineUserStore = onlineUserStore; @@ -36,23 +34,23 @@ namespace StoreHub.Controllers { if (request is null || string.IsNullOrWhiteSpace(request.UserName) || string.IsNullOrWhiteSpace(request.Password)) { - return ApiResult.Error("用户名或密码不能为空", 400); + return ApiResult.Fail("用户名或密码不能为空", 400); } - UserDto? user = await _userService.LoginAsync(request.UserName, request.Password); - if (user is null) - { - return ApiResult.Error("用户名或密码错误", 401); - } + //UserDto? user = await _userService.LoginAsync(request.UserName, request.Password); + //if (user is null) + //{ + // return ApiResult.Error("用户名或密码错误", 401); + //} - var claims = new List - { - new(JwtClaimTypes.UserName, user.UserName), - new(JwtClaimTypes.NickName, user.NickName), - }; + //var claims = new List + //{ + // new(JwtClaimTypes.UserName, user.UserName), + // new(JwtClaimTypes.NickName, user.NickName), + //}; - TokenResult token = _tokenService.CreateToken(claims); - return ApiResult.Success(token); + //TokenResult token = _tokenService.CreateToken(claims); + return ApiResult.Success(111); } /// @@ -67,7 +65,7 @@ namespace StoreHub.Controllers _onlineUserStore.SignOut(username); } - return ApiResult.Success(null); + return ApiResult.Success(null); } /// @@ -77,7 +75,7 @@ namespace StoreHub.Controllers [Authorize] public ApiResult Me() { - return ApiResult.Success(new + return ApiResult.Success(new { UserName = _currentUser.UserName, NickName = _currentUser.NickName, @@ -92,7 +90,7 @@ namespace StoreHub.Controllers public ApiResult OnlineUsers() { IReadOnlyList users = _onlineUserStore.GetOnlineUsers(); - return ApiResult.Success(new + return ApiResult.Success(new { Count = users.Count, Users = users, diff --git a/StoreHub/Controllers/UserController.cs b/Store.API/Controllers/UserController.cs similarity index 74% rename from StoreHub/Controllers/UserController.cs rename to Store.API/Controllers/UserController.cs index 25e6c16..4f11b90 100644 --- a/StoreHub/Controllers/UserController.cs +++ b/Store.API/Controllers/UserController.cs @@ -1,16 +1,15 @@ using Asp.Versioning; using Microsoft.AspNetCore.Mvc; -using StoreHub.Framework.Repositorys; +using Store.Framework.Repositorys; using System.ComponentModel; using System.Text.Json; using System.Text.Json.Serialization; -namespace StoreHub.Controllers +namespace Store.Api.Controllers { [ApiController] - [Description("测试温度")] - [ApiVersion("1.0")] - [Route("api/v{version:apiVersion}/[controller]")] + [ApiVersionNeutral] + [Route("wxxd_user/[action]")] public class UserController : ControllerBase { private readonly IUserRepository _userRepository; diff --git a/StoreHub/Controllers/WeatherForecastController.cs b/Store.API/Controllers/WeatherForecastController.cs similarity index 83% rename from StoreHub/Controllers/WeatherForecastController.cs rename to Store.API/Controllers/WeatherForecastController.cs index 604387d..872bf92 100644 --- a/StoreHub/Controllers/WeatherForecastController.cs +++ b/Store.API/Controllers/WeatherForecastController.cs @@ -1,9 +1,10 @@ using Asp.Versioning; +using MAX.Framework.Dtos; using Microsoft.AspNetCore.Mvc; -using StoreHub.Framework.Commons; +using Store.Api; using System.ComponentModel; -namespace StoreHub.Controllers +namespace Store.Api.Controllers { [ApiController] [Description("测试温度")] @@ -17,7 +18,7 @@ namespace StoreHub.Controllers ]; [HttpGet(Name = "GetWeatherForecast")] - public ApiResult> Get() + public IEnumerable Get() { IEnumerable data = Enumerable.Range(1, 5).Select(index => new WeatherForecast { @@ -27,7 +28,7 @@ namespace StoreHub.Controllers }) .ToArray(); - return ApiResult.Success(data); + return data; } } } diff --git a/StoreHub/Controllers/v2/WeatherForecastController.cs b/Store.API/Controllers/v2/WeatherForecastController.cs similarity index 85% rename from StoreHub/Controllers/v2/WeatherForecastController.cs rename to Store.API/Controllers/v2/WeatherForecastController.cs index bd97d1b..af8af7c 100644 --- a/StoreHub/Controllers/v2/WeatherForecastController.cs +++ b/Store.API/Controllers/v2/WeatherForecastController.cs @@ -1,8 +1,9 @@ using Asp.Versioning; +using MAX.Framework.Dtos; using Microsoft.AspNetCore.Mvc; -using StoreHub.Framework.Commons; +using Store.Api; -namespace StoreHub.Controllers.v2 +namespace Store.Api.Controllers.v2 { [ApiController] [ApiVersion("2.0")] @@ -26,7 +27,7 @@ namespace StoreHub.Controllers.v2 }) .ToArray(); - return ApiResult.Success(data); + return ApiResult>.Success(data); } } } diff --git a/StoreHub/Program.cs b/Store.API/Program.cs similarity index 90% rename from StoreHub/Program.cs rename to Store.API/Program.cs index ee2c58c..42f37d1 100644 --- a/StoreHub/Program.cs +++ b/Store.API/Program.cs @@ -1,6 +1,6 @@ using Asp.Versioning; +using MAX.Framework.DependencyInjection; using Scalar.AspNetCore; -using StoreHub.Framework.DependencyInjection; public partial class Program { @@ -38,13 +38,11 @@ public partial class Program #endregion - - var app = builder.Build(); // 初始化 KJD 迁移的静态提供者(MysqlUtils 连接提供 / 业务配置读取) - StoreHub.KJDWebService.Commons.AdoProvider.SetServiceProvider(app.Services); - StoreHub.KJDWebService.Commons.KJDConfig.SetConfiguration(app.Configuration); + //StoreHub.KJDWebService.Commons.AdoProvider.SetServiceProvider(app.Services); + //StoreHub.KJDWebService.Commons.KJDConfig.SetConfiguration(app.Configuration); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) diff --git a/StoreHub/Properties/launchSettings.json b/Store.API/Properties/launchSettings.json similarity index 91% rename from StoreHub/Properties/launchSettings.json rename to Store.API/Properties/launchSettings.json index 7f2307f..078e74e 100644 --- a/StoreHub/Properties/launchSettings.json +++ b/Store.API/Properties/launchSettings.json @@ -6,7 +6,7 @@ "dotnetRunMessages": true, "launchBrowser": true, "launchUrl": "scalar/v1", - "applicationUrl": "http://localhost:5295", + "applicationUrl": "http://localhost:9000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } @@ -16,7 +16,7 @@ "dotnetRunMessages": true, "launchBrowser": true, "launchUrl": "scalar/v1", - "applicationUrl": "https://localhost:7241;http://localhost:5295", + "applicationUrl": "https://localhost:7241;http://localhost:9000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/StoreHub/StoreHub.csproj b/Store.API/Store.Api.csproj similarity index 53% rename from StoreHub/StoreHub.csproj rename to Store.API/Store.Api.csproj index 0c2b535..42eff63 100644 --- a/StoreHub/StoreHub.csproj +++ b/Store.API/Store.Api.csproj @@ -5,6 +5,13 @@ enable + + + + + + + @@ -12,9 +19,8 @@ - - - + + diff --git a/StoreHub/StoreHub.http b/Store.API/StoreHub.http similarity index 100% rename from StoreHub/StoreHub.http rename to Store.API/StoreHub.http diff --git a/StoreHub/WeatherForecast.cs b/Store.API/WeatherForecast.cs similarity index 91% rename from StoreHub/WeatherForecast.cs rename to Store.API/WeatherForecast.cs index ae119e2..542efad 100644 --- a/StoreHub/WeatherForecast.cs +++ b/Store.API/WeatherForecast.cs @@ -1,4 +1,4 @@ -namespace StoreHub +namespace Store.Api { public class WeatherForecast { diff --git a/StoreHub/WeatherForecastV2.cs b/Store.API/WeatherForecastV2.cs similarity index 92% rename from StoreHub/WeatherForecastV2.cs rename to Store.API/WeatherForecastV2.cs index 256e82b..058a9e1 100644 --- a/StoreHub/WeatherForecastV2.cs +++ b/Store.API/WeatherForecastV2.cs @@ -1,4 +1,4 @@ -namespace StoreHub +namespace Store.Api { public class WeatherForecastV2 { diff --git a/StoreHub/appsettings.Development.json b/Store.API/appsettings.Development.json similarity index 100% rename from StoreHub/appsettings.Development.json rename to Store.API/appsettings.Development.json diff --git a/StoreHub/appsettings.json b/Store.API/appsettings.json similarity index 55% rename from StoreHub/appsettings.json rename to Store.API/appsettings.json index 064798a..82d3a32 100644 --- a/StoreHub/appsettings.json +++ b/Store.API/appsettings.json @@ -6,12 +6,13 @@ } }, "ConnectionStrings": { - "DefaultConnection": "Server=localhost;Port=3306;Database=erp;Uid=root;Pwd=123456;", - "UserConnection": "Server=localhost;Port=3306;Database=wxxd;Uid=root;Pwd=123456;", - "KJDConnection": "Max Pool Size=512;Database='kjd';Data Source='nj-cynosdbmysql-grp-hl51pkfr.sql.tencentcdb.com';Port=27660;User Id='kjd';Password='Temukjd0625';Charset=utf8mb4;", - "KJDBAKConnection": "Max Pool Size=512;Database='kjd';Data Source='nj-cynosdbmysql-grp-9s7x16mt.sql.tencentcdb.com';Port=24049;User Id='kjdbak';Password='Temukjd0126';Charset=utf8mb4;", - "ConnKJD": "Max Pool Size=512;Database='kjd';Data Source='nj-cynosdbmysql-grp-hl51pkfr.sql.tencentcdb.com';Port=27660;User Id='kjd';Password='Temukjd0625';Charset=utf8mb4;", - "ConnKJDBAK": "Max Pool Size=512;Database='kjd';Data Source='nj-cynosdbmysql-grp-9s7x16mt.sql.tencentcdb.com';Port=24049;User Id='kjdbak';Password='Temukjd0126';Charset=utf8mb4;" + "DefaultConnection": "Server=192.168.0.200;Port=3306;Database=mysql;Uid=root;Pwd=123456;", + "KJDConnection": "Server=192.168.0.200;Port=3306;Database=mysql;Uid=root;Pwd=123456;", + "ConnKJD": "Server=192.168.0.200;Port=3306;Database=mysql;Uid=root;Pwd=123456;" + //"KJDConnection": "Max Pool Size=512;Database='kjd';Data Source='nj-cynosdbmysql-grp-hl51pkfr.sql.tencentcdb.com';Port=27660;User Id='kjd';Password='Temukjd0625';Charset=utf8mb4;", + //"KJDBAKConnection": "Max Pool Size=512;Database='kjd';Data Source='nj-cynosdbmysql-grp-9s7x16mt.sql.tencentcdb.com';Port=24049;User Id='kjdbak';Password='Temukjd0126';Charset=utf8mb4;", + //"ConnKJD": "Max Pool Size=512;Database='kjd';Data Source='nj-cynosdbmysql-grp-hl51pkfr.sql.tencentcdb.com';Port=27660;User Id='kjd';Password='Temukjd0625';Charset=utf8mb4;", + //"ConnKJDBAK": "Max Pool Size=512;Database='kjd';Data Source='nj-cynosdbmysql-grp-9s7x16mt.sql.tencentcdb.com';Port=24049;User Id='kjdbak';Password='Temukjd0126';Charset=utf8mb4;" }, "KJDConfig": { "UpdateVersion": "525", diff --git a/Store.Api.slnx b/Store.Api.slnx new file mode 100644 index 0000000..79df559 --- /dev/null +++ b/Store.Api.slnx @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/Store.Framework/Base/AppControllerBase.cs b/Store.Framework/Base/AppControllerBase.cs new file mode 100644 index 0000000..db1d403 --- /dev/null +++ b/Store.Framework/Base/AppControllerBase.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Mvc; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Store.Framework.Base +{ + [ApiController] + public class AppControllerBase : ControllerBase + { + } +} diff --git a/StoreHub.Framework/Models/Users.cs b/Store.Framework/Models/Users.cs similarity index 99% rename from StoreHub.Framework/Models/Users.cs rename to Store.Framework/Models/Users.cs index 8dfb42f..f1bc80c 100644 --- a/StoreHub.Framework/Models/Users.cs +++ b/Store.Framework/Models/Users.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using System.Text; -namespace StoreHub.Framework.Models +namespace Store.Framework.Models { /// /// 用户实体 diff --git a/StoreHub.Framework/Repositorys/SqlRepository.cs b/Store.Framework/Repositorys/SqlRepository.cs similarity index 90% rename from StoreHub.Framework/Repositorys/SqlRepository.cs rename to Store.Framework/Repositorys/SqlRepository.cs index b970a79..36c06d1 100644 --- a/StoreHub.Framework/Repositorys/SqlRepository.cs +++ b/Store.Framework/Repositorys/SqlRepository.cs @@ -1,12 +1,12 @@ using FreeSql; using MAX.Core.DependencyInjection; using Microsoft.Extensions.DependencyInjection; -using StoreHub.Framework.Models; +using Store.Framework.Models; using System; using System.Collections.Generic; using System.Text; -namespace StoreHub.Framework.Repositorys +namespace Store.Framework.Repositorys { public interface IUserRepository : IBaseRepository { diff --git a/StoreHub.Framework/Startup.cs b/Store.Framework/Startup.cs similarity index 58% rename from StoreHub.Framework/Startup.cs rename to Store.Framework/Startup.cs index 3525ac6..489c502 100644 --- a/StoreHub.Framework/Startup.cs +++ b/Store.Framework/Startup.cs @@ -1,11 +1,10 @@ using FreeSql; using MAX.Core.DependencyInjection; -using MAX.Core.Infrastructure; +using MAX.Core.Interfaces; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using StoreHub.Framework.Repositorys; -namespace StoreHub.Framework +namespace Store.Framework { public class Startup : IStartup { @@ -18,23 +17,23 @@ namespace StoreHub.Framework public void Register(IServiceRegistrar registrar) { - Func userFsqlFactory = r => - { - var config = r.GetRequiredService(); - var connection = config.GetConnectionString("UserConnection"); - IFreeSql fsql = new FreeSql.FreeSqlBuilder() - .UseConnectionString(FreeSql.DataType.MySql, connection) - .UseAdoConnectionPool(true) - .Build(); - return fsql; - }; - registrar.RegisterSingleton("UserDB", (s, k) => - { - return userFsqlFactory(s); - }); + //Func userFsqlFactory = r => + //{ + // var config = r.GetRequiredService(); + // var connection = config.GetConnectionString("UserConnection"); + // IFreeSql fsql = new FreeSql.FreeSqlBuilder() + // .UseConnectionString(FreeSql.DataType.MySql, connection) + // .UseAdoConnectionPool(true) + // .Build(); + // return fsql; + //}; + //registrar.RegisterSingleton("UserDB", (s, k) => + //{ + // return userFsqlFactory(s); + //}); + - Func KJDFsqlFactory = r => { var config = r.GetRequiredService(); diff --git a/Store.Framework/Store.Framework.csproj b/Store.Framework/Store.Framework.csproj new file mode 100644 index 0000000..161cbb5 --- /dev/null +++ b/Store.Framework/Store.Framework.csproj @@ -0,0 +1,20 @@ + + + + enable + + + + + + + + + + + + + + + + diff --git a/StoreHub.KJDWebService/Commons/BaseClass.cs b/Store.KJDWebService/Commons/BaseClass.cs similarity index 100% rename from StoreHub.KJDWebService/Commons/BaseClass.cs rename to Store.KJDWebService/Commons/BaseClass.cs diff --git a/StoreHub.KJDWebService/Commons/EncrypUtil.cs b/Store.KJDWebService/Commons/EncrypUtil.cs similarity index 100% rename from StoreHub.KJDWebService/Commons/EncrypUtil.cs rename to Store.KJDWebService/Commons/EncrypUtil.cs diff --git a/StoreHub.KJDWebService/Commons/Extension/Extension.Convert.cs b/Store.KJDWebService/Commons/Extension/Extension.Convert.cs similarity index 100% rename from StoreHub.KJDWebService/Commons/Extension/Extension.Convert.cs rename to Store.KJDWebService/Commons/Extension/Extension.Convert.cs diff --git a/StoreHub.KJDWebService/Commons/Extension/Extension.Enum.cs b/Store.KJDWebService/Commons/Extension/Extension.Enum.cs similarity index 100% rename from StoreHub.KJDWebService/Commons/Extension/Extension.Enum.cs rename to Store.KJDWebService/Commons/Extension/Extension.Enum.cs diff --git a/StoreHub.KJDWebService/Commons/Extension/Extension.Exception.cs b/Store.KJDWebService/Commons/Extension/Extension.Exception.cs similarity index 100% rename from StoreHub.KJDWebService/Commons/Extension/Extension.Exception.cs rename to Store.KJDWebService/Commons/Extension/Extension.Exception.cs diff --git a/StoreHub.KJDWebService/Commons/Extension/Extension.Linq.cs b/Store.KJDWebService/Commons/Extension/Extension.Linq.cs similarity index 100% rename from StoreHub.KJDWebService/Commons/Extension/Extension.Linq.cs rename to Store.KJDWebService/Commons/Extension/Extension.Linq.cs diff --git a/StoreHub.KJDWebService/Commons/Extension/Extension.Validate.cs b/Store.KJDWebService/Commons/Extension/Extension.Validate.cs similarity index 100% rename from StoreHub.KJDWebService/Commons/Extension/Extension.Validate.cs rename to Store.KJDWebService/Commons/Extension/Extension.Validate.cs diff --git a/StoreHub.KJDWebService/Commons/Helper/HtmlHelper.cs b/Store.KJDWebService/Commons/Helper/HtmlHelper.cs similarity index 100% rename from StoreHub.KJDWebService/Commons/Helper/HtmlHelper.cs rename to Store.KJDWebService/Commons/Helper/HtmlHelper.cs diff --git a/StoreHub.KJDWebService/Commons/Helper/JsonHelper.cs b/Store.KJDWebService/Commons/Helper/JsonHelper.cs similarity index 100% rename from StoreHub.KJDWebService/Commons/Helper/JsonHelper.cs rename to Store.KJDWebService/Commons/Helper/JsonHelper.cs diff --git a/StoreHub.KJDWebService/Commons/Helper/ReflectionHelper.cs b/Store.KJDWebService/Commons/Helper/ReflectionHelper.cs similarity index 100% rename from StoreHub.KJDWebService/Commons/Helper/ReflectionHelper.cs rename to Store.KJDWebService/Commons/Helper/ReflectionHelper.cs diff --git a/StoreHub.KJDWebService/Commons/Helper/RegexHelper.cs b/Store.KJDWebService/Commons/Helper/RegexHelper.cs similarity index 100% rename from StoreHub.KJDWebService/Commons/Helper/RegexHelper.cs rename to Store.KJDWebService/Commons/Helper/RegexHelper.cs diff --git a/StoreHub.KJDWebService/Commons/HtmlUtils.cs b/Store.KJDWebService/Commons/HtmlUtils.cs similarity index 100% rename from StoreHub.KJDWebService/Commons/HtmlUtils.cs rename to Store.KJDWebService/Commons/HtmlUtils.cs diff --git a/StoreHub.KJDWebService/Commons/HttpUtils.cs b/Store.KJDWebService/Commons/HttpUtils.cs similarity index 100% rename from StoreHub.KJDWebService/Commons/HttpUtils.cs rename to Store.KJDWebService/Commons/HttpUtils.cs diff --git a/StoreHub.KJDWebService/Commons/JsonUtils.cs b/Store.KJDWebService/Commons/JsonUtils.cs similarity index 100% rename from StoreHub.KJDWebService/Commons/JsonUtils.cs rename to Store.KJDWebService/Commons/JsonUtils.cs diff --git a/StoreHub.KJDWebService/Commons/KJDConfig.cs b/Store.KJDWebService/Commons/KJDConfig.cs similarity index 77% rename from StoreHub.KJDWebService/Commons/KJDConfig.cs rename to Store.KJDWebService/Commons/KJDConfig.cs index 477a3a7..e7120f3 100644 --- a/StoreHub.KJDWebService/Commons/KJDConfig.cs +++ b/Store.KJDWebService/Commons/KJDConfig.cs @@ -15,7 +15,16 @@ namespace StoreHub.KJDWebService.Commons { _configuration = configuration; } - + public static string GetConnection(string key) + { + if (_configuration == null) + return null; + var v = _configuration[$"ConnectionStrings:{key}"]; + if (!string.IsNullOrEmpty(v)) + return v; + v = _configuration.GetConnectionString(key); + return v; + } /// 读取配置值(对应旧 connectionStrings["key"].ConnectionString) public static string Get(string key) { diff --git a/Store.KJDWebService/Commons/MysqlUtils.cs b/Store.KJDWebService/Commons/MysqlUtils.cs new file mode 100644 index 0000000..8f8a61c --- /dev/null +++ b/Store.KJDWebService/Commons/MysqlUtils.cs @@ -0,0 +1,835 @@ +using FreeSql; +using Microsoft.Extensions.DependencyInjection; +using MySql.Data.MySqlClient; +using System.Data; +using System.Security.Cryptography; + + +namespace StoreHub.KJDWebService.Commons +{ + public class MysqlUtils + { + public string Message = ""; + + /// + /// 数据库连接字符串 + /// + private string ConnString = ""; + + /// + /// 数据库连接 + /// + private MySqlConnection Conn; + + /// + /// 数据库读取对象 + /// + private MySqlDataReader reader; + + /// + /// 数据库执行对象 + /// + private MySqlCommand cmd = new MySqlCommand(); + + /// + /// 错误信息 + /// + //public static string ErrorString = ""; + + /// + /// 超时(秒) + /// + public int TimeOut = 120; + + /// + /// 链接状态 + /// + public bool Connected + { + get { return Conn != null && Conn.State == ConnectionState.Open; } + } + + /// + /// 是否正在执行 + /// + public bool IsRunning = false; + + public bool IsUseTrans { get; set; } = false; + public MySqlTransaction Transaction { get; set; } = null; + + /// + /// 初始化数据库链接 + /// + /// 数据库链接 + public MysqlUtils(string connString, bool isUseTrans = false) + { + this.IsUseTrans = isUseTrans; + ConnString = KJDConfig.GetConnection(connString); + ConnTo(); + } + + /// + /// 去掉SQL中的特殊字符 + /// + /// 字符串 + /// + public string ReplaceSql(string value) + { + return ReplaceSqlSpecial(value); + } + + /// + /// 反转字符串,用于mysql反向索引 + /// + /// + /// + public static string ReverseString(string temp) + { + if (string.IsNullOrEmpty(temp)) + return null; + char[] l = temp.ToCharArray(); + Array.Reverse(l); + return new string(l); + } + + /// + /// 去掉SQL中的特殊字符 + /// + /// 字符串 + /// + public static string ReplaceSqlSpecial(string value) + { + if (string.IsNullOrEmpty(value)) + return null; + value = value.Replace("\\", "\\\\"); + value = value.Replace("'", "''"); + //value = value.Replace("\"", "\\\""); + return value; + } + + ///// + ///// 开放一个Conn获取路径,以便外部使用事务提交 + ///// + //public MySqlConnection GetConn() + //{ + // //Transaction = Conn.BeginTransaction(); + // return Conn; + //} + + /// + /// 插入数据 + /// + /// + /// + /// + public int Insert(string table, List keys) + { + string temp = "", tempx = ""; + for (int i = 0; i < keys.Count; i++) + { + temp += ",`" + keys[i].Key + "`"; + tempx += ",'" + keys[i].Value + "'"; + } + temp = temp.Substring(1); + tempx = tempx.Substring(1); + string sql = "insert into " + table + " (" + temp + ") values (" + tempx + ") "; + return (int)ExecuteInsertId(sql); + } + + /// + /// 返回执行表 + /// + /// + /// + public DataTable ExecuteDataTable(string SqlString) + { + return ExecuteDataTable(SqlString, null); + } + + /// + /// 执行sql返回DataTable + /// + /// SQL语句 + /// Sql参数 + /// DataTable + public DataTable ExecuteDataTable(string SqlString, MySqlParameter[] parms) + { + SleepOrGo(); + bool frist = true; + Retry: + if (Conn == null || Conn.State != ConnectionState.Open) + ConnTo(); + try + { + CmdIni(); + cmd.CommandText = SqlString; + if (parms != null) + foreach (MySqlParameter pram in parms) + cmd.Parameters.Add(pram); + DataSet ds = new DataSet(); + MySqlDataAdapter adapter = new MySqlDataAdapter(); + adapter.SelectCommand = cmd; + adapter.Fill(ds); + return ds.Tables[0]; + //DataTable dt = new DataTable(); + //try + //{ + // reader = cmd.ExecuteReader(); + // dt.Load(reader); + //} + //catch + //{ + // reader = cmd.ExecuteReader(); + // dt = Read(ref reader); + //} + //return dt; + } + catch (Exception e) + { + if (frist) + { + frist = false; + ErrorDoing(e.Message); + goto Retry; + } + AddError(e.Message, SqlString); + return null; + } + finally + { + CloseReader(); + IsRunning = false; + } + } + + /// + /// 针对不同错误,执行不同的修正 + /// + /// + private void ErrorDoing(string message) + { + Message = message; + + if (string.IsNullOrEmpty(message)) + return; + if (message.Contains("Incorrect string value")) + return; + if (message.Contains("Connection must be valid and open")) + ConnTo(); + if (message.Contains("There is already an open DataReader")) + { + Thread.Sleep(500); + ConnTo(); + } + if (message.Contains("Fatal error encountered during command execution.")) + ConnTo(); + if (message.Contains(" StreamWriter ") || message.Contains("未将对象引用设置到对象的实例") || message.Contains("未将对象")) + ConnTo(); + CloseReader(); + Random ra = new Random(Guid.NewGuid().GetHashCode()); + Thread.Sleep(ra.Next(200, 2000)); + } + + /// + /// 关闭读取线程 + /// + private void CloseReader() + { + try + { + if (reader != null && !reader.IsClosed) + reader.Close(); + cmd.Dispose(); + //reader = null; + } + catch + { + } + } + + /// + /// 返回执行行 + /// + /// + /// + public DataRow ExecuteDataTableRow(string SqlString) + { + return ExecuteDataTableRow(SqlString, null); + } + + /// + /// 返回第一行 + /// + /// + /// + public DataRow ExecuteDataTableRow(string SqlString, MySqlParameter[] parms) + { + SleepOrGo(); + bool frist = true; + Retry: + if (Conn == null || Conn.State != ConnectionState.Open) + ConnTo(); + try + { + CmdIni(); + cmd.CommandText = SqlString; + if (parms != null) + foreach (MySqlParameter pram in parms) + cmd.Parameters.Add(pram); + DataSet ds = new DataSet(); + MySqlDataAdapter adapter = new MySqlDataAdapter(); + adapter.SelectCommand = cmd; + adapter.Fill(ds); + DataTable dt = ds.Tables[0]; + if (dt.Rows.Count > 0) + return dt.Rows[0]; + } + catch (Exception e) + { + if (frist) + { + frist = false; + ErrorDoing(e.Message); + goto Retry; + } + AddError(e.Message, SqlString); + } + finally + { + CloseReader(); + IsRunning = false; + } + return null; + } + + /// + /// 返回第一个执行结果 + /// + /// + /// + public int ExecuteRowCount(string SqlString) + { + string f = ExecuteFirst(SqlString, null); + int c; + int.TryParse(f, out c); + return c; + } + + public int ExecuteRowCount(string SqlString, MySqlParameter[] parms) + { + string f = ExecuteFirst(SqlString, parms); + int c; + int.TryParse(f, out c); + return c; + } + + /// + /// 返回第一个执行结果 + /// + /// + /// + public string ExecuteFirst(string SqlString) + { + return ExecuteFirst(SqlString, null); + } + + /// + /// 返回第一个执行结果 + /// + /// + /// + public int ExecuteFirstInt(string SqlString, MySqlParameter[] parms = null) + { + string id = ExecuteFirst(SqlString, parms); + int i; + int.TryParse(id, out i); + return i; + } + + + /// + /// 返回第一个执行结果 + /// + /// + /// + public decimal ExecuteFirstDecimal(string SqlString, MySqlParameter[] parms = null) + { + string id = ExecuteFirst(SqlString, parms); + decimal i; + decimal.TryParse(id, out i); + return i; + } + + /// + /// 返回第一个值 + /// + /// + /// + public string ExecuteFirst(string SqlString, MySqlParameter[] parms) + { + SleepOrGo(); + bool frist = true; + Retry: + if (Conn == null || Conn.State != ConnectionState.Open) + ConnTo(); + try + { + CmdIni(); + cmd.CommandText = SqlString; + if (parms != null) + foreach (MySqlParameter pram in parms) + cmd.Parameters.Add(pram); + reader = cmd.ExecuteReader(); + if (reader.Read()) + return reader[0].ToString(); + return ""; + } + catch (Exception e) + { + if (e.Message.Contains("Illegal mix of collations")) + return null; + if (frist) + { + frist = false; + ErrorDoing(e.Message); + goto Retry; + } + AddError(e.Message, SqlString); + } + finally + { + CloseReader(); + IsRunning = false; + } + return null; + } + + /// + /// 返回更改、插入或删除的行数 + /// + /// + /// + /// + public int ExecuteAffectedRows(string SqlString) + { + return ExecuteAffectedRows(SqlString, null); + } + /// + /// 返回更改、插入或删除的行数 + /// + /// + /// + /// + public int ExecuteAffectedRows(string SqlString, MySqlParameter[] parms) + { + SleepOrGo(); + bool frist = true; + Retry: + if (Conn == null || Conn.State != ConnectionState.Open) + ConnTo(); + try + { + CmdIni(); + cmd.CommandText = SqlString; + if (parms != null) + foreach (MySqlParameter pram in parms) + cmd.Parameters.Add(pram); + reader = cmd.ExecuteReader(); + if (reader.Read()) + return reader.RecordsAffected; + return reader.RecordsAffected; + } + catch (Exception e) + { + if (e.Message.Contains("Illegal mix of collations")) + return 0; + if (frist) + { + frist = false; + ErrorDoing(e.Message); + goto Retry; + } + AddError(e.Message, SqlString); + } + finally + { + CloseReader(); + IsRunning = false; + } + return 0; + } + + /// + /// 返回插入ID + /// + /// + /// + public long ExecuteInsertId(string SqlString) + { + return ExecuteInsertId(SqlString, null); + } + + /// + /// 返回第一个值 + /// + /// + /// + public long ExecuteInsertId(string SqlString, MySqlParameter[] parms) + { + SleepOrGo(); + bool frist = true; + Retry: + if (Conn == null || Conn.State != ConnectionState.Open) + ConnTo(); + try + { + CmdIni(); + cmd.CommandText = SqlString; + if (parms != null) + foreach (MySqlParameter pram in parms) + cmd.Parameters.Add(pram); + cmd.ExecuteNonQuery(); + long id = cmd.LastInsertedId; + return id; + } + catch (Exception e) + { + if (e.Message.Contains(" for key 'PRIMARY'") && e.Message.Contains("Duplicate entry")) + return 0; + if (frist) + { + frist = false; + ErrorDoing(e.Message); + goto Retry; + } + AddError(e.Message, SqlString); + } + finally + { + CloseReader(); + IsRunning = false; + } + return 0; + } + + /// + /// 无返回 + /// + /// + /// + public bool ExecuteNonQuery(string SqlString) + { + return ExecuteNonQuery(SqlString, null); + } + + /// + /// 只保留mysql可识别的字符串 + /// + /// + public string JustUtf8Words(string text) + { + JustUtf8Words(ref text); + return text; + } + + /// + /// 只保留mysql可识别的字符串 + /// + /// + public void JustUtf8Words(ref string text) + { + if (string.IsNullOrEmpty(text)) + return; + var cs = text.ToCharArray(); + List cc = new List(); + foreach (char c in cs) + { + if (c > 7814 && c < 8416 || c > 57096 && c < 59501 || c > 63707 && c < 64309 || c > 65505 || c < 0) + { + if (cc.Contains(c)) + continue; + text = text.Replace(c.ToString(), ""); + cc.Add(c); + } + } + } + + /// + /// 执行无返回SQL语句 + /// + /// SQL语句 + /// Sql参数 + ///是否执行成功 + public bool ExecuteNonQuery(string SqlString, MySqlParameter[] parms) + { + SleepOrGo(); + bool frist = true; + Retry: + if (Conn == null || Conn.State != ConnectionState.Open) + ConnTo(); + try + { + CmdIni(); + cmd.CommandText = SqlString; + if (parms != null) + foreach (MySqlParameter pram in parms) + cmd.Parameters.Add(pram); + cmd.ExecuteNonQuery(); + return true; + } + catch (Exception e) + { + if (e.Message.Contains("Illegal mix of collations") || e.Message.Contains("Incorrect string value") + || e.Message.Contains(" for key 'PRIMARY'") && e.Message.Contains("Duplicate entry")) + return false; + + if (frist) + { + frist = false; + ErrorDoing(e.Message); + goto Retry; + } + AddError(e.Message, SqlString); + return false; + } + finally + { + CloseReader(); + IsRunning = false; + } + } + + /// + /// 判断是否存在 + /// + /// + /// + public bool ExecuteExists(string SqlString) + { + return ExecuteExists(SqlString, null); + } + + /// + /// 查询是否存在 + /// + /// SQL语句 + /// SQL参数 + /// 是否存在 + public bool ExecuteExists(string SqlString, MySqlParameter[] parms) + { + SleepOrGo(); + bool frist = true; + Retry: + if (Conn == null || Conn.State != ConnectionState.Open) + ConnTo(); + try + { + + CmdIni(); + cmd.CommandText = SqlString; + if (parms != null) + foreach (MySqlParameter pram in parms) + cmd.Parameters.Add(pram); + reader = cmd.ExecuteReader(); + if (reader.Read()) + return true; + return false; + } + catch (Exception e) + { + if (frist) + { + frist = false; + ErrorDoing(e.Message); + goto Retry; + } + AddError(e.Message, SqlString); + return false; + } + finally + { + CloseReader(); + IsRunning = false; + } + } + + /// + /// 停止还是前进 + /// + private void SleepOrGo() + { + int i = 0; + while (IsRunning && i < 300) + { + Thread.Sleep(100); + i++; + } + IsRunning = true; + } + + /// + /// 初始化CMD + /// + private void CmdIni() + { + if (reader != null && !reader.IsClosed) + reader.Close(); + if (cmd != null) + cmd.Dispose(); + cmd = new MySqlCommand(); + cmd.Connection = Conn; + cmd.CommandType = CommandType.Text; + cmd.CommandTimeout = TimeOut; + + if (Transaction != null) + cmd.Transaction = Transaction; + } + + /// + /// 连接数据库 + /// + private void ConnTo() + { + Close(); + try + { + Conn = new MySqlConnection(ConnString); + Conn.Open(); + + if (IsUseTrans) + this.Transaction = Conn.BeginTransaction(); + } + catch (Exception e) + { + AddError(e.Message, ConnString); + } + } + + /// + /// 错误信息 + /// + /// + /// + private void AddError(string message, string sql) + { + if (string.IsNullOrEmpty(message) || message.Contains("Illegal mix of collations")) + return; + ErrorDoing(message); + string errStr = "数据库连接错误:" + message + "\r\nSQL语句:" + sql; + try + { + if (!Directory.Exists("Log")) + { + Directory.CreateDirectory("Log"); + } + File.AppendAllText("Log\\log-" + DateTime.Now.ToString("yyyyMMdd") + ".txt", errStr); + } + catch (Exception ex) + { + } + } + + public void Commit() + { + if (Transaction != null) + Transaction.Commit(); + } + + public void RollBack() + { + if (Transaction != null) + Transaction.Rollback(); + } + + /// + /// 关闭数据库链接 + /// + public void Close() + { + IsRunning = false; + try + { + if (Conn != null && Conn.State == ConnectionState.Open) + { + Conn.Close(); + } + Conn = null; + + try + { + if (reader != null && !reader.IsClosed) + reader.Close(); + } + catch + { + } + reader = null; + + try + { + if (Transaction != null) + Transaction.Dispose(); + } + catch + { + } + Transaction = null; + } + catch + { + } + } + } + + public class MysqlKeyValues + { + public List List = new List(); + } + + public class MysqlKeyValue + { + public MysqlKeyValue(string key, object value) + { + Key = key; + Value = value == null ? "" : MysqlUtils.ReplaceSqlSpecial(value.ToString()); + } + + public string Key = ""; + public string Value; + } + + + /// + /// 根据连接串 key 提供 FreeSql IAdo(由框架 DI 注册的 keyed IFreeSql 提供) + /// + public static class AdoProvider + { + private static IServiceProvider _serviceProvider; + + public static void SetServiceProvider(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public static IAdo Get(string connKey) + { + if (string.IsNullOrEmpty(connKey)) + connKey = "ConnKJD"; + string key = connKey switch + { + "ConnKJD" => "KJDDB", + "ConnKJDBAK" => "KJDBAKDB", + _ => "KJDDB", + }; + var fsql = _serviceProvider?.GetKeyedService(key); + if (fsql == null) + { + var fsql2 = _serviceProvider?.GetService(); + if (fsql2 != null) + return fsql2.Ado; + throw new InvalidOperationException("IFreeSql not registered for key " + key); + } + return fsql.Ado; + } + } +} \ No newline at end of file diff --git a/StoreHub.KJDWebService/Commons/RandomUtils.cs b/Store.KJDWebService/Commons/RandomUtils.cs similarity index 100% rename from StoreHub.KJDWebService/Commons/RandomUtils.cs rename to Store.KJDWebService/Commons/RandomUtils.cs diff --git a/StoreHub.KJDWebService/Commons/RegexUtils.cs b/Store.KJDWebService/Commons/RegexUtils.cs similarity index 100% rename from StoreHub.KJDWebService/Commons/RegexUtils.cs rename to Store.KJDWebService/Commons/RegexUtils.cs diff --git a/StoreHub.KJDWebService/Commons/TimeUtils.cs b/Store.KJDWebService/Commons/TimeUtils.cs similarity index 100% rename from StoreHub.KJDWebService/Commons/TimeUtils.cs rename to Store.KJDWebService/Commons/TimeUtils.cs diff --git a/StoreHub.KJDWebService/Controllers/BXXPriceController.cs b/Store.KJDWebService/Controllers/BXXPriceController.cs similarity index 100% rename from StoreHub.KJDWebService/Controllers/BXXPriceController.cs rename to Store.KJDWebService/Controllers/BXXPriceController.cs diff --git a/StoreHub.KJDWebService/Controllers/BXXProductController.cs b/Store.KJDWebService/Controllers/BXXProductController.cs similarity index 100% rename from StoreHub.KJDWebService/Controllers/BXXProductController.cs rename to Store.KJDWebService/Controllers/BXXProductController.cs diff --git a/StoreHub.KJDWebService/Controllers/BXXProductThirdController.cs b/Store.KJDWebService/Controllers/BXXProductThirdController.cs similarity index 100% rename from StoreHub.KJDWebService/Controllers/BXXProductThirdController.cs rename to Store.KJDWebService/Controllers/BXXProductThirdController.cs diff --git a/StoreHub.KJDWebService/Controllers/BXXShopController.cs b/Store.KJDWebService/Controllers/BXXShopController.cs similarity index 100% rename from StoreHub.KJDWebService/Controllers/BXXShopController.cs rename to Store.KJDWebService/Controllers/BXXShopController.cs diff --git a/StoreHub.KJDWebService/Controllers/BXXUserController.cs b/Store.KJDWebService/Controllers/BXXUserController.cs similarity index 100% rename from StoreHub.KJDWebService/Controllers/BXXUserController.cs rename to Store.KJDWebService/Controllers/BXXUserController.cs diff --git a/StoreHub.KJDWebService/Controllers/BaseApiController.cs b/Store.KJDWebService/Controllers/BaseApiController.cs similarity index 100% rename from StoreHub.KJDWebService/Controllers/BaseApiController.cs rename to Store.KJDWebService/Controllers/BaseApiController.cs diff --git a/StoreHub.KJDWebService/Controllers/DYShopController.cs b/Store.KJDWebService/Controllers/DYShopController.cs similarity index 100% rename from StoreHub.KJDWebService/Controllers/DYShopController.cs rename to Store.KJDWebService/Controllers/DYShopController.cs diff --git a/StoreHub.KJDWebService/Controllers/DYUserController.cs b/Store.KJDWebService/Controllers/DYUserController.cs similarity index 100% rename from StoreHub.KJDWebService/Controllers/DYUserController.cs rename to Store.KJDWebService/Controllers/DYUserController.cs diff --git a/StoreHub.KJDWebService/Controllers/Login.cs b/Store.KJDWebService/Controllers/Login.cs similarity index 93% rename from StoreHub.KJDWebService/Controllers/Login.cs rename to Store.KJDWebService/Controllers/Login.cs index da24103..982db41 100644 --- a/StoreHub.KJDWebService/Controllers/Login.cs +++ b/Store.KJDWebService/Controllers/Login.cs @@ -1,6 +1,6 @@ using Asp.Versioning; using Microsoft.AspNetCore.Mvc; -using StoreHub.Framework.Commons; +using Store.Framework.Commons; using StoreHub.KJDWebService.Services; namespace StoreHub.KJDWebService.Controllers diff --git a/StoreHub.KJDWebService/Controllers/OrderController.cs b/Store.KJDWebService/Controllers/OrderController.cs similarity index 100% rename from StoreHub.KJDWebService/Controllers/OrderController.cs rename to Store.KJDWebService/Controllers/OrderController.cs diff --git a/StoreHub.KJDWebService/Controllers/PriceController.cs b/Store.KJDWebService/Controllers/PriceController.cs similarity index 100% rename from StoreHub.KJDWebService/Controllers/PriceController.cs rename to Store.KJDWebService/Controllers/PriceController.cs diff --git a/StoreHub.KJDWebService/Controllers/ProductController.cs b/Store.KJDWebService/Controllers/ProductController.cs similarity index 100% rename from StoreHub.KJDWebService/Controllers/ProductController.cs rename to Store.KJDWebService/Controllers/ProductController.cs diff --git a/StoreHub.KJDWebService/Controllers/ProductThirdController.cs b/Store.KJDWebService/Controllers/ProductThirdController.cs similarity index 100% rename from StoreHub.KJDWebService/Controllers/ProductThirdController.cs rename to Store.KJDWebService/Controllers/ProductThirdController.cs diff --git a/StoreHub.KJDWebService/Controllers/ShopController.cs b/Store.KJDWebService/Controllers/ShopController.cs similarity index 100% rename from StoreHub.KJDWebService/Controllers/ShopController.cs rename to Store.KJDWebService/Controllers/ShopController.cs diff --git a/StoreHub.KJDWebService/Controllers/ToolController.cs b/Store.KJDWebService/Controllers/ToolController.cs similarity index 100% rename from StoreHub.KJDWebService/Controllers/ToolController.cs rename to Store.KJDWebService/Controllers/ToolController.cs diff --git a/StoreHub.KJDWebService/Controllers/UpdateController.cs b/Store.KJDWebService/Controllers/UpdateController.cs similarity index 100% rename from StoreHub.KJDWebService/Controllers/UpdateController.cs rename to Store.KJDWebService/Controllers/UpdateController.cs diff --git a/StoreHub.KJDWebService/Controllers/UserController.cs b/Store.KJDWebService/Controllers/UserController.cs similarity index 100% rename from StoreHub.KJDWebService/Controllers/UserController.cs rename to Store.KJDWebService/Controllers/UserController.cs diff --git a/StoreHub.KJDWebService/Controllers/WXOrderController.cs b/Store.KJDWebService/Controllers/WXOrderController.cs similarity index 100% rename from StoreHub.KJDWebService/Controllers/WXOrderController.cs rename to Store.KJDWebService/Controllers/WXOrderController.cs diff --git a/StoreHub.KJDWebService/Controllers/WXProductController.cs b/Store.KJDWebService/Controllers/WXProductController.cs similarity index 100% rename from StoreHub.KJDWebService/Controllers/WXProductController.cs rename to Store.KJDWebService/Controllers/WXProductController.cs diff --git a/StoreHub.KJDWebService/Controllers/WXShopController.cs b/Store.KJDWebService/Controllers/WXShopController.cs similarity index 100% rename from StoreHub.KJDWebService/Controllers/WXShopController.cs rename to Store.KJDWebService/Controllers/WXShopController.cs diff --git a/StoreHub.KJDWebService/Controllers/WXShopRevenueStatisticsController.cs b/Store.KJDWebService/Controllers/WXShopRevenueStatisticsController.cs similarity index 100% rename from StoreHub.KJDWebService/Controllers/WXShopRevenueStatisticsController.cs rename to Store.KJDWebService/Controllers/WXShopRevenueStatisticsController.cs diff --git a/StoreHub.KJDWebService/Controllers/WXUserController.cs b/Store.KJDWebService/Controllers/WXUserController.cs similarity index 100% rename from StoreHub.KJDWebService/Controllers/WXUserController.cs rename to Store.KJDWebService/Controllers/WXUserController.cs diff --git a/StoreHub.KJDWebService/Models/CommentParam.cs b/Store.KJDWebService/Models/CommentParam.cs similarity index 100% rename from StoreHub.KJDWebService/Models/CommentParam.cs rename to Store.KJDWebService/Models/CommentParam.cs diff --git a/StoreHub.KJDWebService/Models/DBPriceResultParam.cs b/Store.KJDWebService/Models/DBPriceResultParam.cs similarity index 100% rename from StoreHub.KJDWebService/Models/DBPriceResultParam.cs rename to Store.KJDWebService/Models/DBPriceResultParam.cs diff --git a/StoreHub.KJDWebService/Models/DBProdDbIdMapProductIdResultParam.cs b/Store.KJDWebService/Models/DBProdDbIdMapProductIdResultParam.cs similarity index 100% rename from StoreHub.KJDWebService/Models/DBProdDbIdMapProductIdResultParam.cs rename to Store.KJDWebService/Models/DBProdDbIdMapProductIdResultParam.cs diff --git a/StoreHub.KJDWebService/Models/DBProductResultParam.cs b/Store.KJDWebService/Models/DBProductResultParam.cs similarity index 100% rename from StoreHub.KJDWebService/Models/DBProductResultParam.cs rename to Store.KJDWebService/Models/DBProductResultParam.cs diff --git a/StoreHub.KJDWebService/Models/ErpModel/ErpMerchantInfo.cs b/Store.KJDWebService/Models/ErpModel/ErpMerchantInfo.cs similarity index 100% rename from StoreHub.KJDWebService/Models/ErpModel/ErpMerchantInfo.cs rename to Store.KJDWebService/Models/ErpModel/ErpMerchantInfo.cs diff --git a/StoreHub.KJDWebService/Models/JSTDownProdSkuInfo.cs b/Store.KJDWebService/Models/JSTDownProdSkuInfo.cs similarity index 100% rename from StoreHub.KJDWebService/Models/JSTDownProdSkuInfo.cs rename to Store.KJDWebService/Models/JSTDownProdSkuInfo.cs diff --git a/StoreHub.KJDWebService/Models/OrderParam.cs b/Store.KJDWebService/Models/OrderParam.cs similarity index 100% rename from StoreHub.KJDWebService/Models/OrderParam.cs rename to Store.KJDWebService/Models/OrderParam.cs diff --git a/StoreHub.KJDWebService/Models/PddModel/BuyerLoginInfo.cs b/Store.KJDWebService/Models/PddModel/BuyerLoginInfo.cs similarity index 100% rename from StoreHub.KJDWebService/Models/PddModel/BuyerLoginInfo.cs rename to Store.KJDWebService/Models/PddModel/BuyerLoginInfo.cs diff --git a/StoreHub.KJDWebService/Models/PddModel/PDDMaterialInfo.cs b/Store.KJDWebService/Models/PddModel/PDDMaterialInfo.cs similarity index 100% rename from StoreHub.KJDWebService/Models/PddModel/PDDMaterialInfo.cs rename to Store.KJDWebService/Models/PddModel/PDDMaterialInfo.cs diff --git a/StoreHub.KJDWebService/Models/PddModel/PDDProductDecorationInfo.cs b/Store.KJDWebService/Models/PddModel/PDDProductDecorationInfo.cs similarity index 100% rename from StoreHub.KJDWebService/Models/PddModel/PDDProductDecorationInfo.cs rename to Store.KJDWebService/Models/PddModel/PDDProductDecorationInfo.cs diff --git a/StoreHub.KJDWebService/Models/PddModel/PDDProductDetailInfo.cs b/Store.KJDWebService/Models/PddModel/PDDProductDetailInfo.cs similarity index 100% rename from StoreHub.KJDWebService/Models/PddModel/PDDProductDetailInfo.cs rename to Store.KJDWebService/Models/PddModel/PDDProductDetailInfo.cs diff --git a/StoreHub.KJDWebService/Models/PddModel/PDDProductInfo.cs b/Store.KJDWebService/Models/PddModel/PDDProductInfo.cs similarity index 100% rename from StoreHub.KJDWebService/Models/PddModel/PDDProductInfo.cs rename to Store.KJDWebService/Models/PddModel/PDDProductInfo.cs diff --git a/StoreHub.KJDWebService/Models/PddModel/PDDProductPropertyInfo.cs b/Store.KJDWebService/Models/PddModel/PDDProductPropertyInfo.cs similarity index 100% rename from StoreHub.KJDWebService/Models/PddModel/PDDProductPropertyInfo.cs rename to Store.KJDWebService/Models/PddModel/PDDProductPropertyInfo.cs diff --git a/StoreHub.KJDWebService/Models/PddModel/PDDSizeSpecInfo.cs b/Store.KJDWebService/Models/PddModel/PDDSizeSpecInfo.cs similarity index 100% rename from StoreHub.KJDWebService/Models/PddModel/PDDSizeSpecInfo.cs rename to Store.KJDWebService/Models/PddModel/PDDSizeSpecInfo.cs diff --git a/StoreHub.KJDWebService/Models/ProductPurchasePriceResultParam.cs b/Store.KJDWebService/Models/ProductPurchasePriceResultParam.cs similarity index 100% rename from StoreHub.KJDWebService/Models/ProductPurchasePriceResultParam.cs rename to Store.KJDWebService/Models/ProductPurchasePriceResultParam.cs diff --git a/StoreHub.KJDWebService/Models/ProductSelectResultParam.cs b/Store.KJDWebService/Models/ProductSelectResultParam.cs similarity index 100% rename from StoreHub.KJDWebService/Models/ProductSelectResultParam.cs rename to Store.KJDWebService/Models/ProductSelectResultParam.cs diff --git a/StoreHub.KJDWebService/Models/Result.cs b/Store.KJDWebService/Models/Result.cs similarity index 100% rename from StoreHub.KJDWebService/Models/Result.cs rename to Store.KJDWebService/Models/Result.cs diff --git a/StoreHub.KJDWebService/Models/ShopModel/JSTListProductSkuInfo.cs b/Store.KJDWebService/Models/ShopModel/JSTListProductSkuInfo.cs similarity index 100% rename from StoreHub.KJDWebService/Models/ShopModel/JSTListProductSkuInfo.cs rename to Store.KJDWebService/Models/ShopModel/JSTListProductSkuInfo.cs diff --git a/StoreHub.KJDWebService/Models/ShopModel/JitCardCodeParam.cs b/Store.KJDWebService/Models/ShopModel/JitCardCodeParam.cs similarity index 100% rename from StoreHub.KJDWebService/Models/ShopModel/JitCardCodeParam.cs rename to Store.KJDWebService/Models/ShopModel/JitCardCodeParam.cs diff --git a/StoreHub.KJDWebService/Models/ShopModel/ShopParam.cs b/Store.KJDWebService/Models/ShopModel/ShopParam.cs similarity index 100% rename from StoreHub.KJDWebService/Models/ShopModel/ShopParam.cs rename to Store.KJDWebService/Models/ShopModel/ShopParam.cs diff --git a/StoreHub.KJDWebService/Models/ShopModel/sku_img_replace.cs b/Store.KJDWebService/Models/ShopModel/sku_img_replace.cs similarity index 100% rename from StoreHub.KJDWebService/Models/ShopModel/sku_img_replace.cs rename to Store.KJDWebService/Models/ShopModel/sku_img_replace.cs diff --git a/StoreHub.KJDWebService/Models/SkuImgResult.cs b/Store.KJDWebService/Models/SkuImgResult.cs similarity index 100% rename from StoreHub.KJDWebService/Models/SkuImgResult.cs rename to Store.KJDWebService/Models/SkuImgResult.cs diff --git a/StoreHub.KJDWebService/Models/TemuDBOrderInfo.cs b/Store.KJDWebService/Models/TemuDBOrderInfo.cs similarity index 100% rename from StoreHub.KJDWebService/Models/TemuDBOrderInfo.cs rename to Store.KJDWebService/Models/TemuDBOrderInfo.cs diff --git a/StoreHub.KJDWebService/Models/UserLoginResultParam.cs b/Store.KJDWebService/Models/UserLoginResultParam.cs similarity index 100% rename from StoreHub.KJDWebService/Models/UserLoginResultParam.cs rename to Store.KJDWebService/Models/UserLoginResultParam.cs diff --git a/StoreHub.KJDWebService/Models/WXAddressInfo.cs b/Store.KJDWebService/Models/WXAddressInfo.cs similarity index 100% rename from StoreHub.KJDWebService/Models/WXAddressInfo.cs rename to Store.KJDWebService/Models/WXAddressInfo.cs diff --git a/StoreHub.KJDWebService/Models/WXShopDefaultAddressInfo.cs b/Store.KJDWebService/Models/WXShopDefaultAddressInfo.cs similarity index 100% rename from StoreHub.KJDWebService/Models/WXShopDefaultAddressInfo.cs rename to Store.KJDWebService/Models/WXShopDefaultAddressInfo.cs diff --git a/StoreHub.KJDWebService/Models/WxModel/WXBuyUrlNotesInfo.cs b/Store.KJDWebService/Models/WxModel/WXBuyUrlNotesInfo.cs similarity index 100% rename from StoreHub.KJDWebService/Models/WxModel/WXBuyUrlNotesInfo.cs rename to Store.KJDWebService/Models/WxModel/WXBuyUrlNotesInfo.cs diff --git a/StoreHub.KJDWebService/Models/WxModel/WXClientProductInfoLite.cs b/Store.KJDWebService/Models/WxModel/WXClientProductInfoLite.cs similarity index 100% rename from StoreHub.KJDWebService/Models/WxModel/WXClientProductInfoLite.cs rename to Store.KJDWebService/Models/WxModel/WXClientProductInfoLite.cs diff --git a/StoreHub.KJDWebService/Models/WxModel/WXGetEditProductInfoLite.cs b/Store.KJDWebService/Models/WxModel/WXGetEditProductInfoLite.cs similarity index 100% rename from StoreHub.KJDWebService/Models/WxModel/WXGetEditProductInfoLite.cs rename to Store.KJDWebService/Models/WxModel/WXGetEditProductInfoLite.cs diff --git a/StoreHub.KJDWebService/Models/WxModel/WXOrderAmountInfo.cs b/Store.KJDWebService/Models/WxModel/WXOrderAmountInfo.cs similarity index 100% rename from StoreHub.KJDWebService/Models/WxModel/WXOrderAmountInfo.cs rename to Store.KJDWebService/Models/WxModel/WXOrderAmountInfo.cs diff --git a/StoreHub.KJDWebService/Models/WxModel/WXOrderDecryptAddressInfo.cs b/Store.KJDWebService/Models/WxModel/WXOrderDecryptAddressInfo.cs similarity index 100% rename from StoreHub.KJDWebService/Models/WxModel/WXOrderDecryptAddressInfo.cs rename to Store.KJDWebService/Models/WxModel/WXOrderDecryptAddressInfo.cs diff --git a/StoreHub.KJDWebService/Models/WxModel/WxOptimizeExpressInfo.cs b/Store.KJDWebService/Models/WxModel/WxOptimizeExpressInfo.cs similarity index 100% rename from StoreHub.KJDWebService/Models/WxModel/WxOptimizeExpressInfo.cs rename to Store.KJDWebService/Models/WxModel/WxOptimizeExpressInfo.cs diff --git a/StoreHub.KJDWebService/Models/WxModel/oem.cs b/Store.KJDWebService/Models/WxModel/oem.cs similarity index 100% rename from StoreHub.KJDWebService/Models/WxModel/oem.cs rename to Store.KJDWebService/Models/WxModel/oem.cs diff --git a/StoreHub.KJDWebService/Models/WxModel/oms_wei_external_procurement_after_sale_info.cs b/Store.KJDWebService/Models/WxModel/oms_wei_external_procurement_after_sale_info.cs similarity index 100% rename from StoreHub.KJDWebService/Models/WxModel/oms_wei_external_procurement_after_sale_info.cs rename to Store.KJDWebService/Models/WxModel/oms_wei_external_procurement_after_sale_info.cs diff --git a/StoreHub.KJDWebService/Models/WxModel/oms_wei_external_procurement_log.cs b/Store.KJDWebService/Models/WxModel/oms_wei_external_procurement_log.cs similarity index 100% rename from StoreHub.KJDWebService/Models/WxModel/oms_wei_external_procurement_log.cs rename to Store.KJDWebService/Models/WxModel/oms_wei_external_procurement_log.cs diff --git a/StoreHub.KJDWebService/Models/WxModel/oms_wei_order.cs b/Store.KJDWebService/Models/WxModel/oms_wei_order.cs similarity index 100% rename from StoreHub.KJDWebService/Models/WxModel/oms_wei_order.cs rename to Store.KJDWebService/Models/WxModel/oms_wei_order.cs diff --git a/StoreHub.KJDWebService/Models/WxModel/oms_wei_order_item.cs b/Store.KJDWebService/Models/WxModel/oms_wei_order_item.cs similarity index 100% rename from StoreHub.KJDWebService/Models/WxModel/oms_wei_order_item.cs rename to Store.KJDWebService/Models/WxModel/oms_wei_order_item.cs diff --git a/StoreHub.KJDWebService/Models/WxModel/oms_wei_refund.cs b/Store.KJDWebService/Models/WxModel/oms_wei_refund.cs similarity index 100% rename from StoreHub.KJDWebService/Models/WxModel/oms_wei_refund.cs rename to Store.KJDWebService/Models/WxModel/oms_wei_refund.cs diff --git a/StoreHub.KJDWebService/Models/WxModel/order_item_ext.cs b/Store.KJDWebService/Models/WxModel/order_item_ext.cs similarity index 100% rename from StoreHub.KJDWebService/Models/WxModel/order_item_ext.cs rename to Store.KJDWebService/Models/WxModel/order_item_ext.cs diff --git a/StoreHub.KJDWebService/Models/WxModel/order_pdd_buyer_after_sale.cs b/Store.KJDWebService/Models/WxModel/order_pdd_buyer_after_sale.cs similarity index 100% rename from StoreHub.KJDWebService/Models/WxModel/order_pdd_buyer_after_sale.cs rename to Store.KJDWebService/Models/WxModel/order_pdd_buyer_after_sale.cs diff --git a/StoreHub.KJDWebService/Models/WxModel/order_pdd_buyer_order.cs b/Store.KJDWebService/Models/WxModel/order_pdd_buyer_order.cs similarity index 100% rename from StoreHub.KJDWebService/Models/WxModel/order_pdd_buyer_order.cs rename to Store.KJDWebService/Models/WxModel/order_pdd_buyer_order.cs diff --git a/StoreHub.KJDWebService/Models/WxModel/user_payment_record_info.cs b/Store.KJDWebService/Models/WxModel/user_payment_record_info.cs similarity index 100% rename from StoreHub.KJDWebService/Models/WxModel/user_payment_record_info.cs rename to Store.KJDWebService/Models/WxModel/user_payment_record_info.cs diff --git a/StoreHub.KJDWebService/Models/WxModel/user_points_record_info.cs b/Store.KJDWebService/Models/WxModel/user_points_record_info.cs similarity index 100% rename from StoreHub.KJDWebService/Models/WxModel/user_points_record_info.cs rename to Store.KJDWebService/Models/WxModel/user_points_record_info.cs diff --git a/StoreHub.KJDWebService/Models/WxModel/wx_qualification_image_info.cs b/Store.KJDWebService/Models/WxModel/wx_qualification_image_info.cs similarity index 100% rename from StoreHub.KJDWebService/Models/WxModel/wx_qualification_image_info.cs rename to Store.KJDWebService/Models/WxModel/wx_qualification_image_info.cs diff --git a/StoreHub.KJDWebService/Models/WxProdSecretSpuIdShopIdMapParam.cs b/Store.KJDWebService/Models/WxProdSecretSpuIdShopIdMapParam.cs similarity index 100% rename from StoreHub.KJDWebService/Models/WxProdSecretSpuIdShopIdMapParam.cs rename to Store.KJDWebService/Models/WxProdSecretSpuIdShopIdMapParam.cs diff --git a/StoreHub.KJDWebService/Properties/launchSettings.json b/Store.KJDWebService/Properties/launchSettings.json similarity index 100% rename from StoreHub.KJDWebService/Properties/launchSettings.json rename to Store.KJDWebService/Properties/launchSettings.json diff --git a/StoreHub.KJDWebService/Repositorys/SqlRepository.cs b/Store.KJDWebService/Repositorys/SqlRepository.cs similarity index 100% rename from StoreHub.KJDWebService/Repositorys/SqlRepository.cs rename to Store.KJDWebService/Repositorys/SqlRepository.cs diff --git a/StoreHub.KJDWebService/Services/BXXPriceService.cs b/Store.KJDWebService/Services/BXXPriceService.cs similarity index 100% rename from StoreHub.KJDWebService/Services/BXXPriceService.cs rename to Store.KJDWebService/Services/BXXPriceService.cs diff --git a/StoreHub.KJDWebService/Services/BXXProductService.cs b/Store.KJDWebService/Services/BXXProductService.cs similarity index 100% rename from StoreHub.KJDWebService/Services/BXXProductService.cs rename to Store.KJDWebService/Services/BXXProductService.cs diff --git a/StoreHub.KJDWebService/Services/BXXProductThirdService.cs b/Store.KJDWebService/Services/BXXProductThirdService.cs similarity index 100% rename from StoreHub.KJDWebService/Services/BXXProductThirdService.cs rename to Store.KJDWebService/Services/BXXProductThirdService.cs diff --git a/StoreHub.KJDWebService/Services/BXXShopService.cs b/Store.KJDWebService/Services/BXXShopService.cs similarity index 100% rename from StoreHub.KJDWebService/Services/BXXShopService.cs rename to Store.KJDWebService/Services/BXXShopService.cs diff --git a/StoreHub.KJDWebService/Services/BXXUserService.cs b/Store.KJDWebService/Services/BXXUserService.cs similarity index 100% rename from StoreHub.KJDWebService/Services/BXXUserService.cs rename to Store.KJDWebService/Services/BXXUserService.cs diff --git a/StoreHub.KJDWebService/Services/DYShopService.cs b/Store.KJDWebService/Services/DYShopService.cs similarity index 100% rename from StoreHub.KJDWebService/Services/DYShopService.cs rename to Store.KJDWebService/Services/DYShopService.cs diff --git a/StoreHub.KJDWebService/Services/DYUserService.cs b/Store.KJDWebService/Services/DYUserService.cs similarity index 100% rename from StoreHub.KJDWebService/Services/DYUserService.cs rename to Store.KJDWebService/Services/DYUserService.cs diff --git a/StoreHub.KJDWebService/Services/DemoService.cs b/Store.KJDWebService/Services/DemoService.cs similarity index 100% rename from StoreHub.KJDWebService/Services/DemoService.cs rename to Store.KJDWebService/Services/DemoService.cs diff --git a/StoreHub.KJDWebService/Services/OrderService.cs b/Store.KJDWebService/Services/OrderService.cs similarity index 100% rename from StoreHub.KJDWebService/Services/OrderService.cs rename to Store.KJDWebService/Services/OrderService.cs diff --git a/StoreHub.KJDWebService/Services/PriceService.cs b/Store.KJDWebService/Services/PriceService.cs similarity index 100% rename from StoreHub.KJDWebService/Services/PriceService.cs rename to Store.KJDWebService/Services/PriceService.cs diff --git a/StoreHub.KJDWebService/Services/ProductService.cs b/Store.KJDWebService/Services/ProductService.cs similarity index 100% rename from StoreHub.KJDWebService/Services/ProductService.cs rename to Store.KJDWebService/Services/ProductService.cs diff --git a/StoreHub.KJDWebService/Services/ProductThirdService.cs b/Store.KJDWebService/Services/ProductThirdService.cs similarity index 100% rename from StoreHub.KJDWebService/Services/ProductThirdService.cs rename to Store.KJDWebService/Services/ProductThirdService.cs diff --git a/StoreHub.KJDWebService/Services/ServiceBase.cs b/Store.KJDWebService/Services/ServiceBase.cs similarity index 94% rename from StoreHub.KJDWebService/Services/ServiceBase.cs rename to Store.KJDWebService/Services/ServiceBase.cs index 45c4978..3a9c29c 100644 --- a/StoreHub.KJDWebService/Services/ServiceBase.cs +++ b/Store.KJDWebService/Services/ServiceBase.cs @@ -28,7 +28,7 @@ namespace StoreHub.KJDWebService.Services /// 创建 MysqlUtils(等效旧 new MysqlUtils(conn[, isUseTrans])) protected MysqlUtils MySql(string connKey, bool isUseTrans = false) { - return new MysqlUtils(AdoProvider.Get(connKey), isUseTrans); + return new MysqlUtils(KJDConfig.GetConnection(connKey), isUseTrans); } /// 读取配置(旧 ConfigurationManager.ConnectionStrings["key"].ConnectionString) diff --git a/StoreHub.KJDWebService/Services/ShopService.cs b/Store.KJDWebService/Services/ShopService.cs similarity index 100% rename from StoreHub.KJDWebService/Services/ShopService.cs rename to Store.KJDWebService/Services/ShopService.cs diff --git a/StoreHub.KJDWebService/Services/ToolService.cs b/Store.KJDWebService/Services/ToolService.cs similarity index 100% rename from StoreHub.KJDWebService/Services/ToolService.cs rename to Store.KJDWebService/Services/ToolService.cs diff --git a/StoreHub.KJDWebService/Services/UpdateService.cs b/Store.KJDWebService/Services/UpdateService.cs similarity index 100% rename from StoreHub.KJDWebService/Services/UpdateService.cs rename to Store.KJDWebService/Services/UpdateService.cs diff --git a/StoreHub.KJDWebService/Services/UserService.cs b/Store.KJDWebService/Services/UserService.cs similarity index 100% rename from StoreHub.KJDWebService/Services/UserService.cs rename to Store.KJDWebService/Services/UserService.cs diff --git a/StoreHub.KJDWebService/Services/WXExpressOptimizeService.cs b/Store.KJDWebService/Services/WXExpressOptimizeService.cs similarity index 100% rename from StoreHub.KJDWebService/Services/WXExpressOptimizeService.cs rename to Store.KJDWebService/Services/WXExpressOptimizeService.cs diff --git a/StoreHub.KJDWebService/Services/WXOemService.cs b/Store.KJDWebService/Services/WXOemService.cs similarity index 100% rename from StoreHub.KJDWebService/Services/WXOemService.cs rename to Store.KJDWebService/Services/WXOemService.cs diff --git a/StoreHub.KJDWebService/Services/WXOrderService.cs b/Store.KJDWebService/Services/WXOrderService.cs similarity index 100% rename from StoreHub.KJDWebService/Services/WXOrderService.cs rename to Store.KJDWebService/Services/WXOrderService.cs diff --git a/StoreHub.KJDWebService/Services/WXProductService.cs b/Store.KJDWebService/Services/WXProductService.cs similarity index 100% rename from StoreHub.KJDWebService/Services/WXProductService.cs rename to Store.KJDWebService/Services/WXProductService.cs diff --git a/StoreHub.KJDWebService/Services/WXShopRevenueStatisticsService.cs b/Store.KJDWebService/Services/WXShopRevenueStatisticsService.cs similarity index 100% rename from StoreHub.KJDWebService/Services/WXShopRevenueStatisticsService.cs rename to Store.KJDWebService/Services/WXShopRevenueStatisticsService.cs diff --git a/StoreHub.KJDWebService/Services/WXShopService.cs b/Store.KJDWebService/Services/WXShopService.cs similarity index 100% rename from StoreHub.KJDWebService/Services/WXShopService.cs rename to Store.KJDWebService/Services/WXShopService.cs diff --git a/StoreHub.KJDWebService/Services/WXUserService.cs b/Store.KJDWebService/Services/WXUserService.cs similarity index 100% rename from StoreHub.KJDWebService/Services/WXUserService.cs rename to Store.KJDWebService/Services/WXUserService.cs diff --git a/StoreHub.KJDWebService/Startup.cs b/Store.KJDWebService/Startup.cs similarity index 100% rename from StoreHub.KJDWebService/Startup.cs rename to Store.KJDWebService/Startup.cs diff --git a/StoreHub.KJDWebService/StoreHub.KJDWebService.csproj b/Store.KJDWebService/StoreHub.KJDWebService.csproj similarity index 88% rename from StoreHub.KJDWebService/StoreHub.KJDWebService.csproj rename to Store.KJDWebService/StoreHub.KJDWebService.csproj index 1183b05..fa1a03e 100644 --- a/StoreHub.KJDWebService/StoreHub.KJDWebService.csproj +++ b/Store.KJDWebService/StoreHub.KJDWebService.csproj @@ -9,7 +9,7 @@ - + diff --git a/StoreHub.Services/Dtos/UserDto.cs b/Store.Services/Dtos/UserDto.cs similarity index 100% rename from StoreHub.Services/Dtos/UserDto.cs rename to Store.Services/Dtos/UserDto.cs diff --git a/StoreHub.Services/Mappers/UserMapper.cs b/Store.Services/Mappers/UserMapper.cs similarity index 94% rename from StoreHub.Services/Mappers/UserMapper.cs rename to Store.Services/Mappers/UserMapper.cs index 2999ffb..135cc77 100644 --- a/StoreHub.Services/Mappers/UserMapper.cs +++ b/Store.Services/Mappers/UserMapper.cs @@ -1,5 +1,5 @@ using Riok.Mapperly.Abstractions; -using StoreHub.Framework.Models; +using Store.Framework.Models; using StoreHub.Services.Dtos; namespace StoreHub.Services.Mappers; diff --git a/StoreHub.Services/StoreHub.Services.csproj b/Store.Services/Store.Services.csproj similarity index 75% rename from StoreHub.Services/StoreHub.Services.csproj rename to Store.Services/Store.Services.csproj index aa71604..f50f998 100644 --- a/StoreHub.Services/StoreHub.Services.csproj +++ b/Store.Services/Store.Services.csproj @@ -9,7 +9,7 @@ - + diff --git a/StoreHub.Services/UserService.cs b/Store.Services/UserService.cs similarity index 96% rename from StoreHub.Services/UserService.cs rename to Store.Services/UserService.cs index bd9308c..4df4bf3 100644 --- a/StoreHub.Services/UserService.cs +++ b/Store.Services/UserService.cs @@ -1,8 +1,8 @@ using FreeSql; using MAX.Core.Cryptography; using MAX.Core.DependencyInjection; -using StoreHub.Framework.Models; -using StoreHub.Framework.Repositorys; +using Store.Framework.Models; +using Store.Framework.Repositorys; using StoreHub.Services.Dtos; using StoreHub.Services.Mappers; diff --git a/StoreHub.Framework/Commons/ApiResult.cs b/StoreHub.Framework/Commons/ApiResult.cs deleted file mode 100644 index 3e29ae4..0000000 --- a/StoreHub.Framework/Commons/ApiResult.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace StoreHub.Framework.Commons -{ - public class ApiResult - { - public int Code { get; set; } - - public T? Data { get; set; } - - public string? Msg { get; set; } - } - - public static class ApiResult - { - public static ApiResult Success(T data, string? msg = null) - => new() - { - Code = 200, - Data = data, - Msg = msg ?? "success" - }; - - public static ApiResult Error(string msg, int code = 500) - => new() - { - Code = code, - Data = null, - Msg = msg - }; - } -} diff --git a/StoreHub.Framework/Logger/NLogger.cs b/StoreHub.Framework/Logger/NLogger.cs deleted file mode 100644 index 0b81559..0000000 --- a/StoreHub.Framework/Logger/NLogger.cs +++ /dev/null @@ -1,138 +0,0 @@ -using MAX.Core.Infrastructure; -using MAX.Core.Logger; -using NLog; -using System; -using LLogLevel = MAX.Core.Logger.LogLevel; -using NLogLevel = NLog.LogLevel; - -namespace StoreHub.Framework.Logger; - -public class NLogger : SingleInstance, ILoggerFacade -{ - private readonly ILogger _log; - - private NLogger() - { - _log = LogManager.GetLogger("GlobalLogger"); - } - - public bool IsDebugEnabled { get; private set; } = true; - - 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/StoreHub.KJDWebService/Commons/MysqlUtils.cs b/StoreHub.KJDWebService/Commons/MysqlUtils.cs deleted file mode 100644 index ba01e85..0000000 --- a/StoreHub.KJDWebService/Commons/MysqlUtils.cs +++ /dev/null @@ -1,503 +0,0 @@ -using FreeSql; -using Microsoft.Extensions.DependencyInjection; -using MySql.Data.MySqlClient; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.IO; -using System.Text; - -namespace StoreHub.KJDWebService.Commons -{ - /// - /// .NET 10 版 MysqlUtils(迁移自 KJD_Web.Utils.MysqlUtils)。 - /// 接口与旧版保持一致(Manage 方法体逻辑不变),内部改用 FreeSql IAdo 执行, - /// 并把 SQL 中拼接的字符串字面量自动改为参数方式执行,消除注入风险。 - /// 连接池由 FreeSql 管理,Close() 为空操作。 - /// - public class MysqlUtils - { - /// 错误信息(与旧版一致,Manage 依赖 mySql.Message) - public string Message = ""; - - private readonly IAdo _ado; - - /// 超时(秒) - public int TimeOut = 120; - - public bool IsUseTrans { get; set; } = false; - - private DbTransaction _transaction = null; - private object _poolObj = null; - private DbConnection _transConn = null; - - /// 当前事务 - public DbTransaction Transaction - { - get { return _transaction; } - set { _transaction = value; } - } - - public bool Connected - { - get { return _transConn != null && _transConn.State == ConnectionState.Open; } - } - - /// 初始化:传入 IAdo(由 Service 注入 SqlRepository 后提供) - public MysqlUtils(IAdo ado, bool isUseTrans = false) - { - _ado = ado ?? throw new ArgumentNullException(nameof(ado)); - this.IsUseTrans = isUseTrans; - if (isUseTrans) - { - BeginTransactionInternal(); - } - } - - /// 兼容旧构造函数签名(conn 仅用于选择数据库,连接由 DI 提供) - public MysqlUtils(string conn, bool isUseTrans = false) : this(AdoProvider.Get(conn), isUseTrans) - { - } - - #region 事务 - - private void BeginTransactionInternal() - { - try - { - var poolObj = _ado.MasterPool.Get(TimeSpan.FromSeconds(TimeOut)); - _poolObj = poolObj; - _transConn = poolObj.Value; - _transaction = _transConn.BeginTransaction(); - Message = ""; - } - catch (Exception e) - { - AddError(e.Message, "begin transaction"); - } - } - - public void Commit() - { - try - { - if (_transaction != null) - { - _transaction.Commit(); - } - } - catch (Exception e) - { - AddError(e.Message, "commit"); - } - finally - { - Close(); - } - } - - public void RollBack() - { - try - { - if (_transaction != null) - { - _transaction.Rollback(); - } - } - catch (Exception e) - { - AddError(e.Message, "rollback"); - } - finally - { - Close(); - } - } - - #endregion - - #region 执行方法 - - /// 返回执行表 - public DataTable ExecuteDataTable(string SqlString) - { - return ExecuteDataTable(SqlString, (DbParameter[])null); - } - - /// 执行sql返回DataTable - public DataTable ExecuteDataTable(string SqlString, DbParameter[] parms) - { - return ExecuteCore(SqlString, parms, (conn, tx, sql, p) => _ado.ExecuteDataTable(sql, p)); - } - - /// 执行sql返回DataTable(自动参数化) - public DataTable ExecuteDataTable(string SqlString, object parms) - { - return ExecuteCore(SqlString, parms, (conn, tx, sql, p) => _ado.ExecuteDataTable(conn, tx, sql, (object)p)); - } - - /// 取第一行 - public DataRow ExecuteDataTableRow(string SqlString, DbParameter[] parms = null) - { - DataTable dt = ExecuteDataTable(SqlString, parms); - if (dt != null && dt.Rows.Count > 0) - return dt.Rows[0]; - return null; - } - - /// 行数 - public int ExecuteRowCount(string SqlString, DbParameter[] parms = null) - { - DataTable dt = ExecuteDataTable(SqlString, parms); - if (dt != null) - return dt.Rows.Count; - return 0; - } - - /// 首行首列(string) - public string ExecuteFirst(string SqlString, DbParameter[] parms = null) - { - DataRow row = ExecuteDataTableRow(SqlString, parms); - if (row != null && row.ItemArray.Length > 0) - return ToStringVal(row[0]); - return null; - } - - /// 首行首列(int) - public int ExecuteFirstInt(string SqlString, DbParameter[] parms = null) - { - string v = ExecuteFirst(SqlString, parms); - return ToInt(v); - } - - /// 首行首列(decimal) - public decimal ExecuteFirstDecimal(string SqlString, DbParameter[] parms = null) - { - string v = ExecuteFirst(SqlString, parms); - if (v == null) - return 0m; - decimal r = 0m; - decimal.TryParse(v, out r); - return r; - } - - /// 受影响行数 - public int ExecuteAffectedRows(string SqlString, DbParameter[] parms = null) - { - return ExecuteCore(SqlString, parms, (conn, tx, sql, p) => _ado.ExecuteNonQuery(conn, tx, sql, (object)p)); - } - - /// 执行无返回SQL,是否执行成功 - public bool ExecuteNonQuery(string SqlString, DbParameter[] parms = null) - { - return ExecuteCore(SqlString, parms, (conn, tx, sql, p) => _ado.ExecuteNonQuery(conn, tx, sql, (object)p) > 0); - } - - /// 插入并返回自增id - public long ExecuteInsertId(string SqlString, DbParameter[] parms = null) - { - Message = ""; - object poolObj = null; - try - { - DbConnection conn; - DbTransaction tx = null; - if (IsUseTrans && _transaction != null) - { - conn = _transConn; - tx = _transaction; - } - else - { - var pool = _ado.MasterPool.Get(TimeSpan.FromSeconds(TimeOut)); - poolObj = pool; - conn = pool.Value; - } - int affected = _ado.ExecuteNonQuery(conn, tx, SqlString, parms); - if (affected < 0 || !string.IsNullOrEmpty(Message)) - return 0; - object v = _ado.ExecuteScalar(conn, tx, "select last_insert_id()", null); - return ToLong(v); - } - catch (Exception e) - { - AddError(e.Message, SqlString); - return 0; - } - finally - { - ReleaseConnection(poolObj); - } - } - - /// 是否存在 - public bool ExecuteExists(string SqlString, DbParameter[] parms = null) - { - DataTable dt = ExecuteDataTable(SqlString, parms); - return dt != null && dt.Rows.Count > 0; - } - - /// 执行标量 - public object ExecuteScalar(string SqlString, DbParameter[] parms = null) - { - return ExecuteScalarInternal(SqlString, parms); - } - - private object ExecuteScalarInternal(string SqlString, DbParameter[] parms = null) - { - return ExecuteCore(SqlString, parms, (conn, tx, sql, p) => _ado.ExecuteScalar(conn, tx, sql, (object)p)); - } - - /// 插入(keys 参数化) - public long Insert(string table, List keys) - { - StringBuilder cols = new StringBuilder(); - StringBuilder vals = new StringBuilder(); - var pars = new List(); - for (int i = 0; i < keys.Count; i++) - { - if (i > 0) - { - cols.Append(","); - vals.Append(","); - } - cols.Append("`").Append(keys[i].Key).Append("`"); - string pname = "@p" + i; - vals.Append(pname); - pars.Add(new MySqlParameter(pname, (object)(keys[i].Value ?? ""))); - } - string sql = "insert into " + table + " (" + cols.ToString() + ") values (" + vals.ToString() + ")"; - bool ok = ExecuteNonQuery(sql, pars.ToArray()); - if (!ok || !string.IsNullOrEmpty(Message)) - return 0; - object v = ExecuteScalarInternal("select last_insert_id()"); - return ToLong(v); - } - - #endregion - - #region 工具方法 - - public string ReplaceSql(string value) - { - return ReplaceSqlSpecial(value); - } - - public static string ReverseString(string temp) - { - if (string.IsNullOrEmpty(temp)) - return null; - char[] l = temp.ToCharArray(); - Array.Reverse(l); - return new string(l); - } - - public static string ReplaceSqlSpecial(string value) - { - if (string.IsNullOrEmpty(value)) - return null; - value = value.Replace("\\", "\\\\"); - value = value.Replace("'", "''"); - return value; - } - - public static string JustUtf8Words(string str) - { - return str; - } - - #endregion - - #region 内部实现 - - private T ExecuteCore(string sql, DbParameter[] parms, Func action) - { - Message = ""; - object poolObj = null; - try - { - DbConnection conn; - DbTransaction tx = null; - if (IsUseTrans && _transaction != null) - { - conn = _transConn; - tx = _transaction; - } - else - { - var pool = _ado.MasterPool.Get(TimeSpan.FromSeconds(TimeOut)); - poolObj = pool; - conn = pool.Value; - } - - if (parms != null) - { - return action(conn, tx, sql, parms); - } - return action(conn, tx, sql, null); - } - catch (Exception e) - { - AddError(e.Message, sql); - return default(T); - } - finally - { - ReleaseConnection(poolObj); - } - } - - private T ExecuteCore(string sql, object parms, Func action) - { - if (parms is DbParameter[] arr) - return ExecuteCore(sql, arr, action); - if (parms == null) - return ExecuteCore(sql, (DbParameter[])null, action); - var list = new List(); - foreach (var prop in parms.GetType().GetProperties()) - { - list.Add(new MySqlParameter("@" + prop.Name, prop.GetValue(parms) ?? DBNull.Value)); - } - return ExecuteCore(sql, list.ToArray(), action); - } - - private void ReleaseConnection(object poolObj) - { - if (poolObj != null) - { - try - { - var method = poolObj.GetType().GetMethod("Dispose"); - method?.Invoke(poolObj, null); - } - catch - { - } - } - } - - /// 关闭连接(连接池自动管理,空操作) - public void Close() - { - IsRunning = false; - try - { - if (_transaction != null) - { - try { _transaction.Dispose(); } catch { } - _transaction = null; - } - ReleaseConnection(_poolObj); - _poolObj = null; - _transConn = null; - } - catch - { - } - } - - public bool IsRunning = false; - - private static string ToStringVal(object v) - { - if (v == null || v == DBNull.Value) - return null; - return v.ToString(); - } - - private static int ToInt(object v) - { - if (v == null || v == DBNull.Value) - return 0; - int r = 0; - int.TryParse(v.ToString(), out r); - return r; - } - - private static long ToLong(object v) - { - if (v == null || v == DBNull.Value) - return 0; - long r = 0; - long.TryParse(v.ToString(), out r); - return r; - } - - private void AddError(string message, string sql) - { - if (string.IsNullOrEmpty(message) || message.Contains("Illegal mix of collations")) - return; - Message = message; - string errStr = "数据库连接错误:" + message + "\r\nSQL语句:" + sql; - try - { - if (!Directory.Exists("Log")) - { - Directory.CreateDirectory("Log"); - } - File.AppendAllText("Log\\log-" + DateTime.Now.ToString("yyyyMMdd") + ".txt", errStr); - } - catch (Exception) - { - } - } - - #endregion - } - - - - /// - /// 根据连接串 key 提供 FreeSql IAdo(由框架 DI 注册的 keyed IFreeSql 提供) - /// - public static class AdoProvider - { - private static IServiceProvider _serviceProvider; - - public static void SetServiceProvider(IServiceProvider serviceProvider) - { - _serviceProvider = serviceProvider; - } - - public static IAdo Get(string connKey) - { - if (string.IsNullOrEmpty(connKey)) - connKey = "ConnKJD"; - string key = connKey switch - { - "ConnKJD" => "KJDDB", - "ConnKJDBAK" => "KJDBAKDB", - _ => "KJDDB", - }; - var fsql = _serviceProvider?.GetKeyedService(key); - if (fsql == null) - { - var fsql2 = _serviceProvider?.GetService(); - if (fsql2 != null) - return fsql2.Ado; - throw new InvalidOperationException("IFreeSql not registered for key " + key); - } - return fsql.Ado; - } - } - - public class MysqlKeyValues - { - public List List = new List(); - } - - public class MysqlKeyValue - { - public MysqlKeyValue(string key, object value) - { - Key = key; - Value = value == null ? "" : MysqlUtils.ReplaceSqlSpecial(value.ToString()); - } - - public string Key = ""; - public string Value; - } -} diff --git a/StoreHub.slnx b/StoreHub.slnx deleted file mode 100644 index f6ac79b..0000000 --- a/StoreHub.slnx +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - -